主讲能力:多研究员并行、观点辩论、综合报告
业务场景:用户提出研究问题,多个 Agent 从技术、商业、用户三个视角独立分析,再由辩论 Agent 找出共识与分歧,最后生成结构化研究报告。
对应代码:backend/projects/p08_research_analysis/
开篇:一场吵不出结果的产品会
周二下午,三楼会议室。CEO 把一杯咖啡往桌上一放:“我们到底要不要做 AI 视频生成?给个结论。”
技术负责人老周第一个开口:“技术上没问题。扩散模型已经成熟,开源方案一大堆,给我三个月,demo 能跑。”
话音没落,商业总监 Lisa 翻了个白眼:“技术是没问题,可市场呢?头部那几家烧了几亿美金,我们这点预算冲进去,连个水花都砸不出来。”
用研的小陈慢吞吞补了一刀:“我上周访谈了 20 个创作者,他们要的是‘省时间’,不是‘炫技’。现有工具够用了,付费意愿很弱。”
三个人,三个方向,谁也说服不了谁。会开了两小时,结论是:下周再议。
散会后,CEO 拉住你叹气:“要是能找个分析师给我个痛快话就好了。”
可问题恰恰在这里——如果只找一个分析师,他大概率会端出一个“平均答案”:技术可行,但市场有风险,用户需求中等,建议谨慎推进。 听着四平八稳,其实什么也没说。技术可行性有多高?市场风险大到什么程度?用户需求“中等”又是什么鬼?
真正有价值的,不是这个和稀泥的结论,而是刚才会议室里那种张力本身——三个视角各自的最大化表达,以及它们相互碰撞后暴露出来的矛盾。于是你有了主意:与其让一个分析师去调和,不如让多个研究员 Agent 各自独立研究,再把它们拉到一起辩论,最后综合成一份既保留张力、又给出判断的报告。
这就是本章的项目八。
💡 本章灵魂:多视角对抗,而非流水线协作
第 9 章的 Agent 是“接力赛”——上一个把棒交给下一个,彼此信任、各司其职。本章的 Agent 是“辩论赛”——谁也不轻信谁,非得把矛盾摆到台面上,辩一辩、掂一掂,才肯下结论。
好的决策不是给一个答案,而是让不同视角充分表达,再形成可解释的综合判断。
10.1 那场吵翻的会
会议室里的那场僵局,本质上是一个“多视角决策”问题。复杂问题很少只有一个正确答案,关键也不在于谁对谁错,而在于不同视角是否都被充分表达了。把这套逻辑搬到一个研究分析平台上,需求就清晰了:
本项目构建一个研究分析平台,让多个研究员 Agent 独立产出观点,再由辩论和综合环节形成结论。
功能需求
- FR-1:支持用户输入开放式研究问题。
- FR-2:技术、商业、用户三个研究员并行分析。
- FR-3:辩论节点找出矛盾、共识、证据不足之处。
- FR-4:综合节点输出决策建议、风险清单、下一步行动。
- FR-5:保留每个研究员原始输出,便于追溯。
10.2 画个样子:它该长啥样
需求清楚了,流程怎么走?一张图胜过千言万语:
%%{init: {'theme':'base','flowchart':{'useMaxWidth':true,'htmlLabels':true}}}%%
graph TD
Q["研究问题"] --> Tech["技术研究员"]
Q --> Biz["商业研究员"]
Q --> User["用户研究员"]
Tech --> Debate["辩论 Agent"]
Biz --> Debate
User --> Debate
Debate --> Synth["综合 Agent"]
Synth --> Report["研究报告"]
classDef r fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064
classDef d fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92
classDef o fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20
class Tech,Biz,User r
class Debate,Synth d
class Report o
三个研究员同时接到同一个问题,各自闷头研究;研究完不急着下结论,先交给辩论 Agent 把共识、分歧、盲区扒出来;最后综合 Agent 拿着这些“辩过”的材料,写出一份报告。
注意这条流水线的形状:前面是扇出(一个问题分给三个人),中间是收束(三份报告汇成一个辩论),最后再收成一份结论。这种“先散后收”的形状,正是多视角对抗的物理结构——没有前面的散,就没有后面的真;没有中间的收,散就只是一盘散沙。
10.3 拆开看:怎么造出来
研究员怎么落地?这里有个关键选择:是用三个独立 Agent,还是用“工具化研究员”?本项目选了后者——每个研究员是一个工具函数,内部调用模型完成特定视角的分析,结果存入 ResearchStore(生产环境走 ResearchRepository 落 SQLite)。
为什么要“工具化”?因为研究员的本质不是“自主决策”,而是“按固定框架执行一次分析”。把它做成工具,既保留了视角的独立性(每个工具有自己的 Prompt),又把调度权交给首席研究员 Agent,让它决定何时调谁。这种模式有三个好处:
- 每个视角 Prompt 独立,职责清晰,互不污染。
- 原始研究结果可追踪,结论追溯到视角、视角追溯到 Prompt。
- 后续可平滑替换为真实 Web Search / RAG / 数据库分析工具,服务层一行不用改。
⚠️ 避坑:本书代码的研究员目前只调模型,没接真实数据源。 也就是说,商业研究员嘴里的“TAM/SAM/SOM”、技术研究员嘴里的“成功率/研发周期”,都是模型凭训练知识“编”的,不是实时检索来的。生产版本必须接入真实搜索、论文数据库、商业数据库并标注引用来源,否则报告再漂亮也是空中楼阁。本书当前代码刻意保留了接口边界,正是为了这一天。
10.4 动手写:三层架构完整代码
理论说够了,上代码。本节给出多智能体研究分析平台的完整实现。项目采用分层架构——说白了就是把“定义数据”“写 Prompt”“跑工具”“存数据”“编排流程”“对外暴露”这几件事分别关进不同的房间,谁也别越界。这样每层都能单独演进、单独测试。
10.4.1 三层架构完整代码
项目按职责划分为以下层次,每一层都对应一个独立文件:
| 层次 | 文件 | 职责 |
|---|---|---|
| 模型层 | models.py | 研究角色、研究发现、辩论结果、报告模型 |
| 提示词层 | prompts.py | 各研究员系统提示词 |
| 工具层 | tools.py | 研究/辩论/综合工具函数 |
| 仓储层 | repositories.py | SQLite 持久化存储 |
| 服务层 | service.py | 并行研究、辩论、综合工作流编排 |
| 项目层 | project.py | 项目注册、对外接口 |
| 入口 | init.py | 注册与 re-export |
下面按层次依次给出每个文件的完整代码。读的时候不妨带着一个问题:如果要把“顺序研究”升级成“真并行”,或者把“调模型”换成“调搜索引擎”,分别该动哪一层? 答案就藏在分层里。
models.py
1 | """项目八:数据模型层。 |
prompts.py
1 | """项目八:Prompt 层。 |
tools.py
1 | """项目八:工具层。 |
repositories.py
1 | """项目八:仓储层。 |
service.py
1 | """项目八:服务层。 |
project.py
1 | """项目八:项目定义层。 |
init.py
1 | """项目八:多智能体研究分析平台(Multi-Agent 深度协作) |
10.4.2 核心代码讲解
① 三个研究员,同一道题,各自作答。 tools.py 里站着三位研究员:research_technical、research_business、research_user。它们长得很像——都是“拿 Prompt → 调模型 → 存结果”——但灵魂不同:技术研究员盯着可行性和架构,商业研究员盯着市场和成本,用户研究员盯着需求和痛点。三套 Prompt 互不见面,谁也不影响谁。
service.py 的 run_parallel_research 把这三个工具串起来,把各自的产出以 ResearchFinding 的形式追加到同一个 ResearchTask 里。于是在同一道题上,我们攒下了三份独立的判断——这正是后面辩论的弹药。
💡 顿悟时刻:为什么要“独立”? 想象商业研究员在动笔前偷看了技术研究员的稿子。他大概率会顺着技术的结论往下写——要么附和(技术都说能做,那市场肯定有戏),要么抬杠(技术太乐观,我得泼冷水)。无论哪种,他都不再是“商业视角”,而成了“技术视角的回声”。独立性是辩论有意义的前提,没有独立,所谓多视角就是一个人换三顶帽子。
⚠️ 避坑:这里的“并行”目前是名义上的。 翻开 run_parallel_research 你会发现,三个工具其实是顺序执行的,代码注释也写明了“为了演示清晰,顺序执行”。真要并行,得用 asyncio.gather 把三个工具并发跑起来——好在分层架构把这层改动关在了服务层一处,工具层一行都不用动。文档里写的“并行研究”是目标态,当前代码是顺序近似,这一点别被名字骗了。
⚠️ 避坑:工具间靠模块级全局变量传话。 三个研究员把结果写进 _in_memory_findings 这个模块级字典,辩论和综合再从这里读。这意味着同时跑两个研究任务会互相踩——A 任务的研究结果会被 B 任务覆盖。单用户演示没问题,多租户生产环境必须把这块状态挪进任务上下文或数据库。
② 辩论:不是再写一份观点,而是给观点照 X 光。 研究做完了,debate_findings 上场。它把三方发现拼成一份统一上下文,交给 DEBATER_PROMPT 驱动的辩论主持人。
注意辩论主持人的活儿和别人不一样——他不产生新观点,他做“元分析”:哪些是三方都点头的共识?哪些是互相打架的分歧?有哪些重要问题是三方都没碰的盲区?每一方又各自带来了什么独特贡献?这套结构沉淀成 DebateResult,to_markdown 能把共识、分歧、盲区清清楚楚地摊开。
💡 顿悟时刻:为什么要先辩论,再综合? 为什么不直接把三份报告扔给综合 Agent 让它合并?因为大模型天生是“和事佬”。你把三份报告一锅端给它,它十有八九会端出一碗粥:“技术可行,市场存在挑战,用户需求中等,建议谨慎推进。” 听着面面俱到,实则把真正的矛盾给抹平了。
辩论这一步,就是逼着系统先把矛盾叫出名来——“技术说三个月能出 demo,商业说预算撑不过两个月,这就是分歧”——矛盾被命名、被看见,才有可能被真正解决。综合 Agent 拿到的不是三份各说各话的报告,而是一份已经把分歧标注好的地图,写出来的结论自然有张力、有取舍。
⚠️ 避坑:当前代码的辩论结果是“整坨”存的。 严格说,DebateResult 定义了 consensus_points、contradiction_points、blind_spots 这些结构化字段,但 run_debate 只把模型返回的整段文本塞进了 overall_assessment 一个字段,并没有解析成结构化列表。也就是说,模型其实照着结构化框架输出了,但代码没接住,to_markdown 的优雅排版在服务流程里基本没派上用场。这是后续可以补强的地方——加个解析器,把模型输出映回结构化字段。
③ 仓储层:让每一次研究都留痕。 repositories.py 的 ResearchRepository 用 SQLite 落库,四张表分工明确:任务表存元信息,研究发现表存三方原始产出,辩论结果表存辩论,最终报告表存结论。
写入策略是“先删后插”——save_task 每次先清掉该任务的旧数据再写新的,保证同一个任务可以反复更新而不留脏数据。列表和字典类字段(共识点、结论、建议)用 json.dumps(ensure_ascii=False) 序列化成文本存,读回时 json.loads 还原。list_tasks 按时间倒序浏览,search_tasks 按关键词检索——每一次研究都可追溯、可复查,这一点在严肃决策场景里比报告本身还重要。
④ 服务层:把流程编排成一部三幕剧。 ResearchAnalysisService 把完整研究拆成三幕:run_parallel_research(研究)→ run_debate(辩论)→ run_synthesis(综合),由 run_full_research 统一指挥。
每一幕开场前,先把 ResearchStatus 推到对应状态(RESEARCHING → DEBATING → SYNTHESIZING)并落库;幕落了再落一次库。于是数据库里留下了一条清晰的状态机轨迹,出了问题一眼能看出卡在哪一幕。build_agent 用 create_agent 装配首席研究员 Agent,工具来自 get_all_tools,系统提示词是 LEAD_RESEARCHER_PROMPT,懒加载、带缓存。
⑤ 错误降级:每一层都给自己留后路。 这套系统在容错上挺“怂”——但怂得有道理。研究员工具用 try/except 兜住模型调用异常,失败了不抛,而是返回一串带 ❌ 的提示;run_parallel_research 给每个视角单独 try/except,只要有一项成功就继续往下走,靠 "失败" not in result 判断产出是否有效;run_debate 看到结果里带“尚无”“失败”就返回 False,run_synthesis 看到“需要先完成”“失败”就返回 None,谁都不让流程硬中断。
project.py 的 run 更是做了双层兜底:先探一探当前有没有在跑的事件循环,据此决定用 asyncio.run 还是 agent.run;外层再套一个 try,万一全崩了,退化为直接调 Agent。宗旨就一条:任何环节出问题,都给用户一个能读的回复,而不是一个 traceback。
⑥ 分层的好处:换零件不拆房子。 模型层只管定义数据,提示词层只管描述角色,工具层只管单步执行,仓储层只管存取,服务层只管编排,项目层只管对外。六层各司其职,带来三样东西:
一是可测试——每层都能单独 mock,仓储层注入个内存库、工具层注入个假模型,单测就能跑;二是可替换——研究员现在直接调模型,将来要换 Web Search、论文库或 RAG,只动工具层,服务层不眨眼;三是可演进——前面说的“顺序升真并行”,也只改 run_parallel_research 一处。__init__.py 把各层符号统一 re-export,再顺手 registry.register,外部 import 一下包就能用。
金句:分层的本质,是给未来的改动画好“施工范围”。 改哪一层,灰尘就只落在哪一层。
10.5 跑一跑:它真的行吗
测试要盯的不是“报告好不好看”,而是“流水线每一段是否站得住”。重点这么几条:
ResearchStore能不能正确保存和聚合多视角结果(这是最底层的数据合同)。- 三个研究员工具各自是否返回非空文本——空文本意味着某一视角“失声”,后面辩论就少了弹药。
- 综合工具产出的报告里,是否真的包含决策建议和风险清单,而不是一团和气的废话。
- 集成测试跑一遍完整流程(研究 → 辩论 → 综合),验证三幕剧能从头演到尾。
10.6 送上线:让它上班
前端输入研究问题后,执行过程面板会依次显示三个研究员的产出,再显示辩论和最终报告——用户看到的不是一锤子结论,而是结论是怎么长出来的。生产环境务必开启日志落库,让每个结论都能追溯到“它来自哪个视角、经过了怎样的辩论”。审计能不能查、敢不敢查,是这套系统从“演示”走向“决策辅助”的分水岭。
10.7 回头看:学到了什么
回到那场吵不出结果的产品会。技术、商业、用户三方僵持不下,其实不是问题,而是信号——它说明这件事值得从三个角度都看一遍。
本章给了这个信号一个去处:让三个研究员 Agent 各自独立研究,让辩论 Agent 把矛盾叫出名来,让综合 Agent 在已知分歧的前提下给出判断。这就是 Multi-Agent 的第二种价值——不是流水线协作,而是多视角对抗。
金句:好的决策不是给一个答案,而是让不同视角充分表达,再形成可解释的综合判断。
第 9 章的 Agent 像接力赛,棒交下去,彼此信任;本章的 Agent 像辩论赛,谁也不轻信谁,非得把矛盾摆上台面。前者解决“怎么把一件事做顺”,后者解决“怎么把一件事想透”。
当然,本章代码离生产级还有几段路要走:研究是顺序而非真并行、工具间靠全局变量传话、辩论结果没解析成结构化、研究员还没接真实数据源。但这些“留白”恰恰是分层架构留下的礼物——每一处短板,都已经被关在它该在的那一层里,等你去填。
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !