# 小财管家

## 功能一句话

小财管家是首页统一财务任务入口，负责把用户的自然语言和附件拆解为多个可确认、可追踪、可分派的申请与报销任务，再调用现有申请助手和报销助手完成执行闭环。

## 背景与问题

当前个人工作台已经提供首页输入框，并能通过本体解析把一句话路由到申请、报销、预算或知识等单一会话。这个能力适合单意图，但用户真实表达经常是多任务组合，例如同时包含出差申请、昨日交通费报销、历史出差费用报销以及多张附件。

现有问题：

- 首页输入框当前会收敛为一个 `sessionType`，无法保留多任务计划。
- 申请助手和报销助手已经具备单任务核对能力，但缺少上层任务拆解、归集和跨助手分派。
- 附件上传后主要进入当前会话，缺少面向多任务的自动归集建议。
- 财务动作需要确认后才能入库、绑定或提交，不能让大模型直接执行高风险动作。
- 新增字段必须尊重本体字段，不能因为小财管家新增一套业务字段。

## 目标与非目标

### 目标

- 首页输入框定位为“小财管家”，作为用户默认财务任务入口。
- 用户提交自然语言和附件后，先展示小财管家的任务识别与附件归集过程。
- 支持把一句话拆成多个任务，第一版只验证费用申请和费用报销。
- 支持多附件按费用场景、时间、地点、任务线索形成归集建议。
- 遇到创建申请单、创建报销草稿、附件绑定、提交审批等动作时必须等待用户确认。
- 保留现有申请助手和报销助手能力，小财管家只做上层编排和分派。
- 外层意图识别必须优先使用大模型 function calling 输出结构化任务计划，规则逻辑只作为模型不可用或结构不合法时的兜底。
- 前端展示“意图识别智能体”过程气泡，并用流式状态逐步呈现，不暴露模型内部推理链。
- 所有业务字段先进入本体字段归一化，再进入下游助手、草稿、风险规则和持久化。

### 非目标

- 第一版不做万能智能体，不覆盖预算、审批、知识问答等全部场景。
- 第一版不引入 LangChain 或 LangGraph，先复用项目内运行时模型配置和 OpenAI-compatible function calling 契约。
- 第一版不自动提交审批，不绕过用户确认。
- 第一版不新增业务语义字段；只新增任务编排态字段。
- 第一版不重写申请助手、报销助手和现有 Orchestrator。

## 用户与场景

### 目标用户

- 普通员工：在首页一次性描述申请、报销和附件处理诉求。
- 财务人员：查看任务拆解、附件归集和用户确认链路是否可追溯。
- 审批/管理角色：后续可扩展为审批待办和预算提醒编排，但不进入第一版。

### 核心场景

用户在首页输入：

```text
我想要申请7月2日去北京出差，辅助北京供电局的税务审核任务，并且我要报销昨天的交通费，还需要报销6月3日出差去上海的费用
```

系统处理：

1. 小财管家识别到三条候选任务。
2. 将“昨天”按客户端日期解析为明确日期，例如 2026-06-03。
3. 将“7月2日去北京出差”归为费用申请任务。
4. 将“昨天的交通费”和“6月3日去上海出差费用”归为费用报销任务。
5. 如果用户同时上传附件，系统先识别附件场景，再建议归集到对应任务。
6. 需要创建申请单或报销草稿时，向用户展示核对摘要和确认动作。

## 功能能力

### 1. 任务识别与拆分

任务识别主链路是“小财管家意图识别智能体”：

1. 后端读取系统设置中的主模型/备模型运行时配置。
2. 将用户话术、客户端日期、附件元信息、上下文和 canonical ontology field 列表传入模型。
3. 通过强制 function calling 调用 `submit_steward_intent_plan`。
4. 模型只能返回结构化参数：`thinking_events`、`tasks`、`attachment_groups`。
5. 服务端再次校验：任务类型只能是 `expense_application` / `reimbursement`，业务字段只能是 canonical ontology fields，附件名必须来自本次上传。
6. 如果模型未配置、调用失败、未返回工具调用或结构不合法，才切换到规则兜底，并在过程摘要中标记兜底原因。

输入：

- 用户自然语言 `message`
- 附件元信息 `attachments`
- 当前用户、部门、角色、客户端时间
- 已有会话上下文，可选

输出：

- `plan_id`：本次小财管家计划 ID
- `tasks`：多个任务条目
- `thinking_events`：面向用户展示的过程摘要
- `confirmation_groups`：需要用户确认的动作集合
- `attachment_groups`：附件归集建议

