Files
X-Financial/document/development/AI意图规划器/LANGGRAPH_RUNTIME_MIGRATION.md

20 KiB
Raw Blame History

小财管家 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_idAgentConversation.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_precheckprecheck 通过后再提交申请。
  • 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等待外部输入后恢复。

参考:

总体目标架构

用户输入 / 前端上下文 / 附件
  ↓
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 不直接管理供应商密钥。
  • StewardFlowStateServiceAgentConversationService 是 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=10max_attempts=3use_failure_cooldown=False
    • 失败:只写入 fallback_reasonmodel_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_previewsave_application_draftsubmit_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_tracesplanning_sourcesteward_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 2slot/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-decisionsruntime-decisionsplans 三个入口都由 LangGraph runtime 路由。
  • legacy 回退仍可用。
  • 图内工具节点失败时仍有可执行规则兜底。

Phase 3action 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_preview
  • save_application_draft
  • submit_application
  • link_existing_application
  • create_reimbursement_draft
  • associate_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
  • StewardTaskStewardPlanResponse 中增加 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 4checkpoint 与 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 行先拆文件。