从 RAG 到 Multi-Agent:手把手教你搭建架构级金融智能体 —— 解决 7B 模型幻觉、引入代码解释器与 Supervisor 模式的全流程复盘
文章目录
- 前言
- 一、为什么金融场景更需要 Agent 而不是 Chatbot?
- 二、拒绝胡乱计算:给大模型装上 Python 代码解释器
- 三、从“搜得到”到“搜得准”:RAG 混合检索与重排序
- 四、告别单兵作战:基于 LangGraph 的 Supervisor 多智能体架构
- 五、那些年我踩过的坑:Local LLM 的鲁棒性工程
- 坑位一:习惯性偷懒的“哑巴 Agent”
- 总结与展望:构建更智能的金融助理
- 未来计划
前言
你是否也遇到过这样的尴尬:问大模型“帮我算一下这只基金的夏普比率”,它一本正经地胡说八道?或者让它画一张 K 线图,它却只能给你吐出一堆文字描述?
在金融场景下,“大概对”就是“完全错”。单纯的 RAG 虽然能找资料,但缺乏严谨的计算能力;单纯的 Prompt 工程,又无法处理复杂的长链条任务。为了解决上述问题,我基于 LangGraph + Qwen-7B,将一个简单的 Demo 逐步重构为一套 “架构级” 的 Multi-Agent 金融系统。它不仅装上了 Python 代码解释器(能算数、能画图),还拥有了 Re-rank 重排序(查得准)和 Supervisor 多智能体协作(分工明确)。
通过本文,你将解锁以下技能点:
1.架构设计:如何用 LangGraph 实现 Supervisor 多智能体路由模式。
2.代码解释器:如何让大模型安全地运行 Python 代码并生成图表。
3.RAG 进阶:引入 Cross-Encoder 重排序模型,让检索准确率翻倍。
4.鲁棒性工程:解决 7B 小模型常见的“偷懒”、“幻觉 JSON”和“上下文污染”问题。
一、为什么金融场景更需要 Agent 而不是 Chatbot?
在开始写代码之前,我想先聊聊为什么我们要折腾这个项目。
如果你尝试过直接用 ChatGPT 或本地部署的 Llama/Qwen 做金融分析,你大概率会遇到一种“一本正经胡说八道”的尴尬。
举个真实的翻车现场:
当我问模型:“请帮我计算一下某只基金近三年的夏普比率,并画出它的收益率曲线。”
普通 Chatbot 的反应: 它会立刻给你列出一个公式,然后凭空捏造几个数字代进去,最后告诉你一个看起来很专业、但实际上完全错误的答案。至于画图?它只能用文字描述:“这里应该有一条向上的曲线……”
如下图:

