- 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
15 KiB
15 KiB
项目架构全景图
最后更新: 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 异常定义
依赖方向
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 模式数据流
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 模式数据流
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 状态机
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 事件通信机制
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 事件 | 保持 | 保持 | 保持 | 保持 | 保持 |
七、发票类型路由
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 |
项目配置 |