任务条目包含：

- `task_id`：编排态任务 ID
- `task_type`：`expense_application` 或 `reimbursement`
- `assigned_agent`：`application_assistant` 或 `reimbursement_assistant`
- `title`：任务标题
- `summary`：任务摘要
- `status`：`planned`、`needs_confirmation`、`ready_to_delegate`、`delegated`、`completed`、`blocked`
- `confidence`：识别置信度
- `ontology_fields`：归一化后的本体字段
- `missing_fields`：缺失字段
- `confirmation_required`：是否需要确认后执行

### 2. 附件归集

附件归集基于以下信号：

- 附件类型：发票、火车票、机票、酒店票、付款截图、招待票据等。
- 费用场景：差旅、交通、招待、住宿、其他。
- 日期：票据日期是否匹配任务时间。
- 地点：票据地点是否匹配任务地点。
- 金额：是否能参与报销草稿。
- 置信度：低置信度必须提示用户核对。

输出示例：

```json
{
  "group_id": "ag_travel_001",
  "target_task_id": "task_reim_002",
  "scene": "travel",
  "scene_label": "差旅费用",
  "attachment_names": ["上海高铁票.jpg", "上海酒店发票.pdf", "出租车票.png"],
  "excluded_attachment_names": ["客户招待发票.jpg"],
  "confidence": 0.86,
  "confirmation_required": true
}
```

### 3. 用户确认

必须确认的动作：

- 创建费用申请单。
- 创建报销草稿。
- 将附件归集并绑定到某一任务。
- 将附件关联到已有报销草稿。
- 提交审批。
- 修改已有草稿字段。

小财管家的确认动作采用“下一步优先”策略：

- 同一个计划里同时存在申请和报销时，前端只展示当前下一步主动作，不一次性摊开全部确认按钮。
- 下一步优先级为：费用申请单创建 > 报销单填写 > 附件归集确认。
- 小财管家先思考和分析，说明下一步将要做的行为；用户输入“确定”或点击确认后，才进入行动。
- 行动完成后重新检查剩余任务队列，继续进入“思考 -> 分析 -> 等待确认 -> 行动”的循环。
- 申请任务完成后，再把剩余报销任务作为后续任务引导到报销助手。
- 附件归集不作为第一屏主动作抢占申请流程；当进入报销任务时，相关附件随报销上下文带入。

小财管家必须维护运行时任务上下文，而不是把每次用户输入都当成新的独立意图。上下文至少包含：

- 当前任务：正在处理的申请或报销任务。
- 剩余任务：已拆解但尚未处理的任务队列。
- 已完成任务：已经形成申请单、报销草稿、附件归集或提交动作的任务。
- 等待动作：当前正在等待用户补字段、确认核对表、确认提交审批或继续下一项。
- 最近结构化结果：当前申请核对表、报销核对结果、附件归集建议等。

用户输入“确认”“无误”“可以提交”等文本时，小财管家必须先匹配当前等待动作；如果当前等待的是申请单提交确认，就提交当前申请单；如果当前等待的是继续下一项，就进入剩余任务队列中的下一项；如果当前核对表仍有缺字段，则提示补字段。只有没有可匹配上下文时，才重新进入任务规划。

上述匹配不应主要依赖前端关键词规则。第一版应新增“小财管家运行时决策智能体”，由后端 function calling 接收 `runtime_state` 和用户当前输入，返回结构化 `next_action`：

- `submit_current_application`：确认当前申请核对表并提交至审批。
- `continue_next_task`：当前任务已完成，继续剩余任务队列中的下一项。
- `fill_current_slot`：用户补充了当前等待字段。
- `ask_user`：当前信息不足，需要继续追问。
- `plan_new_tasks`：当前没有可匹配上下文，重新进入任务规划。
- `cancel_current_action` / `no_op`：取消或不执行当前动作。

前端只执行模型返回的结构化动作，并做安全校验：例如申请核对表必须 `readyToSubmit` 才能提交，已提交消息必须标记避免重复提交，缺字段时必须追问。本地规则只允许作为模型失败后的保守兜底，不作为主判断来源。

可以自动执行的动作：

- 任务拆解。
- 本体字段归一化。
- 附件分类。
- 缺失字段检查。
- 风险和规则预审。
- 生成确认摘要。

### 4. 流式过程摘要