大家看这张图,乍一看是不是觉得分析得头头是道?连字符画都给我整出来了!但仔细一扒:12.5% 的年化收益率完全是它产生的‘幻觉’(实际这只基金近三年可能是负收益);公式虽然列对了,但数据是瞎编的;而那个 ASCII 码拼出来的折线图,更是让人哭笑不得。这就是典型的‘文科生做算术’——逻辑全靠猜,画图全靠脑补。
这背后暴露了通用大模型在垂直领域落地的三大硬伤:
- 算不准:概率生成的先天缺陷
大模型的本质是“文字接龙”。它是一个文科生,它的“计算”其实是在“背诵”它训练数据里见过的数字组合,而不是真的在做逻辑运算。让一个基于概率的模型去做基于逻辑的数学题,不仅效率低,而且极其容易产生幻觉。金融需要的是确定性的计算,而不是概率性的生成。 - 查不准:模糊语义的干扰
传统的 RAG(检索增强生成)大多依赖向量相似度检索。但在金融领域,很多专有名词在语义上极度相似,但含义天差地别。
例如,搜索“000001”,向量模型可能会同时拉回“平安银行(股票)”和“某债券代码”的信息,因为它分不清这串数字在金融语境下的精确指代。这种语义混淆会导致模型读取了错误的财报,进而得出离谱的结论。 - 理不清:长链条任务的上下文污染
真实的金融分析往往是一个长链条任务:
“先查一下最新的 CPI 数据(动作 A),结合 A 股今天的走势(动作 B),分析对债市的影响(动作 C),最后画个图(动作 D)。”
在一个单体 Agent 中,所有这些步骤的中间结果、报错信息、推理过程全部堆积在同一个 Context Window(上下文窗口)里。随着对话轮数的增加,模型很容易“晕头转向”,忘记了自己进行到了哪一步,或者把动作 A 的数据张冠李戴到了动作 C 上。
破局之道:从 Chatbot 进化为 Agent 团队
既然单体模型搞不定,那我们就组建一个团队。
我决定基于 LangGraph 和本地部署的 Qwen-7B,构建一个架构级的金融智能体集群。我将它设计为 Supervisor(主管-员工)模式:
大脑(Brain): 引入 Python 代码解释器,把计算任务外包给 Python,保证 100% 准确。
眼睛(Eyes): 引入 Rerank 重排序机制,让检索像手术刀一样精准。
手脚(Hands): 利用多智能体分工,把复杂的任务拆解给专人去做。
接下来的文章,我将手把手带你还原这个系统的搭建过程,从最基础的代码解释器开始,一步步点亮它的技能树。
二、拒绝胡乱计算:给大模型装上 Python 代码解释器
在上文的“翻车现场”中,我们看到让大模型直接算数、画图简直就是一场灾难。
为什么会这样?因为 LLM 本质上是一个“文科生”,它的强项是语义理解和文本生成,而不是精确的逻辑运算。当面对“计算夏普比率”或“绘制移动平均线”这种任务时,我们需要让它把“做数学题”变成“写代码”。
这就是我们的第一个核心升级:引入 Python 代码解释器 (Python Code Interpreter)。
1. 破局思路:从“基于概率”到“基于逻辑”
借助 LangChain 的 PythonREPL 工具,我们实际上是给了 Agent 一个虚拟的沙盒环境。当 Agent 遇到计算或绘图任务时,它的工作流变成了这样:
1.理解需求:提取数据和指标公式。
2.编写代码:利用 pandas、numpy 或 matplotlib 生成 Python 脚本。
3.执行代码:将脚本扔给解释器运行,获取绝对精准的结果或图表。
4.总结回复:把运行结果用自然语言汇报给用户。
从 Probabilistic Generation (概率生成) 转向 Deterministic Execution (确定性执行),这是消除金融计算幻觉的唯一解。
2. 核心代码与工程踩坑:如何优雅地拦截绘图流?
想法很美好,但在本地真实跑代码时,我遇到了一个极其抓狂的工程大坑——进程阻塞。
当大模型编写绘图代码时,它会习惯性地在最后加上一句 plt.show()。在传统的 Jupyter 环境中这没问题,但在 Agent 工作流中,plt.show() 会直接弹出一个 GUI 窗口,并彻底阻塞整个 Python 进程。Agent 会一直卡在那里苦苦等待窗口关闭,导致对话“死机”。
此外,如果不加限制,模型经常会忘记保存图片,或者把图片保存到各种奇怪的临时目录里,导致前端根本拿不到图。
为了解决这个问题,我写了一个带有强制拦截和劫持功能的自定义工具 financial_code_interpreter。
核心代码实现如下:
import os import matplotlib.pyplot as plt from langchain_core.tools import tool from langchain_experimental.utilities import PythonREPL # 初始化 REPL 并注入常用库,降低大模型 import 失败的概率 python_repl = PythonREPL() python_repl.globals.update({"pd": pd,"plt": plt,"np": np}) SAVE_DIR =r"D:\VScode\VScode projects\personal\LLM\financeLLM"# 修改为自己的本地位置@tooldeffinancial_code_interpreter(code:str)->str:""" 【金融代码解释器 / 绘图工具】 用于执行 Python 代码。Agent 只需负责 plt.plot(),系统会自动处理保存。 """print(f"\n🐍 [Agent 正在真正执行代码] ...\n{code}")# === 核心工程 Hack:代码清洗与劫持 ===# 暴力替换掉 Agent 自己写的 show, close, savefig# 这样即使 7B 模型不听话,我们也能在物理层面上强制接管 code = code.replace("plt.show()","") code = code.replace("plt.close()","") code = code.replace("plt.savefig","# plt.savefig")try:# 执行清洗后的代码 result = python_repl.run(code)# === 核心工程 Hack:状态检测与自动保存 ===# 检查当前内存中是否生成了图表对象if plt.get_fignums(): file_path = os.path.join(SAVE_DIR,'plot.png')try:# 由宿主程序强制接管保存动作 plt.savefig(file_path, dpi=100, bbox_inches='tight') plt.close()# 保存完后务必手动关闭,释放内存 image_msg =f"\n[系统注]:图表已成功保存至: {file_path}"except Exception as img_err: image_msg =f"\n[系统注]:图片保存失败: {img_err}"else: image_msg =""# 封装最终返回给 Agent 的观察结果 (Observation)ifnot result andnot image_msg:return"代码执行成功,无文本输出。"returnf"{result.strip()if result else'无文本输出'}{image_msg}"except Exception as e:returnf"代码执行报错: {e}"3. 改进效果展示
通过这层暴力的 String 替换和状态接管,我们彻底把 7B 模型给“驯服”了。现在的它,不仅算数精准,而且出图极快。
测试 Prompt:“画一个模拟的 A 股今天行情走势图。”


