From a0f6d9f70206022edf9db40686908256222596cc Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Wed, 24 Jun 2026 21:59:15 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20.env.example?= =?UTF-8?q?=E3=80=81=E8=B4=A2=E5=8A=A1=E8=A7=84=E5=88=99=E8=A1=A8=E4=B8=8E?= =?UTF-8?q?=20AI=20=E6=84=8F=E5=9B=BE=E8=A7=84=E5=88=92=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + document/development/AI意图规划器/CONCEPT.md | 146 ++++++ .../LANGGRAPH_RUNTIME_MIGRATION.md | 420 ++++++++++++++++++ document/development/AI意图规划器/TODO.md | 32 ++ document/work-log/2026-06-24.md | 227 ++++++++++ .../rules/finance-rules/交通工具等级标准.xlsx | Bin 6072 -> 6072 bytes .../rules/finance-rules/交通费用预估表.xlsx | Bin 7197 -> 7197 bytes .../finance-rules/公司通信费报销规则.xlsx | Bin 5934 -> 5934 bytes server/rules/finance-rules/出差补助标准.xlsx | Bin 5931 -> 5931 bytes .../rules/finance-rules/地区淡旺季映射表.xlsx | Bin 11427 -> 11427 bytes .../rules/finance-rules/差旅职级映射表.xlsx | Bin 5783 -> 5783 bytes 11 files changed, 826 insertions(+) create mode 100644 document/development/AI意图规划器/CONCEPT.md create mode 100644 document/development/AI意图规划器/LANGGRAPH_RUNTIME_MIGRATION.md create mode 100644 document/development/AI意图规划器/TODO.md diff --git a/.env.example b/.env.example index df3588f..743f212 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,7 @@ ONLYOFFICE_PUBLIC_URL=http://127.0.0.1:8082 ONLYOFFICE_BACKEND_URL=http://127.0.0.1:8000 ONLYOFFICE_JWT_SECRET=change-me-onlyoffice HERMES_AGENT_SHARED_TOKEN=change-me-hermes +STEWARD_AGENT_RUNTIME=langgraph POSTGRES_HOST=127.0.0.1 POSTGRES_PORT=5432 diff --git a/document/development/AI意图规划器/CONCEPT.md b/document/development/AI意图规划器/CONCEPT.md new file mode 100644 index 0000000..6c11a16 --- /dev/null +++ b/document/development/AI意图规划器/CONCEPT.md @@ -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`。 diff --git a/document/development/AI意图规划器/LANGGRAPH_RUNTIME_MIGRATION.md b/document/development/AI意图规划器/LANGGRAPH_RUNTIME_MIGRATION.md new file mode 100644 index 0000000..4837f8b --- /dev/null +++ b/document/development/AI意图规划器/LANGGRAPH_RUNTIME_MIGRATION.md @@ -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,等待外部输入后恢复。 + +参考: + +- +- +- +- + +## 总体目标架构 + +```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 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` + +步骤: + +- [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 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_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 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` + +步骤: + +- [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 行先拆文件。 diff --git a/document/development/AI意图规划器/TODO.md b/document/development/AI意图规划器/TODO.md new file mode 100644 index 0000000..b5ebbd4 --- /dev/null +++ b/document/development/AI意图规划器/TODO.md @@ -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` 的重复编排。 diff --git a/document/work-log/2026-06-24.md b/document/work-log/2026-06-24.md index f90cb36..c483cba 100644 --- a/document/work-log/2026-06-24.md +++ b/document/work-log/2026-06-24.md @@ -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`。 - 影响: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.08s,MiniMax `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`。 @@ -236,6 +418,26 @@ - 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。 - 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:14:LangGraph 第一阶段只替换了 `/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 @@ -264,3 +466,28 @@ - [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] ~~统一 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.08s,MiniMax 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 无动作话术直进申请预览修复) diff --git a/server/rules/finance-rules/交通工具等级标准.xlsx b/server/rules/finance-rules/交通工具等级标准.xlsx index 04fdb956f6c83317ec6dfbfa0830a1e57c51b86a..8152f7c879468ac9fe3b67319237155f4c21fb5e 100644 GIT binary patch delta 408 zcmdm?zeArVz?+#xgn@y9gF(w>BF{k%EteZHd7e=#Ctg*k&pvFx)Arx%qwU1WJqA9r zyifZ{^Mu{FarQ=5wp4vWw#wh{-kZ$s-pI3#dcW9RC@)0dlzUB|U4lw;rs<7ystXUz zlI?6_@Oae`wyZ|wj>Nm#Et8I=+c->krNYml`rI@^Wu>a;jF!lVo;QoGWCgbM-#E*( zmce?<3k}DK;swc^i|c*s&CZ;3;ybAma{qf}{MkD@0vYrD`}IG6npxPnB>2QFH|>Q= zUY|D3)#G};F#AFD#+_&WX)TEB6=s>syzZuH_>yz$u&j14^E{o`+O??k4%H^I9vm^!JCi zIv2P$xOU63YHVV9r?aqr`@{2XmXRk6XS7PSDN3)J*)1*l;n$A(dDH!U#XZWngb!{_ zI<3t=Fg0#Y)s$eHfwR@vx4Z^ zyhhAGdh&UJ=OFG5!B$2P*IxKBh^r#v1LhWq+Q36`@-|U(kdpVJ_8`hY%mqZ1iCKcE VjbdQAS7O#6p1Qajh{_lD1OVIgu(bdH diff --git a/server/rules/finance-rules/交通费用预估表.xlsx b/server/rules/finance-rules/交通费用预估表.xlsx index dc9856a514e50a8630f662389c3bf10c8861a3a6..ebcc26042c27ca1386e29851161cbbeec943c39f 100644 GIT binary patch delta 408 zcmbPhG1r18z?+#xgn@y9gF(w>BF{k%EteZHd7e=#Ctg*k&pvFx)Arx%qwU1WJqA9r zyifZ{^Mu{FarQ=5wp4vWw#wh{-kZ$s-pI3#dcW9RC@)0dlzUB|U4lw;rs<7ystXUz zlI?6_@Oae`wyZ|wj>Nm#Et8I=+c->krNYml`rI@^Wu>a;jF!lVo;QoGWCgbM-#E*( zmce?<3k}DK;swc^i|c*s&CZ;3;ybAma{qf}{MkD@0vYrD`}IG6npxPnB>2QFH|>Q= zUY|D3)#G};F#AFD#+_&WX)TEB6=s>syzZuH_>yz$u&j14^E{o`+O??k4%H^I9vm^!JCi zIv2P$xOU63YHVV9r?aqr`@{2XmXRk6XS7PSDN3)J*)1*l;n$A(dDH!U#XZWngb!{_ zI<3t=Fg0#Y)s$eHfwQQ;{?%6 zLPpF$da{}5a}ZZvteFwST_XM%#O;*u0ducQ+Q36`vb2;rNJ)s4J&2krx$M?i zH+jyCy(}FSO3RbYgx*TX?RsF+f4+ca{l{2&PM3M%4?4D9T$3oYtj9v-{kKI`J*xA{fKfQcg@0#c(E?3!9 z*)H8-HI038-Z}X2_N3aGe?t{EOR0Os;vTLL-u?f4YnhNk;YQ5Nm^sPl zn1S?UcY)_1u7O}PBZ#|I=rM>pOV|g@eIa5455>tkqUInasiO8EYMH1Dh)a`~ym!x}JwreC{Ustn*qcw)FRh zw>lTNHn?`nvTAH%dZ$yraQnmaZI+QI3}>`TwJA!kn%ONa`r+4(`gzm+eZ@V>xP%XG zO**xB$-VBEO68K#eD}NlSp8nzP$jBvu*>3(%JwHqZ`sL|b!vRx!k ze!czV*Ts7ATXC}&|7`!LCLmMyclHk!SAEI1Tkw6Z?2ddh^*?u+snl diff --git a/server/rules/finance-rules/出差补助标准.xlsx b/server/rules/finance-rules/出差补助标准.xlsx index a1e24f4cf97fa5a6c0f9e4ff3e6f8fab0fb919ad..e7c568079456cf0914c860ef061e1326d039eeae 100644 GIT binary patch delta 418 zcmZ3jw_1-Uz?+#xgn@y9gF(w>BF{lVEteZHd7e=#UjxNXGcqs;PIeF#sb4zLH~X*w zPuqX5kG2yh_Zaxh@;>b+%@cOx#@QQL*;4fh*(!g(dv7wkdn3;}>iuGOp}Y`*Q|>i= zb_puYnWi_+sV+P;OSZF#!Q)j&*s>axI}-0|w@f;gZsRcFl?p$H>T}Zwm6fWVGg=}e zdfqI$k`>t2f8#9ES_bPaFEkt{iWk%;b1wF+H#>9EiSMLN$o=n?@n`Ss2xQFn@7MqQ zX=Y*PlHe1!+_V=ed41YASC8xY!t4jp8+V@hr?nuiSD0lk^SYa+m21sz-(}ABj=Ltg ze)Ffi{9s15uB}JOMpIrCR_1 delta 418 zcmZ3jw_1-Uz?+#xgn@y9gW;RpM4p3!-{fw@OgQTC{54SQG$R9p;A96;k@}?*efJ&sVwm zHSaG>KDa?D_crUYJ8G7~mTUr7B-ipps_c@GtIk(Fy4=2T@~hZ+0;g>94=8!+dLB~o zxtrv(&TFmM(%&E6>RjO3;My(AsD1yS_qtyyl}kqR-S7Hi^?P+gm8iPGE{i)V+n+4GWhYbCsquM} z=VvwD*UoVpzs|b)_4bor7wg4u#m!#)v;Cu*fK1)r**{p2L*z!x%$PaK0-HZDTCg#F zliRGt@rDINOYj;o1L?^=0?$EQQ^9IR5OBF{k%EteZHd7e=#Ctg*k&pvFx)Arx%qwU1WJqA9r zyifZ{^Mu{FarQ=5wp4vWw#wh{-kZ$s-pI3#dcW9RC@)0dlzUB|U4lw;rs<7ystXUz zlI?6_@Oae`wyZ|wj>Nm#Et8I=+c->krNYml`rI@^Wu>a;jF!lVo;QoGWCgbM-#E*( zmce?<3k}DK;swc^i|c*s&CZ;3;ybAma{qf}{MkD@0vYrD`}IG6npxPnB>2QFH|>Q= zUY|D3)#G};F#AFD#+_&WX)TEB6=s>syzZuH_?jl55>s~wah_EZfV(rC~0jM5S6HH38Lm` TgXOMjTZ4E)I&L5;O2-ob!T+NT delta 408 zcmZ1+xj2$1z?+#xgn@y9gW;RpM4p2j-{fw@OgQTCeBxDwdjG=)JZ=9^U8#Oux3*p5 zbZW+OMMXo-Bf4IDbQfhVKC>yz$u&j14^E{o`+O??k4%H^I9vm^!JCi zIv2P$xOU63YHVV9r?aqr`@{2XmXRk6XS7PSDN3)J*)1*l;n$A(dDH!U#XZWngb!{_ zI<3t=Fg0#Y)s$eHfwQ2OM&Qi zB_n1aJ$bk4a}allS_>nHtEKT6#O2rY0dr%uY~Z0dd7+j$NXacNdk`h9?E<0_wJkx^ V9Br`NRc&h!Pe{iNL`CU%0stOwu;Ks! diff --git a/server/rules/finance-rules/差旅职级映射表.xlsx b/server/rules/finance-rules/差旅职级映射表.xlsx index 34db88a0bdc08f4fc479845000c81ca52597f254..07cde05afde7200db2bb18d02f52e0476b1d2bcc 100644 GIT binary patch delta 408 zcmbQPJ6)G2z?+#xgn@y9gF(w>BF{k%EteZHd7e=#Ctg*k&pvFx)Arx%qwU1WJqA9r zyifZ{^Mu{FarQ=5wp4vWw#wh{-kZ$s-pI3#dcW9RC@)0dlzUB|U4lw;rs<7ystXUz zlI?6_@Oae`wyZ|wj>Nm#Et8I=+c->krNYml`rI@^Wu>a;jF!lVo;QoGWCgbM-#E*( zmce?<3k}DK;swc^i|c*s&CZ;3;ybAma{qf}{MkD@0vYrD`}IG6npxPnB>2QFH|>Q= zUY|D3)#G};F#AFD#+_&WX)TEB6=s>syzZuH_t-Ma)4;&WYHAD1K2F5EUwF38E&5 Tg5^$%T7!6OVs0SHPs|elrhlNQ delta 408 zcmbQPJ6)G2z?+#xgn@y9gW;RpM4p2j-{fw@OgQTCeBxDwdjG=)JZ=9^U8#Oux3*p5 zbZW+OMMXo-Bf4IDbQfhVKC>yz$u&j14^E{o`+O??k4%H^I9vm^!JCi zIv2P$xOU63YHVV9r?aqr`@{2XmXRk6XS7PSDN3)J*)1*l;n$A(dDH!U#XZWngb!{_ zI<3t=Fg0#Y)s$eHfwPNv4H40 z9wTNTJ$V!Va}am7KqDiFD<||A#AO!t0doUIY~Z0dd8&vxNXa=7dl1Dh>H?xdMJ++p V1W~ZuNl|MMk4?-CMEQw%0ssQ