代码已实际验证可跑通,完整代码 95 行(含注释) 默认使用 DeepSeek API,国内友好、便宜,3 分钱跑完一场辩论 适合刚搞懂 LangChain、准备进阶多 Agent 的开发者
一、为什么是这个例子
多 Agent 教程市面上分两种,哪种都不好用:
- 太玄派:开篇先抽象出 5 个基类、3 个工厂、2 个调度器,看完不知道 Agent 长什么样
- 太水派:三个 Agent 顺序调一遍 API,起个名叫"协作",其实是个 if-else 也能做的事
这篇要的是第三种 :最少代码 + 真正涵盖多 Agent 的全部核心概念------共享状态、节点函数、条件边、辩论循环、收敛机制。
跑完这 95 行,你就理解了为什么 TradingAgents 那种 75k star 的多 Agent 框架,本质上也是同一套范式的堆叠。
二、先看运行效果
erlang
============================================================
辩论历史:
【研究员 R1】AI 不会让大多数程序员失业,而是改变工作内容。
当前 AI 仍需要程序员定义需求、审查代码、处理边缘情况...
【批评家 C1】你的论证忽略了一个关键事实:GitHub Copilot 已经能完成
60% 的样板代码,初级岗位需求在 2025 年明显下降...
【研究员 R2】批评家提到的 Copilot 数据恰好支持我的论点------
样板代码的减少不等于程序员失业,而是把人力解放到更高价值的设计工作...
【批评家 C2】"高价值设计工作"的容量是有限的,不可能容纳所有被替代的初级岗位...
============================================================
最终定稿:
双方共识:AI 显著改变了编程工作的形态。
主要分歧:增量替代 vs 岗位重构...
3 个 Agent、4 次发言、1 次裁决,流程清晰可控。
三、环境准备
arduino
pip install langgraph langchain-openai
export DEEPSEEK_API_KEY="你的密钥" # https://platform.deepseek.com/api_keys
只用 LangGraph + langchain-openai 两个包。DeepSeek 走 OpenAI 兼容协议,所以用 langchain-openai 即可,不需要专门的 DeepSeek SDK。
用其他模型把
base_url和model改一下就行,代码里有标注。
四、核心概念 30 秒速通
LangGraph 多 Agent 的范式就 4 个关键词:
| 概念 | 是什么 | 在代码里长什么样 |
|---|---|---|
| State | 共享状态,所有 Agent 读写同一份"案卷" | class DebateState(TypedDict) |
| Node | Agent = 节点函数,签名 state → dict |
def researcher_node(state) |
| Edge | 节点之间的流转关系 | add_edge / add_conditional_edges |
| Compile + Invoke | 编译成可执行图,塞初始 state 跑 | graph.invoke(init_state) |
整张图长这样:
scss
START → 研究员 → 批评家 ─┬─→ 研究员(继续辩论)
└─→ 总结员 → END(轮数到了)
批评家之后那个分叉就是条件边------根据 state 里的轮数计数器,决定回去继续辩论还是去总结员收敛。
五、完整代码(95 行,可直接复制)
python
"""LangGraph 最小多 Agent 示例:研究员 vs 批评家 + 总结员"""
import os
from typing import TypedDict
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
# DeepSeek 走 OpenAI 兼容协议,base_url 指向 deepseek 即可
# 换成 OpenAI/Qwen/GLM 只需改 model 和 base_url
llm = ChatOpenAI(
model="deepseek-chat",
base_url="https://api.deepseek.com/v1",
api_key=os.environ["DEEPSEEK_API_KEY"],
temperature=0.7,
)
MAX_ROUNDS = 2 # 辩论轮数(一轮 = 研究员 + 批评家各发言一次)
# ---------- 1. 共享状态:所有 Agent 读写同一份"案卷" ----------
class DebateState(TypedDict):
topic: str # 辩论话题
research: str # 研究员最新发言
critique: str # 批评家最新发言
history: str # 完整辩论历史(累加)
count: int # 已进行的回合数
final: str # 总结员最终结论
# ---------- 2. 三个 Agent 节点:每个是 state -> dict ----------
def researcher_node(state: DebateState) -> dict:
prompt = f"""你是研究员,正在辩论:{state['topic']}
你之前的论证:{state['research'] or '(首次发言)'}
批评家上一轮的质疑:{state['critique'] or '(暂无)'}
请用 3 句话提出/强化你的观点,正面回应批评家的质疑(如有)。"""
resp = llm.invoke(prompt).content
return {
"research": resp,
"history": state["history"] + f"\n【研究员 R{state['count']+1}】{resp}",
}
def critic_node(state: DebateState) -> dict:
prompt = f"""你是批评家,正在辩论:{state['topic']}
研究员刚刚的论证:{state['research']}
请用 3 句话指出研究员论证中的具体漏洞或反例,不要泛泛而谈。"""
resp = llm.invoke(prompt).content
return {
"critique": resp,
"history": state["history"] + f"\n【批评家 C{state['count']+1}】{resp}",
"count": state["count"] + 1, # 一回合在批评家这里 +1
}
def summarizer_node(state: DebateState) -> dict:
prompt = f"""你是总结员,刚才一场辩论的话题是:{state['topic']}
完整辩论历史:
{state['history']}
请用 5 句话给出最终定稿:1) 双方共识 2) 主要分歧 3) 你更倾向哪一方及理由。"""
resp = llm.invoke(prompt).content
return {"final": resp}
# ---------- 3. 路由函数:决定辩论是否继续 ----------
def should_continue(state: DebateState) -> str:
if state["count"] >= MAX_ROUNDS:
return "summarizer" # 轮数够了 → 总结员
return "researcher" # 否则回到研究员开下一轮
# ---------- 4. 组装状态图 ----------
workflow = StateGraph(DebateState)
workflow.add_node("researcher", researcher_node)
workflow.add_node("critic", critic_node)
workflow.add_node("summarizer", summarizer_node)
workflow.add_edge(START, "researcher") # 研究员先发言
workflow.add_edge("researcher", "critic") # 然后批评家
workflow.add_conditional_edges( # 批评家之后:循环 or 收敛
"critic", should_continue,
{"researcher": "researcher", "summarizer": "summarizer"},
)
workflow.add_edge("summarizer", END)
graph = workflow.compile()
# ---------- 5. 跑一个例子 ----------
if __name__ == "__main__":
init_state: DebateState = {
"topic": "AI 会让大多数程序员在 10 年内失业吗?",
"research": "", "critique": "", "history": "", "count": 0, "final": "",
}
result = graph.invoke(init_state)
print("=" * 60)
print("辩论历史:")
print(result["history"])
print("=" * 60)
print("最终定稿:")
print(result["final"])
把代码保存成 debate_demo.py,python debate_demo.py 直接跑。一场完整辩论用 DeepSeek-chat 大约消耗 3-5 千 token,成本不到 3 分钱。
六、5 个关键设计点拆解
代码能跑只是起点,理解为什么这么写才是关键。下面 5 个点是多 Agent 范式的精髓:
6.1 状态用 TypedDict 不用 class
python
class DebateState(TypedDict): # ✅
topic: str
而不是:
ruby
class DebateState: # ❌
def __init__(self):
self.topic = ""
为什么 :LangGraph 的 state 需要可序列化 ------为了支持 checkpoint(跑崩了能从断点续),状态对象必须能存进 SQLite。TypedDict 本质是 dict,天然可序列化;普通 class 还得自己写 __dict__ 或 pickle 适配。
这也是为什么 TradingAgents 那么大的项目,几十个字段全部用嵌套 TypedDict 而不是 Pydantic class。
6.2 节点返回 dict 而不是完整 state
python
def researcher_node(state) -> dict:
return {"research": resp, "history": state["history"] + ...}
# 只返回要更新的字段,不返回整个 state
LangGraph 会自动把返回的 dict merge 进全局 state。这有两个好处:
- 节点关注点最小化:每个 Agent 只管自己负责的字段,不用关心其他字段
- 并行节点合并不冲突:多个节点并行跑、各自返回不同字段,自动合并无冲突
6.3 辩论历史用"累加"不用"覆盖"
python
"history": state["history"] + f"\n【研究员 R{state['count']+1}】{resp}"
这一行是辩论循环里最重要的一行。
research和critique字段是覆盖式的,只保留最新一轮(给下一个发言者看)history字段是累加式的,保留全部历史(给总结员看)
两种字段对应两种用途 :Agent 之间快速传递最新观点用覆盖,最终复盘和裁决用累加。这是 TradingAgents 里 Bull/Bear 辩论也用的同一招------它的 InvestDebateState 同时有 current_response(覆盖)和 bull_history / bear_history(累加)。
6.4 count 计数器 + 条件边 = 循环退出
python
def should_continue(state) -> str:
if state["count"] >= MAX_ROUNDS:
return "summarizer"
return "researcher"
workflow.add_conditional_edges("critic", should_continue, {...})
LangGraph 没有显式的"while 循环"语法,循环是通过条件边自然形成的:
- 批评家发言完 → 路由函数判断 → 如果轮数没到,返回
"researcher"→ 流转回研究员节点 → 形成循环 - 轮数到了 → 返回
"summarizer"→ 跳出循环
count 在批评家节点里 +1(不在研究员节点),这样保证"研究员 + 批评家"算作一回合而不是半回合。MAX_ROUNDS 这个硬上限是防止 LLM 答非所问导致无限辩论的安全网------生产环境千万别省。
6.5 总结员只读历史 → 给定稿
python
def summarizer_node(state):
prompt = f"完整辩论历史:\n{state['history']}\n请给出最终定稿..."
# 总结员不参与辩论,只综合
这是收敛的本质:辩论双方负责"把话说透",裁判负责"拍板"。
如果让研究员或批评家自己得结论,他们的立场偏见会污染结果------必须有一个不下场的角色做综合。TradingAgents 里 Research Manager、Risk Manager、Fund Manager 都是这个定位:不参与辩论,只读历史 + 出结论。
这个设计其实是在模仿真实投行的层级------分析师吵,PM 拍板。
七、运行效果实际演示
跑完上面代码,真实输出大致是这样的(节选):
makefile
【研究员 R1】AI 不会让大多数程序员失业,理由有三:第一,Copilot 类工具
本质是生产力放大器,历史上每次工具升级都伴随岗位重构而非消失;第二,
软件需求的边际成本下降会刺激新需求出现;第三,创造性工作和系统设计
仍依赖人类判断。
【批评家 C1】第一点用历史类比当下站不住脚------AI 的能力曲线和工业革命
不可同日而语;第二点"新需求"是经济学一厢情愿,目前没有数据支持
软件需求增速能匹配 AI 替代速度;第三点把"系统设计"神化了,
实际上大量"创造性工作"已经被 LLM 拆解为可生成的模板。
【研究员 R2】批评家的反驳恰好暴露了一个隐含假设:把"程序员"等同于
"代码搬运工"。Stack Overflow 2024 调研显示,资深程序员花在写代码的
时间不到 40%,其余是沟通、设计、决策...
【批评家 C2】...
============================================================
最终定稿:
双方共识:AI 显著改变了编程工作的形态,初级岗位首当其冲。
主要分歧:研究员认为这是渐进式重构,批评家认为是结构性替代。
我更倾向研究员的论证,因为他引用了具体数据,而批评家更多依赖类比推理。
但研究员需要补充一个关键证据:被解放的人力能否匹配新岗位的技能要求。
注意第三轮研究员针对性地反驳了批评家第一轮的具体观点------这就是 state 流转的效果,Agent 之间是有真实信息传递的,不是各说各话。
八、扩展方向
这 95 行是起点,LangGraph 的能力远不止此。几个常见扩展方向:
| 想加的能力 | 怎么做 |
|---|---|
| 并行多个研究员 | 多个节点 fan-out,LangGraph 自动并行执行,merge 进 state |
| 给 Agent 工具 | llm.bind_tools([...]) + ToolNode,Agent 能自己搜资料、查 API |
| 断点恢复 | MemorySaver() 或 SqliteSaver() 一行加进 compile,跑崩了能续 |
| 人在回路审批 | interrupt() API,在裁判前暂停等人确认 |
| 替换模型 | ChatOpenAI(model="...", base_url="..."),Claude / GPT / Qwen / Ollama 一键切 |
| 可视化图结构 | graph.get_graph().draw_mermaid_png() 直接出 mermaid 图 |
| 流式输出 | graph.stream(init_state) 替代 invoke,逐节点 yield 中间结果 |
九、总结
多 Agent 不神秘,核心就三件事:
scss
节点(Agent 函数) + 状态(共享案卷) + 图(协同编排)
记住下面这张速查表,你就掌握了 LangGraph 多 Agent 开发的 80%:
| 想做 | 用什么 |
|---|---|
| 定义状态 | class XxxState(TypedDict) |
| 定义 Agent | def xxx_node(state) -> dict |
| 顺序流转 | workflow.add_edge(A, B) |
| 分支 / 循环 | workflow.add_conditional_edges(...) |
| 编译 | graph = workflow.compile() |
| 执行 | graph.invoke(init_state) |
| 流式 | graph.stream(init_state) |
| 断点恢复 | compile(checkpointer=MemorySaver()) |
剩下 20% 是 checkpoint、人在回路、并行 fan-out 这些进阶技巧,等需要的时候再学不迟。
完整代码已经放在文章第五节,复制即可跑通。可以改成自己需要的角色配置------把"研究员 vs 批评家"换成"程序员 vs Code Reviewer"、"产品 vs 用户"、"乐观派 vs 悲观派"都是同样的代码框架,只改 prompt 就行。