前端展示的是“意图识别智能体”的过程摘要，不是模型内部推理链；过程摘要必须独立于最终回复正文展示。过程摘要必须围绕业务理解展开，例如用户说了什么、被拆成哪些申请/报销任务、已识别哪些业务要素、还缺少哪些关键条件、为什么需要向用户追问。不能只展示“接收确认、协调能力、准备输出”等系统执行日志。

示例：

```text
正在识别用户输入中的财务事项...
已识别到 3 个候选任务。
正在按时间、地点和费用场景核对附件...
发现 3 张附件疑似属于差旅费用，1 张附件需要单独处理。
等待你确认后，我再创建申请单或报销草稿。
```

第一版通过 `POST /steward/plans/stream` 返回 `application/x-ndjson` 流式事件：

- `thinking`：逐条追加到系统回复气泡上方的独立“意图识别智能体”折叠气泡。
- `plan`：返回完整任务计划后，再渲染最终正文、任务卡片、附件归集和确认动作。

流式接口必须在模型 function calling 完成前先返回首个 `thinking` 事件，告知用户“意图识别智能体接管”。后续模型返回后再追加结构化拆解、字段映射、附件归集等过程摘要。

前端收到 `thinking` 事件后，也必须以 typewriter 方式逐字展示过程摘要，不能把一条完整思考事件一次性塞进折叠气泡。多条 `thinking` 事件应排队顺序输出，上一条内容打完后再输出下一条。

前端流式超时必须区分“首包等待”和“流式空闲等待”：首包应快速返回，收到首包后不能再用固定总时长中断仍在思考的模型调用，只能在长时间没有任何新事件时判定空闲超时。

流式过程中正文区域不输出任务结论；计划完成后意图识别气泡默认折叠，正文只保留用户需要确认和执行的信息。

计划完成后的最终正文也必须流式输出。前端不能把完整正文一次性替换到消息气泡里，而应进入 `typing` 状态按字符逐步追加正文；正文输出完成后，再把状态改为“等待用户确认”并展示确认按钮。

用户确认当前步骤后，小财管家隐式委派给申请能力或报销能力时，也必须保持同一套流式体验：先在系统气泡上方的小财管家思考折叠气泡中逐字展示当前业务任务、已识别信息、待补充条件和下一步动作；拿到申请核对表或报销核对结果后，再逐字输出正文。结构化表格、核对卡片、确认按钮可以在正文输出完成后一次性展示，但正文不能一次性替换进消息气泡。

小财管家委派期间不得打开右侧单助手执行流程面板，也不得把“申请助手 / 报销助手”的执行步骤显示成独立助手思考框。用户可见身份保持“小财管家”，具体调用哪个能力只作为小财管家自己的过程摘要，不切换为“财务助手”或单独助手会话。

### 5. 用户可见结果展示

小财管家的第一屏最终正文必须采用适中信息量的分段结构：让用户看懂系统理解了哪些财务事项、先后顺序是什么、每一步会交给哪个助手做什么；但不要把任务摘要、置信度、字段缺口和附件判断提前摊开。

第一屏推荐结构：

1. `我会这样推进`：说明识别到几个财务事项，以及会逐步处理。
2. 顺序列表：说明先做什么、后做什么，每步附一句负责助手和动作边界。
3. 确认提示：请用户回复“确定”后开始第一步，并说明具体缺口会在对应步骤里再判断。

最终正文必须使用 Markdown 块结构渲染，至少包含标题、段落和顺序列表；标题与段落之间必须保留空行，并通过 `steward-plan-markdown` 专属样式拉开块间距。不能只依赖普通换行拼接文本，因为普通换行在对话气泡里会显得拥挤。

第一屏不展示任务详情卡片里的“还需要补充”，也不展示字段缺口说明。用户确认开始后，进入当前步骤的申请助手或报销助手，再由具体助手基于当前任务判断需要补充什么。

后续步骤如果需要展示“还需要补充”，必须是结构化列表，每个待补充项独立成行，包含字段业务名称和填写说明；不得把多个待补充项拼接成一行连续文本。

当后续步骤发现关键条件缺失时，小财管家不能只展示“模型复核不稳定”或“下方表格待补充”。它必须把缺口转成下一轮对话问题，并优先给出可直接选择的业务选项。例如差旅申请缺少 `transport_mode` 时，用户界面展示为“请问你打算怎么出行？火车、飞机或轮船”，不得先展示申请核对表，也不得默认补成火车；用户选择后再生成申请核对表、写回出行方式、重新测算费用，并继续判断是否可以提交申请。这是“思考 -> 行动 -> 再思考 -> 再行动”循环的一部分。

