☰
多智能体协作实战:用LangGraph搭建AI角色辩论系统解析A股
2026/10/2 5:21:35 网站建设 项目流程

1. 为什么我要给 A 股装一个“会辩论的 AI”

先说结论:我花了两周时间,用大模型 API 搭了一个多智能体协作系统,让几个 AI 角色围绕某只 A 股股票的真实行情、公告和新闻数据,像电视辩论节目一样正反交锋,最后由一个“裁判”角色输出综合裁决报告。这个项目纯属技术实验,不构成任何投资建议,但它解决了一个我一直觉得挺痛的问题——单次问大模型“要不要买”,得到的永远是一段四平八稳、两头下注的正确废话。

我试过把同一个问题抛给不同的大模型,回答基本都是“该股票基本面稳健,但需注意估值偏高”“建议结合市场情绪与政策动向综合判断”。这种答案没法用,因为它没有立场、没有交锋、没有推演过程。而人类分析师开会讨论的时候,最值钱的部分恰恰是“多空对抗”里的那些反驳和让步——这些在单模型输出的文本里根本不存在。

所以我就动了念头:能不能用AI Agent的方式,让多个智能体扮演固定角色,各自持有不同立场,围绕同一份真实数据展开辩论?A 股市场的复杂性天然适合这种结构,因为多空逻辑永远同时成立,区别只在于谁在哪个时段占上风。这个项目做完之后,我自己最大的感受是:多智能体系统最难的不是接口调用,而是让每个角色“守得住自己的人设,也接得住别人的话茬”。

这篇博客会从架构设计、数据接入、提示词工程、代码实现到踩坑排查,完整复盘这个项目。适合正在做 AI 应用开发、想了解多智能体协作落地的读者,也适合对大模型在金融场景中的应用感兴趣的朋友。我会尽量把每一步的“为什么这么做”讲清楚,而不是只丢代码。

2. 整体架构设计:四个 AI 角色一台戏

2.1 角色设定与分工

我最终敲定的角色是四个,不是两个。最开始我只想做一个简单版:多头一个、空头一个,互怼两轮结束。但跑完一次内测后我发现,纯粹互怼很容易演变成毫无营养的“你说涨我说跌”,两边都在反复重申立场,却没有论据支撑。问题的根源在于缺少一个关键中间层——研究员。

研究员角色的任务是在辩论开始前,把指定股票的近期行情、市盈率、财务数据、公告事件整理成一份“事实简报”。多头和空头只能基于这份简报来发言,谁引用数据谁才有说服力,不允许凭空捏造。这个设计直接改变了辩论质量:数据一绑定,双方的火力点就集中在“同一份数据怎么解读”上,而不是“我猜会涨”和“我猜会跌”。

第四个角色是裁判,负责在终局阶段读完整场辩论记录,给出三个输出:多空双方谁的理由更充分、当前市场的风险点、以及一份带评分维度的综合报告。裁判的角色目标不是选边站队,而是评估论证质量。

四个角色各有自己的系统提示词、输出格式要求和上下文窗口管理策略。我最初犯的一个错误是让四个角色共享同一个超长上下文,导致模型越到后面越“失忆”,清晨开场的多头观点到辩论中场就全忘了。后面直接改成每个角色维护独立上下文,只在辩论轮次切换时传递结构化消息。

2.2 为什么选多智能体而不是单一大模型

你可以会问,为什么不直接给一个模型做一个“请你分别从多空双方角度分析”的 prompt?这恰恰是这个项目最核心的取舍。我在早期实验里试过这种单模型多角度提示,效果确实也能看,但存在两个根本问题。

第一个问题是立场一致性问题。单模型生成的多空分析,往往带着同一个思考惯性——比如模型整体偏乐观,那么它生成的空头观点也会“客气”地让一步,读起来像换了个温和的反对者,根本没有真正的对抗张力。但多个独立智能体各自持有不同的人设提示词,且彼此看不到对方的系统设定,空头是真觉得自己必须赢,才有那种“刺刀见红”的辩论味。

第二个问题是流程的可控性。单模型一次输出,你只能得到一堆文本,没法在中间插入人工审查、局部重跑、数据更新。但多智能体流程里,我可以在每一轮辩论后检查输出质量,发现某个角色开始复读机就直接终止重跑,这种灵活性对调试阶段特别重要。

