chore: 更新 .env.example、财务规则表与 AI 意图规划开发文档

This commit is contained in:
caoxiaozhu
2026-06-24 21:59:15 +08:00
parent bb681aa1f3
commit a0f6d9f702
11 changed files with 826 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ ONLYOFFICE_PUBLIC_URL=http://127.0.0.1:8082
ONLYOFFICE_BACKEND_URL=http://127.0.0.1:8000 ONLYOFFICE_BACKEND_URL=http://127.0.0.1:8000
ONLYOFFICE_JWT_SECRET=change-me-onlyoffice ONLYOFFICE_JWT_SECRET=change-me-onlyoffice
HERMES_AGENT_SHARED_TOKEN=change-me-hermes HERMES_AGENT_SHARED_TOKEN=change-me-hermes
STEWARD_AGENT_RUNTIME=langgraph
POSTGRES_HOST=127.0.0.1 POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432 POSTGRES_PORT=5432

View File

@@ -0,0 +1,146 @@
# AI 意图规划器
## 功能一句话
把用户一句自然语言先交给大模型识别成“动作和步骤”的结构化计划,再由前端/后端确定性执行器逐步校验和执行;规则只作为模型不可用或结构非法时的兜底。
## 背景
用户真实表达往往不是一个简单关键词,而是一个目标:
```text
去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交
```
这句话包含至少四层语义:
- 业务对象:差旅费用申请。
- 已给字段:地点、事由、交通方式。
- 用户动作:直接提交,不只是生成草稿。
- 执行步骤:生成申请核对表、校验必填字段、查重、提交审批。
如果继续用前端规则直接判断,会越来越像关键词路由;正确方向是让大模型先拆成结构化计划,系统只执行经过白名单校验的步骤。
## 目标
- 大模型作为复杂意图的主识别器,输出结构化 `intent plan`
- `intent plan` 明确描述用户要执行的动作、业务字段、缺失字段和步骤列表。
- 执行器只认计划中的白名单步骤,不直接相信模型文本。
- 字段齐全且用户明确“直接提交”时,系统自动走完整链路。
- 字段缺失、风险阻断、重复申请或低置信度时,系统停在核对表或风险提示。
- 本地规则只作为 `rule_fallback`,不得伪装成模型判断。
## 非目标
- 不让大模型直接写数据库、提交审批或绑定附件。
- 不引入 LangChain 高层 Agent 来替代现有业务服务。
- 不让 LangGraph 直接接管模型供应商密钥、数据库写入或审批副作用。
- 不绕过已有申请预览、规则测算、重复申请核查和提交接口。
- 不把所有财务场景一次性改完;第一阶段先覆盖个人工作台 AI 模式里的差旅申请链路。
## 结构化计划契约
前端执行器消费统一结构:
```json
{
"source": "llm_function_call",
"intent": "create_travel_application",
"requestedAction": "submit",
"confidence": 0.91,
"sourceText": "去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交",
"slots": {
"location": "上海",
"reason": "辅助国网仿生产服务器部署",
"transportMode": "火车"
},
"missingFields": [],
"steps": [
"build_application_preview",
"validate_required_fields",
"run_duplicate_precheck",
"submit_application"
]
}
```
## 步骤白名单
- `build_application_preview`:生成申请核对表,复用现有申请预览能力。
- `validate_required_fields`:用确定性代码校验必填字段和字段格式。
- `run_duplicate_precheck`:提交前查询同日期或重叠申请单。
- `submit_application`:调用现有申请提交动作;只有用户明确要求提交且前置校验通过才执行。
- `save_application_draft`:调用现有申请草稿保存能力。
- `create_reimbursement_draft`:调用现有报销草稿创建能力。
- `associate_attachments`:调用现有附件关联 runner缺少 `receipt_ids` 或匹配不唯一时阻断,不能假成功。
- `link_existing_application`:调用现有报销草稿创建链路并写入申请关联 flag申请单不存在、未审批或已被报销时阻断。
## 数据流
```text
用户输入
steward LLM function calling
模型计划归一化与白名单校验
可执行 intent plan
申请/报销/附件等确定性执行器
核对表、风险提示、草稿或提交结果
```
当模型不可用、超时或返回不可执行结构时:
```text
模型失败
本地 rule_fallback 生成保守计划
同一个执行器继续处理
```
## 安全边界
- 模型只负责“理解”和“拆步骤”,不拥有副作用权限。
- 执行器必须校验 `intent``steps` 是否在白名单内;未知 action 必须阻断,不能调用业务服务。
- 提交前必须走 `readyToSubmit` 和重复申请 precheck。
- 提交类副作用必须带用户确认和 precheck 通过证据。
- 地点、日期、事由等字段仍由现有申请预览和规则校验模块判断。
- 模型置信度低、流程冲突或必填字段缺失时,不自动提交。
## 第一阶段落地
- 新增前端 planner model把后端 `StewardPlanResponse` 映射成前端 `intent plan`
- 个人工作台 AI 模式在命中差旅申请候选时,优先调用现有 `/steward/plans` 获取模型计划。
- 模型计划可执行时,按 `steps` 生成申请核对表并在条件满足时自动提交。
- 模型不可用或计划不可执行时,使用本地 `rule_fallback` 保持体验不倒退。
- 保留既有普通小财管家路径,用来处理无法直接执行或更复杂的多任务场景。
## LangGraph 迁移方向
后端小财管家的规划入口、槽位决策入口和运行时动作决策入口已经开始迁入 LangGraph。
后续不再继续把感知、规划、补字段、运行时动作判断堆进自研 `if/else` 编排,而是按 node 拆分:
- `model_intent_node`:模型 function calling 规划。
- `slot_tool_decision_node`:字段缺口与追问判断,失败时进入规则兜底。
- `runtime_memory_context_node`:合并前端运行时状态和持久化 `steward_state`
- `runtime_tool_decision_node`:用户补充、确认、取消、继续执行等运行时判断。
- `runtime_action_decision_node`:已选流程确认等不需要模型的确定性行动路由。
- `action_plan_node`:生成白名单 `StewardActionStep`,当前已覆盖申请预览、保存草稿、直接提交和报销草稿。
- `human_review_node`:保存草稿、提交审批、关联申请单前的确认点。
- `action_execute_node`:调用现有确定性业务服务;当前已由 `/steward/actions/execute``StewardGraphActionRuntime``StewardActionExecutor` 提供 checkpoint、pending interrupt 和幂等重放。
- `persist_state_node`:合并 conversation state 和 steward state。
当前 `plans` 已返回服务端 `action_steps``slot-decisions` / `runtime-decisions` 已默认走 `StewardGraphRuntime`
如果 LangGraph 图节点异常,服务端会退回原有 Agent 和规则兜底,避免框架层故障影响用户会话。
详细迁移计划见 `LANGGRAPH_RUNTIME_MIGRATION.md`
## 测试策略
- planner model 单元测试模型计划归一化、rule fallback、政策咨询排除。
- 前端静态接线测试:主流程必须先请求 steward plan再 fallback。
- 申请预览回归测试:`直接提交` 不得污染事由;缺日期不得伪造字段。
- 构建验证:`npm --prefix web run build`

View File

@@ -0,0 +1,420 @@
# 小财管家 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 行先拆文件。

View File

