【实战】Dify从0到100进阶--中药科普助手(2)
Agent策略
1.Function Calling
1. 定义
Function Calling 是一种“黑盒”式的工具调用方式:
- 模型在生成回复时,会在内容中“调用”预先注册的函数(Function)。
- 平台拦截到函数调用意图后,按需执行对应 Function,并将结果返回给模型,模型再基于结果生成最终回复。
2. 流程
-
函数注册:在 Agent 启动时,开发者将若干工具 API(如
get_yaocaiInfo
、get_weather
等)以 JSON Schema 的形式注册到 Agent。 -
意图识别:当用户输入到达模型,模型会判断是否需要调用函数,若需要则输出一段类似于:
{"name": "get_yaocaiInfo","arguments": { "name": "当归" } }
-
执行调用:Agent 将该调用片段拦截,依据
name
、arguments
调用对应后端 API,获取函数执行结果(如药材信息)。 -
结果回填:将函数返回值(JSON、文本等)传回到模型的上下文中,模型基于此内容继续生成对用户的自然语言回复。
3. 特点与优劣
- 优势
- 减少模型 hallucination:信息由外部 API 提供,准确且可控。
- 清晰的调用链路:模型意图→函数执行→结果回填。
- 易扩展:只需新增或更新函数定义,无需更改模型本身。
- 劣势
- 对话链路较长:调用→回填→二次生成,会增加响应延迟。
- 受限于注册时定义的 Schema,灵活度略低。
4. 典型场景
- 结构化数据查询:药材百科、天气、股票报价、数据库检索等。
- 确定性操作:下单、日程管理、文件读写等需要精确执行的任务。
2.ReAct(Reason + Action)
1. 定义
ReAct 是一种“透明”式的思考与行动融合策略,由模型在同一次生成中同时输出:
- Thought(思考链路):模型的中间推理过程,用于决策下一步做什么。
- Action(动作指令):模型直接发出对外部工具或 API 的调用请求。
- Observation(观察结果):Agent 工具执行后的即时反馈。
通过多轮 Thought–Action–Observation 循环,最终由模型给出回答。
2. 流程
-
初始思考:模型针对用户提问,先输出一个思考(Thought),如“要查询当归功效,先调用药材信息接口”。
-
动作输出:接着输出对应的 Action,例如:
Action: get_yaocaiInfo(name="当归")
-
工具执行:Agent 执行该动作,并将 Observation(API 返回的 JSON 或文本)附加到对话上下文。
-
循环迭代:模型读取 Observation,可能继续新的 Thought/Action,直到得出最终回答(Answer)。
-
结束回答:模型在最后一步输出清晰、连贯的 Answer,呈现给用户。
3. 特点与优劣
- 优势
- 强可解释性:中间所有的思路(Thought)都对外可见,便于审计和调试。
- 灵活性高:模型可根据 Observation 自由决定下一步动作,支持复杂多步骤任务。
- 劣势
- 对话冗长:中间多次 Thought/Action 循环,响应体积较大,不利于极简场景。
- 需要较强的模型推理能力,否则容易出现多余或无效循环。
4. 典型场景
- 复杂决策流程:多步检索、多源信息融合、动态规划类任务。
- 调试与监控:需要全链路可视的场景,如安全审计、训练评估。
3.选型建议
维度 | Function Calling | ReAct |
---|---|---|
交互速度 | 较快(单次调用+回填) | 较慢(多轮思考—行动—观察循环) |
实现难度 | 低,只需注册函数与 Schema | 中,需要设计思考—行动模板与管理循环终止条件 |
可解释性 | 较低,隐藏了模型中间推理 | 较高,完整暴露模型思考过程 |
任务复杂度 | 适合单步查询或简单操作 | 适合多步骤、需动态决策的复杂任务 |
Hallucination 风险 | 低,调用真实数据源 | 中等,若循环不当可能出现误调用或无效调用 |
- 简单信息查询、确定性任务:优先选用 Function Calling。
- 复杂多步推理、审计可视化:推荐使用 ReAct,可全面暴露中间链路。
MCP工具
1. 插件概述
- 名称:MCP SSE / StreamableHTTP
- 版本:0.2.1
- 仓库:
github.com/junjiem/mcp_sse
- 作用:通过 HTTP 的 SSE(Server‑Sent Events) 或 Streamable HTTP 方式,实现 Dify Agent 与后端工具服务(MCP Server)之间的发现与调用。
- 定位:将后端各类微服务、AI 模型推理接口、业务 API 等封装为“工具”,由 Agent 在对话中动态调用。
2. 核心能力
该插件主要提供两类 Action:
Action 名称 | 描述 |
---|---|
get_tool_list | 向 MCP 服务端查询并获取可用的工具列表。 |
call_tool | 按指定工具名和参数,向 MCP 服务端发起调用,并接收返回结果(可流式返回)。 |
两者结合即可让 Agent 在对话流程中,自动发现、动态调用、并实时处理后端工具。
3. 传输协议
-
SSE(Server‑Sent Events)
- 单向、持久的服务器消息推送通道。
- 客户端发起一次 HTTP 请求后,与服务端建立长连接,服务端通过这条连接不断推送 JSON 格式的事件。
- 适合 流式响应(如实时日志、流水线输出)或需要即时多步反馈的场景。
-
Streamable HTTP
- 基于标准 HTTP/1.1 的分块传输编码(Chunked Transfer Encoding)。
- 服务器将响应分为多个 chunk 逐个发送,客户端逐 chunk 处理并呈现。
- 兼容性更高,适合稍微简单的流式数据场景。
两者在插件中可二选一,也可同时支持,视后端实现及 Agent 需求而定。
4. 配置示例
在 Dify Agent 的配置文件中,只需声明一段 mcpServers
即可接入:
{"mcpServers": {"zhongyao_mcp": {"type": "sse", // 或 "streamable_http""url": "http://10.16.7.24:8003/sse",// 以下为可选字段:// "headers": { "Authorization": "Bearer <token>" },// "timeout": 120000 // 毫秒}}
}
type
:支持"sse"
或"streamable_http"
url
:后端 MCP 服务地址headers
/timeout
:可选,用于接入需要认证或自定义超时的场景
5. 两大 Action 详解
5.1 get_tool_list
-
请求方式:长连接(SSE)或分块 HTTP
-
请求内容:
{ "action": "get_tool_list" }
-
返回示例:
{"tools": [{"name": "get_yaocaiInfo","description": "根据中药名获取药材信息","parameters": { /* JSON Schema 定义 */ }},{"name": "get_yaocai_image","description": "生成中药材图片","parameters": { /* JSON Schema 定义 */ }}// …更多工具…] }
-
应用:Agent 启动时或定期调用,自动加载最新工具列表,动态构建 FunctionCalling/ReAct 的可调用函数集。
5.2 call_tool
-
请求方式:同上
-
请求内容:
{"action": "call_tool","tool_name":"get_yaocaiInfo","arguments": { "name": "当归" } }
-
流式响应:
如果工具后端按 SSE/Chunked 分片返回,则客户端可边取边用,每收到一段就进行 Observation 注入。
工具Tool
import os
import json
import requests
from fastmcp import FastMCP
from volcenginesdkarkruntime import Ark# MCP 服务初始化
mcp = FastMCP("ZhongyiServer", port=8003)# 获取 SiliconFlow API Key(建议通过环境变量 SILICONFLOW_API_KEY 管理)
SILICONFLOW_API_KEY = os.getenv("SILICONFLOW_API_KEY", "XXXXXXX")# 初始化豆包(doubao)Ark 客户端
# 请确保您已将 API Key 存储在环境变量 ARK_API_KEY 中
doubao_client = Ark(base_url="https://ark.cn-beijing.volces.com/api/v3",api_key="XXXXXXX",
)# 通用 Header
HEADERS = {"Authorization": f"Bearer {SILICONFLOW_API_KEY}","Content-Type": "application/json"
}@mcp.tool()
async def get_yaocaiInfo(yaocai: str):"""输入药材名称,调用硅基流动大模型返回中药材信息(JSON 格式)。"""try:url = "https://api.siliconflow.cn/v1/chat/completions"payload = {"model": "Qwen/Qwen2.5-VL-72B-Instruct","messages": [{"role": "system", "content": "你是一个专业的中医药专家,请提供准确的中药材信息。"},{"role": "user", "content": (f"请以 JSON 格式返回关于 \"{yaocai}\" 的中药材信息,包含字段:""name(药材名称)、property(药性)、taste(药味)、meridian(归经)、function(功效主治)、usage(用法用量)。")}]}resp = requests.post(url, headers=HEADERS, json=payload, timeout=60)resp.raise_for_status()content = resp.json()["choices"][0]["message"]["content"]try:return json.loads(content)except json.JSONDecodeError:return {"error": "无法解析为 JSON", "raw": content}except Exception as e:return {"error": str(e)}@mcp.tool()
async def get_yaocai_image(yaocai: str):"""输入药材名称,调用豆包的 doubao-seedream 模型生成并返回图片 URL。"""try:# 调用豆包图生模型images_response = doubao_client.images.generate(model="doubao-seedream-3-0-t2i-250415",prompt=(f"高清晰度的{yaocai}中药材实物图片,白色背景,清晰展示其颜色、形状和质地。"))# 从返回结果中提取 URLdata = images_response.data or []if data and data[0].url:return {"success": True,"yaocai": yaocai,"image_url": data[0].url}else:return {"success": False,"yaocai": yaocai,"error": "无效的图像响应"}except Exception as e:return {"success": False,"yaocai": yaocai,"error": str(e)}if __name__ == "__main__":# 监听所有网卡并启动 SSE 服务mcp.settings.host = "0.0.0.0"mcp.settings.port = 8003mcp.run(transport="sse")
1. 导入与依赖初始化
import os
import json
import requests
from fastmcp import FastMCP
from volcenginesdkarkruntime import Ark
os
/json
/requests
:基础的环境变量读取、JSON 处理和 HTTP 请求库。fastmcp.FastMCP
:来自 FastMCP 的核心类,用于快速创建并注册 MCP 工具(micro‑chain plugin)并启动服务。volcenginesdkarkruntime.Ark
:字节跳动(火山引擎)的 Ark 客户端,用于调用豆包(Doubao)相关模型,比如图像生成。
2. MCP 服务初始化
# MCP 服务初始化
mcp = FastMCP("ZhongyiServer", port=8003)
FastMCP("ZhongyiServer", port=8003)
:- 第一个参数
"ZhongyiServer"
是本服务的名字。 port=8003
指定了 MCP 服务监听的默认端口。
- 第一个参数
3. API Key 管理
SILICONFLOW_API_KEY = os.getenv("SILICONFLOW_API_KEY", "XXXXXXX")
- 从环境变量
SILICONFLOW_API_KEY
中读取 SiliconFlow(硅基流动大模型)所需的 API Key,若未设置则使用"XXXXXXX"
作为占位。 - 建议在生产环境中通过环境变量(或密钥管理服务)来管理,而不要把真实 Key 硬编码在代码里。
4. 豆包(Doubao)Ark 客户端初始化
doubao_client = Ark(base_url="https://ark.cn-beijing.volces.com/api/v3",api_key="XXXXXXX",
)
base_url
:豆包 Ark 服务的接口地址。api_key
:此处也要替换为环境变量中读取的真实 Key(如os.getenv("ARK_API_KEY")
)。
5. 通用请求头
HEADERS = {"Authorization": f"Bearer {SILICONFLOW_API_KEY}","Content-Type": "application/json"
}
- 用于向 SiliconFlow 的聊天接口发送 HTTP 请求时,提供身份认证和声明请求体格式。
6. 定义 MCP 工具:get_yaocaiInfo
@mcp.tool()
async def get_yaocaiInfo(yaocai: str):"""输入药材名称,调用硅基流动大模型返回中药材信息(JSON 格式)。"""try:url = "https://api.siliconflow.cn/v1/chat/completions"payload = {"model": "Qwen/Qwen2.5-VL-72B-Instruct","messages": [{"role": "system", "content": "你是一个专业的中医药专家,请提供准确的中药材信息。"},{"role": "user", "content": (f"请以 JSON 格式返回关于 \"{yaocai}\" 的中药材信息,包含字段:""name(药材名称)、property(药性)、taste(药味)、meridian(归经)、function(功效主治)、usage(用法用量)。")}]}resp = requests.post(url, headers=HEADERS, json=payload, timeout=60)resp.raise_for_status()content = resp.json()["choices"][0]["message"]["content"]try:return json.loads(content)except json.JSONDecodeError:return {"error": "无法解析为 JSON", "raw": content}except Exception as e:return {"error": str(e)}
- 装饰器
@mcp.tool()
:把此异步函数注册为 MCP 工具,外部可以通过 MCP 协议调用get_yaocaiInfo
。 - 功能:向 SiliconFlow 的聊天接口发起请求,请求模型以 JSON 格式返回指定药材的详细信息。
- 错误处理:
- HTTP 异常与超时捕获后返回
{"error": ...}
。 - 如果 API 返回的文本无法解析为合法 JSON,也会反馈原始内容。
- HTTP 异常与超时捕获后返回
7. 定义 MCP 工具:get_yaocai_image
@mcp.tool()
async def get_yaocai_image(yaocai: str):"""输入药材名称,调用豆包的 doubao-seedream 模型生成并返回图片 URL。"""try:images_response = doubao_client.images.generate(model="doubao-seedream-3-0-t2i-250415",prompt=(f"高清晰度的{yaocai}中药材实物图片,白色背景,清晰展示其颜色、形状和质地。"))data = images_response.data or []if data and data[0].url:return {"success": True, "yaocai": yaocai, "image_url": data[0].url}else:return {"success": False, "yaocai": yaocai, "error": "无效的图像响应"}except Exception as e:return {"success": False, "yaocai": yaocai, "error": str(e)}
- 装饰器
@mcp.tool()
:同样注册为 MCP 工具。 - 功能:调用豆包图像生成模型(
doubao-seedream
)生成指定药材的实物图,并提取返回的 URL。 - 返回格式:
- 成功时
{ "success": True, "yaocai": ..., "image_url": ... }
- 失败时带错误信息。
- 成功时
8. 启动 MCP SSE 服务
if __name__ == "__main__":# 监听所有网卡并启动 SSE 服务mcp.settings.host = "0.0.0.0"mcp.settings.port = 8003mcp.run(transport="sse")
host = "0.0.0.0"
:监听所有网络接口,外部机器可访问。transport="sse"
:使用 Server-Sent Events(SSE)协议,支持单向实时推送,适合分布式微服务之间的轻量级消息调用。- 最终在 8003 端口 启动一个 SSE 服务,其他服务或前端就可以通过 MCP 协议(HTTP+SSE)调用你定义的两个工具。