当然,代价也很明显:成本翻了好几倍。每场完整辩论平均消耗的 token 在 40 万到 60 万之间,具体取决于轮次和文本长度。所以性能优化和成本控制是必须认真对待的问题,这部分我会在后面的排查章节细讲。

2.3 辩论流程与裁决逻辑

最终的流程被设计成五个阶段:

  1. 研究员生成事实简报:拉取行情数据 + 新闻 + 最近公告,清洗后输出结构化摘要
  2. 多头发表首轮立论:基于事实简报,提出核心看多论据
  3. 空头发表首轮立论:基于同一份事实简报,提出核心看空论据
  4. 自由辩论几轮(默认 3 轮):双方交替发言,每一轮都必须反驳对方上一轮的某个具体论据,不允许重复自己的旧论点
  5. 裁判阅读全文,输出裁决报告

这个流程的关键设计在于第 4 步的“禁止重复旧论点”规则。我把它写进了空头和多头的系统提示词里,并且在每次轮换时把上一轮对方发言摘要单独传给当前发言方。这个机制倒逼双方必须“接话”,而不是各说各话。实测下来,第二三轮的辩论质量明显高于第一轮,很多有意思的反驳都出现在后期。

至于裁决逻辑,裁判不会做“涨还是跌”的二元判定,因为这是不可能也不负责的。裁判只做三件事:评估论证深度(谁用了更多有效数据)、指出未被充分回应的反驳点、输出一份包含利好因素、利空因素、观察指标的风险矩阵。这样一个工具才有实际使用价值——它帮你梳理思考,而不是替你下注。

3. 数据接入与提示词工程:让 AI 学会“看盘”和“说话”

3.1 行情与资讯数据源怎么接

先说说数据从哪来。我用的是公开免费接口(比如 akshare 这类开源数据源)拉取日 K 线、最新市值、市盈率等基础行情数据,再用新闻聚合 API 拉取指定股票近一周的标题和正文片段。公告数据通过公开渠道获取。整个过程没有任何付费数据服务,因为项目定位是技术验证,不是量化交易系统。

但免费接口有个非常难受的问题:数据格式脏、字段缺失、单位不统一。比如市盈率在有的接口里是 float,有的接口里是字符串“--”;新闻标题里混着大量“研报推荐”“机构评级”这类非结构化噪音。我花了大概一个晚上写清洗脚本,把数据统一成 JSON 格式再交给研究员角色。

研究员这个角色的 prompt 必须明确告诉它:只总结事实,不表达观点。我试过让研究员顺便发表一下“初步看法”,结果它会带偏后续多空双方的辩论方向,因为多头会直接引用研究员的判断作为论据。所以研究员的输出只允许包含:数据摘要 + 事件列表 + 数据不确定性提示。任何带价值判断的词汇都算违规。

这里有一个细节值得所有做 Agent 的人注意:不要把“事实提取”和“观点生成”放在同一个 Agent 里完成。一旦你越界,模型会自动生成一份“有态度的数据报告”,然后整个下游就全被污染了。

3.2 提示词设计的几个关键坑

提示词设计是这个项目里迭代次数最多的环节。我把踩过的坑总结成几条硬性规则:

第一,给每个角色设定身份时,不要只写立场,要写理由和论证风格。比如“你是空头分析师,你习惯从估值泡沫、盈利增速放缓、资金面收紧三个角度寻找风险点,你的表达风格是有理有据,但攻击性很强”。单纯写“你是反对者”会让模型陷入万金油式反驳。

第二,显式禁止贴标签和绝对化表述。我在多头和空头的提示词里都加了同一句话:“不得使用‘必然上涨’‘肯定会跌’这类绝对化表达”。这既是为了安全合规,更是为了让辩论保持理性。没有绝对化表述,双方才会回到数据层面较量。

第三,每一轮发言必须指定格式:核心观点(一句话)、论据列表(至少三条,每条必须引用事实简报或对方发言中的具体内容)、对上一轮对方观点的直接回应(至少一条)。这个结构化的强约束是后来才想明白的:大模型在自由发挥时容易变成大而全的综述,但一旦你锁定输出结构,它反而会在每个栏目里给出更具体的内容。

3.3 人设稳定性与上下文管理

多角色混跑最大的翻车点就是人设漂移。我在第一版代码里遇到过非常诡异的情况:空头说完第一轮之后,第二轮突然说话特别客气,口气听着像裁判。排查下来发现是上下文污染——我为了让辩论连贯,把全文塞给了每个智能体,结果空头看到了裁判的系统提示词,潜移默化被影响了。