@@ -0,0 +1,32 @@
# AI 意图规划器 TODO
## 第一阶段
- [x] 落地 AI 意图规划器概念文档,明确大模型负责拆计划、业务代码负责执行。
- [x] 新增前端 planner model统一模型计划和本地 fallback 的输出结构。
- [x] 个人工作台 AI 模式先请求 steward 模型计划,再使用本地 fallback。
- [x] 差旅申请直提链路改为消费 `intent plan.steps`,不再直接消费正则识别结果。
- [x] 补齐 planner model 与主流程接线回归测试。
- [x] 跑前端构建和定向测试。
- [x] 后端 `/steward/plans` 增加 `requested_action` 输出,减少前端从原句推断“提交/保存”的比例。
- [x] 引入 LangGraph并默认用 `StewardGraphPlannerService` 接管 `/steward/plans` 规划编排。
- [x] 落地 LangGraph runtime 迁移计划文档:`LANGGRAPH_RUNTIME_MIGRATION.md`
- [x] 新增 `StewardGraphRuntime`,接管 `slot-decisions``runtime-decisions` 的 LangGraph 路由。
- [x] 为 LangGraph runtime 增加图内规则兜底和端点级 legacy Agent 兜底。
- [x]`/steward/plans` 增加服务端白名单 `action_steps`,覆盖申请、报销、保存草稿和直接提交的基础动作规划。
- [x] 前端 planner model 优先消费服务端 `task.action_steps`,旧响应才回退本地步骤推导。
- [x] 新增 `/steward/actions/execute``StewardActionExecutor`,把未知 action 拒绝、提交确认门禁、precheck 阻断、申请草稿保存、申请提交和报销草稿创建接到现有业务服务。
- [x] 前端新增 `executeStewardAction()` 服务方法,并让 steward suggested action 携带服务端可执行 action step。
- [x] AI 工作台点击可执行 steward action 时调用 `/steward/actions/execute`;申请直接提交会先跑 `run_duplicate_precheck`,通过后再提交。
- [x] `complete_with_tool_call()` 增加 main 失败后 backup tool-call 成功路径测试,保证模型成功路径能从 backup 返回 `llm_function_call`
- [x] 新增 `StewardGraphActionRuntime`,把 action executor 包成 LangGraph `action_execute_node`,并用 `conversation_id + client_trace_id``AgentConversation.state_json` 中持久化 checkpoint。
- [x] 对提交确认生成 pending interrupt重复 client trace 直接重放 checkpoint避免重复保存草稿或重复提交。
- [x] `link_existing_application` 接入现有报销草稿创建链路并写入申请关联 flag。
- [x] `associate_attachments` 接入现有附件关联 runner按 receipt_ids 归集到可匹配报销草稿。
## 后续阶段
- [ ] 用真实 MiniMax / backup 配置再回放 `/steward/plans`,确认真实环境返回 `planning_source=llm_function_call`,不是规则兜底。
- [ ] 将当前 `AgentConversation.state_json` checkpoint 抽象为可替换的 durable checkpointer并补恢复/清理策略。
- [ ] 为每个 LangGraph node 写入可追溯 trace展示模型调用、规则降级、等待确认和动作执行结果。
- [ ] 在 LangGraph 路径覆盖申请/报销核心链路后,再逐步删除旧 `StewardPlannerService` 的重复编排。

View File

