--- last_reviewed: 2026-06-13 --- # 财务报销自动化 — API 文档 > 基础地址: `http://localhost:5000` > 启动: `uv run python src/web/app.py` ## 总览 | # | 方法 | 路径 | 说明 | |---|------|------|------| | 1 | GET | `/` | PC 端主页 | | 2 | GET | `/mobile/` | 移动端上传页面 | | 3 | POST | `/api/session` | 创建会话 | | 4 | POST | `/api/upload/` | 上传文件(PDF/图片) | | 5 | GET | `/api/files/` | 列出会话目录中的文件 | | 6 | GET | `/api/download//` | 下载生成的文件 | | 7 | POST | `/api/mobile-upload/` | 移动端上传图片 | | 8 | GET | `/api/config/` | 获取会话配置 | | 9 | GET | `/api/data/` | 获取发票数据(JSON) | | 10 | POST | `/api/save/` | 保存编辑后的发票数据 | | 11 | POST | `/api/process/` | 启动管道处理(仅发票提取,不自动提交) | | 12 | GET | `/api/logs/` | SSE 日志流 | | 13 | POST | `/api/submit-financial/` | 提交到财务系统 | | **14** | **GET** | **`/api/agent/state/`** | **获取 Agent 会话状态** | | **15** | **POST** | **`/api/agent/process/`** | **启动 Agent 多轮处理(主入口)** | | **16** | **POST** | **`/api/agent/supplement/`** | **补充文件后重新分析** | | **17** | **POST** | **`/api/agent/user-supplement/`** | **通过文字补充信息** | | **18** | **POST** | **`/api/agent/force-submit/`** | **强制提交,跳过校验** | > 加粗条目为 Agent 多轮校验流程新增接口。 ### 入口选择建议 - **推荐使用** `/api/agent/process`:完整流程,包含发票提取、LLM 信息校验、自动提交财务系统。 - **仅发票提取** `/api/process`:跳过 Agent 校验,只做文档解析和发票分类。适合调试发票提取本身,或仅需导出 CSV 的场景。 --- ## 会话与目录 - 调用 `POST /api/session` 获得 `session_id` - 该会话下所有文件存放在 `src/web/uploads//` - 典型产物:`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/ Content-Type: multipart/form-data ``` | 字段 | 类型 | 说明 | |------|------|------| | file | File | PDF 发票或支付截图 | **响应(成功):** ```json { "ok": true, "filename": "1. 电容一批.pdf" } ``` **响应(失败):** ```json { "error": "未选择文件" } ``` HTTP `400` --- ### 3. 列出会话文件 ``` GET /api/files/ ``` **响应:** ```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// ``` **响应:** 文件二进制流,带 `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/ ``` 返回移动端 HTML 页面。 --- ### 6. 移动端上传图片 ``` POST /api/mobile-upload/ Content-Type: multipart/form-data ``` | 字段 | 类型 | 说明 | |------|------|------| | file | File | 图片文件 | 逻辑与 `POST /api/upload/` 相同。 --- ### 7. 获取会话配置 ``` GET /api/config/ ``` 获取当前会话的配置,供前端回填表单。优先读取会话目录下的 `config.json`,未找到则使用项目全局配置。 **响应:** ```json { "username": "", "password": "", "default_name": "", "default_card_no": "", "default_person_id": "", "consumable_storage": "" } ``` 注意:`password` 字段始终返回空字符串。 --- ### 8. 获取发票数据 ``` GET /api/data/ ``` **响应:** ```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/ 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//%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/ 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//config.json`。 **响应(立即):** ```json { "status": "started" } ``` 处理在后台线程执行,进度与结果通过 `GET /api/logs/`(SSE)获取。 **SSE 完成时 `result` 示例(成功):** ```json { "ok": true, "elapsed": "45.2s", "invoice_count": 4, "csv_url": "/api/download//invoice_summary.csv", "travel_count": 2, "general_count": 2, "doc_url": "/api/download//%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/ 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/ ``` **前置条件:** - 会话目录存在 `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/ ``` **响应:** ```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/ 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/ 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/ 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/ ``` 当 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 校验
最多 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)。