Dify 插件开发实验(09):Agent策略插件——如何控制 Agent 的工具使用策略?
Dify 插件开发实验(09):Agent策略插件——如何控制 Agent 的工具使用策略?
Dify 实验系列 · 插件开发 09/12 | 实验编号:DIFY-106-09
基于 Dify 1.16.1 实测(2026-08)
1. 业务场景
先讲一个我们实际遇到的场景。
客服工单 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. 整体架构
链路很清晰:用户问题 → 策略循环(选工具/调工具/评估)→ 有答案或达上限结束。关键设计是硬约束在策略层拦截——达上限时不再把工具调用发给模型,直接强制结束并说明,而不是「建议」模型停止。
5. 模块设计
5.1 策略参数声明(strategies/limited.yaml)
自定义参数写在 strategy yaml 的 parameters 里,安装后即出现在 Agent 节点配置面板:
parameters:
- name: maximum_iterations
type: number
required: true
label:
zh_Hans: 最大迭代次数
default: 3
min: 1
max: 10
- name: force_retrieve
type: boolean
required: false
label:
zh_Hans: 强制先检索后回答
default: false
5.2 硬约束实现(strategies/limited.py)
达上限时不再把工具调用发给模型,直接拦截并说明:
for step in range(1, max_iter + 1):
result = self.session.model.llm.invoke(
model_config=model_config, prompt_messages=messages,
stream=False, tools=prompt_tools)
msg = result.message
tool_calls = msg.tool_calls or []
if not tool_calls:
yield self.create_text_message(msg.content or "")
return
# 达上限:不再调用工具,强制结束并说明(硬约束)
if step >= max_iter:
names = ", ".join(tc.function.name for tc in tool_calls)
yield self.create_text_message(
f"已达到最大工具调用次数上限({max_iter} 次),已停止调用工具"
f"(本次意图调用:{names})。请基于已有信息回答。")
return
5.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_iterations=1,构造连续调工具问题 | 策略直接拦截工具调用并说明 | ✅ 明确拦截(非建议模型停止) |
| 强制检索 | 知识类问题(工单/退款/钉钉) | 先调 external_retrieve 再回答 | ✅ 回答引用检索内容 |
| 双策略对照 | 6 题 × 双策略 | 行为差异表 | ✅ 延迟接近;知识题 limited 稳定先检索 |
对照结论:limited 的强制检索指令使知识题稳定先检索(业务约束生效);上限硬约束在构造场景下明确拦截——默认策略无此能力(模型自主决定,不受硬限制)。
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| sdk 版本锁(重要) | daemon uv sync 默认装 dify_plugin 0.10.0,AgentStrategy 接口不兼容,报 No module named ‘dify_plugin.invocations.storage’ | pyproject 锁 dify_plugin>=0.7.4,<0.8,daemon 装 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 直接拦截工具调用并说明(max=1 实测拦截成功) |
排障提示:策略执行错误出现在 run error 的 PluginInvokeError 里(无 traceback)——用「故意 raise 带类型信息」的探针 + daemon cwd 代码目录核对运行版本,是本实验实测有效的排障法。
8. 实验文档及源码获取
- 实验文档:DIFY-106-09:Agent策略插件.md
- 验证应用 DSL:dify106_09_验证对话.yml | dify106_09_对照默认策略.yml | dify106_09_上限验证.yml
- 插件安装包:dify106_09_agent_strategy.signed.difypkg
- 源码目录:dify-106/dsl | dify-106/plugins
文章聚焦核心配置与采坑点,完整分步操作与双策略对照实验记录见实验文档原文。
下一篇:Dify 插件开发实验(10):自定义节点扩展——不改平台代码,插件如何补节点能力?
💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。
更多推荐

所有评论(0)