@@ -214,6 +214,188 @@
- 操作:重启当前实际运行容器 `x-financial-local-linux` 并确认 `/api/v1/health` 正常;真实 5173 `/api/v1/ocr/recognize` 重新上传 `2月23_上海-武汉.pdf` 后返回 `document_type=train_ticket``preview_kind=image``receipt_preview_url=/receipt-folder/.../preview`,对应 `/preview` 响应 `content-type: image/png` - 操作:重启当前实际运行容器 `x-financial-local-linux` 并确认 `/api/v1/health` 正常;真实 5173 `/api/v1/ocr/recognize` 重新上传 `2月23_上海-武汉.pdf` 后返回 `document_type=train_ticket``preview_kind=image``receipt_preview_url=/receipt-folder/.../preview`,对应 `/preview` 响应 `content-type: image/png`
- 影响AI 会话上传 PDF 火车票后,附件条和预览弹窗都会走统一预览资产判定;后续其它入口只要使用 `documentPreviewAssets.js`,就不会再各自维护一套 PDF/图片判断。 - 影响AI 会话上传 PDF 火车票后,附件条和预览弹窗都会走统一预览资产判定;后续其它入口只要使用 `documentPreviewAssets.js`,就不会再各自维护一套 PDF/图片判断。
- 12:52我重新修改了一键关联票据对话框的 UI 样式,并根据“卡片过大、不够精致”的反馈进行了尺寸收敛与精细化微调。
- Git 提交检查:`git fetch --all --prune` 成功执行;`HEAD..origin/main``origin/main..HEAD` 均无提交。
- 修改:在 `receipt-folder-view.css` 中,为弹窗外框加上轻量圆角 (6px) 与精致投影;将弹窗 header padding 收至 `20px 24px 12px 24px`body padding 收至 `0 24px 20px 24px`,确保紧凑挺拔。
- 修改:将列表行间距设为 `8px`,每个条目高度收敛至 `52px`,内边距调整为 `10px 14px`,文件名与说明文本大小降为 `13px``11.5px`,以求更加精致细腻。
- 修改:将底部操作区的按钮高度设为标准 `36px`,水平内边距设为 `16px`,字重保持专业的 `500`,使比例更协调,消除挤压感的同时保留小巧玲珑感。
- 操作:执行前端生产编译构建 `npm run build` 并运行 `node --test web/tests/receipt-folder-view.test.mjs`
- 验证:`npm run build` 构建成功,单元测试通过,`git diff --check` 无输出。
- 影响:弹窗彻底摆脱了原本的拥挤挤压感,同时避免了尺度过大、粗糙的问题,整体显得小巧精致、清爽洗练。
- 13:03我把 AI 工作台里的“去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交”接成首轮完整申请链路,不再落回一步步追问。
- Git 提交检查:`git fetch --all --prune` 已在 Git 写权限升级后成功执行;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只触碰 AI 工作台申请意图与测试文件。
- 修改:`workbenchAiApplicationGateModel.js` 新增 `resolveInlineTravelApplicationRequest()`,识别“出差/差旅/交通 + 具体地点或交通方式”的申请意图,并排除查标准、问制度、查进度等咨询类请求。
- 修改:`usePersonalWorkbenchAiMode.js` 在已有申请表动作之后、报销创建入口之前接入差旅申请识别;命中后直接调用 `startAiApplicationPreview()`,并把同句里的“直接提交”转成 `autoSubmit`
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 支持申请预览生成后自动提交;只有 `normalizeApplicationPreview(preview).readyToSubmit` 为真时才带 `confirmed: true` 复用现有提交前核查和提交动作,缺日期等必填项时仍停在核对表。
- 修改:`workbench-ai-application-gate-model.test.mjs``expense-application-fast-preview.test.mjs` 增加回归测试,锁定紧凑差旅直提句子的识别、分流和自动提交接线。
- 验证:`node --test web/tests/workbench-ai-application-gate-model.test.mjs` 通过 4/4`node --test --test-name-pattern "direct-submit" web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`git diff --check -- ...` 无输出;`npm --prefix web run build` 构建通过。
- 验证补充:整跑 `node --test web/tests/expense-application-fast-preview.test.mjs` 时,本次新增用例通过,但该文件仍有 12 个既有失败,集中在其它文案/静态结构断言,已单独记录为遗留问题。
- 影响用户把差旅目的地、事由、交通方式和“直接提交”放在一句话里时AI 工作台会直接生成申请核对表;字段齐全时自动进入提交前核查和提交,不再用多轮问答确认同一件事。
- 13:20我先落了 AI 意图规划器文档,再把个人工作台 AI 模式改成“模型计划优先、规则兜底、执行器按步骤执行”的结构。
- Git 提交检查:`git fetch --all --prune` 首次被 `.git/FETCH_HEAD` 权限拦截,随后在升级权限下成功执行;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 `AI意图规划器` 文档和 planner 接线。
- 修改:新增 `document/development/AI意图规划器/CONCEPT.md``TODO.md`,明确大模型负责识别动作与步骤,业务执行器负责白名单校验、必填校验、查重和提交。
- 修改:新增 `workbenchAiIntentPlannerModel.js`,把后端 `StewardPlanResponse` 或本地 fallback 统一归一为 `intent plan`,计划中包含 `intent``requestedAction``slots``missingFields``steps`
- 修改:`useWorkbenchAiStewardFlow.js` 增加 `resolveInlineExecutionPlan()`,复用现有 `/steward/plans` 大模型 function calling 能力,返回模型计划;调用失败或超时时由前端 fallback 接管。
- 修改:`usePersonalWorkbenchAiMode.js` 不再直接消费正则识别结果,而是先尝试 steward 模型计划,再用 `buildRuleFallbackWorkbenchAiIntentPlan()` 兜底,最后通过 `resolveExecutableTravelApplicationPlan()` 驱动申请预览和自动提交;模型规划等待期间会锁住发送态,避免重复触发。
- 修改:`workbench-ai-intent-planner-model.test.mjs` 增加模型计划归一化、fallback、政策咨询排除和主流程接线测试`expense-application-fast-preview.test.mjs` 的直提断言同步为 planner 语义。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 8/8`node --test --test-name-pattern "direct-submit" web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`npm --prefix web run build` 构建通过;`git diff --check -- ...` 无输出。
- 验证补充:整跑 `expense-application-fast-preview.test.mjs` 仍有 12 个既有失败,当前与本次 planner 新增用例无关;本轮未在真实页面输入框回放模型返回路径。
- 影响:复杂意图链路现在有了清晰的模型计划适配层;后续扩展报销、附件归集、关联申请单时,可以继续让模型输出 steps再由确定性执行器逐步执行。
- 13:35我补修了 AI 意图规划入口过窄的问题让“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿。”这种轻微改写也会进入计划执行链路。
- Git 提交检查:`git fetch --all --prune` 普通沙箱首次失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;随后在升级权限下成功执行。当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只继续修改 AI 工作台意图规划、申请预览执行和对应测试。
- 修改:`workbenchAiIntentPlannerModel.js` 新增 `shouldRequestWorkbenchAiIntentPlan()`,把差旅、申请、交通方式、保存草稿、直接提交等财务执行候选先送进 steward 计划器;同时新增 `save_application_draft` step并把 `requestedAction=save_draft` 归一成可执行的 `autoSaveDraft`
- 修改:`workbenchAiApplicationGateModel.js` 放宽本地兜底识别,支持“上海出差”这种目的地前置写法和单独出现的“火车/高铁/飞机”等交通方式。
- 修改:`usePersonalWorkbenchAiMode.js` 不再只在 fallback 已经完整命中时才规划;只要 `shouldRequestWorkbenchAiIntentPlan()` 认为是财务执行候选,就先走模型计划,模型失败才用本地 fallback。
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在生成申请核对表后支持 `autoSaveDraft`,直接复用现有 `AI_APPLICATION_ACTION_SAVE_DRAFT` 保存草稿动作;自动提交仍只在 `readyToSubmit` 为真时执行。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 通过 5/5`node --test --test-name-pattern direct-submit web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`node --test web/tests/workbench-ai-application-gate-model.test.mjs` 通过 4/4`npm --prefix web run build` 构建通过;`git diff --check -- ...` 无输出;尾随空白扫描无命中。
- 验证补充:用 Node 直接解析用户原句,结果为 `requestedAction=save_draft`steps 为 `build_application_preview -> validate_required_fields -> save_application_draft`,可执行参数为 `autoSubmit=false``autoSaveDraft=true`
- 影响用户一句话给日期、地点、事由、交通方式和“保存草稿”时AI 工作台会进入模型规划/兜底规划,生成申请核对表后自动保存草稿,不再因为表述顺序变化回到逐步问答。
- 13:58我按用户反馈重构了 AI 工作台意图入口,让用户话语进入后先走大模型 function calling 规划,再按模型拆出的动作和字段执行,规则只保留为模型失败后的降级。
- Git 提交检查:`git fetch --all --prune` 普通沙箱首次失败,错误是 `.git/FETCH_HEAD: Operation not permitted`;随后在升级权限下成功执行。当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮继续修改 steward function calling、AI 工作台 planner、申请预览字段接力和测试。
- 修改:后端 `StewardTask` 增加 `requested_action``StewardIntentAgent` 的 tool schema 要求模型输出 `preview/save_draft/submit``StewardModelPlanBuilder` 将模型动作写入任务,缺省时才按原文保守推断。
- 修改:`usePersonalWorkbenchAiMode.js` 将入口从“先构造 fallback 再规划”改成 `executeModelPlannedWorkbenchIntent()`:先调用 `resolveInlineExecutionPlan()`,再把模型 plan 归一为可执行申请;只有模型失败时才执行 `buildRuleFallbackWorkbenchAiIntentPlan()`,并保留模型识别到 reimbursement task 后进入原报销关联门。
- 修改:`workbenchAiIntentPlannerModel.js` 保留模型拆出的 canonical ontology fields`shouldRequestWorkbenchAiIntentPlan()` 改为普通有效输入都先进入 planner不再把政策查询等话术排除在规划入口外。
- 修改:`workbenchAiApplicationPreviewModel.js``useWorkbenchAiApplicationPreviewFlow.js` 支持把模型拆出的 `time_range/location/reason/transport_mode` 转为申请预览 ontology再生成核对表避免继续只靠本地文本解析导致地点和事由粘连。
- 测试:先让 `workbench-ai-intent-planner-model.test.mjs` 红灯失败,确认旧实现缺少 `ontologyFields`、政策查询被排除、入口仍携带 `fallbackIntentPlan`;随后实现并跑绿。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 通过 7/7`node --test --test-name-pattern direct-submit web/tests/expense-application-fast-preview.test.mjs` 通过 2/2`node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 11/11容器 `x-financial-local-linux``pytest -q server/tests/test_steward_intent_agent.py server/tests/test_steward_planner.py` 通过 27/27容器内 `py_compile` 通过;`npm --prefix web run build` 构建通过;`git diff --check` 无输出,尾随空白扫描无命中。
- 验证补充:用 Node 直接归一模型 plan用户原句输出 `requestedAction=save_draft``ontologyFields.time_range=2026-02-20 至 2026-02-23``location=上海``reason=国网仿生产服务器部署``transport_mode=火车``autoSaveDraft=true`
- 影响AI 工作台不会再“瞬间靠规则识别并执行”;可执行链路的第一步是模型 function calling 规划,模型负责拆动作和字段,前端执行器只消费结构化 plan 并调用确定性申请/报销流程。
- 14:33我按“每次先经过大模型规划10s 超时、最多 3 次重试后才规则降级”的要求收紧了 AI 工作台意图规划链路。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮继续修改 steward planner/runtime、申请字段解析、AI 工作台等待时间和相关测试。
- 修改:`StewardPlannerService.build_plan()` 改成先调用模型 intent agent再根据模型结果或失败原因进入 off-topic/rule fallback不再只对多任务或显式动作走模型。
- 修改:`StewardIntentAgent` 的 function calling 调用改为 `timeout_seconds=10``max_attempts=3`,并通过 `RuntimeChatService.complete_with_tool_call(use_failure_cooldown=False)` 避免第 1 次失败后第 2、3 次被 cooldown 跳过。
- 修改:`useWorkbenchAiStewardFlow.js` 将 inline execution plan 等待时间从 12s 调到 35s覆盖后端 3 次 10s 模型等待,避免前端提前本地 fallback。
- 修改:`ApplicationFactResolver``StewardPlannerExtractionMixin` 的规则降级补齐 `requested_action`、完整日期范围和申请事由清洗;即使模型 3 次超时也能把“保存草稿”“2026-02-20 至 2026-02-23”“国网仿生产服务器部署”“火车”传给执行器。
- 测试:先新增红灯测试覆盖“单一出差描述也要先调用模型”“模型调用参数为 10s/3 次”“tool-call 规划可关闭 cooldown 连续重试”“前端等待 35s”“规则降级保留保存草稿和日期范围”随后实现并跑绿。
- 验证:容器内 `pytest -q server/tests/test_runtime_chat_service.py server/tests/test_steward_intent_agent.py server/tests/test_steward_planner.py` 通过 38/38前端 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 11/11`npm --prefix web run build` 通过;`git diff --check -- ...` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后直接请求 5173 的 `/api/v1/steward/plans`,原句耗时 32.08sMiniMax `attempt=1/2/3` 均约 10.2s 超时后才返回 `planning_source=rule_fallback`;返回任务已包含 `requested_action=save_draft`、完整 `time_range=2026-02-20 至 2026-02-23``location=上海``reason=国网仿生产服务器部署``transport_mode=train`
- 影响:用户不会再看到毫秒级规则直出;当前模型服务超时时会先完整等待三次,再用可执行的基础规则计划继续流程。
- 15:14我把小财管家规划入口第一阶段迁到 LangGraph默认用框架接管规划编排同时保留 legacy 回退。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 LangGraph 依赖、graph planner、runtime 开关和测试。
- 修改:新增 `StewardGraphPlannerService`,用 LangGraph `StateGraph` 编排 `prepare_context -> detect_model_intent -> off_topic/rule_fallback/完成`,不再把规划主流程继续写在一个手工 `if/else` 方法里。
- 修改:`/steward/plans``/steward/plans/stream` 的 planner 工厂默认返回 `StewardGraphPlannerService``STEWARD_AGENT_RUNTIME=legacy` 可回退到旧 `StewardPlannerService`
- 修改:`server/pyproject.toml``server/uv.lock``egg-info` 同步加入 `langgraph>=1.2,<2.0``.env.example` 增加 `STEWARD_AGENT_RUNTIME=langgraph`
- 测试:先新增 `test_steward_graph_planner.py` 红灯测试,覆盖 LangGraph planner 的模型成功路径、模型无 tool call 后规则降级、默认 LangGraph、显式启用 LangGraph 和 legacy 回退;随后实现并跑绿。
- 验证:容器内 `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 server/tests/test_config_settings_reload.py` 通过 45/45`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出;`pip check` 无 broken requirements。
- 真实接口验证:通过真实 5173 `/api/v1/steward/plans` 回放保存草稿原句,服务端仍先等待 MiniMax 3 次 10s 超时,再返回 `rule_fallback`;返回字段包含 `requested_action=save_draft`、完整日期范围、上海、事由和 `transport_mode=train`。容器内直接探针确认默认 planner 类型为 `StewardGraphPlannerService`
- 影响:小财管家的规划骨架已经由 LangGraph 承接;后续可以继续把 slot/runtime decision、checkpoint 记忆、human-in-the-loop 和 action node 迁到框架里,而不是继续扩写自研分支编排。
- 15:22我按“先落文档文档完了再继续开发”的要求补齐 LangGraph 后续迁移文档。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只修改 AI 意图规划器文档和当天工作日志。
- 修改:新增 `LANGGRAPH_RUNTIME_MIGRATION.md`,记录当前已完成的 LangGraph 第一阶段、目标架构、runtime state 草案、节点边界、Phase 1-5 迁移计划、回退策略、验证矩阵和风险处理。
- 修改:更新 `CONCEPT.md`,删除“非目标:不新增 LangGraph”的过期表述改为不引入 LangChain 高层 Agent、不让 LangGraph 直接接管密钥或副作用,并补充后续 node 迁移方向。
- 修改:更新 `TODO.md`,把 `requested_action`、LangGraph 接入和迁移文档标为完成并新增模型成功路径、slot/runtime node、action node、checkpoint、人审中断和 trace 的后续事项。
- 操作:查询 LangGraph 官方文档,确认 Graph API、persistence 和 interrupt 的能力边界;读取当前 `StewardGraphPlannerService` 与已有 AI 意图规划文档后再落文档。
- 验证:`rg` 确认旧“不能新增 LangGraph”说法没有残留`git diff --check -- document/development/AI意图规划器/...` 无输出;新增迁移文档 356 行。
- 影响:后续开发有了明确的执行顺序和验收标准;下一步应先按文档 Phase 1 修通真实 `llm_function_call` 成功路径,再迁 slot/runtime/action/checkpoint。
- 15:31我继续把小财管家会话内的槽位识别、运行时记忆合并和行动决策入口迁到 LangGraph并加了图内和端点级双层兜底。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 graph runtime、runtime 测试并更新 AI 意图规划器文档。
- 修改:新增 `StewardGraphRuntime`,用 LangGraph `StateGraph` 编排 `slot_prepare_context -> slot_tool_decision -> done/rule_fallback`,把原 `StewardSlotDecisionAgent` 包成图节点;工具节点异常时会带 `langgraph_slot_decision` 失败 trace 进入规则兜底。
- 修改:`StewardGraphRuntime` 运行时图新增 `runtime_memory_context -> runtime_action_decision/runtime_tool_decision -> done/rule_fallback`,先归一化并合并 `steward_state`,已选流程确认走确定性 action 节点,不再调用模型;普通补字段/确认继续由 runtime tool 节点判断。
- 修改:`/steward/slot-decisions``/steward/runtime-decisions` 默认使用 `StewardGraphRuntime`;如果 LangGraph 实例化或执行异常,端点 helper 会退回旧 `StewardSlotDecisionAgent` / `StewardRuntimeDecisionAgent`,保证框架失效时仍能实际响应。
- 测试:先新增 `test_steward_graph_runtime.py` 红灯测试,覆盖槽位工具节点、图内规则兜底、运行时记忆合并、确定性行动选择,以及端点 helper 在 graph runtime 失败后 legacy 兜底;随后实现并跑绿。
- 文档:更新 `LANGGRAPH_RUNTIME_MIGRATION.md` 的当前状态和 Phase 2 完成项;更新 `CONCEPT.md` 的 LangGraph 节点命名;更新 `TODO.md` 勾掉 slot/runtime graph runtime 迁移,保留副作用 action node、checkpoint、人审中断和 trace 后续事项。
- 验证:容器内 `pytest -q server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 59/59`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出;`pip check` 无 broken requirements。
- 影响:会话内“识别缺什么、读记忆、判断补字段/继续/确认”的入口已经被 LangGraph 接管;当前仍没有让 LangGraph 直接执行保存草稿、提交审批、关联申请单等副作用动作。
- 15:53我把 `/steward/plans` 的基础业务动作规划补成服务端白名单 `action_steps`,让申请、报销、保存草稿和直接提交都有明确动作序列。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 action contract、扩展 steward schema、更新前端 planner model、修复申请事由清洗并同步文档。
- 修改:`StewardTask``StewardPlanResponse` 增加 `action_steps`;新增 `StewardActionStep``StewardActionType``StewardActionStatus`,把可执行动作显式收进 Pydantic 协议。
- 修改:新增 `StewardActionPlanBuilder`,由服务端确定性生成白名单动作:申请直接提交输出 `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`
- 修改:`StewardGraphPlannerService` 增加 `attach_action_steps` 节点,模型成功、模型失败兜底和 off-topic 都统一经过动作规划节点legacy `StewardPlannerService` 返回前也补同一套 action contract。
- 修改:`workbenchAiIntentPlannerModel.js` 优先消费服务端 `task.action_steps`,旧响应才回退本地 `requested_action` 推导;这样后端 LangGraph 规划出的动作能真正驱动前端执行计划。
- 修改:`ApplicationFactResolver` 修复“交通火车”污染申请事由的问题,同时保留“高铁往返”这类业务描述;新增解析回归测试。
- 测试:先新增红灯测试,覆盖申请直接提交 action steps、保存草稿 action steps、报销 action steps、前端优先消费服务端 action steps、`交通火车` 事由清洗;随后实现并跑绿。
- 验证:容器内 `pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 66/66前端 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 12/12`npm --prefix web run build` 通过;`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后回放 `2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车直接提交`,真实 5173 `/api/v1/steward/plans` 返回 `requested_action=submit`,字段为 `time_range=2026-02-20 至 2026-02-23``location=上海``reason=辅助国网仿生产服务器部署``transport_mode=train`,动作序列为 `detect_intent:completed -> fill_application_fields:planned -> build_application_preview:planned -> validate_required_fields:planned -> run_duplicate_precheck:planned -> submit_application:pending_confirmation`
- 影响:基础业务已经不是“只识别一个 requested_action”了后端计划会直接给出可执行动作序列当前仍只规划动作不直接执行保存草稿或提交审批。
- 16:13我把小财管家的白名单 action 从“只规划”推进到“可执行 executor”让申请草稿、申请提交门禁和报销草稿能通过统一 `/steward/actions/execute` 入口处理。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 action executor、执行接口、前端执行服务和相关测试。
- 修改:新增 `StewardActionExecuteRequest` / `StewardActionExecuteResponse`,并在 `steward.py` 暴露 `/steward/actions/execute`;新增 `StewardActionExecutor`,统一处理未知 action 拒绝、缺字段阻断、提交确认门禁、precheck 阻断和业务服务调用。
- 修改:`save_application_draft` / `submit_application` 复用现有 `UserAgentService` 申请保存/提交能力;`create_reimbursement_draft` 复用 `ExpenseClaimService.save_or_submit_from_ontology()`,避免再写一套入库逻辑。
- 修改:`associate_attachments``link_existing_application` 保留在执行白名单里,但在真实接线完成前统一返回阻断,不允许以 noop 形式“假成功”或产生隐式副作用。
- 修改:前端 `steward.js` 新增 `executeStewardAction()``stewardPlanModel.js` 保留服务端 `action_steps`,并在 suggested action payload 中携带最终可执行 action step旧申请预览流继续保留为兜底。
- 修改:`useWorkbenchAiActionRouter.js` 接入 `steward_execute_action` 点击执行;申请直接提交会先调用 `run_duplicate_precheck`precheck 通过后再把结果交给 `submit_application`,执行结果回写到当前 AI 会话。
- 修改:`test_reimbursement_endpoints.py` 两个申请快路径断言从旧 `AP-` 前缀同步到当前短单号 `A...` 规范。
- 文档:更新 `LANGGRAPH_RUNTIME_MIGRATION.md``TODO.md``CONCEPT.md`,标记第一版 action registry / executor 完成,并把 checkpoint、interrupt、关联申请和附件真实执行列为后续。
- 验证:容器内 `pytest -q server/tests/test_steward_action_executor.py` 通过 5/5迁移集合 `pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_action_executor.py server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 71/71。
- 验证:前端 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 21/21`npm --prefix web run build` 通过;`ruff check --ignore E501 ...` 通过;`git diff --check` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后,真实 5173 `/api/v1/steward/plans` 仍返回直接提交 action 链;`/api/v1/steward/actions/execute` 对未确认的 `submit_application` 返回 `needs_confirmation`,没有执行入库副作用。
- 影响:基础业务链路已经具备“规划 action -> 执行器门禁 -> 复用现有业务服务”的后端闭环;下一步需要把 executor 包进 LangGraph checkpoint / interrupt并补齐关联申请和附件关联的真实执行。
- 16:40我把 action executor 包成 LangGraph 可执行节点,并补完 checkpoint、人工确认中断、幂等重放、关联申请单和附件关联三阶段收尾。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮新增 `StewardGraphActionRuntime`,扩展 action executor 测试并更新 AI 意图规划器文档。
- 修改:新增 `StewardGraphActionRuntime`,用 LangGraph `StateGraph` 编排 `action_checkpoint_load -> action_execute_node -> action_checkpoint_persist`,并把 `/steward/actions/execute` 默认切到这个 graph runtime。
- 修改:复用 `AgentConversation.state_json` 持久化 `steward_action_checkpoint`;同一 `conversation_id + client_trace_id` 的已完成 action 会直接重放终态结果,`needs_confirmation` 会保存 pending interrupt后续同 trace 完成后清理对应中断。
- 修改:`link_existing_application` 走报销草稿真实创建链路并写入申请关联风险标记;`associate_attachments` 接入 `AttachmentAssociationJobRunner`,无 `receipt_ids` 时阻断,避免假成功。
- 修改:为 `complete_with_tool_call()` 补上主模型失败后 backup 返回 tool call 的回归测试,锁住“模型规划失败优先重试/切 backup再降级规则”的成功路径。
- 文档:更新 `LANGGRAPH_RUNTIME_MIGRATION.md``TODO.md``CONCEPT.md`,把 action node、checkpoint、pending interrupt、幂等重放、关联申请单和附件关联标为已完成同时保留真实模型配置未修通的验收缺口。
- 验证:容器内 `pytest -q server/tests/test_steward_action_executor.py` 通过 9/9`pytest -q server/tests/test_runtime_chat_service.py::test_runtime_chat_complete_with_tool_call_fails_over_to_backup_before_retrying_main` 通过 1/1。
- 验证:迁移集合 `pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_action_executor.py server/tests/test_steward_graph_runtime.py server/tests/test_steward_graph_planner.py server/tests/test_steward_slot_decision_agent.py server/tests/test_steward_runtime_decision_agent.py server/tests/test_steward_planner.py server/tests/test_steward_intent_agent.py server/tests/test_runtime_chat_service.py server/tests/test_config_settings_reload.py` 通过 76/76附件/关联任务 `pytest -q server/tests/test_attachment_association_jobs.py server/tests/test_linked_reimbursement_draft_jobs.py` 通过 7/7。
- 验证:前端 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs` 通过 21/21`ruff check --ignore E501 ...` 通过;`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真实接口验证:重启 `x-financial-local-linux` 后,真实 5173 `/steward/actions/execute` 对同一未确认 `submit_application` trace 第二次返回 `needs_confirmation``result_payload.idempotent_replay=true`,证明 checkpoint 重放生效。
- 真实接口验证:真实 5173 `/steward/plans` 回放“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿”仍因 backup skipped、MiniMax 三次失败后进入 `rule_fallback`,动作链和字段可用,但 `planning_source=llm_function_call` 仍需修模型配置。
- 影响申请、报销、保存草稿、直接提交、关联申请单、附件关联已经具备“LangGraph 规划/行动节点 -> 业务服务执行 -> checkpoint/interrupt 兜底”的基础可测闭环;真实大模型成功路径还差外部模型连通性。
- 16:53我修复 AI 模式输入后按回车“看起来没响应”的问题,把模型规划等待期改成先显示对话反馈。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只改 AI 模式前端等待反馈和对应测试/日志。
- 根因:`executeModelPlannedWorkbenchIntent()` 在等待 `/steward/plans` 返回前只设置 `sending=true`,没有先激活对话、清空输入框、展示用户气泡或 pending 卡片;真实 MiniMax 三次超时会让用户看到 30 多秒“无响应”。
- 修改:`usePersonalWorkbenchAiMode.js` 新增 `startModelPlanningConversation()`,进入模型规划前立即推入用户消息和“正在识别意图,准备拆解申请、报销和附件任务。”的 pending 卡片,并持久化到最近对话。
- 修改:`startModelPlannedApplicationPreview()` 把规划 pending 的 `pendingMessageId` 传给申请预览;`useWorkbenchAiApplicationPreviewFlow.js` 支持复用这张 pending 卡片,避免重复用户消息和重复等待卡。
- 测试:先新增 `workbench AI mode shows a visible planning response before waiting for steward model plan` 红灯测试,确认当前缺少即时反馈;实现后同文件 9/9 通过。
- 验证:前端相关集合 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 31/31`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真页验证:使用本地 5173 和本地模拟员工账号登录 AI 模式输入“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿”并按回车1.5 秒内看到用户气泡、输入框清空和 pending 卡片;约 38 秒后仍正常保存草稿,草稿单号 `AZ8QSX9QA`
- 影响:用户按回车后不再空等模型规划,长耗时仍存在但前端会立即反馈“已接收并正在规划”;真实 `llm_function_call` 成功路径仍依赖模型配置修复。
- 16:59我修复了 AI 模式普通问候被拆成两条助手消息的问题,把规划、工具/行动整理和最终回复收敛到同一张助手卡片。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只继续修改 AI 模式前端消息复用、思考事件合并和对应测试。
- 根因16:53 的等待态修复先创建了模型规划 pending 卡片,但普通管家回复路径会把这张卡片替换成“已完成意图识别,继续为您整理回复。”,随后 `requestInlineAssistantReply()` 又新建第二张助手卡片;所以用户输入“你好”时会看到两段会话。
- 修改:`usePersonalWorkbenchAiMode.js` 删除中间态独立回复,普通管家回复也把 `pendingMessageId` 传给 `requestInlineAssistantReply()`,让同一轮输入继续复用规划 pending 卡片。
- 修改:`useWorkbenchAiStewardFlow.js` 支持 `requestInlineAssistantReply(prompt, entry, files, options)`,当传入 `pendingMessageId` 时替换原 pending 卡片而不是追加新消息;新增 `mergeInlineThinkingEvents()` 合并模型规划和管家回复思考事件,避免同一事件重复或丢失。
- 测试:先新增红灯测试 `workbench AI mode reuses planning pending message for regular steward replies`,确认普通回复必须复用规划 pending实现后同文件 10/10 通过。
- 验证:前端集合 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 32/32`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真页验证:本地 5173 AI 模式输入“你好”后1.5 秒内只有 1 张 pending 助手卡;最终回复后 `assistantCardCount=1``pendingCards=0`、思考区标题为“小财业务思考5 条”,截图保存为 `/tmp/x-financial-ai-single-message-hello.png`
- 影响:一次用户输入现在对应一条助手消息;意图识别、规划等待、普通回复整理都会落在同一个思考区域,不再把“已完成意图识别”单独展示成一轮会话。
- 17:26我把 AI 模式的规划等待态从静态“思考中”改成持续追加过程摘要,让用户能看到意图判断、字段抽取、动作规划和兜底策略正在推进。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有多项既有未提交改动,本轮只继续修改 AI 模式前端 thinking 事件、流式规划接线和对应测试。
- 根因:`executeModelPlannedWorkbenchIntent()` 虽然会先创建 pending 卡片,但 `resolveInlineExecutionPlan()` 仍走非流式 `/steward/plans`,模型等待期间只有一条静态 thinking等计划返回后才一次性显示完整步骤用户会感觉像假思考。
- 修改:新增 `workbenchAiPlanningThinkingModel.js`,集中维护可复用的 thinking 事件合并、完成态转换、初始意图判断和阶段性进度计划,避免继续把主 composable 写大。
- 修改:`usePersonalWorkbenchAiMode.js` 在模型规划 pending 卡片上启动可取消的阶段性进度:判断办理意图、抽取关键信息、规划执行步骤、匹配业务工具、准备兜底策略;模型返回或进入 fallback 后会清理定时器。
- 修改:`useWorkbenchAiStewardFlow.js` 支持 `resolveInlineExecutionPlan(prompt, entry, files, { pendingMessageId })`;传入 pending id 时改走 `/steward/plans/stream`,只把服务端 thinking 实时写回当前卡片,不把 answer delta 提前塞进规划消息。
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 生成申请核对表时保留并完成前置规划 thinking再追加“整理申请表字段 / 同步费用测算”,避免申请表阶段把前面的规划过程清掉。
- 测试:新增 `workbench AI mode streams planning thinking into the pending message`,锁定阶段性 thinking、可取消定时器、流式规划接线和申请预览 thinking 合并;同文件 11/11 通过。
- 验证:相关前端集合 `node --test web/tests/workbench-ai-action-router.test.mjs web/tests/steward-actions-service.test.mjs web/tests/steward-plan-message-copy.test.mjs web/tests/workbench-ai-intent-planner-model.test.mjs web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-composer-components.test.mjs` 通过 33/33`npm --prefix web run build` 通过;`git diff --check` 无输出。
- 真页验证:本地 5173 干净会话输入“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿。”后thinking 数量按时间增长:点击后 2 条、1.1 秒 3 条、2.5 秒 4 条、5.7 秒 5 条、9.6 秒 6 条;约 36 秒后仍成功保存草稿,草稿单号 `AZ4A98BSF`,截图保存为 `/tmp/x-financial-ai-progressive-thinking.png`
- 验证补充:我额外跑了 `workbench-ai-mode-switch.test.mjs`,该文件仍有 2 个既有静态断言失败,断言点还停在旧的内联附件卡片和旧的 `selectedFiles` watch 结构;本轮相关测试和真页回放不受影响。
- 影响:用户现在不再只看到静态“思考中”,而是能在同一张思考卡片里持续看到业务意图、槽位、动作链和兜底策略的推进过程;服务端流式 thinking 到达后也会合并进同一个列表。
- 17:40我修复了 AI 工作台保存申请草稿后,再输入“提交这个单据”会重新进入意图规划的问题。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有大量既有未提交改动,本轮只补 AI 工作台申请上下文提交识别、申请预览执行分支和对应测试。
- 根因:`resolveInlineApplicationPreviewTextAction()` 只识别“提交 / 确认提交 / 直接提交”等精确词,用户输入“提交这个单据”会绕过当前申请核对表上下文,继续进入 `shouldRequestWorkbenchAiIntentPlan()` 和小财管家规划,导致再次识别意图并等待模型。
- 修改:`workbenchAiApplicationGateModel.js` 把“提交这个单据 / 提交这个申请单 / 提交当前单据 / 提交刚才的草稿”等上下文指代短句映射为 `AI_APPLICATION_ACTION_SUBMIT`;同类保存指代短句也映射到保存草稿动作。
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在提交动作里新增“已保存草稿 + 上下文提交短句”快路径;当最近申请核对表已有 `draftPayload.claim_id` 或单号,且用户明确说提交当前/刚才的单据时,跳过二次确认弹窗,直接执行提交前核查和 `/reimbursements/application-preview-action`,并带上 `application_edit_claim_id` 复用原草稿。
- 测试:先新增 `workbench-ai-application-context-submit.test.mjs` 红灯测试,复现“已有草稿 + 提交这个单据”只打开确认弹窗、不调用提交接口;实现后该测试通过,并确认 payload 写入 `application_edit_claim_id=claim-saved-draft`
- 验证:`node --test web/tests/workbench-ai-application-context-submit.test.mjs` 通过;`node --test web/tests/workbench-ai-application-gate-model.test.mjs` 通过;相邻 `workbench-ai-action-router.test.mjs``workbench-ai-intent-planner-model.test.mjs``ai-application-preview-actions.test.mjs` 均通过;本轮改动文件 `git diff --check -- ...` 无输出。
- 验证补充:`workbench-ai-mode-expense-scene-action.test.mjs``expense-application-fast-preview.test.mjs` 仍有既有静态断言失败,主要还在扫描旧 `PersonalWorkbenchAiMode.vue` 或过窄源码片段;新增的上下文提交行为测试已覆盖真实执行路径。
- 影响用户先说“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车保存草稿”草稿保存后再说“提交这个单据”前端不会再重新规划而会把“这个单据”绑定到最近申请草稿并提交到审批链路。
- 17:48我修复了“完整出差信息但未显式说申请/报销”时AI 模式停在候选流程确认、不直接推进申请预览的问题。
- Git 提交检查:`git fetch --all --prune` 成功;当前 upstream 为 `origin/main``HEAD..@{u}``@{u}..HEAD` 均未输出新提交;工作区仍有大量既有未提交改动,本轮只继续修改 `workbenchAiIntentPlannerModel.js` 和对应意图规划测试。
- 根因:后端已经返回 `pending_flow_confirmation`,且候选流只有 `travel_application / 先发起出差申请`,但前端 `normalizeWorkbenchAiIntentPlan()` 只识别 `tasks`,没有把这个唯一候选流转成可执行申请计划,最后又回到普通 steward 回复路径。
- 修改:`workbenchAiIntentPlannerModel.js` 增加 pending flow 候选解析;当 `pending_flow_confirmation.status=pending` 且唯一候选为“先发起出差申请”时,直接生成 `create_travel_application` 计划,包含 `build_application_preview`、必填校验、模型抽取的时间/地点/事由/交通方式。
- 测试:先新增红灯用例 `workbench AI intent planner turns single application candidate flow into executable preview payload`,复现该计划原本返回 `null`;实现后同文件 12/12 通过。
- 验证:`node --test web/tests/workbench-ai-intent-planner-model.test.mjs``node --test web/tests/workbench-ai-application-gate-model.test.mjs``node --test web/tests/workbench-ai-application-context-submit.test.mjs``node --test web/tests/steward-plan-model-pending-flow.test.mjs` 均通过;`git diff --check -- web/src/composables/workbenchAiMode/workbenchAiIntentPlannerModel.js web/tests/workbench-ai-intent-planner-model.test.mjs` 无输出。
- 影响用户输入“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车”时如果门禁已查明没有可关联申请单前端会把“应先申请单据”直接落到申请预览链路不再表现成只会反复识别意图。
## 遗留问题 ## 遗留问题
- 09:41当前 Skill 是新建在项目级 `.codex/skills` 目录里,本轮可以通过文件检查验证结构,但是否被未来会话自动加载还依赖 Codex 对项目 Skill 的刷新机制。建议后续新开会话或下一次任务时确认 Skill 列表是否出现 `agent-change-log` - 09:41当前 Skill 是新建在项目级 `.codex/skills` 目录里,本轮可以通过文件检查验证结构,但是否被未来会话自动加载还依赖 Codex 对项目 Skill 的刷新机制。建议后续新开会话或下一次任务时确认 Skill 列表是否出现 `agent-change-log`
@@ -236,6 +418,26 @@
- 11:39本轮新增的附件归集回归测试还没有在容器内真正执行原因仍是 Docker socket 权限拒绝。建议 Docker 权限恢复后优先运行 `server/tests/test_attachment_association_jobs.py` 新增两条测试,并在 5173 重新打开刚才那张坏票据验证自动修复是否生效。 - 11:39本轮新增的附件归集回归测试还没有在容器内真正执行原因仍是 Docker socket 权限拒绝。建议 Docker 权限恢复后优先运行 `server/tests/test_attachment_association_jobs.py` 新增两条测试,并在 5173 重新打开刚才那张坏票据验证自动修复是否生效。
- 11:55当前实际运行容器仍叫 `x-financial-local-linux`,且容器内 `poppler-data` 未安装、`mutool` 不存在;本轮文本层兜底已恢复 OCR 字段,但 PDF 图片预览仍会带转图失败 warning。12:04 已在当前运行容器补齐依赖并刷新本批票据预览;建议后续仍按最新 compose 统一到 `local-x-financial-linux`,避免旧容器继续抢占 5173。 - 11:55当前实际运行容器仍叫 `x-financial-local-linux`,且容器内 `poppler-data` 未安装、`mutool` 不存在;本轮文本层兜底已恢复 OCR 字段,但 PDF 图片预览仍会带转图失败 warning。12:04 已在当前运行容器补齐依赖并刷新本批票据预览;建议后续仍按最新 compose 统一到 `local-x-financial-linux`,避免旧容器继续抢占 5173。
- 12:23本轮没有拿到可用的浏览器自动化插件来生成真页截图已用前端构建、组件测试和真实 5173 OCR/preview 接口替代验证。建议用户侧刷新页面后重新上传同类 PDF若历史会话里旧附件卡片仍停留在旧状态则重新选择附件触发 OCR 状态刷新。 - 12:23本轮没有拿到可用的浏览器自动化插件来生成真页截图已用前端构建、组件测试和真实 5173 OCR/preview 接口替代验证。建议用户侧刷新页面后重新上传同类 PDF若历史会话里旧附件卡片仍停留在旧状态则重新选择附件触发 OCR 状态刷新。
- 13:03`expense-application-fast-preview.test.mjs` 整文件仍有 12 个既有失败,失败点包括 unsupported business guidance 文案、旧申请 session 静态结构、typewriter/table 静态断言等;本次新增的直提申请用例已通过。建议后续单独清理这些陈旧断言,避免掩盖真正的申请链路回归。
- 13:20本轮完成了模型计划接线和结构测试但没有在真实 5173 页面输入框回放 `/steward/plans` 返回 `llm_function_call` 的端到端路径。建议后续用真实模型配置测试一次“去上海出差...直接提交”,确认模型返回任务计划后前端进入申请核对表。
- 13:35本轮已经用模型规划入口、fallback 解析和前端构建验证了“保存草稿”变体,但还没有在真实 5173 输入框回放一次完整点击/保存草稿链路。建议后续用当前句子做真页验证,确认草稿保存后出现申请单详情入口。
- 13:58本轮已经用容器后端测试和前端模型计划归一测试验证 function calling contract但还没有在真实 5173 页面观察 `/steward/plans` 的网络返回是否包含新的 `requested_action`。建议后续打开浏览器 DevTools 或后端日志,用真实模型配置回放一次保存草稿话术。
- 14:33真实接口已确认模型必经和 3 次重试,但当前主模型 MiniMax 连续超时backup 槽位 API key 未解密/未配置,因此仍然进入 `rule_fallback`。建议下一步先修复模型连通性或配置可用 backup再确认返回 `planning_source=llm_function_call` 的成功路径。
- 14:33用户提出 LangChain 方向是合理的但本轮没有直接引入新依赖。15:14 已完成 LangGraph 第一阶段迁移;后续仍要继续补 memory/checkpoint、action node 和人审中断能力。
- 15:14LangGraph 第一阶段只替换了 `/steward/plans` 的规划编排骨架,还没有把 slot decision、runtime decision、草稿保存/提交 action 和跨轮 checkpoint 迁入 graph。建议下一步按节点边界继续拆迁避免又在 LangGraph node 内写回大块自研流程。
- 15:14`langgraph` 依赖把 `websockets` 锁到 15.0.1;当前 `pip check` 和 steward 相关测试通过,但如果后续实时流或 WebSocket 功能异常,需要优先核对这个依赖变化。
- 15:22本轮只补迁移文档没有继续实现 Phase 1-5。建议下一轮严格按 `LANGGRAPH_RUNTIME_MIGRATION.md` 从模型成功路径开始推进,不要跳过文档直接扩写 graph node。
- 15:31当前 `StewardGraphRuntime` 已覆盖 slot/runtime decision但保存草稿、提交审批、关联申请单等副作用动作还只是下游既有服务执行并未接成 LangGraph 白名单 action node。建议下一步优先实现 `steward_action_nodes.py` / action contract再接 checkpoint 和 human-in-the-loop。
- 15:53当前已完成服务端 action contract但还没有实现真正执行副作用的 action registry / action node`save_application_draft``submit_application``create_reimbursement_draft` 仍需要下一步接到现有业务服务并补幂等、确认来源和 trace。建议下一步先做 action registry再接 checkpoint/human-in-the-loop。
- 16:13第一版 action executor 和 AI 工作台点击执行闭环已可执行申请/报销基础副作用,但还没有接入 LangGraph checkpoint / interrupt也没有给所有副作用写持久化幂等 trace。16:40 已完成后端 action graph runtime、pending interrupt 和幂等重放,剩余前端恢复 UI 还需单独做。
- 16:13`link_existing_application``associate_attachments` 已进入执行白名单并在未接线时阻断,但真实执行还没有统一接入 action executor。16:40 已接入真实执行和回归测试,后续仍需在真页补充可视化恢复/继续动作。
- 16:40真实 `/steward/plans` 仍未跑到 `planning_source=llm_function_call`,当前阻塞点是 backup skipped、MiniMax 三次失败;代码层 backup tool-call 成功路径已有测试,但环境层还要修模型配置或连通性。
- 16:40后端已把 pending interrupt 写进 conversation checkpoint但 AI 工作台还没有在刷新或重新进入会话时从 checkpoint 主动恢复“继续/确认”按钮;建议下一步补前端恢复 UI。
- 16:53`usePersonalWorkbenchAiMode.js` 当前已超过 800 行,后续继续加 AI 模式功能前建议拆出模型规划等待态、普通对话启动和申请预览桥接 composable避免继续扩大主运行时文件。
- 16:59本轮没有新增独立遗留风险仍沿用 16:40 的真实模型连通性问题和 16:53 的主 composable 过大问题。建议后续先修 `llm_function_call` 成功路径,再拆分 AI 模式前端运行时文件。
- 17:26`workbench-ai-mode-switch.test.mjs` 仍有 2 个旧静态断言失败,分别断言附件卡片还在主模板内联、`selectedFiles` watch 还在主 AI 模式文件里;当前真实代码已经拆成组件/composable。建议后续单独整理这个老测试文件避免它继续误报已迁移结构。
- 17:40`workbench-ai-mode-expense-scene-action.test.mjs``expense-application-fast-preview.test.mjs` 仍有多条旧静态断言失败,失败点主要是代码已拆到 composable 后测试还读取旧 Vue 单文件或固定片段。建议后续把这些静态断言改成导入 composable/model 的行为测试,避免真实功能修复被旧结构误报淹没。
- 17:48本轮修复已用模型归一化和相邻前端测试覆盖但还没有在真实 5173 页面输入最新无动作话术截图回放。建议下一步刷新 AI 工作台用“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车”验证是否直接进入申请核对表。
## TODO ## TODO
@@ -264,3 +466,28 @@
- [x] ~~重新跑截图同批火车票 PDF 的 OCR 接口,确认不再返回“其他单据/空字段”。~~(完成于 11:55证据5173 `/api/v1/ocr/recognize` 返回 `2月20_武汉-上海.pdf``2月23_上海-武汉.pdf` 均为 `火车/高铁票`,并写回票据夹字段) - [x] ~~重新跑截图同批火车票 PDF 的 OCR 接口,确认不再返回“其他单据/空字段”。~~(完成于 11:55证据5173 `/api/v1/ocr/recognize` 返回 `2月20_武汉-上海.pdf``2月23_上海-武汉.pdf` 均为 `火车/高铁票`,并写回票据夹字段)
- [x] ~~重建或补齐当前运行容器的 `poppler-data` / `mupdf-tools`,确认 `mutool` 可用后再上传同类中文 PDF目标是同时恢复 PNG 预览和 OCR 字段。~~(完成于 12:04证据`apt-get install poppler-data mupdf-tools` 成功,`/usr/bin/mutool` 可用;两条 `/preview` 返回 `image/png`,并写入 `preview_kind=image` - [x] ~~重建或补齐当前运行容器的 `poppler-data` / `mupdf-tools`,确认 `mutool` 可用后再上传同类中文 PDF目标是同时恢复 PNG 预览和 OCR 字段。~~(完成于 12:04证据`apt-get install poppler-data mupdf-tools` 成功,`/usr/bin/mutool` 可用;两条 `/preview` 返回 `image/png`,并写入 `preview_kind=image`
- [x] ~~统一 AI 会话附件、票据夹和报销附件的预览类型判定,避免会话上传卡片继续把已生成 PNG 预览的 PDF 当成 PDF 展示。~~(完成于 12:23证据新增 `documentPreviewAssets.js`;前端相关测试 24/24 通过,真实 OCR 返回 `preview_kind=image``/preview``image/png` - [x] ~~统一 AI 会话附件、票据夹和报销附件的预览类型判定,避免会话上传卡片继续把已生成 PNG 预览的 PDF 当成 PDF 展示。~~(完成于 12:23证据新增 `documentPreviewAssets.js`;前端相关测试 24/24 通过,真实 OCR 返回 `preview_kind=image``/preview``image/png`
- [x] ~~重新设计一键关联票据的弹窗 UI调整内边距、行间距、按钮尺寸与悬浮效果并按精致化要求收敛尺寸以恢复精致感。~~(完成于 12:52证据`npm run build` 构建成功,`receipt-folder-view.test.mjs` 测试通过,样式已应用)
- [ ] 单独修复 `expense-application-fast-preview.test.mjs` 里 12 个既有失败或拆分陈旧静态断言恢复整文件可作为申请链路回归套件使用。来源13:03 差旅申请直提链路验证)
- [ ] 在真实 5173 AI 工作台输入“去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交”,确认模型计划路径命中 `/steward/plans`并在缺日期时停在申请核对表、日期补齐后进入提交前核查。来源13:20 AI 意图规划器接线)
- [x] ~~在真实 5173 AI 工作台输入“2026-02-20 至 2026-02-23上海出差国网仿生产服务器部署火车保存草稿。”确认模型计划或 fallback 计划生成申请核对表,并自动保存草稿后给出详情入口。~~(完成于 16:53证据1.5 秒内显示用户气泡和 pending 卡,约 38 秒后保存草稿 `AZ8QSX9QA`
- [ ] 在真实 5173 网络面板或后端日志核对 `/steward/plans` 返回 `planning_source=llm_function_call``tasks[0].requested_action=save_draft` 和完整 `ontology_fields` 后,再确认前端没有走 rule fallback。来源13:58 模型优先规划重构)
- [x] ~~确认真实 `/steward/plans` 不再毫秒级规则直出,并在模型不可用时按 10s * 3 次后降级。~~(完成于 14:33证据真实 5173 API 回放耗时 32.08sMiniMax attempt 1/2/3 均超时后才 `rule_fallback`
- [ ] 修复 MiniMax 连通性或配置可用 backup 模型,再用同一句保存草稿话术确认 `/steward/plans` 返回 `planning_source=llm_function_call`来源14:33 模型必经链路验证16:40 已补代码层 backup tool-call 测试,但真实环境仍 fallback
- [x] ~~评估 LangChain/LangGraph 是否作为 steward agent 层框架,引入前明确 memory、tools/action、planner、runtime state、observability 和依赖风险。~~(完成于 15:14证据已引入 LangGraph 第一阶段并默认接管 `/steward/plans` 规划编排45 个容器测试通过)
- [x] ~~将 `StewardSlotDecisionAgent` 和 `StewardRuntimeDecisionAgent` 继续迁入 LangGraph 节点,形成统一 steward graph runtime。~~(完成于 15:31证据新增 `StewardGraphRuntime`,容器内 graph runtime 相关回归 59/59 通过)
- [x] ~~为 `/steward/plans` 增加服务端白名单 `action_steps`,覆盖申请、报销、保存草稿和直接提交的基础动作规划。~~(完成于 15:53证据新增 `StewardActionPlanBuilder`,真实 5173 回放返回完整 action steps容器内相关回归 66/66 通过)
- [x] ~~为 LangGraph steward runtime 增加 checkpoint/persistence 设计,复用现有 conversation state支持暂停、恢复和可观测 trace。~~(完成于 16:40证据新增 `StewardGraphActionRuntime`,同一 `conversation_id + client_trace_id` 重放返回 `idempotent_replay=true`,未确认 action 写入 pending interrupt
- [ ] 修通真实 `llm_function_call` 成功路径,用真实模型配置验证 `/steward/plans` 不再进入 rule fallback。来源15:22 LangGraph 迁移文档action node、checkpoint 和 human-in-the-loop 后端部分已于 16:40 完成)
- [x] ~~实现 LangGraph 白名单 action node把保存草稿、提交审批、关联申请单、创建报销草稿和附件关联接入显式 action contract。~~(完成于 16:40证据`StewardGraphActionRuntime` 默认接管 `/steward/actions/execute`action executor 9/9 通过,关联任务回归 7/7 通过)
- [x] ~~实现 action registry 的未知 action 拒绝、确认来源校验、重复申请 precheck 阻断和基础业务执行。~~(完成于 16:13证据新增 `StewardActionExecutor`,容器内 action executor 5/5 和迁移回归 71/71 通过;前端点击执行回归 21/21 通过;真实 5173 未确认 submit 返回 `needs_confirmation`
- [x] ~~为第一版 `StewardActionExecutor` 增加 checkpoint / interrupt 恢复、持久化 trace 和幂等键。~~(完成于 16:40证据`StewardGraphActionRuntime` 持久化 `steward_action_checkpoint`,重复 trace 不重复建单)
- [x] ~~补齐 `link_existing_application` 和 `associate_attachments` 的真实 action executor 接线与回归测试。~~(完成于 16:40证据申请关联报销草稿测试和附件关联 runner 测试已通过)
- [ ] 前端 AI 工作台从 `steward_action_checkpoint.pending_interrupt` 恢复继续/确认按钮,避免刷新页面后只剩后端 checkpoint、用户看不到可继续 action。来源16:40 action graph runtime
- [ ] 拆分 `usePersonalWorkbenchAiMode.js` 的模型规划等待态和对话启动逻辑,优先把文件降回 800 行以内。来源16:53 回车无反馈修复)
- [x] ~~修复 AI 模式普通回复复用规划 pending 卡片,确保一次用户输入只生成一条助手消息。~~(完成于 16:59证据真页输入“你好”最终 `assistantCardCount=1`,前端相关测试 32/32 通过)
- [x] ~~让 AI 模式模型规划等待期间持续追加 thinking 事件,而不是静态显示“思考中”后一次性出现步骤。~~(完成于 17:26证据真页干净会话 thinking 数量从 2 条逐步增长到 6 条,相关前端测试 33/33 通过)
- [x] ~~修复 AI 工作台“保存草稿”后的上下文提交短句,让“提交这个单据”复用最近申请草稿而不是重新规划。~~(完成于 17:40证据`workbench-ai-application-context-submit.test.mjs``workbench-ai-application-gate-model.test.mjs` 通过)
- [x] ~~修复“完整出差信息但未说申请/报销”时只停在候选流程确认的问题,让唯一的“先发起出差申请”候选流直接进入申请预览计划。~~(完成于 17:48证据`workbench-ai-intent-planner-model.test.mjs` 新增回归通过,相邻 4 组前端测试通过)
- [ ] 单独修复或重写 `workbench-ai-mode-switch.test.mjs` 的旧静态结构断言,使它适配 `WorkbenchAiFileStrip` 和 OCR composable 拆分后的真实代码。来源17:26 额外测试发现)
- [ ] 单独修复或重写 `workbench-ai-mode-expense-scene-action.test.mjs``expense-application-fast-preview.test.mjs` 中继续扫描旧 Vue 单文件的静态断言,改为覆盖 composable/model 的行为测试。来源17:40 上下文提交验证)
- [ ] 在真实 5173 AI 工作台回放“2026-02-20 至 2026-02-23去上海出差辅助国网仿生产服务器部署交通火车”确认唯一申请候选流直接生成申请核对表。来源17:48 无动作话术直进申请预览修复)