解决方案是严格的上下文隔离。每个角色的消息列表里只有:自己的系统提示词、研究员的事实简报、对方上一轮发言中提炼的“论点摘要”、自己上一轮发言的历史。至于长对话记忆,我不用“全文重放”,而是用一个轻量级摘要模块,只把双方各轮发言压缩成三到五条核心论据存下来。这一步把 token 成本砍掉了将近一半。

人设稳定性还可以用温度参数来辅助。多头和空头用偏高的温度(0.7 到 0.8)保证语言风格鲜明,裁判用较低的温度(0.2)确保裁决稳定客观。我发现温度设置在人设稳定中的作用被很多开发者低估了——多个 Agent 协作时,角色的“个性”不只在 prompt 里,也在采样参数里。

4. 核心代码实现:从零搭一个可运行的辩论系统

4.1 技术选型与工程框架

技术栈非常简单:Python 3.11 + LangGraph 做流程编排 + 大模型 API(我选的是国产大模型服务商的接口,国内直接调用,网络延迟低,成本也合适)+ Streamlit 做前端展示。之所以选 LangGraph 而不是手动编排,是因为这个项目的流程天然是一个有状态的状态机:每个辩论阶段之间需要共享状态、转换角色、有条件地继续或终止,这些正好是 LangGraph 的主场。

我用的是 Streamlit 来做界面,效果出乎意料地好。Streamlit 的st.chat_message组件可以直接渲染多角色气泡对话,配合st.spinner模拟每个角色“思考中”的状态,看起来就像一场真实的线上辩论赛。整个前端代码不到两百行,对后端开发者非常友好。

4.2 用状态机编排辩论轮次

LangGraph 的核心用法是定义一个StateGraph,每个节点是一个函数,节点之间通过边相连。我把五个阶段映射成五个节点,节点间的跳转条件用一个简单的计数器控制。辩论轮次作为全局状态存进字典里,每一轮结束判断轮次是否达到预设值,达到就跳转到裁判节点,没达到就继续在多头和空头之间循环。

from langgraph.graph import StateGraph, END class DebateState(TypedDict): stock_code: str brief: str bull_history: list bear_history: list round: int final_report: str def research_node(state): brief = generate_fact_brief(state["stock_code"]) return {"brief": brief} def bull_node(state): response = call_llm( role_prompt="你是多头分析师...", context=state["brief"] + "\n对方论点摘要:" + extract_key_points(state["bear_history"]) ) return {"bull_history": state["bull_history"] + [response]} def bear_node(state): response = call_llm( role_prompt="你是空头分析师...", context=state["brief"] + "\n对方论点摘要:" + extract_key_points(state["bull_history"]) ) return {"bear_history": state["bear_history"] + [response]} def judge_node(state): state["final_report"] = call_llm( role_prompt="你是裁判...", context=format_debate_transcript(state) ) return state def should_continue(state): return "judge" if state["round"] >= 3 else "bull"

这段代码看起来短,但每个节点内部实则封装了近百行逻辑:输出格式校验、token 长度截断、重试机制、非法内容过滤。简短的框架代码是结果,不是起点,真正常态运行的堡垒都在节点的内部细节里。

4.3 结构化输出与裁决报告生成

为了让裁判的输出稳定可用,我不用自由文本,而是要求模型返回 JSON。预设的 schema 包含这些字段:

{ "verdict": "bull_or_bear", "winning_side": "多头", "evaluation": { "data_usage_bull": 8.5, "logic_strength_bear": 7.0, "refutation_quality_bull": 6.5 }, "unanswered_points": ["空头提出的估值风险未被有效反驳"], "risk_matrix": { "upside_factors": ["行业景气度回升", "盈利改善预期"], "downside_factors": ["估值高于历史中位数", "短期资金流出"], "watch_list": ["下一季度毛利率变化"] } }

JSON 格式的好处显而易见:可以做严格的字段校验,不符合就触发一次重跑,而不是拿着不知道格式的文本去解析。同时也能把裁决报告直接渲染成前端表格,阅读体验远超一段黑压压的文字。

