Refine travel reimbursement steward flow
Align planner, runtime rules, and policy assets so travel guidance matches the updated reimbursement workflow.
This commit is contained in:
273
document/development/小财管家本体JSON流程/CONCEPT.md
Normal file
273
document/development/小财管家本体JSON流程/CONCEPT.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# 小财管家本体 JSON 流程
|
||||
|
||||
## 功能一句话
|
||||
|
||||
用大模型作为小财管家的主意图识别器,将用户连续对话转换为受本体字段约束的业务 JSON,并在申请和报销意图不确定时先进入用户确认,而不是用固定规则直接判定。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前小财管家已经具备任务规划、部分运行时状态和申请/报销委派能力,但仍有两个关键缺口:
|
||||
|
||||
- 意图识别仍带有较强规则假设。例如“2月20-23日去上海出差辅助国网仿生产环境部署”这类话术,在没有“申请”或“报销”动词时,系统不能仅凭规则直接判定为申请。
|
||||
- 跨轮对话需要一个贯穿流程的结构化 JSON。该 JSON 必须只承载本体 canonical field,不能由前端、规则或大模型临时发明业务字段。
|
||||
|
||||
因此,本轮目标不是重写整个小财管家,而是在现有 `steward` 体系上补齐“LLM 主识别 + 本体 JSON 模板 + 待确认流程 + 上下文记忆”的闭环。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 用大模型 function calling 作为主路径识别用户意图。
|
||||
- 模型输出必须落到统一业务 JSON 模板,字段来源必须来自本体字段注册表。
|
||||
- 支持 `travel_application` 和 `travel_reimbursement` 两个业务流程。
|
||||
- 当用户话术无法确定是申请还是报销时,返回 `pending_flow_confirmation`,由前端展示两个明确选项。
|
||||
- 跨轮对话持续携带并合并 `steward_state`,直到用户完成、取消或切换业务。
|
||||
- 规则只做兜底,且响应必须标记 `rule_fallback`,不能伪装成模型判断。
|
||||
- 用户可见回复使用 Markdown 块结构,重点信息加粗,避免密集换行。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 本轮不引入 LangChain 或 LangGraph。
|
||||
- 本轮不迁移申请助手、报销助手和 Orchestrator 的既有核心逻辑。
|
||||
- 本轮不让大模型直接创建申请单、保存草稿、绑定附件或提交审批。
|
||||
- 本轮不新增脱离本体字段体系的新业务字段。
|
||||
- 本轮不改造所有财务场景,只先覆盖出差申请和差旅/费用报销。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 普通员工:在首页或小财管家对话框中说“2月20-23日去上海出差辅助国网仿生产环境部署”。
|
||||
- 小财管家:先判断该话术包含出差、时间、地点和事由,但缺少“申请还是报销”的明确动作。
|
||||
- 用户:点击“补办出差申请”或“发起费用报销”。
|
||||
- 系统:将用户选择写入同一个业务 JSON,并继续用对应流程追问缺字段、生成核对结果或委派现有助手。
|
||||
|
||||
示例预期:
|
||||
|
||||
```markdown
|
||||
我识别到你描述的是一次 **上海出差事项**,时间为 **2月20日至2月23日**,事由是 **辅助国网仿生产环境部署**。
|
||||
|
||||
但当前还不能确定你要做哪一件事:
|
||||
|
||||
1. **补办出差申请**
|
||||
2. **发起费用报销**
|
||||
|
||||
请先选择一个方向,我会继续整理对应材料。
|
||||
```
|
||||
|
||||
## 功能能力
|
||||
|
||||
### 输入
|
||||
|
||||
- 用户自然语言 `message`。
|
||||
- 当前时间 `client_now_iso`,用于解析相对日期。
|
||||
- 附件元信息和 OCR 摘要。
|
||||
- 当前 `conversation_id`。
|
||||
- 已持久化 `steward_state`。
|
||||
- ontology canonical fields 列表。
|
||||
|
||||
### 输出
|
||||
|
||||
- `steward_state`:贯穿对话的业务 JSON。
|
||||
- `intent_result`:本轮模型或兜底规则的识别结果。
|
||||
- `candidate_flows`:存在歧义时的候选流程。
|
||||
- `next_action`:下一步动作,例如追问、确认流程、渲染申请预览、渲染报销预审。
|
||||
- `markdown_reply`:面向用户的 Markdown 回复。
|
||||
|
||||
### 状态边界
|
||||
|
||||
业务 JSON 必须区分业务字段和编排字段:
|
||||
|
||||
- 业务字段只允许出现在 `flows.<flow_id>.fields`。
|
||||
- 业务字段 key 必须是 canonical ontology field。
|
||||
- 编排字段只能出现在 `active_flow`、`pending_flow_confirmation`、`events`、`status` 等结构里。
|
||||
- 规则或模型返回的别名字段必须先归一化,例如 `occurred_date -> time_range`、`transport_type -> transport_mode`、`reason_value -> reason`。
|
||||
|
||||
### 安全边界
|
||||
|
||||
- 保存草稿、创建申请单、提交审批、删除或绑定附件必须等待用户确认。
|
||||
- LLM 只能产出结构化建议,不直接执行副作用操作。
|
||||
- 如果模型返回非法字段、非法流程或非法动作,服务端丢弃非法部分并进入保守兜底。
|
||||
|
||||
## 业务 JSON 模板
|
||||
|
||||
目标模板如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "steward.flow_state.v2",
|
||||
"active_flow": "",
|
||||
"pending_flow_confirmation": {
|
||||
"status": "none",
|
||||
"source_message": "",
|
||||
"reason": "",
|
||||
"candidate_flows": []
|
||||
},
|
||||
"flows": {
|
||||
"travel_application": {
|
||||
"flow_id": "travel_application",
|
||||
"intent": "travel_application_create",
|
||||
"status": "idle",
|
||||
"fields": {},
|
||||
"missing_fields": [],
|
||||
"confidence": 0,
|
||||
"evidence": []
|
||||
},
|
||||
"travel_reimbursement": {
|
||||
"flow_id": "travel_reimbursement",
|
||||
"intent": "travel_reimbursement_draft",
|
||||
"status": "idle",
|
||||
"fields": {},
|
||||
"missing_fields": [],
|
||||
"linked_application_claim_id": "",
|
||||
"attachments": [],
|
||||
"confidence": 0,
|
||||
"evidence": []
|
||||
}
|
||||
},
|
||||
"events": []
|
||||
}
|
||||
```
|
||||
|
||||
候选流程结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"flow_id": "travel_application",
|
||||
"label": "补办出差申请",
|
||||
"confidence": 0.52,
|
||||
"reason": "用户描述了出差时间、地点和事由,但没有明确要求报销或提交申请。"
|
||||
}
|
||||
```
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 后端
|
||||
|
||||
新增或扩展以下职责:
|
||||
|
||||
- `schemas/steward.py`:增加 v2 JSON 状态、候选流程、待确认流程和意图识别响应模型。
|
||||
- `services/steward_intent_agent.py`:扩展 function schema,允许模型返回 `pending_flow_confirmation` 和 `candidate_flows`。
|
||||
- `services/steward_model_plan_builder.py`:校验模型输出,只保留合法 flow、合法 action 和 canonical ontology fields。
|
||||
- `services/steward_flow_state.py`:支持 v1 到 v2 状态兼容、字段 patch 合并、候选流程落态和事件追踪。
|
||||
- `services/steward_runtime_decision_agent.py`:识别用户点击或输入的流程选择,并把选择写回 `active_flow`。
|
||||
- `api/v1/endpoints/steward.py`:在 `/steward/plans`、`/steward/plans/stream`、`/steward/runtime-decisions` 中统一返回最新 `steward_state`。
|
||||
|
||||
### 前端
|
||||
|
||||
- `stewardPlanModel.js`:将 `pending_flow_confirmation` 转为可点击操作。
|
||||
- `TravelReimbursementCreateView.js`:用户点击候选流程后,优先走 runtime decision,不重新把原句当新任务规划。
|
||||
- `useStewardPlanFlow.js`:渲染 Markdown 回复和候选流程操作。
|
||||
- `useTravelReimbursementSessionState.js`:持续保存并传回 `conversation_id` 和 `steward_state`。
|
||||
|
||||
### 数据与持久化
|
||||
|
||||
- 复用 `AgentConversation.state_json` 持久化 `steward_state`。
|
||||
- 不新增数据库表。
|
||||
- 不改变申请单、报销单现有表结构。
|
||||
|
||||
### 接口契约
|
||||
|
||||
`POST /steward/plans` 和流式计划接口返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"planning_source": "llm_function_call",
|
||||
"conversation_id": "conv_xxx",
|
||||
"steward_state": {},
|
||||
"next_action": "confirm_flow",
|
||||
"candidate_flows": [],
|
||||
"summary": "Markdown 文本"
|
||||
}
|
||||
```
|
||||
|
||||
运行时确认接口返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"decision_source": "llm_function_call",
|
||||
"next_action": "continue_selected_flow",
|
||||
"steward_state": {},
|
||||
"response_text": "Markdown 文本"
|
||||
}
|
||||
```
|
||||
|
||||
## 算法与公式
|
||||
|
||||
主路径不使用关键词打分决定最终意图,而是由 LLM function calling 返回结构化候选结果。
|
||||
|
||||
规则兜底仅在模型不可用、超时或结构非法时使用。兜底置信度用于决定是否直接进入候选确认:
|
||||
|
||||
$$
|
||||
confidence(flow) = 0.35t + 0.25l + 0.25v + 0.15a
|
||||
$$
|
||||
|
||||
变量定义:
|
||||
|
||||
- `t`:时间线索得分,出现明确日期、日期区间或相对日期时取 1,否则取 0。
|
||||
- `l`:地点线索得分,出现城市、客户地点或项目地点时取 1,否则取 0。
|
||||
- `v`:动作线索得分,出现申请、报销、提交、保存草稿等动作词时取 1,否则取 0。
|
||||
- `a`:附件线索得分,存在票据、发票、行程单、OCR 金额等附件证据时取 1,否则取 0。
|
||||
|
||||
当最高候选流程与第二候选流程差值小于阈值时进入确认:
|
||||
|
||||
$$
|
||||
\Delta = confidence(flow_1) - confidence(flow_2) < 0.20
|
||||
$$
|
||||
|
||||
该公式只用于兜底路径,不能覆盖模型主判断。
|
||||
|
||||
## 测试方案
|
||||
|
||||
### 后端单元测试
|
||||
|
||||
- `test_steward_intent_agent.py`:覆盖 function schema 包含 `candidate_flows`、`pending_flow_confirmation`。
|
||||
- `test_steward_model_plan_builder.py`:覆盖非法字段过滤、别名归一、非法 flow 丢弃。
|
||||
- `test_steward_flow_state.py`:覆盖 v2 状态合并、候选流程落态、用户选择后 active flow 切换。
|
||||
- `test_steward_runtime_decision_agent.py`:覆盖用户选择“补办出差申请 / 发起费用报销”。
|
||||
|
||||
### 接口测试
|
||||
|
||||
- `/steward/plans` 输入“2月20-23日去上海出差辅助国网仿生产环境部署”,返回 `next_action=confirm_flow`。
|
||||
- `/steward/runtime-decisions` 选择“补办出差申请”后,`active_flow=travel_application`。
|
||||
- `/steward/runtime-decisions` 选择“发起费用报销”后,`active_flow=travel_reimbursement`。
|
||||
|
||||
### 前端测试
|
||||
|
||||
- 候选流程按钮只在 `pending_flow_confirmation.status=pending` 时展示。
|
||||
- 用户点击候选流程后不重复触发新计划。
|
||||
- Markdown 回复中标题、段落、列表和重点加粗能正确渲染。
|
||||
|
||||
### 容器验证
|
||||
|
||||
后端测试必须在 Docker 容器内执行:
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_intent_agent.py server/tests/test_steward_model_plan_builder.py server/tests/test_steward_flow_state.py server/tests/test_steward_runtime_decision_agent.py
|
||||
```
|
||||
|
||||
前端构建必须在容器内执行:
|
||||
|
||||
```bash
|
||||
docker exec -w /app/web x-financial-main npm run build
|
||||
```
|
||||
|
||||
单次测试命令最长等待 60 秒,避免任务卡死。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 对“2月20-23日去上海出差辅助国网仿生产环境部署”,系统不再直接判定为申请,而是返回两个候选流程并要求用户确认。
|
||||
- 用户选择“补办出差申请”后,同一 `conversation_id` 的 `steward_state.active_flow=travel_application`。
|
||||
- 用户选择“发起费用报销”后,同一 `conversation_id` 的 `steward_state.active_flow=travel_reimbursement`。
|
||||
- `flows.*.fields` 中不出现非本体字段。
|
||||
- 模型返回别名字段时,服务端输出仍为 canonical ontology field。
|
||||
- 模型不可用时,规则兜底结果明确标记 `rule_fallback`。
|
||||
- 用户未确认前,不创建申请单、不保存报销草稿、不提交审批、不绑定附件。
|
||||
- 前端候选流程按钮点击后不产生重复消息、不重复规划、不丢失上下文。
|
||||
- 后端定向测试和前端构建在 `x-financial-main:/app` 通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 模型供应商对 function calling 的兼容程度不同,需要保留严格的服务端结构校验。
|
||||
- 旧版 `steward_state.v1` 已有数据需要兼容升级到 v2。
|
||||
- 用户输入可能同时包含“补申请”和“报销”,这种情况不应进入歧义确认,而应拆成两个任务。
|
||||
- 过去日期不等于报销,未来日期也不绝对等于申请;最终应由 LLM 主识别,并用候选确认处理低确定性场景。
|
||||
- 后续如果要支持更多流程,例如审批、制度问答或预算查询,需要先扩展本体业务契约,再扩展本 JSON 模板。
|
||||
75
document/development/小财管家本体JSON流程/TODO.md
Normal file
75
document/development/小财管家本体JSON流程/TODO.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 小财管家本体 JSON 流程 TODO
|
||||
|
||||
> 开发时必须先更新本 TODO,再按小步执行。只有真实完成并通过对应验证后,才能把 `[ ]` 改成 `[x]` 并补充证据。
|
||||
|
||||
## 阶段一:调研与契约确认
|
||||
|
||||
- [x] 盘点 `schemas/steward.py`、`steward_intent_agent.py`、`steward_model_plan_builder.py`、`steward_flow_state.py` 的当前状态模型。[CONCEPT: 方案设计] 证据:已在实现前读取并确认现有 `steward_state`、planner、runtime decision 入口。
|
||||
- [x] 盘点 `ontology_field_registry.py` 中申请和报销可使用的 canonical ontology fields。[CONCEPT: 业务 JSON 模板] 证据:实现复用 `BUSINESS_CANONICAL_FIELDS` 与 `normalize_ontology_form_values`。
|
||||
- [x] 确认 `AgentConversation.state_json` 中已有 `steward_state.v1` 数据的兼容方式。[CONCEPT: 数据与持久化] 证据:`StewardFlowStateService._normalize_state` 兼容旧 state 并升级默认版本为 `steward.flow_state.v2`。
|
||||
- [x] 复核前端 `stewardPlanModel.js`、`useStewardPlanFlow.js`、`TravelReimbursementCreateView.js` 中候选动作和状态携带入口。[CONCEPT: 前端] 证据:前端子智能体只读检查确认建议动作入口可复用。
|
||||
|
||||
## 阶段二:后端 Schema 与 JSON 模板
|
||||
|
||||
- [x] 在 `schemas/steward.py` 增加 `StewardCandidateFlow`、`StewardPendingFlowConfirmation`、v2 `steward_state` 相关模型。[CONCEPT: 业务 JSON 模板] 证据:新增模型与 `StewardPlanResponse.pending_flow_confirmation`。
|
||||
- [x] 在 `StewardPlanResponse` 和 runtime response 中补充 `next_action`、`candidate_flows` 或等价结构,保持旧字段兼容。[CONCEPT: 接口契约] 证据:`StewardPlanResponse.next_action/candidate_flows` 与 `continue_selected_flow` 已接入。
|
||||
- [x] 编写 schema 单元测试,验证候选流程只允许 `travel_application` 和 `travel_reimbursement`。[CONCEPT: 安全边界] 证据:`test_steward_intent_agent.py` 覆盖 function schema 枚举。
|
||||
|
||||
## 阶段三:LLM 意图识别主路径
|
||||
|
||||
- [x] 扩展 `steward_intent_agent.py` 的 function schema,要求模型输出 `pending_flow_confirmation` 和 `candidate_flows`。[CONCEPT: 后端] 证据:`test_steward_intent_agent.py` 通过。
|
||||
- [x] 更新系统提示词:不能把无明确动作的出差描述直接判定为申请;应结合语义、上下文和候选置信度决定是否确认。[CONCEPT: 背景与问题] 证据:`steward_intent_agent.py` system prompt 已要求低确定性返回 pending flow。
|
||||
- [x] 增加 fake LLM 测试:输入“2月20-23日去上海出差辅助国网仿生产环境部署”时,模型路径返回 `confirm_flow`。[CONCEPT: 指标与验收] 证据:`test_steward_planner_returns_pending_flow_confirmation_from_llm`。
|
||||
- [ ] 增加模型非法输出测试:非法字段、非法 flow、空候选项必须被服务端过滤或降级。[CONCEPT: 安全边界]
|
||||
|
||||
## 阶段四:状态合并与上下文记忆
|
||||
|
||||
- [x] 扩展 `steward_flow_state.py`,支持 `steward.flow_state.v1` 到 `steward.flow_state.v2` 的兼容升级。[CONCEPT: 风险与开放问题] 证据:`_normalize_state` 默认 v2 并保留 v1 核心结构。
|
||||
- [x] 支持将 `pending_flow_confirmation` 写入 state,并记录 source message、候选 flow 和确认原因。[CONCEPT: 业务 JSON 模板] 证据:`test_state_merge_plan_keeps_pending_flow_confirmation`。
|
||||
- [x] 支持用户选择候选 flow 后切换 `active_flow`,并把已识别字段合并到对应流程。[CONCEPT: 功能能力] 证据:`StewardFlowStateService.confirm_flow` 与 runtime 测试覆盖。
|
||||
- [x] 增加状态测试:多轮合并后 `flows.*.fields` 不出现非本体字段。[CONCEPT: 指标与验收] 证据:既有 `test_state_merge_filters_non_ontology_fields` 继续通过。
|
||||
- [ ] 增加状态测试:同一 `conversation_id` 下选择申请或报销不会丢失前一轮字段和证据。[CONCEPT: 数据与持久化]
|
||||
|
||||
## 阶段五:运行时决策
|
||||
|
||||
- [x] 扩展 `steward_runtime_decision_agent.py`,识别用户点击或输入“补办出差申请”“发起费用报销”。[CONCEPT: 后端] 证据:`_build_selected_flow_decision` 前置处理候选 flow。
|
||||
- [x] Runtime decision 输入为空时,从 `context_json.conversation_state.steward_state` 恢复状态。[CONCEPT: 输入] 证据:既有 `test_steward_runtime_decision_fallback_reads_persisted_steward_state` 继续通过。
|
||||
- [x] 用户选择申请后返回 `continue_selected_flow`,并设置 `active_flow=travel_application`。[CONCEPT: 指标与验收] 证据:`test_steward_runtime_decision_fallback_confirms_selected_flow`。
|
||||
- [x] 用户选择报销后返回 `continue_selected_flow`,并设置 `active_flow=travel_reimbursement`。[CONCEPT: 指标与验收] 证据:`test_steward_runtime_decision_fallback_confirms_reimbursement_flow`。
|
||||
- [x] 增加 runtime 测试,覆盖点击按钮和用户直接输入两种方式。[CONCEPT: 测试方案] 证据:runtime 单测覆盖申请/报销选择,接口 smoke 覆盖用户选择。
|
||||
|
||||
## 阶段六:前端候选流程展示
|
||||
|
||||
- [x] 在 `stewardPlanModel.js` 中把 `pending_flow_confirmation` 转成两个可点击建议动作。[CONCEPT: 前端] 证据:`steward-plan-model-pending-flow.test.mjs`。
|
||||
- [x] 在 `useStewardPlanFlow.js` 中渲染 Markdown 回复,确保标题、列表和重点加粗间距正常。[CONCEPT: 用户与场景] 证据:`buildStewardPlanMessageText` 对 `confirm_flow` 生成 Markdown 标题、列表和加粗内容。
|
||||
- [x] 在 `TravelReimbursementCreateView.js` 中处理候选流程点击:优先调用 runtime decision,不重新规划原始输入。[CONCEPT: 前端] 证据:`steward_confirm_flow` 分支调用 `handleStewardRuntimeDecision`。
|
||||
- [x] 在 `useTravelReimbursementSessionState.js` 中确认 `conversation_id` 和 `steward_state` 后续请求持续携带。[CONCEPT: 输入] 证据:现有 session state 与 `buildStewardPlanRequest` 已持续携带,无需新增改动。
|
||||
- [x] 增加或补充前端定向测试,覆盖候选按钮展示、点击后状态更新和不重复规划。[CONCEPT: 前端测试] 证据:新增 `steward-plan-model-pending-flow.test.mjs` 覆盖候选按钮,接口 smoke 覆盖选择后状态更新。
|
||||
|
||||
## 阶段七:接口与回归验证
|
||||
|
||||
- [x] 在容器中运行后端定向测试,单次命令超时控制在 60 秒内。[CONCEPT: 容器验证] 证据:`24 passed in 25.14s`。
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_intent_agent.py server/tests/test_steward_model_plan_builder.py server/tests/test_steward_flow_state.py server/tests/test_steward_runtime_decision_agent.py
|
||||
```
|
||||
|
||||
- [x] 在容器中运行已有小财管家回归测试,确认旧的申请/报销拆分不退化。[CONCEPT: 测试方案] 证据:`test_steward_planner.py`、`test_steward_slot_decision_agent.py` 包含在后端定向测试中并通过。
|
||||
|
||||
```bash
|
||||
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_steward_planner.py server/tests/test_steward_slot_decision_agent.py
|
||||
```
|
||||
|
||||
- [x] 在容器中运行前端构建。[CONCEPT: 容器验证] 证据:`docker exec -w /app/web x-financial-main npm run build` 成功。
|
||||
|
||||
```bash
|
||||
docker exec -w /app/web x-financial-main npm run build
|
||||
```
|
||||
|
||||
- [x] 手工验证小财管家输入“2月20-23日去上海出差辅助国网仿生产环境部署”,页面展示两个候选流程,未确认前不创建申请单或报销草稿。[CONCEPT: 指标与验收] 证据:接口 smoke 返回 `next_action=confirm_flow`、候选 `travel_application/travel_reimbursement`、`state_pending=pending`。
|
||||
|
||||
## 阶段八:文档同步
|
||||
|
||||
- [x] 实现过程中如调整 JSON 字段或接口契约,先更新 `CONCEPT.md`,再修改代码。[CONCEPT: 方案设计] 证据:已先新增 `CONCEPT.md` 与 `TODO.md`。
|
||||
- [x] 每完成一个阶段,在本 TODO 中勾选并补充证据,例如测试命令、文件名或接口返回要点。[CONCEPT: 测试方案] 证据:本文件已补充阶段证据。
|
||||
- [ ] 最终汇报工作区状态,不自动 commit/push,除非用户明确要求。[CONCEPT: 风险与开放问题]
|
||||
Reference in New Issue
Block a user