用户补齐关键字段也不是终态动作。以“出行方式”为例，用户选择火车后，小财管家必须先进入下一轮业务思考，基于已识别的时间、地点、事由和出行方式模拟查询交通票据或票价口径，完成系统预估金额测算，再流式输出正文并展示申请核对表；不能在用户点击选项后直接把旧核对表补字段后闪现出来。

费用申请核对表阶段不得把系统档案字段或非阻塞归档字段当作用户待补充项。`employee_no`、`employee_name`、`department_name` 应从当前登录用户档案和组织上下文读取；`attachments` 在差旅申请阶段不阻塞核对表生成，可在后续报销、归档或审批材料补充阶段处理；`amount` 在申请阶段由系统规则估算。字段决策模型即使返回这些字段为缺失，服务端也必须过滤，不能向用户展示“附件/凭证和员工编号为合规必需字段”这类错误追问。

任务卡片和正文不得直接暴露本体字段名，例如 `transport_mode`、`amount`、`attachments`。本体字段只允许作为内部结构化数据进入后端、助手委派和持久化链路；用户界面必须翻译为业务中文，并提供可理解的填写说明：

- `transport_mode` 展示为“出行方式”，说明可填写高铁、飞机、自驾、出租车等。
- `amount` 在申请任务中展示为“预计金额”，在报销任务中展示为“报销金额”。
- `attachments` 展示为“附件/凭证”，说明可上传发票、行程单、付款截图或其他证明材料。
- `merchant_name` 展示为“商户/开票方”。
- `customer_name` 展示为“客户或项目对象”。

## 本体字段约束

业务字段必须使用本体 canonical field：

- `expense_type`
- `time_range`
- `location`
- `reason`
- `amount`
- `transport_mode`
- `attachments`
- `customer_name`
- `merchant_name`
- `department_name`
- `employee_name`
- `employee_no`

兼容字段只能作为输入别名，例如：

- `occurred_date` -> `time_range`
- `business_time` -> `time_range`
- `reason_value` -> `reason`
- `transport_type` -> `transport_mode`
- `application_transport_mode` -> `transport_mode`

小财管家的编排态字段不进入业务语义本体：

- `plan_id`
- `task_id`
- `planning_source`
- `model_call_traces`
- `task_status`
- `assigned_agent`
- `confirmation_status`
- `attachment_group_id`
- `thinking_event_id`

这些字段只用于编排、展示和审计，不参与费用规则判断。

## 方案设计

### 后端

新增小财管家规划服务：

- `schemas/steward.py`：定义请求、任务计划、附件归集、确认动作等契约。
- `services/runtime_chat.py`：新增 `complete_with_tool_call`，复用主/备模型配置发送 `tools` 与 `tool_choice`。
- `services/steward_intent_agent.py`：负责构造 `submit_steward_intent_plan` function schema 与模型调用。
- `services/steward_model_plan_builder.py`：负责把模型工具参数转换为服务端可校验计划。
- `services/steward_planner.py`：负责“大模型 function calling 优先、规则兜底”的编排和本体字段归一化。
- `api/v1/endpoints/steward.py`：提供 `POST /steward/plans` 和 `POST /steward/plans/stream`。

后端第一版不直接落库业务单据，只返回计划和确认动作。确认后的执行仍走现有申请助手、报销助手和 Orchestrator。

### 前端

新增或改造能力：

- 首页输入框标题和提示文案改为“小财管家”。
- 工作台打开时默认使用 `sessionType=steward`。
- 小财管家模式下隐藏“智能体切换”工具条。
- 小财管家模式下不展示欢迎界面。
- 小财管家模式下使用专属底部输入框，仅保留附件、自然语言输入和发送动作。
- 小财管家模式下先流式渲染独立过程摘要，再渲染任务计划正文。
- 用户确认当前下一步后，再切换/分派到申请助手或报销助手执行；多任务按顺序推进，不把所有任务动作一次性展示给用户。

### 执行流

```text
首页输入
  ↓
小财管家计划接口
  ↓
意图识别智能体 function calling
  ↓
思考过程流式输出 + 任务分析 + 下一步动作说明
  ↓
等待用户输入“确定”或点击确认
  ↓
小财管家隐式调用申请助手创建申请单核对结果
  ↓
申请动作完成后重新思考剩余队列
  ↓
继续等待确认并隐式调用报销助手填写报销单
  ↓
执行结果汇总
```

## 算法与公式

第一版主路径不以关键词规则定义“意图”，而是使用大模型 function calling 生成结构化计划。
模型输出后由服务端做确定性校验、字段归一化和确认动作生成。