Agent 成功调用 Python 脚本生成并保存到本地的趋势图,告别了干巴巴的纯文本。
三、从“搜得到”到“搜得准”:RAG 混合检索与重排序
给 Agent 装上了 Python 大脑后,它的计算能力毋庸置疑了。但作为一名“金融研究员”,它还得会查资料。
在最初的版本中,我使用的是最基础的 RAG 方案:把用户的问题变成向量,去 Chroma 向量数据库里做相似度检索(Vector Search),取 Top 3 返回给大模型。
很快,现实就给我上了一课。
1. 痛点:向量检索的“语义盲区”
向量模型(Bi-Encoder)的原理是把一段话压缩成空间中的一个点,距离越近代表越相似。但在金融领域,这会带来一个致命问题——高度依赖精确匹配,而对“模糊语义”极度敏感。
比如,你搜索 “A股”,向量模型可能会觉得 “B股” 或者 “港股” 在语义空间上和它非常接近,从而把一堆 B 股的新闻塞给 Agent。再比如,“净利润”和“净资产”,一字之差,谬以千里,但它们的向量距离可能近得吓人。
这种“只看个大概”的检索方式,会导致正确的文档经常被挤出 Top 3 之外。
2. 破局:引入 Cross-Encoder 重排序机制 (Rerank)
为了解决这个问题,我放弃了“单刀直入”的检索,转而采用了目前工业界主流的 “两阶段检索” (Two-Stage Pipeline):
第一阶段 - 广撒网 (Recall):先用普通向量模型粗筛出前 10 篇最相关的文档。宁可错杀,绝不漏网。
第二阶段 - 精挑选 (Rerank):引入专门的 Cross-Encoder 模型(这里我选用了中文效果极佳的 BAAI/bge-reranker-base)。它不再是比较两个向量的距离,而是把“用户问题”和“文档内容”直接拼在一起,让模型逐字逐句地交叉打分(0~1分)。
图注:两阶段检索架构图。第一阶段保证召回率(Recall),第二阶段保证准确率(Precision)。
3. 核心代码实现
在 search_financial_knowledge 工具中,我是这样实现重排序的:
# --- 第一阶段:广撒网 (Recall) ---# 先粗略获取 10 个候选片段,防止正确答案遗漏 initial_docs = vectorstore.similarity_search(query, k=10)# --- 第二阶段:精挑选 (Rerank) ---# 构造 (Query, Document) 对,准备交给严苛的“考官”打分 pairs =[[query, doc.page_content]for doc in initial_docs]# Cross-Encoder 计算绝对相关性得分 scores = reranker_model.predict(pairs)# 获取得分最高的 Top 3 的索引 (从大到小排序) top_k_indices = np.argsort(scores)[::-1][:3]# 提取最终的高质量文档喂给大模型 final_results =[initial_docs[idx].page_content for idx in top_k_indices]4. 效果对比:用数据说话
加上 Rerank 机制后,检索的精准度有了肉眼可见的提升。通过查看后台的日志打分,我们可以直观地感受到 Cross-Encoder 这个“考官”有多严格:

