Files
Auto-Finance/docs/API.md
wandering 1b35f07fd7 refactor: 架构重组 — doc/bot → core/infra,新增 Agent 调度模块
- src/doc/ 拆分为 src/core/extraction/, matching/, validation/(核心业务逻辑)
- src/bot/ 重命名为 src/infra/browser/(浏览器自动化基础设施)
- fill_consumable_doc.py → src/infra/documents/consumable.py
- 新增 Agent 调度模块:coordinator.py, events.py, session.py,重构 orchestrator.py
- 更新 AGENTS.md、README.md 及所有子目录 README
2026-07-02 18:36:19 +08:00

856 lines
19 KiB
Markdown
Raw Permalink 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.
---
last_reviewed: 2026-06-13
---
# 财务报销自动化 — API 文档
> 基础地址: `http://localhost:5000`
> 启动: `uv run python src/web/app.py`
## 总览
| # | 方法 | 路径 | 说明 |
|---|------|------|------|
| 1 | GET | `/` | PC 端主页 |
| 2 | GET | `/mobile/<session_id>` | 移动端上传页面 |
| 3 | POST | `/api/session` | 创建会话 |
| 4 | POST | `/api/upload/<session_id>` | 上传文件PDF/图片) |
| 5 | GET | `/api/files/<session_id>` | 列出会话目录中的文件 |
| 6 | GET | `/api/download/<session_id>/<filename>` | 下载生成的文件 |
| 7 | POST | `/api/mobile-upload/<session_id>` | 移动端上传图片 |
| 8 | GET | `/api/config/<session_id>` | 获取会话配置 |
| 9 | GET | `/api/data/<session_id>` | 获取发票数据JSON |
| 10 | POST | `/api/save/<session_id>` | 保存编辑后的发票数据 |
| 11 | POST | `/api/process/<session_id>` | 启动管道处理(仅发票提取,不自动提交) |
| 12 | GET | `/api/logs/<session_id>` | SSE 日志流 |
| 13 | POST | `/api/submit-financial/<session_id>` | 提交到财务系统 |
| **14** | **GET** | **`/api/agent/state/<session_id>`** | **获取 Agent 会话状态** |
| **15** | **POST** | **`/api/agent/process/<session_id>`** | **启动 Agent 多轮处理(主入口)** |
| **16** | **POST** | **`/api/agent/supplement/<session_id>`** | **补充文件后重新分析** |
| **17** | **POST** | **`/api/agent/user-supplement/<session_id>`** | **通过文字补充信息** |
| **18** | **POST** | **`/api/agent/force-submit/<session_id>`** | **强制提交,跳过校验** |
> 加粗条目为 Agent 多轮校验流程新增接口。
### 入口选择建议
- **推荐使用** `/api/agent/process`完整流程包含发票提取、LLM 信息校验、自动提交财务系统。
- **仅发票提取** `/api/process`:跳过 Agent 校验,只做文档解析和发票分类。适合调试发票提取本身,或仅需导出 CSV 的场景。
---
## 会话与目录
- 调用 `POST /api/session` 获得 `session_id`
- 该会话下所有文件存放在 `src/web/uploads/<session_id>/`
- 典型产物:`invoice_summary.csv``payment_records.csv``易耗品、出库单.doc``config.json``session.log``result.json``agent_state.json``agent_events.log``file_events.log``llm_stream.log`
---
## 接口详情
### 1. 创建会话
```
POST /api/session
```
**响应:**
```json
{ "session_id": "a1b2c3d4e5f6" }
```
---
### 2. 上传文件PDF/图片)
```
POST /api/upload/<session_id>
Content-Type: multipart/form-data
```
| 字段 | 类型 | 说明 |
|------|------|------|
| file | File | PDF 发票或支付截图 |
**响应(成功):**
```json
{ "ok": true, "filename": "1. 电容一批.pdf" }
```
**响应(失败):**
```json
{ "error": "未选择文件" }
```
HTTP `400`
---
### 3. 列出会话文件
```
GET /api/files/<session_id>
```
**响应:**
```json
{
"files": [
{ "name": "1. 电容一批.pdf", "type": "pdf", "size": 12345 },
{ "name": "payment_01.jpg", "type": "image", "size": 67890 }
],
"pdfs": ["1. 电容一批.pdf"],
"images": ["payment_01.jpg"]
}
```
`images` 包含扩展名:`.png``.jpg``.jpeg``.bmp``.webp`
---
### 4. 下载文件
```
GET /api/download/<session_id>/<filename>
```
**响应:** 文件二进制流,带 `Content-Disposition: attachment` 与 UTF-8 文件名。
| 扩展名 | Content-Type |
|--------|----------------|
| `.csv` | `text/csv; charset=utf-8` |
| `.doc` | `application/msword` |
| 其它 | `application/octet-stream` |
**常见文件名:**
| 文件名 | 说明 |
|--------|------|
| `invoice_summary.csv` | 发票汇总 |
| `payment_records.csv` | 支付记录 |
| `易耗品、出库单.doc` | 自动填写的出库单 |
| `travel_applications.json` | 差旅申请信息 |
| `result.json` | 处理结果 |
| `agent_state.json` | Agent 会话状态 |
**错误:**
```json
{ "error": "文件不存在" }
```
HTTP `404``filename` 仅允许会话目录内的文件名(防止路径穿越)。
---
### 5. 移动端上传页面
```
GET /mobile/<session_id>
```
返回移动端 HTML 页面。
---
### 6. 移动端上传图片
```
POST /api/mobile-upload/<session_id>
Content-Type: multipart/form-data
```
| 字段 | 类型 | 说明 |
|------|------|------|
| file | File | 图片文件 |
逻辑与 `POST /api/upload/<session_id>` 相同。
---
### 7. 获取会话配置
```
GET /api/config/<session_id>
```
获取当前会话的配置,供前端回填表单。优先读取会话目录下的 `config.json`,未找到则使用项目全局配置。
**响应:**
```json
{
"username": "",
"password": "",
"default_name": "",
"default_card_no": "",
"default_person_id": "",
"consumable_storage": ""
}
```
注意:`password` 字段始终返回空字符串。
---
### 8. 获取发票数据
```
GET /api/data/<session_id>
```
**响应:**
```json
{
"csv_filename": "payment_records.csv",
"fields": [
"序号", "发票号码", "开票日期", "项目名称", "规格型号",
"价税合计", "销售方名称", "人员姓名", "刷卡日期",
"公务卡号", "刷卡金额", "备注", "工号"
],
"data": [
{
"__row": 0,
"序号": "1",
"发票号码": "26442000005432755951",
"价税合计": "2900.00"
}
]
}
```
读取优先级:`payment_records.csv``invoice_summary.csv` → 任意 `.csv` 文件。
- `fields`:列顺序
- `data[].__row`:内部行索引(保存时不需要提交,服务端按数组顺序写回)
**错误:** `404` 未找到 CSV`500` 读取失败。
---
### 9. 保存编辑后的发票数据
```
POST /api/save/<session_id>
Content-Type: application/json
```
**请求体:**
```json
{
"csv_filename": "invoice_summary.csv",
"data": [
{
"序号": "1",
"发票号码": "26442000005432755951",
"开票日期": "2026/05/18",
"项目名称": "...",
"规格型号": "...",
"价税合计": "2900.00",
"销售方名称": "...",
"人员姓名": "",
"刷卡日期": "2026/04/28",
"公务卡号": "",
"刷卡金额": "2850.00",
"备注": "",
"工号": "202407021"
}
]
}
```
**响应(成功):**
```json
{
"ok": true,
"doc_ok": true,
"doc_url": "/api/download/<session_id>/%E6%98%93%E8%80%97%E5%93%81%E3%80%81%E5%87%BA%E5%BA%93%E5%8D%95.doc"
}
```
保存后会根据最新 CSV **重新生成** 出库单 Word与会话 `config.json` 中的 `consumable_storage` 等配置一致)。
**纯差旅发票跳过出库单:**
```json
{
"ok": true,
"doc_ok": null,
"doc_skipped": true
}
```
**出库单生成失败:**
```json
{
"ok": true,
"doc_ok": false,
"doc_error": "出库单模板不存在,请将模板放在项目根目录"
}
```
---
### 10. 启动管道处理(仅发票提取)
```
POST /api/process/<session_id>
Content-Type: application/json
```
> 此接口仅执行发票提取和 LLM 识别,**不会**触发 Agent 多轮校验,也**不会**自动提交到财务系统。如需完整的 Agent 校验流程,请使用 `/api/agent/process`。
**请求体:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 否 | 财务系统工号 |
| password | string | 否 | 登录密码 |
| default_name | string | 否 | 默认报销人姓名 |
| default_card_no | string | 否 | 默认公务卡号 |
| default_person_id | string | 否 | 默认人员编号 |
| consumable_storage | string | 否 | 出库单存放地点 |
**处理内容:**
1. 从会话目录 PDF 提取发票信息 → `invoice_summary.csv`
2. 对支付截图多模态 LLM 识别,回填刷卡字段
3. 根据发票类型自动分类:差旅发票(高铁票/酒店住宿)不生成出库单;普通发票从模板复制并自动填写
配置会写入 `src/web/uploads/<session_id>/config.json`
**响应(立即):**
```json
{ "status": "started" }
```
处理在后台线程执行,进度与结果通过 `GET /api/logs/<session_id>`SSE获取。
**SSE 完成时 `result` 示例(成功):**
```json
{
"ok": true,
"elapsed": "45.2s",
"invoice_count": 4,
"csv_url": "/api/download/<session_id>/invoice_summary.csv",
"travel_count": 2,
"general_count": 2,
"doc_url": "/api/download/<session_id>/%E6%98%93%E8%80%97%E5%93%81%E3%80%81%E5%87%BA%E5%BA%93%E5%8D%95.doc",
"doc_ok": true
}
```
---
### 11. SSE 日志流
```
GET /api/logs/<session_id>
Accept: text/event-stream
```
每 0.5 秒轮询 4 个日志文件,通过文件 size 增量检测新内容:
| 文件 | 内容 |
|------|------|
| `session.log` | 普通日志extractor、llm_extractor、matcher、pipeline、bot、agent、validator 等模块) |
| `file_events.log` | 文件处理进度事件 |
| `llm_stream.log` | LLM 流式输出 |
| `agent_events.log` | Agent 调度事件 |
检测到 `result.json` 存在时,读取后发送 `done` 事件并断开连接。
**SSE 超时:** 900 秒。
---
### 12. 提交到财务系统
```
POST /api/submit-financial/<session_id>
```
**前置条件:**
- 会话目录存在 `config.json`,否则返回 `400`
- 存在可用的发票 CSV通常为 `invoice_summary.csv``payment_records.csv`
**说明:**
- 前端一般在提交前调用 `/api/save` 保存表格修改
- 根据发票类型选择填报模式:纯差旅发票走差旅报销流程,含普通发票走普通报销流程
**响应(立即):**
```json
{ "status": "started" }
```
**SSE 完成示例:**
```json
{
"type": "done",
"result": {
"ok": true,
"submit_ok": true
}
}
```
失败时 `submit_ok: false``submit_error` 为错误描述。
---
## Agent 多轮校验流程
Agent 是系统的调度中枢,负责编排信息提取、规则校验、补充材料请求的完整流程。推荐使用 `/api/agent/process` 作为主入口。
### Agent 状态机
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> EXTRACTING: 启动处理
EXTRACTING --> READY: can_submit == true
EXTRACTING --> AWAITING_SUPPLEMENT: can_submit == false
EXTRACTING --> ERROR: 异常 / 轮次超限
READY --> SUBMITTING: _emit_ready_and_submit()
SUBMITTING --> DONE: 财务提交完成
AWAITING_SUPPLEMENT --> EXTRACTING: 用户补充文件/文字
AWAITING_SUPPLEMENT --> READY: 用户强制提交
note right of EXTRACTING
LLM 提取 + validator 校验\n最多 3 次重试
end note
```
### 13. 获取 Agent 会话状态
```
GET /api/agent/state/<session_id>
```
**响应:**
```json
{
"session_id": "a1b2c3d4e5f6",
"state": "extracting",
"rounds": 1,
"max_rounds": 5,
"invoice_type": "travel",
"extracted_info": { ... },
"validation_reports": [ ... ],
"user_supplements": [ ... ],
"error_message": ""
}
```
**状态值:**
| 状态 | 含义 |
|------|------|
| `idle` | 初始状态 |
| `extracting` | LLM 正在分析文件 |
| `awaiting_supplement` | 信息不完整,等待用户补充 |
| `ready` | 信息完整,可以提交 |
| `submitting` | 正在提交到财务系统 |
| `done` | 流程结束 |
| `error` | 出错 |
---
### 14. 启动 Agent 多轮处理(主入口)
```
POST /api/agent/process/<session_id>
Content-Type: application/json
```
**请求体:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 否 | 财务系统工号 |
| password | string | 否 | 登录密码 |
| default_name | string | 否 | 默认报销人姓名 |
| default_card_no | string | 否 | 默认公务卡号 |
| default_person_id | string | 否 | 默认人员编号 |
| consumable_storage | string | 否 | 出库单存放地点 |
**处理流程:**
1. 发票提取(同 `/api/process`
2. Agent 调度 LLM 分析提取结果
3. validator 规则校验(最多 3 次校验-修正循环)
4. LLM 语义判断信息完整性(`can_submit` 字段)
5. 校验通过 → 自动提交到财务系统
6. 校验未通过 → 等待用户补充材料
**响应(立即):**
```json
{ "status": "started" }
```
**SSE done 事件 - 信息完整(成功提交):**
```json
{
"type": "done",
"result": {
"ok": true,
"agent_ready": true,
"submit_ok": true,
"round": 1,
"message": "信息完整,已自动提交到财务系统"
}
}
```
**SSE done 事件 - 信息完整但提交失败:**
```json
{
"type": "done",
"result": {
"ok": true,
"agent_ready": true,
"submit_ok": false,
"submit_error": "提交失败原因",
"round": 1,
"message": "校验通过但提交失败"
}
}
```
**SSE done 事件 - 需补充材料:**
```json
{
"type": "done",
"result": {
"ok": true,
"agent_ready": false,
"agent_state": "awaiting_supplement",
"round": 1,
"waiting_for_supplement": true
}
}
```
**SSE done 事件 - 处理失败:**
```json
{
"type": "done",
"result": {
"ok": false,
"error": "未提取到任何发票数据"
}
}
```
---
### 15. 补充文件后重新分析
```
POST /api/agent/supplement/<session_id>
Content-Type: application/json
```
在 Agent 请求补充材料后,用户上传新文件并调用此接口触发重新分析。
**请求体:**
```json
{
"files": ["补充材料1.pdf", "补充材料2.jpg"]
}
```
**处理流程:**
1. 记录补充文件
2. 重新提取所有发票(包含新文件)
3. 加载上一轮分析结果作为历史上下文
4. 重新执行 Agent 校验
**响应(立即):**
```json
{ "status": "started" }
```
**SSE done 事件:** 同上(可能仍需补充或校验通过自动提交)。
**错误:**
```json
{ "error": "未找到 Agent 状态" }
```
HTTP `404`(未先调用 `/api/agent/process` 或 Agent 状态已丢失)。
---
### 16. 通过文字补充信息
```
POST /api/agent/user-supplement/<session_id>
Content-Type: application/json
```
用户通过对话方式提供补充信息LLM 解析后更新已提取的信息并重新校验。
**请求体:**
```json
{
"text": "报销人是张三,公务卡号是 6228480402564890001"
}
```
**处理流程:**
1. LLM 分析用户文字,提取需要更新的字段
2. 合并到已提取的信息中
3. 保存到缓存
4. 重新执行 Agent 校验
**响应(立即):**
```json
{ "status": "started" }
```
**SSE done 事件:** 同上(可能仍需补充或校验通过自动提交)。
**错误:**
```json
{ "error": "请输入补充信息" }
```
HTTP `400`(文本为空)。
---
### 17. 强制提交,跳过校验
```
POST /api/agent/force-submit/<session_id>
```
当 Agent 校验未通过或出错时,用户可选择强制提交,跳过所有校验直接提交到财务系统。
**处理流程:**
1. 将 Agent 状态设为 `READY`
2. 执行财务提交
**响应(立即):**
```json
{ "status": "started" }
```
**SSE done 事件:**
```json
{
"type": "done",
"result": {
"ok": true,
"submit_ok": true,
"submit_error": null
}
}
```
---
## SSE 事件类型
### Agent 事件
通过 `agent_events.log` 轮询推送:
| 事件类型 | 数据结构 | 触发条件 |
|---|---|---|
| `agent_state_change` | `{type, state, round, attempt, message}` | 状态切换 |
| `agent_ready` | `{type, round, message}` | 双重校验通过 |
| `agent_request_supplement` | `{type, round, missing_fields, missing_materials, semantic_issues, suggestion}` | 校验未通过 |
| `agent_supplement_received` | `{type, files}` | 收到用户补充 |
| `agent_force_submit` | `{type, message}` | 用户强制提交 |
| `agent_extract_status` | `{type, state, round, attempt, message}` | 校验-修正循环中的每次尝试结果 |
| `agent_error` | `{type, message}` | 提取失败 |
| `agent_max_rounds` | `{type, message}` | 达到最大轮次 |
### 文件进度事件
通过 `file_events.log` 轮询推送:
| 事件类型 | 数据结构 | 触发条件 |
|---|---|---|
| `file_progress` | `{type, file, status, summary?, error?}` | 文件处理状态变更 |
`status` 取值: `processing` / `done` / `cached` / `error`
### LLM 流式事件
通过 `llm_stream.log` 轮询推送:
| 事件类型 | 数据结构 | 触发条件 |
|---|---|---|
| `llm_stream` | `{type, phase, text?}` | LLM 输出流 |
`phase` 取值: `start` / `reasoning` / `chunk` / `end` / `error`
### 完成事件
SSE 检测到 `result.json` 后直接发送:
```json
{
"type": "done",
"result": { ... }
}
```
---
## 错误码汇总
| HTTP | 场景 |
|------|------|
| 400 | 参数缺失、未找到配置、文本为空等 |
| 404 | `session_id` 不存在、文件不存在、Agent 状态丢失 |
| 500 | CSV 读取失败、服务未初始化等内部错误 |
统一错误体:
```json
{ "error": "错误描述" }
```
---
## 发票类型分类
系统自动将发票分为两类,影响出库单生成和后续报销流程:
| 类型 | 判断依据 | 出库单 | 报销流程 |
|------|----------|--------|----------|
| 差旅发票 | 高铁票、酒店住宿等 | 不生成 | 差旅报销 |
| 普通发票 | 其他(办公用品、耗材等) | 自动生成 | 普通报销 |
`/api/process``/api/save` 的响应中 `travel_count` / `general_count` 即为分类统计。
---
## 端到端流程
```mermaid
sequenceDiagram
participant F as 前端
participant S as SSE连接
participant B as 后端线程
participant A as Agent调度器
F->>B: POST /api/session
B-->>F: session_id
F->>B: POST /api/upload/{sid} (多次)
F->>B: POST /api/agent/process/{sid}
B-->>F: {status: "started"}
F->>S: GET /api/logs/{sid}
Note over B,A: 后台线程启动
B->>A: extract_invoices()
S-->>F: file_progress (processing/done)
S-->>F: llm_stream (start/chunk/end)
S-->>F: agent_state_change (extracting)
Note over A: LLM 提取 + validator 校验<br/>最多 3 次重试
alt 信息完整
A->>A: state → READY
A->>A: _emit_ready_and_submit()
S-->>F: agent_ready
S-->>F: done (submit_ok=true)
F->>F: es.close()
else 信息不完整
A->>A: state → AWAITING_SUPPLEMENT
S-->>F: agent_request_supplement
S-->>F: done (waiting_for_supplement=true)
F->>F: es.close()
F->>B: 上传补充文件或输入文字
alt 文件补充
F->>B: POST /api/agent/supplement/{sid}
else 文字补充
F->>B: POST /api/agent/user-supplement/{sid}
end
B-->>F: {status: "started"}
F->>S: GET /api/logs/{sid}
Note over A: 重新分析 + 校验
alt 仍不完整
S-->>F: agent_request_supplement
S-->>F: done (waiting=true)
F->>F: es.close()
Note over F: 可继续补充或强制提交
else 完整
A->>A: state → READY
A->>A: _emit_ready_and_submit()
S-->>F: agent_ready
S-->>F: done (submit_ok=true)
F->>F: es.close()
end
alt 强制提交
F->>B: POST /api/agent/force-submit/{sid}
B-->>F: {status: "started"}
F->>S: GET /api/logs/{sid}
S-->>F: agent_force_submit
S-->>F: done
F->>F: es.close()
end
end
F->>B: GET /api/data/{sid}
B-->>F: fields + data
F->>B: POST /api/save/{sid}
Note over B: 更新 CSV重新生成出库单
B-->>F: doc_url
F->>B: GET /api/download/{sid}/易耗品、出库单.doc
```
---
## 相关 CLI
不经过 Web、在本地直接填写出库单
```bash
uv run python -m src.infra.documents.consumable --csv invoice_summary.csv --doc "易耗品、出库单.doc"
```
详见 [README.md](./README.md)。