Files
Auto-Finance/.agents/docs/guides/项目架构全景图.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

402 lines
15 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.
# 项目架构全景图
> 最后更新: 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["提取差旅信息<br/>travel_info.json"]
TRAVEL_INFO --> TRAVEL_BOT["browser/travel.py<br/>填报差旅报销单"]
NORMAL --> NORMAL_INFO["提取普通发票信息<br/>normal_info.json"]
NORMAL_INFO --> NORMAL_BOT["browser/normal.py<br/>填报普通报销单"]
NORMAL_INFO --> CONSUMABLE["生成易耗品出库单<br/>(仅普通报销)"]
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` | 项目配置 |