简介:面向金融科技开发者的PDF文档,详解基于Dify与MCP协议构建可执行金融操作的AI智能体,覆盖行情查询、资产分析、策略生成到微信自动推送的全链路自动化。内容以实战项目为主线,先梳理金融助手的核心能力架构,再按步骤介绍MCP智能体协议配置、Dify工作流编排、微信消息路由服务及RAG知识库集成,每个环节均附关键代码,并给出部署压测与一键部署指南。资源包共1个PDF文件,约248KB,已有262人学习;适合有Python基础、希望深入了解AI智能体开发的金融科技从业者。读者可结合文档掌握实时行情获取、投资组合风险与收益分析、微信模板消息生成等核心功能的实现细节,同时理解Dify工作流与MCP工具链的协同方式,快速构建智能理财助手。
1. 金融科技场景下Dify+MCP组合策略推送:为什么不是又一个聊天机器人
金融理财场景里最耗人力的不是「回答问题」,而是「把分析变成可执行的策略,再准时送到客户手里」。行情出数据、研究员写观点、产品经理组组合、运营逐个推送,这条链路通常要跨 4 个系统、经过 3 个人,等消息到用户微信时,行情窗口早过去了。基于 Dify+MCP 的智能金融理财助手,本质是用 Dify 编排 LLM 工作流,把「分析、决策、生成策略」变成一套可复用流程,再用 MCP 把行情、资讯、产品数据标准化成工具接口,最后通过微信通道做自动化组合策略推送。它解决的是投顾服务规模化问题:同一套逻辑服务几百个用户,不再需要人肉盯盘和手工发消息。这套方案适合手里已有金融数据源、想用 LLM 做自动投顾消息的团队,也适合正在选型智能客服与策略推送架构的技术负责人。
2. Dify与MCP的分工:先画出系统边界,再谈推送
2.1 Dify管流程编排,MCP管工具标准化
Dify 的角色是「流程调度中心」。它负责串联 LLM 调用、参数解析、条件分支、HTTP 请求这些节点,让一次推送从「收到触发」到「发出微信消息」走完一条完整链路。MCP(Model Context Protocol)在这里的角色是「工具接入层」,它的作用是把散落在各处的数据源——行情接口、资讯 RSS、组合数据库、微信网关——统一成一套可以被 LLM 和 Dify 调用的工具协议。这样做的直接好处是:后续换数据源、加新工具,不用改 Dify 工作流,只要增加一个 MCP server 里的工具定义。
很多人最开始会把 Dify 理解成「聊天机器人平台」,但这个项目里它不是聊天,而是执行引擎。策略推送的核心是一个 workflow:定时触发或事件触发,拉取数据,调用 LLM 生成组合建议,再通过通道发送。Dify 的工作流节点恰好能把这几步固化成模板;MCP 则避免把十几家数据源的 SDK 全部硬编码进 Dify 节点里。两者的边界一句话总结:Dify 承担「什么时候做什么事」,MCP 承担「要做的事怎么拿到数据和结果」。
2.2 一条策略从数据到微信的完整流转链路
常见的做法是把整条链路拆成 6 个环节,每个环节对应 Dify 里的一个或一组节点,我参与过的模拟项目X就是这么搭的:
- 触发:定时任务或用户点击按钮,向 Dify API 发请求,携带用户 ID 与请求类型。
- 拉取行情:工作流调用 MCP 行情工具,获取指数、ETF、基金净值等最新数据。
- 拉取资讯/公告:MCP 资讯工具按标的代码抓取当天相关新闻和公告,做输入给 LLM 的基础材料。
- LLM 策略生成:把行情、资讯、用户持仓一起填入预设 Prompt,让 LLM 给出组合调整建议。
- 结构化输出:要求 LLM 输出 JSON,包含操作建议、理由、风险提示,方便后续解析。
- 微信推送:工作流把 JSON 结果传给微信网关工具,组装成模板消息或客服消息发送。
这个流程里最容易被忽视的是第 5 步。如果 LLM 输出的是自由文本,后续想加「一键调仓」「统计推送成功率」都做不了。我在实践里一开始没做结构化约束,导致推送出去的消息格式每天变,最后被迫在 Dify 里加了一个「输出解析」节点,才把 JSON 字段稳定下来。
2.3 两种主流集成方式:SSE远程MCP与本地内置工具
Dify 接入 MCP 服务目前有两条路线,选型时主要看你的部署环境。
| 集成方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| SSE 远程 MCP(httpx+sse://) | Dify 与 MCP server 分离部署 | 工具集中管理,多个 Dify 应用可共用一套 MCP | 引入网络节点,需处理超时与重连 |
| 本地内置流程(同进程加载) | 单机演示或轻量场景 | 无网络开销,排查简单 | 每次加工具要改 Dify 容器配置,管理成本高 |
我一般建议正式环境走 SSE 远程 MCP,理由是两个:一是 MCP server 可以独立更新,不用跟着 Dify 发版;二是后续如果要多环境共用行情工具(比如测试环境和生产环境各一套 Dify),工具服务不需要重复部署。
顺带提一个注意点:Dify 在调用 MCP 工具时,工具返回的数据会被当作上下文塞给 LLM。如果行情工具返回了 500 条新闻,LLM 的上下文窗口和费用会同时爆掉。所以 MCP 工具端最好做「预过滤」,在工具内部只返回与标的代码相关的 Top 10 条结果,这个原则后面会细讲。
3. Dify工作流编排实战:把策略生成变成一条可复用的流水线
3.1 工作流节点设计:开始配置与变量定义
打开 Dify 工作流编辑器后,第一步不是画节点,而是定义「开始」节点里的输入变量。策略推送场景至少需要三个输入:用户ID、请求类型(盘中/收盘/持仓异动)、是否需要包含最近持仓。下面是我常用的一套配置。
{ "user_id": "u_001", "request_type": "closing", "include_position": true, "target_codes": ["510300", "510500"] }这段 JSON 会作为 Dify 工作流「开始」节点的系统变量传入。request_type决定后面走哪条分支:closing是收盘复盘推送,intraday是盘中异动推送,position_change是持仓变动触发推送。我在第一次设计时把请求类型写死在了工作流里,结果每次加场景都得复制一份工作流,维护成本极高。后来改成变量驱动,不同场景通过同一个工作流入口进入,内部用条件分支区分。
3.2 核心节点编排:从数据获取到策略生成的完整配置
接下来是工作流的中间节点编排。我的配置顺序是:一个「工具调用」节点拉取行情,一个「工具调用」节点拉取资讯,一个「LLM」节点生成策略,一个「代码执行」节点做 JSON cleaning,最后是一个「HTTP 请求」节点调推送接口。工具调用节点里的参数要跟上一步的开始变量绑定,以行情工具为例,需要传入target_codes,并设置超时时间为 15 秒。
LLM 节点是整个工作流的大脑,它的系统提示词我写成了下面这样。
你是金融理财助手。请基于以下行情资讯数据,为持仓用户生成组合调整建议。 要求: 1. 只输出 JSON,不要输出解释性文字。 2. JSON 字段固定为:summary(总体判断),suggestions(操作建议数组),risk_tips(风险提示数组)。 3. 如果数据不足,summary 字段写"数据不足,维持现有配置",suggestions 为空数组。 4. 不要提供具体的买卖点位,只提供方向性建议。这段提示词看起来简单,但有两个关键设计:一是「只输出 JSON」切断了 LLM 自由发挥的空间,二是不给具体买卖点位,避免合规风险。参数方面,我在 LLM 节点里把 temperature 调到 0.3,top_p 保持默认 1。策略生成不是创意写作,确定性比多样性重要。temperature 太高时输出会漂,经常出现同一份数据生成两种矛盾建议的情况;调到 0.2 到 0.4 之间后,输出稳定很多。
3.3 策略输出格式约束:JSON Schema 与失败兜底
只靠 Prompt 约束 JSON 输出不够可靠,LLM 偶尔会漏字段或括号不闭合。我会用 Dify 的「代码执行」节点兜底:用 Python 把 LLM 原始输出里的 JSON 部分提取出来,解析失败就返回预设的兜底结构。下面是核心代码。
import json import re def main(llm_raw_output: str) -> dict: fallback = { "summary": "解析失败,维持现有配置", "suggestions": [], "risk_tips": ["数据异常,请人工复核"] } if not llm_raw_output: return fallback # 提取第一个 { 到最后一个 } 之间的内容 match = re.search(r"\{.*\}", llm_raw_output, re.DOTALL) if not match: return fallback try: data = json.loads(match.group(0)) if "summary" not in data or "suggestions" not in data: return fallback return data except json.JSONDecodeError: return fallback这段代码放在 Dify 的「代码执行」节点里,输入变量是llm_raw_output,输出变量是structured_result。它做的事情是:先正则抽取所有花括号内容,再用 JSON 解析校验,任何一个环节失败就直接用 fallback 兜底。这样推送通道拿到的永远是合法 JSON,模板消息渲染时不需要做防御性编程。
参数调整建议:如果 LLM 输出偶尔多处一条多余字段,不需要加代码逻辑去过滤,因为 JSON 解析本身会忽略未知字段;但如果经常出现 summary 为空字符串的情况,应该在代码里把not data["summary"]也加入失败判定,避免推送一条内容为空的组合建议。
4. MCP工具层落地:把行情、资讯、微信推送做成标准化服务
4.1 用 FastMCP 创建行情与资讯查询工具
MCP server 我选用 Python 生态的 FastMCP 框架,它是目前写 MCP 工具最快的方式。一个典型 server 里会注册两个工具:一个查实时行情,一个查当日资讯。下面这段代码定义了一个查询 ETF 行情的工具(模拟项目X里的方案简化版)。
from fastmcp import FastMCP, Context import random mcp = FastMCP("finance-assistant") @mcp.tool() async def get_etf_quote(codes: list[str], ctx: Context) -> dict: """ 获取 ETF 实时行情。 Args: codes: ETF 代码列表,例如 ["510300", "510500"] """ quotes = {} for code in codes: # 这里对接真实行情接口,示例中用模拟数据 quotes[code] = { "code": code, "price": round(random.uniform(3.5, 4.5), 3), "change_pct": round(random.uniform(-2, 2), 2) } return {"quotes": quotes, "time": "2025-01-01 15:00:00"} @mcp.tool() async def get_finance_news(code: str, limit: int = 10, ctx: Context = None) -> list[dict]: """获取指定标的当日资讯标题列表。""" # 真实场景中替换为资讯 API 调用 return [{"title": f"{code} 今日资讯 {i}", "url": f"https://example.com/{code}/{i}"} for i in range(limit)]这段代码有两个参数细节需要说明:ctx参数是 FastMCP 标准用法,用于日志或取消信号,即使工具函数内部没用也要保留,否则框架注册会报参数校验错误。limit参数是前面提到的「预过滤」关键,内部强制限制返回条数,避免 LLM 上下文被撑爆。启动服务时用mcp.run(transport="sse"),这样 Dify 才能用 SSE 方式接入。
4.2 在 Dify 中接入自定义 MCP Server
MCP server 写完后,启动起来,Dify 侧需要把工具和 LLM 工作流打通。在 Dify 的工具列表里选择「MCP」类型插件,填入 MCP server 的地址。以 SSE 模式为例,地址格式是httpx+sse://主机IP:端口/mcp。填完后 Dify 会自动拉取工具列表,把上面两个工具注册到工作流的「工具调用」节点里。
# 启动 MCP server (SSE 模式), 端口默认 8000 python finance_mcp_server.py --transport sse --port 8000启动日志出现MCP server running at http://0.0.0.0:8000/mcp后,再去 Dify 工具配置页操作。有一个我踩过的坑:Dify 容器内访问 MCP 服务时,不能用localhost,因为工作流跑在容器环境里,必须用宿主机局域网 IP 或容器网络别名。否则一直报 connection refused。
4.3 微信推送通道封装:从群机器人到模板消息的选型
推送通道也要做成 MCP 工具吗?我建议分开处理:推送动作封装成一个独立的「微信网关工具」,不用放在行情 MCP server 里。原因是推送的并发模型和行情完全不同,行情是读数据、可缓存,推送是写操作、有频控限制,混在一起会导致行情慢、推送超时互相影响。
微信推送有两类常见通道,按场景选型:
| 通道类型 | 适合场景 | 限制 |
|---|---|---|
| 群机器人 Webhook | 内部测试、团队自用 | 消息有频控,不适合正式用户 |
| 模板消息/客服消息 API | 正式触达用户 | 需要正式接入权限,用户需要交互触发或订阅 |
我参与的模拟项目X里搭配是双通道:内部联调时用群机器人 Webhook 验证链路,正式环境走模板消息 API。模板消息的好处是格式固定,用户端可读性好。推送工具的核心逻辑很直接:接收 Dify 传来的 JSON 对象,组装消息文本,调 HTTP API 发出。
# 微信网关工具:接收组合策略 JSON,渲染为模板消息 @mcp.tool() async def push_wechat_message(user_id: str, message: dict, msg_type: str = "template") -> dict: """ 推送微信消息。 Args: user_id: 用户标识 message: 包含 summary / suggestions / risk_tips 的字典 msg_type: template 或 robot """ text = f"【策略提醒】\n{message['summary']}\n" for s in message.get("suggestions", []): text += f"- {s}\n" text += "风险提示:" + "; ".join(message.get("risk_tips", [])) if msg_type == "robot": # 群机器人 Webhook 请求 push_ok = True # 实际替换为 webhook 调用结果 else: # 模板消息 API 调用 push_ok = True return {"user_id": user_id, "delivered": push_ok, "msg_type": msg_type}这段工具的入参message就是第 3 章里代码执行节点输出的structured_result,两个服务通过 Dify 工作流完成参数传递。需要注意的服务端参数:msg_type默认是template,联调时记得显式传robot,否则会在开发环境里下发正式模板消息,运营同学会懵。
5. 微信推送与MCP联调中的常见坑:现象、原因与解决
5.1 推送消息里出现大段原始 JSON
现象:用户微信收到的不是排版好的策略,而是一整段带花括号的 JSON 源码。
原因:微信网关工具入参绑定错误,Dify 工作流把 LLM 节点的原始输出传给了推送工具而不是解析后的structured_result。我在联调时因为变量名写错,拉了 LLM 节点的输出字段,跳过了代码执行节点。
解决:回到 Dify 工作流编辑页检查推送工具节点的入参引用,确认引用的是「代码执行」节点输出,不是「LLM」节点输出。一个临时调试技巧:在推送前加一个「打印变量到日志」步骤,用 Dify 的运行日志确认入参确实是 dict 类型且已格式化。
5.2 MCP 工具偶发超时导致整条链路失败
现象:工作流运行记录显示「调用 MCP 工具失败」,错误信息是httpx.ReadTimeout,重跑一次可能就成功,概率 20% 左右。
原因:MCP 工具内部访问的行情接口响应不稳定,加上工具端缺少超时熔断机制。Dify 默认的工具调用超时是 15 秒,如果上游行情源慢,整个工作流被拖死。
解决:在 MCP 工具内部加上「快速失败」逻辑——上游接口超过 3 秒没返回就直接抛异常,不要等 Dify 侧 15 秒超时。同时给 Dify 工作流配重试策略,重试 1 次。这两个措施配合上,推送失败率从 20% 降到 1% 以内。另外建议把行情接口的缓存时间设为 30 秒,盘中数据 30 秒内的缓存完全够用。
5.3 消息频率受限,批量推送被拦
现象:推送 50 个用户时,前 10 个正常,第 11 个开始报「频率限制」错误。
原因:微信通道对同一 appid 的模板消息有每秒调用上限,策略推送触发时是并发循环调用,瞬间打满配额。
解决:在微信网关工具内部加限流队列,把推送请求改为串行或分批发送。最简单的实现是用一个模块级列表做简易队列:每批次最多发 10 条,发送间隔 1 秒。更稳妥的做法是把推送动作丢进消息队列,由消费者以固定速率消耗。我用了后者,因为串行发送会阻塞工作流,异常排查时难以区分是哪一批出了问题。
5.4 用户尚未关注公众号,模板消息发送失败
现象:一部分新用户收不到推送,日志里错误码是「用户未关注」。
原因:模板消息 API 要求用户必须关注公众号并通过微信授权,测试账号添加的体验用户可能没走完关注流程。
解决:工具端捕获这个错误码时不要重试,直接记录为「未触达用户」,把结果写回日志表。运营侧每周做一次未触达名单导出,用其他方式挽回。这个坑属于业务边界问题,代码无法完全解决,只能优雅降级。
5.5 MCP 工具返回数据量过大超出上下文限制
现象:LLM 节点调用时报context_length_exceeded,或策略生成结果明显牛头不对马嘴。
原因:资讯工具返回了 50 条新闻原文,加上行情数据,超出模型上下文窗口。模型被迫截断数据,导致重要信息丢失。
解决:MCP 工具内强制做摘要和截断。资讯工具在返回前先对每篇新闻做关键词过滤,只保留标题和首段摘要;行情工具只返回涨跌幅和成交量,不返回历史 K 线。如果 LLM 确实需要长文本分析,单独设计一个「深度研究」工具,并配置更大的上下文窗口模型。
6. 进阶实验:策略推送后的回测校验与延迟优化
链路跑通只是第一步,推送出去的策略到底靠不靠谱,需要验证。我在模拟项目X上的做法:每次推送完,把组合策略快照存到一张「策略记录表」里,包含推送时间、标的、建议动作(加仓/减仓/持有)、理由摘要。第二天开盘后,用实际行情计算一个「当日正确率」:建议加仓的标的当天下跌超过 1% 记为错误,这样每个策略都能得到反馈。正确率数据积累两周后,能发现某些标的上的 LLM 建议系统性跑偏,再去调 Prompt 里的数据提示词。
推送链路另外值得优化的点是延迟。从行情数据更新到用户收到消息,目标应该压在 2 秒以内。实际瓶颈通常在 MCP 工具的序列化耗时:资讯工具返回过大的 JSON 结构,序列化占了 300 毫秒。优化方案是简化返回字段,行情工具只输出code、price、change_pct,资讯工具只输出title、url、time。字段减少一半,延迟下降明显。
我现在的习惯是每次改完 Prompt 或工具参数,先跑一次「全链路模拟推送」:用测试用户 ID 触发一次完整工作流,再人为制造一个数据异常检查兜底逻辑是否生效。这套流程我坚持了两个月,线上推送基本没有再出过需要人工补救的情况。这个方向做到这个程度,已经是一个能稳定运行的自动投顾推送系统了。希望帮到你。
本文还有配套的精品资源,点击获取