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

421 lines
20 KiB
Markdown
Raw Normal View 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_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 通过后再提交申请。
- `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>
## 总体目标架构
```text
用户输入 / 前端上下文 / 附件
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。
```python
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
状态:已完成。
完成标准:
- [x] 默认 runtime 为 `langgraph`
- [x] `STEWARD_AGENT_RUNTIME=legacy` 可回退。
- [x] `/steward/plans` 真实接口仍返回兼容的 `StewardPlanResponse`
- [x] 模型失败后仍按 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`
步骤:
- [x] 写失败测试:模拟 main provider 超时但 backup provider 可用时,`complete_with_tool_call()` 返回 backup 的 tool call。
- [x] 确认 backup function calling 会在 main 失败后返回 tool call不等待 main 重试结束。
- [ ] 写失败测试MiniMax 返回非 OpenAI tool call 兼容格式时,服务端能记录清晰 trace 并降级。
- [x] 容器内运行:
```bash
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`
- [x] `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`
步骤:
- [x] 写失败测试:`StewardGraphRuntime.decide_slot()` 调用 slot node 后返回与现有 `StewardSlotDecisionAgent.decide()` 等价的 response。
- [x] 写失败测试:`StewardGraphRuntime.decide_runtime()` 在用户补充字段时合并 `steward_state`
- [x]`StewardSlotDecisionAgent` 包成 `slot_tool_decision` 节点。
- [x]`StewardRuntimeDecisionAgent` 包成 `runtime_tool_decision` 节点。
- [x] 把已选流程确认和当前运行时记忆归一化拆成 `runtime_memory_context` / `runtime_action_decision` 节点。
- [x] endpoint 在 `STEWARD_AGENT_RUNTIME=langgraph` 时使用 graph runtime。
- [x] endpoint 在 graph runtime 异常时回退旧 Agent避免框架层失效影响会话。
验收:
- [x] `slot-decisions``runtime-decisions``plans` 三个入口都由 LangGraph runtime 路由。
- [x] legacy 回退仍可用。
- [x] 图内工具节点失败时仍有可执行规则兜底。
### 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`
步骤:
- [x] 写失败测试:申请直接提交计划必须输出 `fill_application_fields -> build_application_preview -> validate_required_fields -> run_duplicate_precheck -> submit_application`
- [x] 写失败测试:保存草稿计划必须输出 `save_application_draft`,并在字段缺失时阻断后续副作用。
- [x] 写失败测试:报销计划必须输出 `fill_reimbursement_fields -> build_reimbursement_preview -> validate_required_fields -> create_reimbursement_draft`
- [x]`StewardTask``StewardPlanResponse` 中增加 `action_steps`
- [x] 实现 `StewardActionPlanBuilder`,由服务端确定性生成白名单 action step。
- [x] 在 LangGraph planner 中新增 `attach_action_steps` 节点。
- [x] 前端 planner model 优先消费服务端 `task.action_steps`,旧响应才回退本地推导。
- [x] 写失败测试:未知 action step 被拒绝,不调用任何业务服务。
- [x] 写失败测试:`submit_application` 缺少确认来源时返回 `needs_confirmation`
- [x] 写失败测试:`submit_application` 必须看到 precheck 通过后才允许真实提交。
- [x] 实现 action registry只允许白名单 action。
- [x] 接入现有申请保存草稿、申请提交和报销草稿服务。
- [x] 前端新增 `executeStewardAction()` 并让 suggested action 携带服务端 action step。
- [x] AI 工作台点击 suggested action 时调用 `StewardActionExecutor`;直接提交先串行执行 precheck再把结果交给 submit。
- [x] 接入 `link_existing_application` 和附件关联的真实执行。
- [x] 把 action executor 包成 LangGraph `action_execute_node`,并接入 checkpoint / interrupt 恢复。
验收:
- [x] 模型不能通过自由文本触发数据库写入,服务端只输出白名单 `StewardActionStep`
- [x] 申请提交动作没有确认时返回 `needs_confirmation`,不会写库。
- [x] 申请提交动作没有 precheck 通过证据时被阻断。
- [x] 保存申请草稿和创建报销草稿通过现有业务服务真实入库,测试覆盖。
- [x] 前端点击可执行 steward action 会真实调用 executor并把结果回写到会话消息。
- [x] 基础副作用动作有 `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`
步骤:
- [x] 设计 `conversation_id -> thread_id` 映射,当前直接以 `conversation_id` 作为 graph action thread。
- [x] 实现基于数据库的 checkpoint先复用 `AgentConversation.state_json`
- [x] 在提交审批缺确认时生成 pending interrupt payload。
- [x] 用户确认后用同一 `client_trace_id` / `conversation_id` 继续执行;终态请求会幂等重放。
验收:
- [x] 刷新页面后,后端仍保留当前等待确认的 action checkpoint。
- [x] 同一个 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 路径覆盖当前核心申请/报销链路测试。
## 回退策略
- 环境变量:
```bash
STEWARD_AGENT_RUNTIME=legacy
```
- 回退后:
- `/steward/plans` 使用旧 `StewardPlannerService`
- 新 action node 和 checkpoint 不应影响 legacy。
- 回退原因必须写入当天工作日志。
## 验证矩阵
每个阶段至少跑:
```bash
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
```
涉及前端执行器时补跑:
```bash
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
```
每次完成后执行:
```bash
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 行先拆文件。