在没有 Rerank 之前,那个 0.0511 分的文档可能仅仅因为里面包含了“风险”、“产品”等词汇,就被向量库推到了大模型面前,导致 Agent 发生幻觉。而现在,Rerank 机制就像一个“过滤器”,把大模型的幻觉扼杀在了摇篮里。
至此,我们的 Agent 不仅拥有了“理科大脑”(代码解释器),还拥有了“火眼金睛”(精准检索)。
万事俱备,只欠东风。接下来,我们要面对最大的工程挑战——如何让 Qwen-7B 这种轻量级模型,能够不出错地把这些工具串联起来?这就是我们要探讨的终极架构——多智能体协作(Multi-Agent Supervisor)。
四、告别单兵作战:基于 LangGraph 的 Supervisor 多智能体架构
有了精准的检索(RAG 重排序)和强大的计算能力(代码解释器),我们的 Agent 似乎已经无所不能了。但当我满怀信心地把所有工具打包扔给 Qwen-7B,并给它下达一个复合指令时,灾难再次发生。
我的指令: “先帮我查一下 A 股今天的行情新闻,然后根据你知道的信息,画一个模拟的趋势图。”
单体 Agent 的灾难现场:
它要么直接去调用 Python 工具企图“计算”出今天的新闻;要么在查完新闻后,长篇大论地给我总结了一番,然后心满意足地结束了对话,完全忘记了还要画图这回事。
1. 痛点:单兵作战的“认知过载”与“上下文污染”
让一个 7B 级别的小参数模型同时挂载 4、5 个完全不同领域的工具(搜索、知识库、Python、查余额),还要在漫长的对话历史中保持清醒,这超出了它的能力边界。
在单兵模式下,中间步骤的搜索结果、代码报错日志全部堆积在同一个上下文窗口里。模型极易产生“上下文污染”,导致逻辑断链或参数传错。
解决复杂系统问题的终极武器永远是——解耦。
2. 破局:Supervisor (主管-员工) 协作模式
我决定利用 LangGraph 重构底层逻辑,将单体 Agent 升级为一个分工明确的多智能体集群 (Multi-Agent System)。
在这个微型金融团队中,我设定了三个角色:
👨💼 Supervisor (主管):大脑与大脑的连接器。他不干具体脏活累活,只负责看懂用户的整体需求,然后把任务拆解并派单。
🕵️ Researcher (研究员):信息收集专家。只配备联网搜索和 RAG 工具,严禁写代码。
💻 Coder (程序员):技术大牛。只配备 Python 代码解释器工具,只看数据画图,不闻窗外事。
图注:基于 LangGraph 的多智能体状态机架构。在这个图(Graph)中,State(状态)在节点之间流转,主管负责控制整体的执行流(Control Flow)。
3. 核心代码解析:构建状态图 (StateGraph)
要实现这个架构,最核心的部分在于主管的路由逻辑和图结构的编排。
最终框架如下图所示:

步骤一:定义 Supervisor 节点
主管的核心任务是输出标准的 JSON 格式指令,告诉系统下一步该谁上场。
defsupervisor_node(state: MessagesState):# 主管看完整的历史对话,并遵循系统提示词 messages =[{"role":"system","content": system_prompt}]+ state["messages"] response = llm.invoke(messages) content = response.content # 强制解析逻辑:提取下一步动作 next_step ="FINISH"if"Researcher"in content: next_step ="Researcher"elif"Coder"in content: next_step ="Coder"print(f"\n👨💼 [主管派单] -> {next_step}")# 如果任务结束,向用户输出总结if next_step =="FINISH":return{"next": next_step,"messages":[AIMessage(content="✅ 任务流程已结束。请检查上方是否有生成的图表和报告。")]}# 核心:仅返回路由指令,不将主管的 JSON 写入全局上下文,防止污染员工的视野return{"next": next_step}步骤二:使用 LangGraph 编排工作流
通过 StateGraph,我们将主管和两个员工连接起来,形成一个严密的闭环流转体系。
from langgraph.graph import StateGraph, START, END, MessagesState # 1. 初始化图结构 workflow = StateGraph(MessagesState)# 2. 添加所有节点 (员工+主管) workflow.add_node("Supervisor", supervisor_node) workflow.add_node("Researcher", researcher_node) workflow.add_node("Coder", coder_node)# 3. 定义边 (Edges):员工干完活,必须向主管汇报 workflow.add_edge("Researcher","Supervisor") workflow.add_edge("Coder","Supervisor")# 4. 定义条件边 (Conditional Edges):主管决定下一步去哪 workflow.add_conditional_edges("Supervisor",lambda x: x["next"],# 根据 supervisor_node 返回的 'next' 字段进行路由{"Researcher":"Researcher","Coder":"Coder","FINISH": END # 任务彻底完成})# 5. 设进入口:用户提问后,第一个接客的永远是主管 workflow.add_edge(START,"Supervisor")# 编译成可执行的 Agent 引擎 agent_executor = workflow.compile()4. 见证奇迹的时刻
当这套架构运转起来时,控制台打印出的日志极其极度令人极度舒适。面对同样的复合指令,我们的智能体团队展现出了惊人的默契:

专人做专事,不仅成功率飙升,哪怕是 7B 参数的小模型,也能跨级打怪,完成复杂的业务逻辑闭环。
五、那些年我踩过的坑:Local LLM 的鲁棒性工程
如果你一直使用的是 GPT-4 或 Claude 3 这样的大语言模型 API,你可能会觉得开发 Agent 就像搭积木一样简单。但当你把底层引擎换成本地部署的 7B 小模型时,画风就突变了。
小模型的指令遵循能力较弱、极易受上下文干扰。在将这套系统落地的过程中,我遇到了无数个让系统“死机”的 Bug。以下是我总结的三大“天坑”以及我的修复方案(鲁棒性工程):
坑位一:习惯性偷懒的“哑巴 Agent”
灾难现场:主管成功把单派给了 Coder(程序员),Coder 也成功写出代码并保存了图片。但到了最后一步汇报时,Agent 的输出竟然是一个空字符串!整个控制台陷入死寂。
原因分析:这是 7B 级别模型常见的“偷懒”现象。当它看到工具的返回结果(比如:图表已成功保存至 D盘…)已经很清晰时,它会觉得“既然工具已经把结果说得这么明白了,我就没必要再总结一遍了”,于是直接跳过了生成最终回复(AIMessage)的步骤。
解决方案:强制溯源读取 (ToolMessage Fallback)
既然模型不开口,我们就直接去后台翻它的工作日志。我编写了一个 get_real_content 辅助函数:
如果在 Agent 跑完后检测到它的 AIMessage 是空的,系统会自动倒序遍历历史消息,强行把最近一次 ToolMessage(工具执行结果)提取出来打印给用户。即使 AI 装哑巴,我们依然能看到干货。
坑位二:越权指挥的“幻觉指令”
灾难现场:Researcher(研究员)查完了今天的新闻,并在报告的最后煞有介事地加上了一句:{“next”: “Coder”}。导致系统逻辑彻底错乱。
原因分析:这是经典的“上下文污染 (Context Pollution)”。
在初版设计中,Supervisor(主管)为了派单,会在全局对话历史中留下一句 {“next”: “Researcher”}。当轮到 Researcher 干活时,它看到了这句 JSON,立刻产生了“幻觉”——它以为在这个团队里说话必须带上这种 JSON 格式,于是依葫芦画瓢,企图越权指挥下一步动作。
解决方案:上下文净化与“主管隐身模式”
我们必须切断这种不良的示范。我重构了主管节点的逻辑,实现了“隐身路由”:
主管依然会读取全部历史并做出决策,但它的决策 JSON 绝不会被 append 到全局的 state[“messages”] 中。它只负责指路,不留下痕迹。这样一来,员工 Agent 看到的历史记录始终是纯净的业务数据,彻底杜绝了格式模仿带来的幻觉。
坑位三:被大括号逼疯的“语法报错”
灾难现场:为了防止 Researcher 再次输出 JSON,我在系统提示词(Prompt)里严厉警告它:【严禁】:不要指挥下一步,不要说 {“next”: …}。结果代码一跑,LangChain 直接抛出致命异常:Input to ChatPromptTemplate is missing variables {“‘next’”}。
原因分析:这是框架层的语法冲突。LangChain 的 ChatPromptTemplate 默认使用 Python f-string 风格的格式化。它只要看到 {},就认为里面包着的是一个需要外部传入的动态变量。它到处找不到名为 next 的变量,自然就崩溃了。
解决方案:双大括号转义 (Jinja2 / f-string Escaping)
解决方式说来好笑,其实非常简单。在 Prompt 模板中,如果我们只是想表达“纯文本的大括号”(比如用来展示一段 JSON 示例),必须使用双大括号进行转义:
将 {“next”: …} 改写为 {{ “next”: … }}。这样 LangChain 就会乖乖把它当作普通字符串处理了。
经历了这些“毒打”与修复,这套基于 Qwen-7B 的多智能体系统终于变得像磐石一样稳定。不再有死循环,不再有空输出,指令传达精准无误。
这也让我深刻认识到:在大模型应用开发中,Prompt 决定了下限,而工程架构决定了上限。
总结与展望:构建更智能的金融助理
从最开始连 K 线图都画不出来的“人工智障”,到如今能够自主拆解任务、查阅研报、编写代码并生成图表的金融智能体集群,我们走过了一段不平凡的重构之路。
回顾整个项目,核心的突破口其实不在于更换更强的模型,而在于架构设计的升级:
1.以“确定性计算”对抗“概率生成”:通过引入 Python 代码解释器,我们让大模型学会了“遇到数学题不瞎猜,而是写代码算”,彻底解决了金融数据的计算精度问题。
2.以“精准排序”对抗“模糊检索”:Cross-Encoder 重排序机制的加入,让 RAG 系统拥有了“火眼金睛”,在嘈杂的金融噪音中精准锁定关键信息。
3.以“分层架构”对抗“逻辑混乱”:LangGraph 的 Supervisor 模式证明了,通过合理的路由分发和状态管理,7B 参数量的本地小模型也能展现出惊人的逻辑闭环能力。
这也印证了一个观点: 在垂直领域落地 AI,系统工程的重要性往往大于单纯的模型能力。 即使是本地部署的轻量级模型,只要给它配上好用的工具和清晰的流程,它也能成为顶尖的生产力工具。
未来计划
目前的系统已经具备了一定的金融分析能力。下一步,我计划接入真实交易接口(如 Qlib),让 Agent 从“纸上谈兵”走向“实战交易”。
希望这篇从 RAG 到 Multi-Agent 的踩坑实录,能为你构建自己的 AI 应用提供一些灵感。