Files
X-Financial/document/development/小财管家本体JSON流程/CONCEPT.md
caoxiaozhu 9f7b8b46a3 Refine travel reimbursement steward flow
Align planner, runtime rules, and policy assets so travel guidance
matches the updated reimbursement workflow.
2026-06-15 22:55:18 +08:00

274 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小财管家本体 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 模板。