我在解析模型输出时用了一个小技巧:不直接json.loads,而是先提取文本中的 JSON 代码块再解析。因为大模型偶尔会在 JSON 外面包一层 markdown 代码块标记,直接解析一定报错。这个 20 行的解析函数帮我躲过了无数次线上故障。

4.4 部署与前端展示

部署过程不复杂,Flask 后端接收股票代码请求,触发辩论流程,每个阶段结果通过 WebSocket 推给前端。前端没有任何复杂交互,就三个部分:股票代码输入框、辩论对话流、裁决报告面板。

真正需要注意的是并发控制。因为每场辩论要跑 4 个角色、5 个阶段、平均耗时 3 到 5 分钟,如果用户同时提交多个请求,API 配额瞬间就会被打爆。我最后做了一层非常粗暴但有效的限制:单机同一时刻只允许 2 个辩论任务并行,其余排队等待。为了让用户不觉得卡,前端会展示实时队列位置和预计等待时间。这个经验对任何大模型应用都有参考价值——模型推理是慢操作,必须把并发控制当一等公民对待。

5. 实测效果与常见问题排查

5.1 跑了几场真实辩论后的观察

项目上线内测后,我拿几只有名的 A 股股票跑了十几场辩论。从结果看,辩论质量远超我的预期,但也暴露出几个值得思考的现象。

第一场测试是某白酒龙头股。多头押注消费复苏和品牌壁垒,空头主攻估值回归和年轻人消费习惯改变。这场辩论非常精彩,空头在第二轮直接回怼多头“高毛利并不能保证增长,品牌溢价同样有天花板”,后来裁判给的综合评分里双方差距极小。这类对抗是单模型生成式分析完全给不出的质感。

第二个观察是,辩论质量高度依赖研究员简报的信息密度。有一场测试因为数据接口故障,简报里只有最近五天的 K 线数据,整个辩论就只能围着短期波动打转,深度明显不足。后面我强制要求研究员在简报末尾加一段“数据覆盖范围说明”,一旦发现数据缺失严重就直接中止流程,提醒用户补充数据源。

第三个现象是关于轮次的边际效应。3 轮辩论和 5 轮辩论的最终裁决差别很小,但 token 成本差距却有将近一倍。我测试过最优解:如果第一轮双方就已经收到高质量简报,3 轮辩论已经足够产生有区分度的结果,再多轮只会变成“复读式换皮”。所以默认轮次定在 3 轮。

5.2 高频踩坑与解决方案速查表

以下是我在这个项目里真正遇到过、并且花了很长时间解决的问题汇总:

问题现象解决方案
人设漂移空头突然语气温和,像在分析而不是辩论上下文隔离,每个角色只看自己的系统提示词和辩论摘要,不共享完整全文
绝对化表述模型生成“必然上涨”等违规输出系统提示词显式禁止,输出校验阶段用正则过滤,命中直接重跑该轮
上下文超长辩论到中后段 token 超出模型窗口每轮发言后立即压缩摘要,只保留核心论据,不把全文带入下一轮
JSON 解析失败模型返回带 markdown 代码块的 JSON提取代码块内容后解析,解析失败触发一次带“请只输出 JSON”的重试
数据缺失简报只有零散行情,无法支撑深度辩论研究员输出“数据覆盖范围”,数据稀疏则中止流程并提示用户
API 限流多个角色并发调用频繁触发限流信号量控制全局并发数为 2,配合队列机制,前端展示排队状态
结果不可复现同一只股票每次跑出来结论都不一样裁判温度调低至 0.2,多头和空头保留默认温度,同时记录种子参数
输出格式漂移角色发言不按指定格式输出在节点内做格式校验,不通过就强制重试,最高重试 3 次

这里面最值得展开的是“结果不可复现”的问题。金融场景里,如果同一只股票你问两遍 AI 得到截然相反的结论,这个工具就失去了可信度。但多智能体系统天然充满随机性:每个角色调用 API 时的采样参数独立变化。我后来做了一项调整:裁判的温度固定为 0.2,并让多头/空头的首轮发言共享一个初始随机种子。实测下来,同一输入两次跑动的结论一致性从不到 60% 提升到了 80% 以上,代价是个性化表达略微收敛,但在金融场景里这是完全值得的取舍。

5.3 成本与性能调优经验

成本控制是这个项目差点放弃的一个坎。最开始一场辩论消耗 100 万 token 一点也不夸张,因为我让每个角色都带着完整辩论记录进入下一轮。后来逐步调优,最终稳定在每场 40 到 50 万 token 之间,成本降了 50% 以上。整个调优过程有三板斧。

