← 返回文章列表

Dify 中级实验(08):代码节点进阶——如何用标准库处理文件与数据?

1. 业务场景

先讲一个我们实际遇到的场景。

一家电商公司的运营/数据团队,每周要出一份销售数据周报:从后台导出 CSV,用 Excel 打开,手工算各列 min/max/均值、看趋势、找异常(某天销量突然暴涨或暴跌)。数据里日期还有五种写法(2026-01-01、2026/01/01、01/02/2026……),Excel 打开就是乱,每周重复一遍。

我们第一次接这类需求时,第一反应也是「把 CSV 丢给 LLM,让它算完写段总结」。真正动手才发现——LLM 算数慢、贵、还不一定对,统计口径飘忽不定,周报数据错一个数全废。后来翻 Dify 的节点列表才发现:平台原生的 Code(代码节点) 就是干这个的——确定性计算、毫秒级完成、0 Token。

这不是个例。任何「确定性计算」的业务环节都是这个模式:解析、清洗、统计、转换——CSV 对账、日志解析、报表统计……能算的别让 LLM 算,能用标准库的别指望第三方库。

2. 场景痛点

这个流程的痛点,在运营/数据团队身上体现得最直接:

本质上,确定性计算交给 LLM 是既慢又贵还不可控——这类活本该是代码节点的,代码节点才是「算得准、算得快、不花钱」的正主

3. 方案:为什么是代码节点

选代码节点的理由,我们实际对比过:

这篇文章我们就用它搭一个「数据加工工坊」:CSV 销售数据分析(统计/趋势/异常检测)+ 日期标准化两个代表性分支并行跑。

4. 整体架构

graph TD start["开始:csv_text / analysis_type"] csv["解析 CSV 销售数据:Code"] end_csv["结束:CSV result_csv / record_count"] date["日期时间处理:Code"] end_date["结束:日期 result_date / normalized_dates"] start --> csv --> end_csv start --> date --> end_date

链路很清晰:入口收数据 → 两个并行分支各做各的确定性计算 → 各走各的结束。两个分支互不依赖,从开始节点并行拉出——CSV 统计和日期标准化互不相干,一把跑完。

5. 模块设计

5.1 开始节点变量(长文本坑)

CSV 是长文本粘贴,paragraphmax_length 必须显式写大——默认 48 字符会让 Service API 直接拦截:

variables:

- label: CSV数据(含表头)

  max_length: 10000          # ⚠️ 长文本粘贴必须显式放大,否则报 48 字符限制

  required: true

  type: paragraph

  variable: csv_text

- label: 分析类型

  max_length: 48

  options: [summary, trend, anomaly]

  required: true

  type: select

  variable: analysis_type

5.2 CSV 解析 + 统计(标准库实现)

不用 Pandas,纯 csv 标准库完成:数值列自动识别 → 按分析类型分支 → 统计/趋势/异常检测:

def main(csv_text: str, analysis_type: str) -> dict:

    import csv

    import io

    rows = []

    try:

        reader = csv.DictReader(io.StringIO(csv_text or ""))

        rows = list(reader)

    except Exception:

        rows = []

    if not rows:

        return {"record_count": 0, "result_text": "空数据,请粘贴 CSV 文本"}

    atype = (analysis_type or "summary").strip()

    cols = list(rows[0].keys())

    # 识别数值列

    numeric_cols = []

    for col in cols:

        vals = []

        for r in rows:

            v = (r.get(col) or "").strip()

            if v:

                try:

                    float(v)

                    vals.append(float(v))

                except Exception:

                    pass

        if vals:

            numeric_cols.append(col)

    lines = ["共 {} 行,列:{}".format(len(rows), ", ".join(cols))]

    if atype == "trend" and numeric_cols:

        col = numeric_cols[0]

        lines.append("{} 趋势(按行序):".format(col))

        for i, r in enumerate(rows):

            lines.append("  第{}{} = {}".format(i + 1, r.get(cols[0], ""), r.get(col, "")))

    elif atype == "anomaly" and numeric_cols:

        col = numeric_cols[0]

        vals = [float(r[col]) for r in rows if (r.get(col) or "").strip()]

        if len(vals) > 3:

            mean = sum(vals) / len(vals)

            std = (sum((x - mean) ** 2 for x in vals) / len(vals)) ** 0.5

            lines.append("{} 异常检测(均值 {:.2f},2σ={:.2f}):".format(col, mean, 2 * std))

            for i, r in enumerate(rows):

                v = (r.get(col) or "").strip()

                if v and abs(float(v) - mean) > 2 * std:

                    lines.append("  第{}{}={} 超出正常范围".format(i + 2, col, v))

        else:

            lines.append("数据不足 4 行,无法做异常检测")

    else:

        for col in numeric_cols:

            vals = [float(r[col]) for r in rows if (r.get(col) or "").strip()]

            if vals:

                lines.append("{}: min={} max={} avg={:.2f} sum={:.2f}".format(

                    col, min(vals), max(vals), sum(vals) / len(vals), sum(vals)))

    return {"record_count": len(rows), "result_text": "\n".join(lines)}

