# 项目架构全景图 > 最后更新: 2026-06-15 > 用途: 理解项目整体结构、模块职责、依赖关系和数据流 --- ## 一、分层架构总览 ``` src/ ├── agent/ Agent 调度层(协调提取-校验-修正循环,状态机管理) ├── core/ 核心业务层(纯逻辑,零框架依赖) ├── infra/ 基础设施层(浏览器、文档、LLM 提示词) ├── web/ Web 界面层(Flask + SSE) ├── pipeline.py CLI 流程编排 ├── pipeline_core.py CLI/Web 公共管道逻辑 ├── main.py CLI 入口 ├── config.py 配置加载 └── exceptions.py 异常定义 ``` ### 依赖方向 ```mermaid graph TD classDef entry fill:#e8eaf6,stroke:#3f51b5,color:#1a237e classDef orchestrate fill:#e0f2f1,stroke:#00897b,color:#004d40 classDef agent fill:#fff8e1,stroke:#ff8f00,color:#3e2723 classDef core fill:#e3f2fd,stroke:#1565c0,color:#0d47a1 classDef infra fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20 subgraph 入口层 CLI["main.py"]:::entry WEB["web/app.py"]:::entry end subgraph 编排层 PIPE["pipeline.py"]:::orchestrate PIPE_WEB["web/pipeline_web.py"]:::orchestrate PIPE_CORE["pipeline_core.py"]:::orchestrate end subgraph Agent调度层 AGENT["agent/orchestrator.py"]:::agent SESSION["agent/session.py"]:::agent EVENTS["agent/events.py"]:::agent end subgraph 核心业务层 EXTRACT["core/extraction/"]:::core MATCH["core/matching/"]:::core VALID["core/validation/"]:::core end subgraph 基础设施层 BROWSER["infra/browser/"]:::infra DOCS["infra/documents/"]:::infra LLM["infra/llm/"]:::infra end CLI --> PIPE WEB --> PIPE_WEB PIPE --> PIPE_CORE PIPE --> EXTRACT PIPE --> BROWSER PIPE_WEB --> AGENT PIPE_WEB --> PIPE_CORE PIPE_WEB --> EXTRACT AGENT --> EXTRACT AGENT --> VALID AGENT --> LLM AGENT --> PIPE_CORE EXTRACT --> MATCH EXTRACT --> DOCS EXTRACT --> LLM MATCH --> DOCS BROWSER --> DOCS ``` **关键约束**: - `infra` 不依赖 `core` 和 `agent`,只提供工具能力 - `core` 零外部依赖,不依赖 Flask、Playwright 等框架 - `agent` 依赖 `core` 和 `infra`,作为调度中枢编排各模块 - 所有跨层调用均通过 `__init__.py` 导出的稳定接口 --- ## 二、模块清单 ### 2.1 Agent 调度层 (`src/agent/`) | 文件 | 职责 | |------|------| | `coordinator.py` | 核心协调逻辑:提取-校验-修正循环(最多 3 次重试)、用户补充处理、强制提交 | | `session.py` | 会话状态:`AgentState` 枚举、`AgentSession` 数据类、状态持久化(原子写入) | | `events.py` | SSE 事件发射:事件去重、事件日志追加、事件读取 | | `orchestrator.py` | 兼容层:从子模块重新导出所有符号,保持旧导入路径可用 | **对外接口**:`AgentSession`, `AgentState`, `run_agent_round()`, `force_submit()`, `add_supplement()`, `process_user_text_supplement()`, `load_agent_state()`, `save_agent_state()` ### 2.2 核心业务层 (`src/core/`) | 子模块 | 职责 | 对外接口 | |------|------|------| | `extraction/extractor.py` | 编排入口:扫描目录 → 逐文件提取 → 分类 → 金额匹配 | `extract_invoices()`, `extract_document()` | | `extraction/llm_extractor.py` | LLM 多模态提取核心:统一文档提取、差旅/普通信息提取、缓存管理、SSE 流式事件 | `llm_query_text()`, `extract_travel_info()`, `extract_normal_info()`, `load_cache()` | | `matching/matcher.py` | 发票与支付记录按金额匹配(一对一 / 一对多贪心,相对容差 3%) | `match_invoices_to_cards()` | | `validation/validator.py` | 声明式规则校验引擎,规则从 JSON 配置文件加载 | `validate_extracted_info()`, `ValidationReport` | ### 2.3 基础设施层 (`src/infra/`) | 子模块 | 职责 | 对外接口 | |------|------|------| | `browser/base.py` | `BaseBot` 基类:Playwright 浏览器生命周期、登录、导航、截图 | 内部基类 | | `browser/travel.py` | 差旅报销填报:基本信息 → 明细 → 支付 → 补助 → 附件上传 | 内部流程 | | `browser/normal.py` | 普通报销填报:基本信息 → 总明细 → 支付 → 附件上传 | 内部流程 | | `browser/__init__.py` | 浏览器入口:类型路由和流程调度 | `run_bot()`, `run_bot_web()` | | `documents/invoice.py` | 发票数据模型、CSV/JSON 读写、发票分类 | `load_csv()`, `save_csv()`, `save_invoice_csv()`, `classify_invoice_batch()` | | `documents/pdf.py` | PDF 渲染为图片(PyMuPDF) | `render_pdf_to_images()` | | `documents/consumable.py` | 易耗品出库单填写:CSV → Word 模板 | `fill_consumable_doc()` | | `llm/prompt.py` | LLM 提示词加载 | `build_invoice_system_prompt()`, `build_travel_info_system_prompt()`, `build_normal_info_system_prompt()` | ### 2.4 Web 界面层 (`src/web/`) | 文件/目录 | 职责 | |------|------| | `app.py` | Flask 应用入口,注册蓝图和模板 | | `routes.py` | 路由定义:会话管理、文件上传、配置、SSE 日志流、Agent 交互 API | | `pipeline_web.py` | Web 管道逻辑:发票提取 + 出库单生成 + 财务提交 | | `sse_handler.py` | SSE 日志收集器、日志转义、文件轮询 | | `templates/` | `index.html`(PC 端主界面)、`mobile_upload.html`(移动端上传) | | `static/js/` | 前端逻辑(按加载顺序):`state.js` → `utils.js` → `chat.js` → `upload.js` → `config.js` → `process.js` → `sync.js` → `index.js` | --- ## 三、CLI 模式数据流 ```mermaid graph TD CLI_ENTRY["main.py --step all"] --> PIPE["pipeline.py run_pipeline()"] subgraph Step1["Step 1: 发票提取"] PIPE --> EXT["core/extraction/extractor.py extract_invoices()"] EXT --> DOC["逐文件提取"] DOC --> LLM["LLM 多模态识别 (infra/llm)"] LLM --> CLASS["分类: train/hotel/general/payment/application"] CLASS --> MATCH["core/matching/matcher.py 金额匹配"] MATCH --> SAVE["infra/documents/ CSV/JSON 保存"] end subgraph Step2["Step 2: 信息提取"] SAVE --> TYPE{"判断报销类型"} TYPE -->|差旅| TRAVEL["提取差旅信息 → travel_info.json"] TYPE -->|普通| NORMAL["提取普通发票信息 → normal_info.json"] end subgraph Step3["Step 3: 浏览器填报"] TRAVEL --> BOT["infra/browser/ 填报"] NORMAL --> BOT BOT -->|差旅| BOT_T["browser/travel.py"] BOT -->|普通| BOT_N["browser/normal.py"] end ``` **关键文件输出**: | 文件 | 来源 | 说明 | |------|------|------| | `payment_records.csv` | Step 1 | 支付记录级别(每笔刷卡记录一行) | | `invoice_summary.csv` | Step 1 | 发票级别(每张发票一行) | | `travel_applications.json` | Step 1 | 出差事前申请单 | | `invoice_groups.json` | Step 1 | 发票分类结果 | | `travel_info.json` | Step 2 | 差旅信息:交通/住宿明细、补贴、附件清单 | | `normal_info.json` | Step 2 | 普通发票信息:报销说明、发票总数、总金额、附件清单 | --- ## 四、Web 模式数据流 ```mermaid sequenceDiagram participant F as 前端 (浏览器) participant API as routes.py participant PW as pipeline_web.py participant AG as agent/orchestrator.py participant EX as core/extraction/ participant VA as core/validation/ participant SSE as SSE 轮询 F->>API: POST /api/session → 创建 session F->>API: POST /api/upload/:sid → 上传文件 F->>API: POST /api/agent/process/:sid API-->>F: {status: "started"} F->>SSE: GET /api/logs/:sid (SSE 长连接) Note over API: 后台 daemon 线程启动 API->>PW: extract_invoices(session_dir) PW->>EX: 发票提取 + 分类 + 匹配 EX-->>PW: payment_records, applications, groups API->>AG: run_agent_round(session_dir, session) loop 校验-修正循环 (最多 3 次) AG->>EX: llm_query_text() 提取信息 AG->>VA: validate_extracted_info() 规则校验 alt 校验失败 AG->>AG: 构建修正提示 end end AG-->>API: session (READY 或 AWAITING_SUPPLEMENT) SSE-->>F: file_progress, llm_stream, agent_state_change, agent_ready/agent_request_supplement API->>API: 写入 result.json SSE-->>F: done (携带 result) F->>F: 关闭 SSE, 展示结果 ``` ### Web 模式特有的 Agent 调度 CLI 模式中 `pipeline.py` 直接调用 `extract_invoices()` → `infra/browser/`,不经过 Agent 层。 Web 模式中 `routes.py` 启动后台线程,调用 `agent/orchestrator.py` 作为调度中枢: ``` run_agent_round() ├── 1. load_cache() — 检查缓存 ├── 2. _do_extraction_with_validation() — 提取-校验-修正循环 │ ├── llm_query_text() — LLM 提取结构化信息 │ ├── validate_extracted_info() — 规则校验 │ └── 校验失败 → 构建修正提示 → 再次调用 LLM (最多 3 次) ├── 3. 判断 can_submit 字段 │ ├── true → READY → 自动触发财务提交 │ └── false → AWAITING_SUPPLEMENT → 等待用户补充 ├── 4. 用户补充处理 │ ├── add_supplement() — 记录补充文件 │ └── process_user_text_supplement() — LLM 解析文字补充 └── 5. save_agent_state() — 持久化状态 ``` --- ## 五、Agent 状态机 ```mermaid stateDiagram-v2 [*] --> IDLE: 会话创建 IDLE --> EXTRACTING: POST /api/agent/process IDLE --> EXTRACTING: POST /api/agent/supplement IDLE --> EXTRACTING: POST /api/agent/user-supplement 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 校验 最多 3 次重试 end note ``` ### 终态保护 以下状态为终态,再次触发 `run_agent_round()` 会被跳过: - `DONE` — 提交完成 - `SUBMITTING` — 提交中 - `READY` — 准备提交 ### 轮次保护 默认最多 5 轮(`AgentSession.max_rounds`),超限后进入 `ERROR` 状态,用户可选择强制提交。 --- ## 六、SSE 事件通信机制 ```mermaid graph LR subgraph 后端写入 AGENT[agent/orchestrator.py] -->|追加写入| AE[agent_events.log] LLM[LLM 回调] -->|追加写入| LS[llm_stream.log] PW[pipeline_web.py] -->|追加写入| FE[file_events.log] SH[sse_handler.py] -->|追加写入| SL[session.log] RT[_run_agent_task] -->|finally 原子写入| RJ[result.json] end subgraph SSE 轮询 (0.5s) POLL[SSE 端点] -->|读取| AE POLL -->|读取| LS POLL -->|读取| FE POLL -->|读取| SL POLL -->|检测| RJ end POLL -->|event: agent_*| FRONT[前端 agent.js] POLL -->|event: llm_stream| FRONT POLL -->|event: file_progress| FRONT POLL -->|event: done| FRONT ``` ### 信号文件生命周期 | 阶段 | `result.json` | `llm_stream.log` | `agent_events.log` | `file_events.log` | `session.log` | |------|:--:|:--:|:--:|:--:|:--:| | 会话创建 | 不存在 | 不存在 | 不存在 | 不存在 | 不存在 | | 后台线程启动 | 已删除 | 已删除 | 已删除 | 保持 | 保持 | | 文件提取中 | 不存在 | 不存在 | 不存在 | 持续追加 | 持续追加 | | LLM 提取中 | 不存在 | 持续追加 | 持续追加 | 保持 | 持续追加 | | 校验中 | 不存在 | 保持 | 持续追加 | 保持 | 持续追加 | | 任务完成 | 已写入 | 保持 | 保持 | 保持 | 保持 | | SSE done 事件 | 保持 | 保持 | 保持 | 保持 | 保持 | --- ## 七、发票类型路由 ```mermaid graph TD INPUT["上传文件 (PDF/图片)"] --> EXT["LLM 多模态识别"] EXT --> TYPE{"invoice_type?"} TYPE -->|train| TRAVEL["差旅报销流程"] TYPE -->|hotel| TRAVEL TYPE -->|general| NORMAL["普通报销流程"] TYPE -->|payment| MATCH["参与金额匹配"] TYPE -->|application| APP["存储为 JSON"] TRAVEL --> TRAVEL_INFO["提取差旅信息
travel_info.json"] TRAVEL_INFO --> TRAVEL_BOT["browser/travel.py
填报差旅报销单"] NORMAL --> NORMAL_INFO["提取普通发票信息
normal_info.json"] NORMAL_INFO --> NORMAL_BOT["browser/normal.py
填报普通报销单"] NORMAL_INFO --> CONSUMABLE["生成易耗品出库单
(仅普通报销)"] MATCH --> MERGE["合并到对应发票组"] style TRAVEL fill:#cfe2ff,stroke:#0d6efd style NORMAL fill:#f8d7da,stroke:#dc3545 style MATCH fill:#d1e7dd,stroke:#198754 style APP fill:#fff3cd,stroke:#ffc107 ``` | 发票类型 | `invoice_type` | 报销流程 | 生成出库单 | |----------|---------------|---------|:--:| | 高铁票/火车票 | `train` | 差旅报销 | 否 | | 酒店住宿 | `hotel` | 差旅报销 | 否 | | 普通发票 | `general` | 普通报销 | 是 | | 支付记录 | `payment` | 参与匹配 | 否 | | 出差申请单 | `application` | 单独存储 | 否 | > 差旅发票和普通发票不支持混报,混合时系统按普通报销处理。 --- ## 八、设计原则 | 原则 | 说明 | |------|------| | **Agent 是调度中枢** | 校验-修正循环由 Agent 编排,不内嵌在 `llm_extractor` 中 | | **模块职责单一** | `llm_extractor` 只管提取,`validator` 只管校验,Agent 负责编排 | | **core 零外部依赖** | 不依赖 Flask、Playwright 等框架 | | **infra 不依赖业务** | 基础设施层只提供工具能力,不包含业务逻辑 | | **缓存优先** | 信息提取优先读取 `.invoice_cache`,避免重复调用 LLM | | **轮次保护** | 默认 5 轮上限,校验-修正循环最多重试 3 次 | | **终态保护** | `DONE`/`SUBMITTING`/`READY` 状态下不再重复处理 | | **容错降级** | 规则校验 3 次重试后返回最佳结果,不阻断流程 | | **原子写入** | 状态文件先写 `.tmp` 再 `rename()`,防止读取不完整数据 | --- ## 九、关键文件索引 | 文件 | 职责 | |------|------| | `src/main.py` | CLI 入口 | | `src/web/app.py` | Web 入口 | | `src/pipeline.py` | CLI 流程编排 | | `src/pipeline_core.py` | CLI/Web 公共管道逻辑 | | `src/web/pipeline_web.py` | Web 管道逻辑 + 财务提交 | | `src/web/routes.py` | Web 路由 + 后台线程启动 | | `src/agent/coordinator.py` | Agent 核心协调逻辑 | | `src/agent/session.py` | 会话状态定义与持久化 | | `src/agent/events.py` | SSE 事件发射 | | `src/core/extraction/extractor.py` | 发票提取编排入口 | | `src/core/extraction/llm_extractor.py` | LLM 多模态提取核心 | | `src/core/matching/matcher.py` | 金额匹配 | | `src/core/validation/validator.py` | 声明式规则校验 | | `src/infra/browser/base.py` | 浏览器自动化基类 | | `src/infra/documents/invoice.py` | 发票数据模型 | | `src/web/sse_handler.py` | SSE 日志收集器 | | `src/web/static/js/process.js` | 前端主提交流程 | | `src/web/static/js/agent.js` | 前端 Agent 交互处理 | | `config.json` | 项目配置 |