1. 这件事到底在讲什么
最近圈子里有个事儿挺有意思:有人给 A 股市场搞了一个“会辩论的 AI”。乍一听像是玩梗,但仔细看下来,这其实是在把大模型接进投资研究的日常流程里,让多个 AI 角色站在不同立场上对同一份数据、同一条逻辑争来争去,最后形成一份带有“交锋记录”的投资分析结论。
先说清楚这玩意儿到底解决什么问题。传统研究里最耗时间的不是查数据,而是“把多空逻辑拉出来对打”。卖方报告说利好,市场情绪说利空,技术面又给出完全不同的信号,最后谁来拍板?通常还是靠人脑把各种矛盾观点硬揉到一起。这个“会辩论的 AI”本质上就是把“多方观点对抗”这件事自动化了——你给它一个标的或一个行业话题,它会分饰多角,一边是看多派,一边是看空派,一边是中性派,三边各自举证、互相反驳、不断修正,最终输出一份带有完整辩论过程和结论倾向的报告。
适合谁看?如果你平时做个股研究、行业分析、复盘纪要,或者只是对“AI 怎么落地到金融场景”感兴趣的,这篇文章都值得读一读。代码不多,但思路可以完整复现,属于那种“看完了马上能动手搭一个”的类型。
2. 为什么要用“辩论”而不是“单选”
2.1 单一模型输出天然有立场盲区
直接让大模型写一份“XX 股票分析报告”,你会发现一个典型问题:它太容易顺着你的问题走了。你问“这公司有什么风险”,它能给你列一堆风险;你问“这公司有什么亮点”,它又能列一堆亮点。表面看内容很丰富,实际上模型只是在迎合你的提问框架,并没有真的站在对立面去推翻自己。
这就是所谓的“谄媚效应”。模型训练过程中大量数据都倾向于给出“合理、正面、结构完整”的回答,而不是真正去质疑前提。你让它分析一只票,它默认就会假设这只票值得分析;你让它写空头逻辑,它也能写,但那种“空头逻辑”通常只是把“估值高、增速慢”这类词换个说法再说一遍,根本没有形成真正的对抗。
2.2 辩论结构逼出真正的“反面证据”
把分析过程改成辩论结构之后,情况就不一样了。看多方的任务是找一切支持上涨的理由,它的输出会被看空方逐条拆解。比如看多方说“订单增长 40%”,看空方第一轮会反驳“那是因为去年基数低,实际上环比已经走平”。如果没有这个对抗角色,单纯让模型写“订单增长 40% 是利好”,这个基数效应就不会被主动提出来。
更关键的是,辩论是多轮的。第一轮双方各自说完,第二轮要针对对方的论据逐条回应。这个“回应”的过程,天然逼着模型去重新审视自己刚才说过的话。很多之前被忽略的数据细节,恰恰是在被对方“攻击”之后才暴露出来的。这一点单独靠提示词优化很难做到,必须靠流程设计来保证。
我总是建议那些刚开始接触这类玩法的人记住一句话:好用的 AI 不是“一个特别聪明的模型”,而是“一个逼着模型把话说全的流程”。辩论结构起到的就是这个作用。
3. 这个系统是怎么搭起来的
3.1 整体架构设计
先交代一下整体结构。这套“辩论 AI”并不依赖某个特殊的大模型,只要一个支持多轮对话的 API 就能跑通。核心逻辑分四层:
- 数据层:输入标的代码或行业关键词,拉取公开行情数据、财务指标、新闻舆情等基础数据。
- 角色定义层:用提示词固化三到四个 AI 角色,每个角色有独立的立场、知识侧重和表达风格。
- 辩论引擎层:控制轮次,每一轮收集各方观点,交换给其他角色作为下一轮辩论的输入。
- 总结层:辩论结束后,由独立的“裁判角色”汇总各方论据,输出一份带倾向性结论和风险提示的完整报告。
| 模块 | 核心职责 | 常用实现手段 |
|---|---|---|
| 数据层 | 提供事实依据,减少模型凭空编造 | 财报 API + 行情接口 + 新闻摘要 |
| 角色定义层 | 固定立场,约束模型不要两边讨好 | 系统级提示词 + few-shot 样例 |
| 辩论引擎层 | 控制辩论流程,管理上下文 | Python 多轮 API 调用 |
| 总结层 | 收敛结论,输出最终决策参考 | 独立裁判角色 + 评分卡模板 |
我实测下来,前三层缺一不可。如果省掉数据层,模型会在辩论中编造“某公司市占率 60%”这类不存在的数字;如果省掉角色定义层,辩论到第三轮就开始互相妥协,慢慢变成“你说得也有道理”;如果省掉辩论引擎层,那本质上就是换了壳的普通问答。
3.2 角色提示词的写法要点
角色提示词是整个系统里最值得花时间打磨的部分。我一开始偷懒,直接写“你现在是一名看空分析师”,效果非常差。为什么?因为模型只知道立场,不知道该用什么框架、什么指标、什么口径去支撑这个立场。
后来我调整了写法,每个角色都补齐四个维度:
- 立场声明:不仅说“我看空”,还要声明“我只关注下行风险因子”。
- 分析框架:指定它优先使用的分析方法,比如看空方侧重 DCF 估值敏感性、应收账款质量、经营性现金流趋势。
- 证据来源偏好:告诉它优先使用哪些类型的数据,比如财报中的附注细节、审计意见、股东变化等。
- 反驳策略:规定它如何攻击对方观点,常见策略包括“质疑数据口径”“质疑时间区间选择性”“质疑逻辑链条跳跃”等。
改善最明显的是对“数据口径”的关注。之前看多方说“毛利率提升 5 个百分点”,看空方只会弱弱地说“提升可能不可持续”,加了反驳策略之后,它会主动追问“毛利率提升的驱动因素是什么?是产品涨价还是成本下降?如果成本下降,原材料价格反弹后是否还能维持?”这一下就从“感觉上的反对”变成了“有依据的攻击”。
3.3 辩论轮次与上下文的控制
辩论流程我建议做得简单直接:三轮辩论加一轮裁判总结。
- 第一轮:立场陈述。各方基于初始数据亮出核心观点。
- 第二轮:交叉攻击。各方阅读其他角色的第一轮观点,针对弱点展开攻击。
- 第三轮:回应与收束。各方回应攻击,同时有条件地修正自身观点,形成“最终立场”。
- 裁判总结:裁判角色不偏向任何一方,提取三方的核心论据,给出综合结论和剩余风险。
第三轮里有一个容易被忽略的细节:要让模型“有条件地修正自身观点”。很多人设计辩论时,只让各方互怼,最后突然跳到裁判总结,结果就是各方到第三轮还在重复自己之前的话。加了一个“如果对方证据充分,请说明你在什么条件下愿意下调自己的结论置信度”的要求之后,输出质量马上不一样。它会让模型被迫承认“如果应收账款周转天数继续拉长,我原来看多的逻辑确实会松动”。
上下文管理上有一个非常实在的坑。如果你把所有轮次的对话都塞进一次 API 请求里,很快就会撞到上下文长度上限,而且费用会指数级增长。我的做法是:每一方只保留“自己的历史观点 + 对方最近的攻击点”,而不是把全部历史内容都传给所有角色。这样可以大幅降低 token 消耗,同时辩论的对抗性并没有明显下降。
4. 实操:从零搭一个“会辩论的 AI”
4.1 数据准备与目标选择
动手第一步是选一个具体场景来做实验。我不建议一开始就上“全市场扫描”这种大目标,最好选一个你相对熟悉的行业或个股,方便你事后判断 AI 的辩论质量靠不靠谱。
我自己跑实验时用的是“新能源汽车电池材料”这个行业话题。原因很简单:数据公开、多空逻辑都很鲜明、行业争议点足够多,模型很难回避。你如果自己复现,也可以选白酒、半导体、光伏这样的话题。
数据准备不需要特别复杂的工程。先用 Tushare 或者 AKShare 拉一下目标公司最近几个季度的营收、净利润、毛利率、经营现金流、资产负债率这些核心指标;再用新闻 API 抓最近一个月的相关标题和摘要,简单做一下分词和去重;最后把所有资料整理成一段结构化的文本,塞进每个角色的上下文中作为“共同事实基础”。
这一步就值得注意了。不管哪一方,用的都是同一份数据,这一点非常明确写在提示词里。没有这一步,辩论很容易变成“各说各话的平行宇宙”,一方说业绩好,另一方说政策风险,彼此之间根本不打照面。
4.2 用 Python 串起多角色辩论流程
这里给出一个最小可运行的代码框架。假设你将调用 deepseek-chat 或者通义千问这类兼容 OpenAI 格式接口的模型。
import openai import json client = openai.OpenAI( api_key="your-api-key", base_url="your-base-url" ) def call_model(messages, temperature=0.5): resp = client.chat.completions.create( model="your-model-name", messages=messages, temperature=temperature ) return resp.choices[0].message.content role_bull = """你是多头分析师。你的任务是从基本面、行业趋势、政策利好等角度找出一切支持上涨的证据。 你重点关注:订单增长、毛利率提升、市占率扩张、政策支持、新产品放量。 你在辩论中需要针对空头的观点逐条反驳,指出其数据口径问题或逻辑漏洞。 当前共同事实基础:{context} """ role_bear = """你是空头分析师。你的任务是从估值、财务风险、竞争格局、需求周期等角度揭露看涨逻辑的脆弱性。 你重点关注:应收账款质量、现金流状况、估值分位数、产能过剩风险、渗透率放缓。 你在辩论中需要针对多头的核心证据,质疑其时间区间选择性、基数效应以及假设的极端敏感性。 当前共同事实基础:{context} """ role_judge = """你是裁判分析师。你不持有任何多空立场。 你将独立阅读多空双方的辩论记录,提取双方最有力的论据各两条,指出双方共同的盲区。 最终输出一份 300 字以内的综合结论,并给出你当前倾向是“偏多”“偏空”还是“中性”及理由。 """ def debate(topic, context, rounds=3): bull_history = [] bear_history = [] bull_system = role_bull.format(context=context) bear_system = role_bear.format(context=context) print("===== 第一轮:立场陈述 =====") bull_1 = call_model([{"role":"system","content": bull_system}, {"role":"user","content": f"请阐述你对该议题的核心观点:{topic}"}]) bull_history.append(bull_1) print("[多头]", bull_1) bear_1 = call_model([{"role":"system","content": bear_system}, {"role":"user","content": f"请阐述你对该议题的核心观点:{topic}"}]) bear_history.append(bear_1) print("[空头]", bear_1) for r in range(2, rounds+1): print(f"===== 第 {r} 轮:交叉攻击 =====") bear_input = "多头的上一轮观点如下:\n" + bull_history[-1] + \ "\n请针对该观点中你不同意的部分逐条反驳。" bear_r = call_model([{"role":"system","content": bear_system}, {"role":"user","content": bear_input}]) bear_history.append(bear_r) print("[空头]", bear_r) bull_input = "空头的上一轮观点如下:\n" + bear_history[-1] + \ "\n请针对该观点中你不同意的部分逐条反驳。" bull_r = call_model([{"role":"system","content": bull_system}, {"role":"user","content": bull_input}]) bull_history.append(bull_r) print("[多头]", bull_r) print("===== 裁判总结 =====") judge_context = "多头最终观点:\n" + bull_history[-1] + \ "\n\n空头最终观点:\n" + bear_history[-1] judge_out = call_model([{"role":"system","content": role_judge}, {"role":"user","content": judge_context}]) print("[裁判]", judge_out) return {"bull": bull_history, "bear": bear_history, "judge": judge_out}这段代码本质上是把“多轮对话”变成了“多个角色之间的对话”。核心逻辑没有任何黑魔法,就是轮流调用模型,把对方的输出作为下一次提问的上下文。实测跑三轮大约需要 6 次 API 调用,单次成本很低,但输出的对抗强度要比单次提问高一个档次。
这里插一句我踩过的坑。最开始我把多头和空头的历史记录全部拼在一个 messages 列表里,结果模型经常“串戏”,多头会引用空头的结论说自己“也有类似担忧”。后来把每一方的上下文独立管理,只在攻击轮次选择性传入对方的最近观点,这种现象就基本消失了。模型的角色一致性,很大程度上取决于你喂给它的“最近记忆”是不是连贯的。
4.3 辩论质量的客观评估
辩论 AI 最容易被质疑的一点就是“怎么判断它辩得好不好”。我试过几个维度,实际用下来比较靠谱的有三个:
- 论据多样性:统计整场辩论中出现的高信息量短语数量,比如具体数字、具体公司名、具体政策条款。数量越多,说明模型越少说空话。
- 反驳针对性:把攻击轮次中“针对上一轮观点的回应”与“自说自话的新论点”分开统计。理想情况是前者占比超过一半。
- 结论修正幅度:对比第一轮和第三轮的观点措辞,看模型是否在吸收对方合理证据后调整了表述。完全不调整说明辩论无效,大幅倒戈也说明角色设定不稳定。
| 评估维度 | 差的表现 | 好的表现 |
|---|---|---|
| 论据多样性 | 反复使用“行业景气度高”“竞争格局好”这类套话 | 引用具体季度增速、毛利率拆分、库存周转天数等数据 |
| 反驳针对性 | 各说各话,不提对方观点 | 明确引用对方原句并指出漏洞 |
| 结论修正幅度 | 三轮观点完全一样,只是字数更多 | 第三轮开始出现“如果 XX 成立,则下调评级” |
说实话,这三条标准里最难的其实是“结论修正幅度”。很多模型在对抗压力下要么死撑到底,要么快速滑向中庸。我后来在提示词里加了一条:要求每一方在第三轮必须明确回答“对方最强的一个论据是什么,你如何回应”。这个动作看着简单,实际效果却很显著,它会逼着模型真正去处理对方给出的信息,而不是简单重复自己的立场。
5. 这套玩法的真实边界在哪里
5.1 数据时效性会导致“事实幻觉”
这一点我必须单独拿出来提醒。大模型的训练数据是有截止日期的,你让它辩论“当前市场对某某板块的情绪如何”,它有非常大的概率把三个月以前甚至一年以前的信息当作最新情况说出来。这也是为什么前面强调必须把实时数据作为共同事实基础输入进去。
跑过一轮完整的辩论之后你就知道,凡是模型“凭空说”的行业观点,大多数是过时的、泛化的老套话。你不给它当期财务数据,它就只能从记忆里翻出“三年前这个行业还在高增长”这种滞后的印象。所以,辩论质量的上限取决于你喂给它的数据新鲜度,而不是模型的推理能力。
5.2 角色对抗不等于真正理性
还要泼一盆冷水:辩论式 AI 能提高思考的全面性,但不等于最终结论更接近真相。很多情况下,它只是把“单一模型的一种偏见”变成了“多个方向的不同偏见”。多头角色天然倾向于寻找所有利好,空头角色天然倾向于忽略利好只看风险——这种对立的丰富性有助于暴露盲点,但如果基本面本身一边倒,硬凑出来的辩论就会显得很别扭。
遇到这种情况,我会在裁判总结环节额外加入一条指令:要求裁判判断双方是否存在“为辩而辩”的痕迹,并将这种无效辩论标注出来。跑了几轮之后你会发现,“为辩而辩”恰恰是辩论制 AI 最容易出现的问题。比如行业整体销量暴增 80% 时,空头硬要从“去年的基数更低”这个角度找一条风险,纯属没话找话。承认这一点,对使用者理性判断输出结论很有帮助。
5.3 合规红线:只能当辅助,不能当决策依据
最后必须强调合规问题。这个“会辩论的 AI”目前最合适的定位是“研究助理”,用来生成初稿、梳理多空逻辑、发掘潜在风险点。它不能直接给出买卖建议,更不能替代持牌机构的合规流程。从技术上你可以让它说出“我建议买入”这种话,但从使用角度我强烈不建议这么做,原因不仅仅是合规风险——说实话,模型在“建议”维度的可靠性,远低于它在“信息整理与逻辑对打”维度的可靠性。
我个人比较负责的做法是:把辩论生成的结论当作“待验证清单”,逐条去原始财报和公告里核对,确认有依据的内容才进入正式研究笔记。
6. 从“辩论”到“协作”:还能怎么延伸
跑通单个话题的辩论之后,你会发现这套流程很容易横向扩展。我现在正在把结构从“多头 vs 空头”改成“四角色会议”:战略分析师、财务风控师、行业研究员、技术面分析师。四个人坐在一张桌上,各自从自己的框架出发发表观点,再互相质询。最终输出不是一份多空结论,而是一份“多维度风险清单”。
扩展中的几个小经验可以分享一下:
- 角色数量不是越多越好。三个角色是最优平衡点,四个角色勉强可行,五个以上就开始产生大量重复论据,而且 token 消耗直线上升。
- 辩论轮次同样不是越多越好。三轮是甜点区,四轮边际收益很低,五轮以上几乎全是原地绕圈。
- 每次辩论结束后,把“裁判结论”和“被双方共同忽略的盲区”单独存下来,积累一定数量后可以拿来当 few-shot 样例,让下一轮辩论的模型更快进入状态。
按我这段时间的实测,这个“会辩论的 AI”最有价值的产出不是最终那段裁判结论,而是辩论过程中那些“被翻出来互相攻击的具体问题”。这些东西放在普通分析报告里,往往只会用一行带过。放到辩论语境里,却被反复拉扯、放大、澄清,特别能帮人看出一个投资逻辑到底站不站得住脚。
有个细节我印象很深。有一轮讨论“某材料公司的季度业绩低于预期”时,多方最初只说“这是短期扰动”,但空头反击时把过去两年每个季度的毛利率单独列了出来,指出该公司每年的第一季度都存在同样的季节性走弱。多方的第二轮回答立刻变了,不再用“短期扰动”这个词,转而承认“季节性因素已在股价中部分消化”。这种从“口头解释”到“接受数据反驳”的变化,恰恰是单一问答模式里最难出现的。
我现在的使用习惯是:每周挑一个关注的话题丢进去跑一轮,把裁判结论和各方最佳论据导出,存入自己的研究笔记。出来的东西不用全信,但用来做逻辑自检确实比对着空白屏发呆效率高很多。如果你也想试试,就从选一个你熟悉的行业话题开始,先让多空双方好好掐一架,再回来判断这场架有没有吵到点子上。