第 1 章 · LangGraph 编排与状态机
本章讲整个流程怎么被组装成一台状态机、又怎么跑起来。读完你能在脑子里画出完整的图,并说清“辩论为什么会停、崩溃为什么不会连累全图”。
1.1 一句话:它是一台共享状态的接力机
TradingAgents 的“图”是 LangGraph 的 StateGraph。核心只有三样东西:
- 一个共享状态
AgentState——一个大 TypedDict,装着报告、辩论历史、最终决策等所有字段。 - 一堆节点——每个 agent 是一个节点函数:读 state 里它要的字段,返回一个 dict 去更新 state。
- 一堆边——决定节点之间怎么走;有的是固定边,有的是条件边(看 state 决定去哪)。
组装发生在 GraphSetup.setup_graph(graph/setup.py:61),编译和运行发生在 TradingAgentsGraph(graph/trading_graph.py)。
1.2 共享状态 AgentState 长什么样
AgentState 继承 LangGraph 的 MessagesState(自带 messages 列表),再挂上业务字段(agent_states.py:47):
| 字段 | 谁写 | 干什么 |
|---|---|---|
company_of_interest / trade_date / asset_type | 初始化 | 本次分析的标的、日期、资产类型 |
instrument_context | 运行开始 | 确定性解析出的标的身份(公司名/行业),防幻觉的关键 |
market_report / sentiment_report / news_report / fundamentals_report | 四个分析师 | 四份分析报告 |
investment_debate_state | 多空研究员 + 研究经理 | 多空辩论的全部历史与裁决(见下) |
investment_plan | 研究经理 | 给交易员的投资计划 |
trader_investment_plan | 交易员 | 可执行的交易提案 |
risk_debate_state | 风险三方 + 组合经理 | 风险辩论历史与裁决 |
final_trade_decision | 组合经理 | 最终决策(整段文字) |
past_context | 运行开始 | 从记忆日志注入的历史教训 |
两个辩论子状态是嵌套 TypedDict,关键是各自带一个 count(轮数计数器)和 current_response/latest_speaker(谁刚说完)——这两样就是辩论循环的“节拍器”(agent_states.py:8-44)。
1.3 图的骨架:节点和边怎么连
setup_graph 的组装顺序(graph/setup.py:95-154):
START
│ (固定边)
▼
[分析师 1]──条件边──▶ tools_X ──固定边──▶ 回到[分析师 1] ← 取数循环
│ (要么进 tools_X,要么进 Msg Clear X)
▼ Msg Clear X (清空消息)
[分析师 2] … 同样的结构 … [分析师 N]
│ 最后一个分析师 clear 后固定边
▼
[Bull Researcher]──条件边(should_continue_debate)
├─▶ Bear Researcher ──条件边──┐
│ ▲ │
└────────┴─────────────────────┘ ← 多空辩论循环
│ count 到顶 →
▼
[Research Manager] ──固定边──▶ [Trader] ──固定边──▶ [Aggressive Analyst]
│ 条件边(should_continue_risk_analysis)
Aggressive → Conservative → Neutral 轮转 ◀─────────┘ ← 风险辩论循环
│ count 到顶 →
▼
[Portfolio Manager] ──固定边──▶ END
分析师节点是按 selected_analysts 动态生成的:每个分析师配三件套——agent 节点、清理节点、工具节点(setup.py:98-101)。这三件套的名字由 build_analyst_execution_plan 统一分配(见 1.6)。