Dify 插件开发实验(09):Agent策略插件——如何控制 Agent 的工具使用策略?

📅 2026/8/22 11:50:47
Dify 插件开发实验(09):Agent策略插件——如何控制 Agent 的工具使用策略?
Dify 插件开发实验09Agent策略插件——如何控制 Agent 的工具使用策略Dify 实验系列 · 插件开发 09/12 | 实验编号DIFY-106-09基于 Dify 1.16.1 实测2026-081. 业务场景先讲一个我们实际遇到的场景。客服工单 SaaS 的门户对话 Agent 上线前业务方提了两条硬要求第一限制工具调用次数——单轮最多 3 次工具调用防止模型反复试错把 token 烧光第二强制先检索后回答——涉及知识的问题必须先查知识库再回答不许凭空编。但默认的 Agent 策略把「要不要调工具、调几次」完全交给模型自主决定——模型试错多少次、先回答还是先检索业务方一概管不了。我们第一次接这类需求时第一反应也是「把约束写进系统提示词不就行了」。真正动手才发现——提示词只是「建议」不是「约束」模型在错误路径上反复试错一次对话烧掉几十次工具调用知识类问题不先检索就张口就来——业务方要的是「自主但有边界」这需要一个能落地的策略层而不是一段话术。这不是个例。任何要对外提供 Agent 服务的场景都是这个模式银行客服要限定查询次数防滥用、医疗问答要求必须先检索证据、电商助手要控制工具调用成本——「模型自主」和「业务可控」之间的平衡是企业定制 Agent 的常见诉求。2. 场景痛点这个流程的痛点在业务方身上体现得最直接工具调用次数不可控模型在错误路径上反复试错一次对话烧掉几十次工具调用成本失控。幻觉直接回答不先检索就回答知识类问题张口就来错误信息发给客户信任直接崩塌。业务约束无法落地业务方说「最多查 3 次」「先查再答」默认策略根本不认这些规则——约束写在需求文档里落不到系统里。行为不可观测模型为什么调这个工具、为什么不调过程不可见出问题无从排查。本质上默认策略给的是「模型自主」企业要的是「自主但有边界」——工具调用次数、检索顺序这类业务约束需要一个能落地的策略层。3. 方案为什么是 Agent 策略插件选 Agent 策略插件我们实际对比过官方扩展点agent-strategy 策略插件是 Dify 官方支持的扩展点自定义 Agent 推理循环选工具 → 调工具 → 评估结果 → 继续/结束不改平台代码硬约束软约束分层maximum_iterations是硬约束达上限强制结束不依赖模型自觉force_retrieve是软约束策略引导先检索最终由模型执行——约束可强可弱按业务需要配置可对照验证同一组问题双策略各跑一遍行为差异可量化业务方看得见约束是否生效。这篇文章我们就用它写一个limited策略插件maximum_iterations硬约束上限force_retrieve强制先检索复用 106-01 的 time_tool 与 106-08 的 retrieve_tool双策略对照验证约束是否真正落地。4. 整体架构【策略切换】DSL 三字段切换agent_strategy_provider_name / name / labellimited ↔ function_calling【插件结构】manifestplugins.agent_strategiesprovider/agent.yamlstrategies 列表strategies/limited.yamlparameters 声明strategies/limited.py_invoke 唯一抽象【验证应用agent】有答案达 maximum_iterations信息不足用户问题Agent 节点策略limited策略循环选工具 → 调工具 → 评估结果评估结果最终回答强制结束并说明硬约束不再调工具继续受次数上限约束链路很清晰用户问题 → 策略循环选工具/调工具/评估→ 有答案或达上限结束。关键设计是硬约束在策略层拦截——达上限时不再把工具调用发给模型直接强制结束并说明而不是「建议」模型停止。5. 模块设计5.1 策略参数声明strategies/limited.yaml自定义参数写在 strategy yaml 的 parameters 里安装后即出现在 Agent 节点配置面板parameters:-name:maximum_iterationstype:numberrequired:truelabel:zh_Hans:最大迭代次数default:3min:1max:10-name:force_retrievetype:booleanrequired:falselabel:zh_Hans:强制先检索后回答default:false5.2 硬约束实现strategies/limited.py达上限时不再把工具调用发给模型直接拦截并说明forstepinrange(1,max_iter1):resultself.session.model.llm.invoke(model_configmodel_config,prompt_messagesmessages,streamFalse,toolsprompt_tools)msgresult.message tool_callsmsg.tool_callsor[]ifnottool_calls:yieldself.create_text_message(msg.contentor)return# 达上限不再调用工具强制结束并说明硬约束ifstepmax_iter:names, .join(tc.function.namefortcintool_calls)yieldself.create_text_message(f已达到最大工具调用次数上限{max_iter}次已停止调用工具f本次意图调用{names}。请基于已有信息回答。)return5.3 参数类型转换策略接口收到的parameters[tools]/parameters[model]是 dict需要ToolEntity.model_validate/AgentModelConfig.model_validate转成实体官方FunctionCallingParams(**parameters)也是靠 pydantic 自动转。6. 运行验证验证项输入/场景预期结果注册安装策略插件工作流 Agent 节点可选 limited 策略✅ DSL 三字段可切换基线6 题知识×3/查询×2/闲聊×1默认策略全 succeeded✅ 1.3-4.6s自定义策略同 6 题 limited 策略全 succeeded✅ 1.6-4.5s上限硬约束maximum_iterations1构造连续调工具问题策略直接拦截工具调用并说明✅ 明确拦截非建议模型停止强制检索知识类问题工单/退款/钉钉先调 external_retrieve 再回答✅ 回答引用检索内容双策略对照6 题 × 双策略行为差异表✅ 延迟接近知识题 limited 稳定先检索对照结论limited 的强制检索指令使知识题稳定先检索业务约束生效上限硬约束在构造场景下明确拦截——默认策略无此能力模型自主决定不受硬限制。7. 实战坑坑现象修复sdk 版本锁重要daemon uv sync 默认装 dify_plugin 0.10.0AgentStrategy 接口不兼容报 No module named ‘dify_plugin.invocations.storage’pyproject 锁dify_plugin0.7.4,0.8daemon 装 0.7.4 与本地一致工具插件不触发 storage策略插件必须锁卸载字段plugin uninstall 传 plugin_installation_id 报 400用 installation_id 字段卸载重装实例残留卸载失败残留旧实例应用调旧代码错误特征不变正确字段卸载 清理 daemon cwd 目录 重装策略参数类型parameters[“tools”]/[“model”] 是 dict直接透传报类型错误ToolEntity.model_validate / AgentModelConfig.model_validate 转换上限是建议不是约束默认策略下模型自主决定工具调用次数不可控硬约束写在策略层达 maximum_iterations 直接拦截工具调用并说明max1 实测拦截成功排障提示策略执行错误出现在 run error 的 PluginInvokeError 里无 traceback——用「故意 raise 带类型信息」的探针 daemon cwd 代码目录核对运行版本是本实验实测有效的排障法。8. 实验文档及源码获取实验文档DIFY-106-09Agent策略插件.md验证应用 DSLdify106_09_验证对话.yml | dify106_09_对照默认策略.yml | dify106_09_上限验证.yml插件安装包dify106_09_agent_strategy.signed.difypkg源码目录dify-106/dsl | dify-106/plugins文章聚焦核心配置与采坑点完整分步操作与双策略对照实验记录见实验文档原文。下一篇Dify 插件开发实验10自定义节点扩展——不改平台代码插件如何补节点能力 你在这个实验的场景里踩过什么坑欢迎评论区分享你的实战经验。