直接说结论:在 Dify 工作流中为 Agent 节点选择 Function Calling 还是 ReAct,并没有想象中那么复杂,但方向一旦选错,后果非常直接——任务反复重试、工具无法正常调用、甚至返回空结果。这并非小概率事件,根本原因在于策略与业务场景不匹配。

先厘清最核心的区别:是一次性完成,还是多轮循环。
Function Calling 的机制是让大模型在“单次推理”内完成所有动作——识别意图、匹配函数名称、提取参数、输出结构化的 JSON 指令。整个过程一气呵成,输出格式必须严格遵循 JSON Schema,少一个字符都不行。Dify 引擎只识别这个格式,任何自然语言描述都会被直接忽略。
ReAct 则完全是另一套逻辑。它允许模型分步推进:先写 Thought 说明“我打算查询天气”,再写 Action 指定调用 get_weather,然后等待 Observation 返回真实数据,再决定下一步如何执行。这个过程可以迭代 3 到 5 轮,模型拥有充足的纠错和转向空间。
这一步不能含糊,后续所有配置都建立在这个基础之上。
到底该用哪个?看懂三类输入信号就够了
第一类:用户输入中动词+宾语+可映射实体,清晰明确。
例如“查订单号 ORD‑78901 的状态”、“把客户张伟的手机号改成 139****5678”、“导出上月销售报表为 Excel”。这类输入结构特征非常明显,Function Calling 能稳定命中对应的三个函数——query_order_status、update_customer、export_report。不需要额外推理,直接匹配即可。
第二类:输入带有条件分支或隐含依赖。
比如“如果今天北京下雨,就推荐室内展馆;否则推荐公园”。这句话中的“是否下雨”是前置判断条件,必须先调用天气工具获取结果,才能决定后续调用哪个推荐工具。Function Calling 无法在一次输出中同时处理“判断+分支执行”,这种情况下必须使用 ReAct。
第三类:输入模糊、包含反问、或需要上下文补全。
例如:“那个上周说要改地址的客户,现在地址是多少?”——“那个客户”指代不明,“上周”是相对时间。ReAct 可以在第一轮 Thought 中先确认指代对象,第二轮 Action 调用 CRM 搜索,第三轮 Observation 比对时间戳后返回结果。Function Calling 遇到这类输入,大概率报错,或者参数匹配出现混乱。
实测数据:速度 vs 容错,你怎么选
我们使用同一组工具集——天气查询、订单查询、用户信息更新——以及 20 条真实客服对话记录进行了对比测试。分别用 Function Calling 和 ReAct 部署 Agent,记录了平均首字响应时间(TTFT)和任务完成率。
结果非常直观:Function Calling 平均 TTFT 为 320 毫秒,任务完成率 81%。失败的主要原因集中在参数提取错误上,比如将“订单#A20240515”误识别为城市名,或者函数名匹配出现偏差。ReAct 平均 TTFT 为 1.8 秒,但任务完成率高达 96%。失败案例主要是 Observation 返回内容过长导致模型截断,或者循环超限未设置终止条件。
数据给出了一个很直白的结论:如果追求速度,且用户提问足够规范,Function Calling 是首选;但如果追求稳定性,业务问题天生带有跳转、歧义、动态分支,那么 ReAct 就是唯一可行的路径。
模型支持不是选择题,是硬性门槛
在 Dify 后台 Agent 节点的策略下拉菜单中,ReAct 选项能否出现,取决于你选用的 LLM 是否原生支持 ReAct 输出格式。GPT‑4‑turbo、Qwen2.5‑72B‑Instruct、GLM‑4‑Flash 等新模型默认支持;而 Llama3‑8B、Phi‑3‑mini 这类轻量模型只支持 Function Calling 协议。
如果你已经选定 Llama3‑8B 作为底座模型,那么 ReAct 选项根本不会显示——这不是策略优劣的问题,而是模型能力不支持,强行配置只会静默失效。
同理,某些开源模型虽然标称支持 Function Calling,但实际输出中经常混入自然语言前缀,例如“我将为您调用天气查询函数…”,导致 Dify 无法解析 JSON。此时必须启用“输出清洗”开关,或者更换一个经过 Dify 官方适配的模型版本。
