20 KiB
小财管家 LangGraph Runtime 迁移计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 把小财管家的感知、规划、记忆、确认和行动逐步迁到 LangGraph runtime,减少自研流程编排里的隐藏状态和分支 bug。
Architecture: 保留现有 FastAPI API、Pydantic schema、RuntimeChatService 模型供应商抽象和业务服务;LangGraph 只接管 agent 编排。副作用动作必须通过白名单 action node 调用现有业务服务,保存草稿、提交和关联单据都不能由模型文本直接触发。
Tech Stack: Python 3.11、FastAPI、Pydantic、LangGraph 1.x、SQLAlchemy、现有 AgentConversationService / StewardFlowStateService。
当前状态
已经完成第一阶段接入:
server/src/app/services/steward_graph_planner.py- 已新增
StewardGraphPlannerService。 - 已用 LangGraph
StateGraph编排prepare_context -> detect_model_intent -> done/off_topic/rule_fallback。
- 已新增
server/src/app/services/steward_graph_runtime.py- 已新增
StewardGraphRuntime。 - 槽位决策已编排为
slot_prepare_context -> slot_tool_decision -> done/rule_fallback。 - 运行时决策已编排为
runtime_memory_context -> runtime_action_decision/runtime_tool_decision -> done/rule_fallback。 - 图内失败会降级到原有规则兜底;端点层图运行异常会回退旧 Agent。
- 已新增
server/src/app/services/steward_action_contracts.py- 已新增
StewardActionPlanBuilder。 - 已把申请、报销、保存草稿、直接提交规划成
StewardActionStep白名单动作。 StewardGraphPlannerService已增加attach_action_steps节点,计划返回前统一补全动作列表。
- 已新增
server/src/app/services/steward_action_executor.py- 已新增
StewardActionExecutor。 - 已接入
/steward/actions/execute,支持未知动作拒绝、缺字段阻断、提交确认门禁、重复申请 precheck、申请/报销草稿真实执行、关联申请单和附件关联。 - 申请动作复用
UserAgentService的申请保存/提交能力,报销草稿复用ExpenseClaimService.save_or_submit_from_ontology()。
- 已新增
server/src/app/services/steward_graph_action_runtime.py- 已新增
StewardGraphActionRuntime。 - 已用 LangGraph
StateGraph编排action_checkpoint_load -> action_execute_node -> action_checkpoint_persist。 - 已通过
conversation_id + client_trace_id在AgentConversation.state_json中记录 checkpoint、pending interrupt 和幂等重放结果。
- 已新增
web/src/services/steward.js/web/src/views/scripts/stewardPlanModel.js- 前端已新增
executeStewardAction()服务方法。 - steward suggested action 已携带服务端可执行 action step,旧申请预览流仍保留兜底。
- 前端已新增
web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js- AI 工作台点击
steward_execute_action时会调用/steward/actions/execute。 - 直接提交会先执行
run_duplicate_precheck,precheck 通过后再提交申请。
- AI 工作台点击
server/src/app/api/v1/endpoints/steward.py/steward/plans和/steward/plans/stream默认使用StewardGraphPlannerService。/steward/slot-decisions和/steward/runtime-decisions默认使用StewardGraphRuntime。STEWARD_AGENT_RUNTIME=legacy可回退旧StewardPlannerService。
server/pyproject.toml/server/uv.lock- 已加入
langgraph>=1.2.0,<2.0.0。
- 已加入
server/tests/test_steward_graph_planner.py- 已覆盖默认 LangGraph、显式 LangGraph、legacy 回退、模型成功计划和模型失败后规则兜底。
server/tests/test_steward_graph_runtime.py- 已覆盖槽位工具节点、图内规则兜底、运行时记忆合并、确定性行动选择和端点级 legacy 兜底。
server/tests/test_application_fact_resolver.py- 已覆盖
交通火车不污染申请事由,以及高铁往返这类业务描述仍保留。
- 已覆盖
当前已完成规划入口、slot decision、runtime decision、基础 action contract、第一版 action executor、前端点击执行闭环,以及 action runtime 的 checkpoint / interrupt / 幂等重放。 真实外部模型连通性仍需要用当前 MiniMax / backup 配置再回放确认。
官方能力边界
LangGraph 官方文档强调它是低层 agent orchestration runtime,重点能力是 durable execution、streaming、human-in-the-loop 和 persistence。
本项目只采用这些底层编排能力:
- Graph API:用 state、node、edge 表达流程,node 可以是 LLM 调用,也可以是普通业务代码。
- Persistence:用 checkpointer 保存 thread scoped state,用 store 保存跨线程长期信息。
- Interrupt:在需要用户确认时暂停 graph,等待外部输入后恢复。
参考:
- https://docs.langchain.com/oss/python/langgraph/overview
- https://docs.langchain.com/oss/python/langgraph/graph-api
- https://docs.langchain.com/oss/python/langgraph/persistence
- https://docs.langchain.com/oss/python/langgraph/interrupts
总体目标架构
用户输入 / 前端上下文 / 附件
↓
StewardGraphRuntime
├── prepare_context_node
├── model_intent_node
├── slot_decision_node
├── flow_guard_node
├── action_plan_node
├── human_review_node
├── action_execute_node
├── persist_state_node
└── response_adapter_node
↓
现有前端协议:StewardPlanResponse / StewardRuntimeDecisionResponse / StewardSlotDecisionResponse
关键原则:
- API 响应协议稳定,前端不因为 runtime 替换被迫大改。
- 模型只负责理解、拆步骤、给候选 action,不直接写库。
- 所有副作用节点必须调用现有业务服务,并保留确认和审计。
RuntimeChatService继续是唯一模型调用入口,LangGraph 不直接管理供应商密钥。StewardFlowStateService和AgentConversationService是 checkpoint 落库的第一候选,不另起一套状态源。
Runtime State 草案
第一版 graph state 用 TypedDict,后续需要持久化时再评估 Pydantic schema。
class StewardGraphState(TypedDict, total=False):
request: StewardPlanRequest
conversation_id: str
thread_id: str
message: str
base_date: date
steward_state: dict[str, Any]
model_call_traces: list[dict[str, Any]]
intent_result: StewardIntentAgentResult | None
slot_decision: StewardSlotDecisionResponse | None
runtime_decision: StewardRuntimeDecisionResponse | None
action_plan: list[StewardActionStep]
pending_interrupt: dict[str, Any]
action_result: dict[str, Any]
response: StewardPlanResponse | StewardRuntimeDecisionResponse | StewardSlotDecisionResponse
fallback_reason: str
thread_id 推荐使用现有 conversation_id,这样 LangGraph checkpoint、业务会话和前端会话能对齐。
节点边界
感知节点
-
prepare_context_node- 输入:
StewardPlanRequest或 runtime decision request。 - 输出:清洗后的消息、base date、当前 conversation state。
- 禁止:模型调用、数据库写入。
- 输入:
-
model_intent_node- 调用:
StewardIntentAgent.detect()。 - 约束:保留
timeout_seconds=10、max_attempts=3、use_failure_cooldown=False。 - 失败:只写入
fallback_reason和model_call_traces,不吞掉错误上下文。
- 调用:
决策节点
-
slot_decision_node- 迁移对象:
StewardSlotDecisionAgent.decide()。 - 目标:让字段缺口判断进入同一个 graph state,不再由前端和后端多个入口各自推断。
- 迁移对象:
-
runtime_decision_node- 迁移对象:
StewardRuntimeDecisionAgent.decide()。 - 目标:用户补字段、确认流程、取消当前动作、继续下一任务都走 graph routing。
- 迁移对象:
-
flow_guard_node- 负责申请/报销歧义、重复单据、时间重叠、低置信度、off-topic。
- 低置信度或强冲突必须进入确认或阻断,不允许继续 action 执行。
行动节点
-
action_plan_node- 输出白名单步骤,例如
build_application_preview、save_application_draft、submit_application。 - 禁止输出自由文本 action。
- 输出白名单步骤,例如
-
human_review_node- 对保存草稿、提交审批、关联申请单等副作用动作生成 interrupt payload。
- 第一阶段可继续返回前端确认动作;第二阶段再接 LangGraph
interrupt()。
-
action_execute_node- 只能调用现有业务服务。
- 必须幂等,或在执行前检查动作是否已完成。
- 提交类动作必须检查
confirmation_required和用户确认来源。
-
persist_state_node- 合并
StewardFlowStateService.merge_plan()/merge_state()。 - 后续接入 LangGraph checkpointer 后,这里负责业务状态与 checkpoint 的一致性。
- 合并
响应节点
response_adapter_node- 输出现有 Pydantic response。
- 必须保留
model_call_traces、planning_source、steward_state。
分阶段计划
Phase 0:规划入口接入 LangGraph
状态:已完成。
完成标准:
- 默认 runtime 为
langgraph。 STEWARD_AGENT_RUNTIME=legacy可回退。/steward/plans真实接口仍返回兼容的StewardPlanResponse。- 模型失败后仍按 10s * 3 次再规则降级。
Phase 1:修通模型成功路径
目标:让真实保存草稿话术至少能走一次 llm_function_call 成功路径。
文件:
- 修改:
server/src/app/services/runtime_chat.py - 修改:
server/src/app/services/settings.py - 修改:
server/tests/test_runtime_chat_service.py - 测试:
server/tests/test_steward_intent_agent.py
步骤:
- 写失败测试:模拟 main provider 超时但 backup provider 可用时,
complete_with_tool_call()返回 backup 的 tool call。 - 确认 backup function calling 会在 main 失败后返回 tool call,不等待 main 重试结束。
- 写失败测试:MiniMax 返回非 OpenAI tool call 兼容格式时,服务端能记录清晰 trace 并降级。
- 容器内运行:
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux \
timeout 60s /tmp/x-financial-server-venv/bin/pytest -q \
server/tests/test_runtime_chat_service.py \
server/tests/test_steward_intent_agent.py
验收:
- 真实
/api/v1/steward/plans对保存草稿原句返回planning_source=llm_function_call。 model_call_traces能区分 main 失败、backup 成功或全失败。
Phase 2:slot/runtime decision 迁入 graph
目标:把字段缺口和运行时动作判断从独立 agent 类迁进同一个 graph runtime。
状态:已完成。
文件:
- 已创建:
server/src/app/services/steward_graph_runtime.py - 已修改:
server/src/app/api/v1/endpoints/steward.py - 已测试:
server/tests/test_steward_graph_runtime.py - 保留测试:
server/tests/test_steward_slot_decision_agent.py - 保留测试:
server/tests/test_steward_runtime_decision_agent.py
步骤:
- 写失败测试:
StewardGraphRuntime.decide_slot()调用 slot node 后返回与现有StewardSlotDecisionAgent.decide()等价的 response。 - 写失败测试:
StewardGraphRuntime.decide_runtime()在用户补充字段时合并steward_state。 - 把
StewardSlotDecisionAgent包成slot_tool_decision节点。 - 把
StewardRuntimeDecisionAgent包成runtime_tool_decision节点。 - 把已选流程确认和当前运行时记忆归一化拆成
runtime_memory_context/runtime_action_decision节点。 - endpoint 在
STEWARD_AGENT_RUNTIME=langgraph时使用 graph runtime。 - endpoint 在 graph runtime 异常时回退旧 Agent,避免框架层失效影响会话。
验收:
slot-decisions、runtime-decisions和plans三个入口都由 LangGraph runtime 路由。- legacy 回退仍可用。
- 图内工具节点失败时仍有可执行规则兜底。
Phase 3:action node 标准化
目标:把保存草稿、提交审批、关联申请单这些副作用动作变成显式 action node。
状态:基础 action contract、第一版 action executor、前端点击执行闭环、LangGraph action node、checkpoint、pending interrupt 和幂等重放已完成;后续只剩 durable checkpointer 抽象与更完整 trace UI。
文件:
- 已创建:
server/src/app/services/steward_action_executor.py - 已创建:
server/src/app/services/steward_action_contracts.py - 已创建:
server/src/app/services/steward_graph_action_runtime.py - 已修改:
server/src/app/api/v1/endpoints/steward.py - 已修改:
server/src/app/services/steward_graph_planner.py - 已修改:
server/src/app/schemas/steward.py - 已修改:
web/src/services/steward.js - 已修改:
web/src/views/scripts/stewardPlanModel.js - 已修改:
web/src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js - 已修改:
web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js - 已测试:
server/tests/test_steward_action_executor.py - 已测试:
server/tests/test_steward_graph_planner.py - 已测试:
server/tests/test_steward_planner.py - 已测试:
web/tests/workbench-ai-intent-planner-model.test.mjs - 已测试:
web/tests/steward-actions-service.test.mjs - 已测试:
web/tests/steward-plan-message-copy.test.mjs - 已测试:
web/tests/workbench-ai-action-router.test.mjs
白名单 action:
build_application_previewsave_application_draftsubmit_applicationlink_existing_applicationcreate_reimbursement_draftassociate_attachments
步骤:
- 写失败测试:申请直接提交计划必须输出
fill_application_fields -> build_application_preview -> validate_required_fields -> run_duplicate_precheck -> submit_application。 - 写失败测试:保存草稿计划必须输出
save_application_draft,并在字段缺失时阻断后续副作用。 - 写失败测试:报销计划必须输出
fill_reimbursement_fields -> build_reimbursement_preview -> validate_required_fields -> create_reimbursement_draft。 - 在
StewardTask和StewardPlanResponse中增加action_steps。 - 实现
StewardActionPlanBuilder,由服务端确定性生成白名单 action step。 - 在 LangGraph planner 中新增
attach_action_steps节点。 - 前端 planner model 优先消费服务端
task.action_steps,旧响应才回退本地推导。 - 写失败测试:未知 action step 被拒绝,不调用任何业务服务。
- 写失败测试:
submit_application缺少确认来源时返回needs_confirmation。 - 写失败测试:
submit_application必须看到 precheck 通过后才允许真实提交。 - 实现 action registry,只允许白名单 action。
- 接入现有申请保存草稿、申请提交和报销草稿服务。
- 前端新增
executeStewardAction()并让 suggested action 携带服务端 action step。 - AI 工作台点击 suggested action 时调用
StewardActionExecutor;直接提交先串行执行 precheck,再把结果交给 submit。 - 接入
link_existing_application和附件关联的真实执行。 - 把 action executor 包成 LangGraph
action_execute_node,并接入 checkpoint / interrupt 恢复。
验收:
- 模型不能通过自由文本触发数据库写入,服务端只输出白名单
StewardActionStep。 - 申请提交动作没有确认时返回
needs_confirmation,不会写库。 - 申请提交动作没有 precheck 通过证据时被阻断。
- 保存申请草稿和创建报销草稿通过现有业务服务真实入库,测试覆盖。
- 前端点击可执行 steward action 会真实调用 executor,并把结果回写到会话消息。
- 基础副作用动作有
client_trace_id幂等键、conversation checkpoint 和 pending interrupt 记录。
Phase 4:checkpoint 与 human-in-the-loop
目标:让 LangGraph 负责暂停、恢复和跨轮状态,而不是只靠前端消息状态。
文件:
- 已创建:
server/src/app/services/steward_graph_action_runtime.py - 已复用:
server/src/app/models/agent_conversation.py - 已测试:
server/tests/test_steward_action_executor.py
步骤:
- 设计
conversation_id -> thread_id映射,当前直接以conversation_id作为 graph action thread。 - 实现基于数据库的 checkpoint,先复用
AgentConversation.state_json。 - 在提交审批缺确认时生成 pending interrupt payload。
- 用户确认后用同一
client_trace_id/conversation_id继续执行;终态请求会幂等重放。
验收:
- 刷新页面后,后端仍保留当前等待确认的 action checkpoint。
- 同一个 action 不会因为重复点击或重试重复写库。
- 前端完整恢复 UI 仍需继续把 checkpoint 状态展示为可恢复按钮。
Phase 5:可观测性与 legacy 收敛
目标:让 agent 规划过程可解释、可测试、可回放,并逐步删除重复手写编排。
文件:
- 修改:
server/src/app/services/agent_traces.py - 修改:
server/src/app/models/agent_run.py - 修改:
document/development/Agent链路追踪中心/CONCEPT.md - 测试:
server/tests/test_agent_traces.py
步骤:
- 每个 graph node 写入 trace event:输入摘要、输出摘要、耗时、错误。
- 前端显示“模型规划中 / 字段判断中 / 等待确认 / 执行动作”阶段。
- 对 legacy planner 增加废弃标记和删除条件。
- 删除重复的手写分支前,先保留至少一轮灰度开关。
验收:
- 任何一次用户请求都能从 trace 看出走过哪些 node。
- 删除 legacy 前,LangGraph 路径覆盖当前核心申请/报销链路测试。
回退策略
- 环境变量:
STEWARD_AGENT_RUNTIME=legacy
- 回退后:
/steward/plans使用旧StewardPlannerService。- 新 action node 和 checkpoint 不应影响 legacy。
- 回退原因必须写入当天工作日志。
验证矩阵
每个阶段至少跑:
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux \
timeout 60s /tmp/x-financial-server-venv/bin/pytest -q \
server/tests/test_steward_graph_planner.py \
server/tests/test_steward_planner.py \
server/tests/test_steward_intent_agent.py \
server/tests/test_runtime_chat_service.py
涉及前端执行器时补跑:
node --test web/tests/workbench-ai-intent-planner-model.test.mjs \
web/tests/workbench-ai-application-gate-model.test.mjs \
web/tests/steward-actions-service.test.mjs \
web/tests/steward-plan-message-copy.test.mjs
npm --prefix web run build
每次完成后执行:
git diff --check
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-local-linux \
/tmp/x-financial-server-venv/bin/python -m pip check
不做的事
- 不引入 LangChain 高层 Agent 来替代现有业务服务。
- 不让模型直接调用数据库写入、提交审批或绑定附件。
- 不为了接框架重写申请、报销、OCR、票据夹、审批规则。
- 不在没有测试覆盖时删除 legacy planner。
风险与处理
- 模型连通性不稳定:先修 provider 和 backup,再扩大 graph 节点。
- checkpoint 与业务 state 双写不一致:先复用
AgentConversation.state_json,不要新增第二个业务状态源。 - action node 副作用重复执行:每个 action 必须有幂等键和执行前状态检查。
- LangGraph 传递依赖影响 WebSocket:当前
pip check通过;后续若流式接口异常,先核对websockets版本。 - 节点内继续堆大块逻辑:每个 node 只做一类事,超过 300 行先拆文件。