5.3 日期标准化

5 种格式逐个 strptime 试,识别后统一 strftime 输出:

def main(csv_text: str) -> dict:

    import csv

    import io

    from datetime import datetime

    formats = [

        ("%Y-%m-%d", "YYYY-MM-DD"),

        ("%Y/%m/%d", "YYYY/MM/DD"),

        ("%m/%d/%Y", "MM/DD/YYYY"),

        ("%Y-%m-%d %H:%M:%S", "YYYY-MM-DD HH:MM:SS"),

        ("%Y年%m月%d日", "中文格式"),

    ]

    # ... 解析 CSV 后找到第一个日期列,逐行识别并标准化

    return {"normalized_dates": normalized, "date_summary": "...每行 原始值 -> 标准值 (格式名)..."}

6. 运行验证

用实验文档的 7 行销售数据(date/product/revenue/quantity/city)测试:

输入 analysis_type 预期输出 实测
summary 共 7 行 + 各数值列 min/max/avg/sum 与预期一致
trend revenue 按行序逐行趋势 与预期一致
anomaly 超出 2σ 的行被标出(如 2000 那行) 与预期一致
(日期分支) 所有日期统一为 YYYY-MM-DD + 格式名标注 与预期一致

7. 实战坑

现象 修复
假设 Pandas/Requests 可用 运行报 ModuleNotFoundError Dify 代码节点环境不保证第三方库——csv/io/json/re/collections/datetime 标准库覆盖 80% 场景
长文本变量没放大 max_length Service API 报 {var} in input form must be less than 48 characters CSV/JSON 粘贴类变量显式 max_length: 10000(实测,text-input 和 paragraph 都查)
代码里 "\n" 写成跨行字符串 SyntaxError: unterminated string literal,节点 failed 字符串字面量单行写 "\n".join(...);写完本地 exec() 实跑验证再导入
main() 参数名与变量名不一致 运行报 TypeError: main() got an unexpected keyword argument 代码节点按参数名传参:签名参数名必须 = variables 的 variable 名
返回值含 datetime/set 运行报 JSON 序列化失败 输出必须 JSON 可序列化,先 strftime/转 list
code 字段格式 校验报「code 用了内联字符串格式」 code 用 YAML | 块格式 + code_language: python3 必填

💡 选型心法:能算的别让 LLM 算。CSV 统计、异常检测这类确定性计算,代码节点毫秒级完成且 0 Token;LLM 只负责「理解与生成」。实验文档里的 Pandas/外部 API 实验,本 DSL 全部用标准库等价实现——这也是生产环境更稳的选择。

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

本系列 · Dify 实验 · 中级
  1. Dify 中级实验(01):参数提取器实战——如何从自然语言中提取结构化数据?
  2. Dify 中级实验(02):问题分类器——智能路由引擎如何四路分发?
  3. Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工?
  4. Dify 中级实验(04):迭代进阶——如何批量处理数据并守住性能边界?
  5. Dify 中级实验(05):并行执行——如何让多路任务同时跑?
  6. Dify 中级实验(06):变量聚合——如何确定性合并多路分支结果?
  7. Dify 中级实验(07):子工作流——如何把公共逻辑做成可复用积木?
  8. Dify 中级实验(08):代码节点进阶——如何用标准库处理文件与数据?
  9. Dify 中级实验(09):HTTP 节点进阶——如何搞定认证、分页与错误重试?
  10. Dify 中级实验(10):知识库深度调优——如何科学评估检索质量?
  11. Dify 中级实验(11):高级 RAG 流水线——如何搭建多路检索与精排?
  12. Dify 中级实验(12):Agent 深度配置——如何让智能体自主调用工具?
  13. Dify 中级实验(13):多 Agent 协作——如何编排多个智能体分工干活?
  14. Dify 中级实验(14):对话变量与状态管理——如何让工作流记住多轮对话的状态?
  15. Dify 中级实验(15):条件分支高阶策略——多条件路由如何避免分支爆炸?
  16. Dify 中级实验(16):错误处理与降级——工作流如何有尊严地失败?
  17. Dify 中级实验(17):调试监控与性能优化——响应慢和 Token 超支如何定位?
  18. Dify 中级实验(18):插件开发入门——如何把工作流变成 Agent 可调用的工具?
  19. Dify 中级实验(19):综合实战——如何把 19 个实验串成一条生产级流水线?
  20. Dify 中级实验(20):综合实战——自动化报告生成流水线如何从数据到周报一步到位?

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过