规则置信度评分仅用于模型不可用或模型返回结构不可用时的兜底路径。

任务拆解之后还需要第二层“任务字段决策智能体”。这一步不能由前端关键词或固定 required 字段直接决定，而要把当前任务类型、用户原话、上游任务拆解结果、canonical ontology fields、已抽取字段、缺失字段、附件和申请/报销上下文交给模型，通过 function calling 返回下一步动作：

- `ask_user`：当前信息不足，必须先把缺口转成业务问题和可选项。
- `render_preview`：当前信息足够生成可核对结果，但提交、入库、绑定附件前仍需用户确认。

字段决策规则只能作为模型不可用或结构化结果非法时的兜底，兜底结果必须标记为 `rule_fallback`，不能伪装成智能体判断。字段名必须来自 ontology registry；UI 只展示中文业务名称，不展示 canonical 字段名。

任务置信度：

$$
confidence = \min(1, 0.35s_i + 0.25s_t + 0.2s_l + 0.2s_a)
$$

变量说明：

- `s_i`：意图关键词得分，命中申请/报销核心动词。
- `s_t`：时间得分，识别到明确日期、相对日期或时间范围。
- `s_l`：地点得分，识别到城市、客户或业务对象。
- `s_a`：附件/费用场景得分，识别到票据、交通、住宿、招待等费用线索。

附件归集置信度：

$$
group\_score = 0.4m_s + 0.3m_t + 0.2m_l + 0.1m_n
$$

变量说明：

- `m_s`：附件场景与任务场景匹配度。
- `m_t`：附件日期与任务日期匹配度。
- `m_l`：附件地点与任务地点匹配度。
- `m_n`：附件名称和任务关键词匹配度。

## 测试方案

### 后端单元测试

- function calling 路径能把模型工具参数转换为 `planning_source=llm_function_call` 的任务计划。
- 模型返回 `occurred_date`、`transport_type`、`reason_value` 等别名时，服务端仍只输出 canonical 字段。
- 一句话中同时包含申请和报销时，返回多个任务。
- “昨天”能根据 `client_now_iso` 解析为明确日期。
- `occurred_date`、`transport_type`、`reason_value` 等兼容字段不会作为业务 canonical 字段输出。
- 多附件能生成差旅归集建议和排除项。
- 创建/绑定/提交类动作必须带 `confirmation_required=true`。

### 前端测试

- 首页输入复杂话术后打开小财管家模式。
- 小财管家模式标题显示“小财管家”，不展示智能体切换。
- 过程摘要按步骤渐进展示。
- 任务计划卡片展示申请任务和报销任务。
- 附件归集建议展示包含项、排除项和确认按钮。

### 容器验证

后端测试必须在 `x-financial-main` 容器内执行：

```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
```

前端构建优先使用宿主机 `npm.cmd` 或项目既有脚本，并设置合理超时。

## 指标与验收

- 输入包含 3 个任务的示例话术时，至少识别出 1 个申请任务和 2 个报销任务。
- 输入“明天出差北京3天，支撑国网仿生产部署，并且报销昨天业务招待费”时，必须识别出 1 个申请任务和 1 个报销任务。
- 模型可用时，小财管家计划响应包含 `planning_source=llm_function_call`。
- 小财管家计划响应中业务字段只出现 canonical ontology fields。
- 附件场景混合时，能区分差旅相关附件和非差旅附件。
- 前端弹窗标题为“小财管家”，并隐藏智能体切换。
- 前端确认区只展示当前下一步主动作；存在申请任务时，第一步必须是“先创建申请单”。
- 意图识别折叠气泡不得宽于正文气泡，且流式首包必须先于最终计划到达。
- 用户未确认前，不创建申请单、不创建报销草稿、不绑定附件、不提交审批。
- 后端定向测试通过。

## 风险与开放问题

- 模型供应商对 tools/function calling 的兼容度可能不同；第一版保留规则兜底和主备模型 failover。
- 规则兜底无法覆盖所有自然语言，需要保留人工确认和低置信度提示。
- 附件真实 OCR 归集依赖现有票据识别质量；第一版先使用附件名称和已有 OCR 摘要做轻量归集。
- NDJSON 流式输出展示的是过程摘要，不是模型内部推理链。
- 多任务之间可能共享日期、地点、申请单上下文，需要后续完善任务图依赖。
- 如果未来接入 LangGraph，应基于当前计划契约迁移，而不是推翻现有申请/报销助手。