第一板斧是摘要替代全文。每个角色只接收对方论点的压缩摘要,而不是对方发言的完整文本。摘要模块本身也是用模型生成的,但只用一次调用,输出三到五条一句话论点。这一步的 token 节省最大,同时辩论体验几乎没有下降。

第二板斧是限制每轮发言长度。我在格式约束里强制要求单轮发言不超过 500 字,并写死了“论据不超过五条”。这个约束看起来简单粗暴,实际效果很好——模型不会为了凑字数而写空洞内容,反而会把每一条论据打磨得更扎实。

第三板斧是缓存研究员简报。同一只股票在短时间内多次辩论,研究员简报其实大同小异。我把简报按股票代码和日期作为缓存键,有效期内不重复调用数据源和大模型。这个优化对多用户同时查同一只股票的场景特别管用,效果立竿见影。

6. 多智能体协作的通用方法论与个人体会

6.1 这个项目教会了我什么

做完这个项目回头看,最大的收获不是“给 A 股做了个 AI”,而是搞明白了多智能体协作系统里最容易翻车的那几个机制问题。

第一个机制是角色间的信息对称性。如果你的每个角色拿到的数据不一样,辩论就会变成信息差碾压,而不是观点交锋。我的解决方案是让所有角色共用同一份研究员简报,在此基础上再叠加各自的立场提示。这看起来理所当然,但实际操作中很多开发者会忽略——有的角色看到了更完整的数据,有的只能看到摘要,结果辩论质量迅速失衡。

第二个机制是对抗性任务需要“接得住话”的能力。这是我认为整个项目最有价值的工程技巧:仅仅让两个立场相反的模型各说各话,产生不了辩论,只是两篇平行分析。真正的对抗来自“你上一轮的某个论据存在逻辑漏洞”这类回应。实现方式就是我在第三小节提过的——每次轮换时,把对方上一轮发言的“论点摘要”强力注入当前发言方的上下文中,并强制要求至少回应其中一条。这一步是辩论质量的灵魂,少说多干。

第三个机制是裁决者必须跟辩手隔离。裁判如果参与过程辩论,它一定会被某一方的语言风格裹挟,失去客观性。我的做法是,裁判的上下文中只有“格式化后的辩论记录”,看不到任何辩手的系统提示词,甚至不知道辩手有多少个角色。这种信息隔离对裁决的客观性帮助巨大,强烈推荐做类似项目的朋友试试。

第四,必须保持实验心态。这个系统没有任何投资参考价值,我用它,是因为它强迫我去思考“数据如何影响观点”“立场如何塑造论证”。如果你也对 AI Agent 协作感兴趣,完全可以换个题材——让 AI 辩论“周末去哪儿玩”“今晚吃什么”——技术框架都一样,语言风格和角色设定改一改就能跑起来。

6.2 后续能往哪些方向扩展

这个项目其实只是开了个头,我在实践里想到的扩展方向至少有这几个。

短期想做的,是把辩论周期拉长。目前只分析当下某一时刻的情况,接下来可以做一个周度辩论回顾:每周结束跑一次辩论,让 AI 对照上周的辩论记录,看看哪些预测对上了、哪些判断被打脸。这个“自我反思”机制可能会让系统产生很强的学习感。

另一个方向是可视化辩论逻辑链。目前输出是文本和表格,但辩论中很多论据之间存在互相反驳关系。我想做一个知识图谱式的可视化界面,把每条论据、反驳、支撑、让步之间的关系用节点和边画出来。技术上没什么难点,但对用户理解辩论过程会有很大帮助。

最后,我现在正在把整个系统封装成一个可配置的多 Agent 框架。核心角色不再固定为“多头空头裁判”,而是可以由用户自定义角色设定和辩论规则。这样一来,同样的基础代码就能应用在更多场景里,比如产品方案评审、技术选型讨论、甚至写议论文框架构思。多智能体协作的潜力远远不止“帮我看股票”这一个方向。

个人体会是:AI 辩论类应用的技术门槛并不高,真正的壁垒在于提示词工程、流程编排和角色设计的细节经验。我把这次实践的代码结构和调参心得完整记录下来,以后再做任何多 Agent 项目,这套方法论都能直接复用,这才是我觉得这个项目最有价值的部分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询