实现Agent对话,合格自动提交,不合格补充材料的能力
This commit is contained in:
506
.agents/docs/plans/系统事件流全景图.md
Normal file
506
.agents/docs/plans/系统事件流全景图.md
Normal file
@@ -0,0 +1,506 @@
|
||||
# 系统事件流全景图
|
||||
|
||||
> 最后更新: 2026-06-13
|
||||
> 用途: 排查 SSE 事件问题、提交流程中断、状态不一致等 Bug
|
||||
|
||||
---
|
||||
|
||||
## 一、核心概念
|
||||
|
||||
### 1.1 前后端状态映射
|
||||
|
||||
| 前端 `App.processState` | 后端 `AgentState` | 含义 |
|
||||
|---|---|---|
|
||||
| `idle` | `IDLE` | 初始状态,等待用户操作 |
|
||||
| `processing` | `EXTRACTING` | LLM 正在分析文件 |
|
||||
| `awaiting_supplement` | `AWAITING_SUPPLEMENT` | 信息不完整,等待用户补充 |
|
||||
| `submitting` | `SUBMITTING` | 正在提交到财务系统 |
|
||||
| `done` | `DONE` / `ERROR` | 流程结束(成功或失败) |
|
||||
|
||||
### 1.2 通信机制
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant S as SSE连接
|
||||
participant B as 后端线程
|
||||
|
||||
F->>B: POST /api/agent/process/:sid
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/:sid (SSE长连接)
|
||||
S-->>F: message: file_progress (轮询 file_events.log)
|
||||
S-->>F: message: llm_stream (轮询 llm_stream.log)
|
||||
S-->>F: message: agent_* (轮询 agent_events.log)
|
||||
S-->>F: message: done (检测到 result.json)
|
||||
S->>S: 连接关闭
|
||||
```
|
||||
|
||||
**关键约束**:
|
||||
- 后端所有处理接口均返回 `{status: "started"}`,实际工作在 daemon 线程中执行
|
||||
- SSE 通过每 0.5 秒轮询 4 个日志文件实现(非原生 SSE,是长轮询模拟)
|
||||
- `result.json` 的原子写入:先写 `.tmp`,再 `replace()` 重命名
|
||||
- SSE 超时:600 秒后自动断开
|
||||
|
||||
---
|
||||
|
||||
## 二、场景一:用户提交材料 → LLM 分析完整 → 直接提交
|
||||
|
||||
### 2.1 时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant S as SSE连接
|
||||
participant B as 后端线程
|
||||
participant A as Agent调度器
|
||||
|
||||
F->>F: startProcess()
|
||||
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)
|
||||
Note over S: 轮询 file_events.log
|
||||
|
||||
S-->>F: llm_stream (start/chunk/end)
|
||||
Note over S: 轮询 llm_stream.log
|
||||
|
||||
S-->>F: agent_state_change (state=extracting)
|
||||
Note over S: 轮询 agent_events.log
|
||||
|
||||
Note over A: _do_extraction_with_validation()<br/>LLM提取 → validator校验<br/>最多3次重试
|
||||
|
||||
S-->>F: agent_state_change (校验通过/未通过)
|
||||
|
||||
Note over A: can_submit == true
|
||||
|
||||
A->>A: state → READY
|
||||
A->>A: _emit_agent_event (agent_ready)
|
||||
A->>A: _emit_ready_and_submit()
|
||||
A->>A: run_financial_submit()
|
||||
|
||||
S-->>F: agent_ready
|
||||
|
||||
Note over B: 写入 result.json
|
||||
|
||||
S-->>F: done (携带 result)
|
||||
Note over S: 检测到 result.json
|
||||
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'done'
|
||||
F->>F: addChatMessage(成功)
|
||||
Note over B: remove_log_collector
|
||||
```
|
||||
|
||||
### 2.2 事件流清单
|
||||
|
||||
| 序号 | 事件类型 | 来源文件 | 触发时机 | 前端处理 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `file_progress` | `file_events.log` | 每个文件处理开始/完成 | 更新文件状态 UI |
|
||||
| 2 | `llm_stream` | `llm_stream.log` | LLM 流式输出 | 显示聊天气泡 |
|
||||
| 3 | `agent_state_change` | `agent_events.log` | 状态变为 `extracting` | 显示瞬态状态提示 |
|
||||
| 4 | `agent_state_change` | `agent_events.log` | 校验通过/未通过 | 更新瞬态状态 |
|
||||
| 5 | `agent_ready` | `agent_events.log` | 双重校验通过 | 由 `done` 事件统一处理 |
|
||||
| 6 | `done` | SSE 检测到 `result.json` | 流程结束 | 根据 `result` 判断终态 |
|
||||
|
||||
### 2.3 result.json 结构(成功路径)
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"agent_ready": true,
|
||||
"submit_ok": true,
|
||||
"round": 1,
|
||||
"message": "信息完整,已自动提交到财务系统"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、场景二:用户提交材料 → 需补充 → 用户上传文件
|
||||
|
||||
### 3.1 时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant S as SSE连接
|
||||
participant B as 后端线程
|
||||
participant A as Agent调度器
|
||||
|
||||
Note over F,A: 阶段1: 初次分析
|
||||
F->>B: POST /api/agent/process/:sid
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/:sid
|
||||
|
||||
S-->>F: agent_state_change (state=extracting)
|
||||
|
||||
Note over A: can_submit == false
|
||||
|
||||
A->>A: state → AWAITING_SUPPLEMENT
|
||||
|
||||
S-->>F: agent_request_supplement
|
||||
|
||||
Note over B: 写入 result.json<br/>(waiting_for_supplement=true)
|
||||
|
||||
S-->>F: done
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'awaiting_supplement'
|
||||
F->>F: showStatus('请补充')
|
||||
F->>F: showAgentRequest()
|
||||
Note over B: remove_log_collector
|
||||
|
||||
Note over F,A: 阶段2: 用户上传补充文件
|
||||
F->>F: 用户点击"上传补充材料"
|
||||
F->>F: 文件上传完成
|
||||
F->>F: handleSupplementUpload(newFilenames)
|
||||
F->>B: POST /api/agent/supplement/:sid<br/>{files: [...]}
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/:sid
|
||||
|
||||
Note over B: 新后台线程启动
|
||||
|
||||
A->>A: add_supplement()<br/>(记录文件名, 发射收到事件)
|
||||
|
||||
S-->>F: agent_supplement_received
|
||||
|
||||
A->>A: extract_invoices()<br/>(重新提取所有文件)
|
||||
A->>A: run_agent_round(new_files=[...])
|
||||
Note over A: 加载上一轮结果作为<br/>previous_analysis
|
||||
|
||||
S-->>F: agent_state_change (state=extracting)
|
||||
|
||||
Note over A: LLM提取 → 校验循环
|
||||
|
||||
alt 分支A: 补充后仍不完整
|
||||
S-->>F: agent_request_supplement
|
||||
S-->>F: done (waiting=true)
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'awaiting_supplement'
|
||||
else 分支B: 补充后完整
|
||||
Note over A: can_submit == true
|
||||
A->>A: state → READY
|
||||
A->>A: _emit_ready_and_submit()
|
||||
S-->>F: agent_ready
|
||||
S-->>F: done (submit_ok=true)
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'done'
|
||||
F->>F: addChatMessage(成功)
|
||||
end
|
||||
```
|
||||
|
||||
### 3.2 事件流清单(补充文件路径)
|
||||
|
||||
| 序号 | 事件类型 | 来源文件 | 触发时机 | 前端处理 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `agent_supplement_received` | `agent_events.log` | 收到补充文件列表 | 显示"已收到补充文件" |
|
||||
| 2 | `agent_state_change` | `agent_events.log` | 开始重新分析 | 显示瞬态状态 |
|
||||
| 3 | `agent_request_supplement` | `agent_events.log` | 仍不完整 | 更新补充请求面板 |
|
||||
| 4 | `agent_ready` | `agent_events.log` | 校验通过 | 由 `done` 统一处理 |
|
||||
| 5 | `done` | SSE 检测到 `result.json` | 流程结束 | 判断终态 |
|
||||
|
||||
### 3.3 result.json 结构(需补充)
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"agent_ready": false,
|
||||
"agent_state": "awaiting_supplement",
|
||||
"round": 1,
|
||||
"waiting_for_supplement": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、场景三:用户提交材料 → 需补充 → 用户通过对话提供信息
|
||||
|
||||
### 4.1 时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant S as SSE连接
|
||||
participant B as 后端线程
|
||||
participant A as Agent调度器
|
||||
|
||||
Note over F,A: 阶段1: 初次分析
|
||||
F->>B: POST /api/agent/process/:sid
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/:sid
|
||||
|
||||
S-->>F: agent_request_supplement
|
||||
S-->>F: done (waiting=true)
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'awaiting_supplement'
|
||||
Note over B: remove_log_collector
|
||||
|
||||
Note over F,A: 阶段2: 用户输入文字
|
||||
F->>F: 用户在输入框输入文字
|
||||
F->>F: handleUserSupplement()
|
||||
F->>B: POST /api/agent/user-supplement/:sid<br/>{text: "..."}
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/:sid
|
||||
|
||||
Note over B: 新后台线程启动
|
||||
|
||||
S-->>F: agent_supplement_received
|
||||
|
||||
A->>A: process_user_text_supplement()
|
||||
A->>A: process_user_supplement()<br/>(LLM解析用户文字)
|
||||
|
||||
S-->>F: llm_stream (解析过程)
|
||||
|
||||
A->>A: merge_supplement_into_info()<br/>(合并到 extracted_info)
|
||||
Note over A: 保存到缓存文件
|
||||
|
||||
A->>A: run_agent_round()<br/>(重新校验)
|
||||
|
||||
S-->>F: agent_state_change<br/>(state=extracting, 正在重新校验)
|
||||
|
||||
alt 分支A: 补充后仍不完整
|
||||
S-->>F: agent_request_supplement
|
||||
S-->>F: done (waiting=true)
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'awaiting_supplement'
|
||||
else 分支B: 补充后完整
|
||||
Note over A: can_submit == true
|
||||
A->>A: state → READY
|
||||
A->>A: _emit_ready_and_submit()
|
||||
S-->>F: agent_ready
|
||||
S-->>F: done (submit_ok=true)
|
||||
F->>F: es.close()
|
||||
F->>F: App.processState = 'done'
|
||||
F->>F: addChatMessage(成功)
|
||||
end
|
||||
```
|
||||
|
||||
### 4.2 事件流清单(文字补充路径)
|
||||
|
||||
| 序号 | 事件类型 | 来源文件 | 触发时机 | 前端处理 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `agent_supplement_received` | `agent_events.log` | 收到用户文字 | 显示"已收到补充" |
|
||||
| 2 | `llm_stream` | `llm_stream.log` | LLM 解析用户文字 | 显示解析过程 |
|
||||
| 3 | `agent_state_change` | `agent_events.log` | 开始重新校验 | 显示"正在重新校验" |
|
||||
| 4 | `agent_request_supplement` | `agent_events.log` | 仍不完整 | 更新补充请求 |
|
||||
| 5 | `agent_ready` | `agent_events.log` | 校验通过 | 由 `done` 统一处理 |
|
||||
| 6 | `done` | SSE 检测到 `result.json` | 流程结束 | 判断终态 |
|
||||
|
||||
---
|
||||
|
||||
## 五、强制提交流程
|
||||
|
||||
### 5.1 时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as 前端
|
||||
participant S as SSE连接
|
||||
participant B as 后端线程
|
||||
|
||||
F->>F: handleForceSubmit()
|
||||
F->>F: App.forceSubmitting = true
|
||||
F->>B: POST /api/agent/force-submit/:sid
|
||||
B-->>F: {status: "started"}
|
||||
F->>S: GET /api/logs/:sid
|
||||
|
||||
Note over B: 后台线程启动
|
||||
|
||||
B->>B: force_submit()<br/>(state → READY)
|
||||
|
||||
S-->>F: agent_force_submit
|
||||
|
||||
B->>B: run_financial_submit()
|
||||
|
||||
Note over B: 写入 result.json
|
||||
|
||||
S-->>F: done
|
||||
F->>F: es.close()
|
||||
F->>F: App.forceSubmitting = false
|
||||
F->>F: App.processState = 'done'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、错误处理路径
|
||||
|
||||
### 6.1 错误场景和事件
|
||||
|
||||
| 错误场景 | 后端行为 | 发射事件 | 前端表现 |
|
||||
|---|---|---|---|
|
||||
| LLM 提取异常 | `state → ERROR` | `agent_error` | 聊天显示错误,`processState → 'done'` |
|
||||
| 规则校验 3 次失败 | 返回最后一次结果,继续语义判断 | `agent_state_change` | 依赖 `can_submit` 字段决定 |
|
||||
| 轮次超限 (5 轮) | `state → ERROR` | `agent_max_rounds` | 聊天显示错误,可强制提交 |
|
||||
| 财务提交失败 | `result.submit_ok = false` | 无独立事件 | `done` 事件携带错误信息 |
|
||||
| SSE 连接中断 | 无 | `es.onerror` 触发 | 显示"连接中断" |
|
||||
| 超时 (600s) | SSE 轮询循环退出 | 连接自然断开 | 连接断开 |
|
||||
|
||||
### 6.2 agent_error 事件结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "agent_error",
|
||||
"message": "LLM 提取失败: ..."
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 agent_max_rounds 事件结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "agent_max_rounds",
|
||||
"message": "已达到最大轮次 (5),请检查信息或强制提交"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、状态机完整图
|
||||
|
||||
### 7.1 后端 AgentState 状态机
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> IDLE
|
||||
|
||||
IDLE --> EXTRACTING: POST /api/agent/process\nPOST /api/agent/supplement\nPOST /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: 用户补充文件/文字
|
||||
|
||||
READY: 准备提交\n(终态保护)
|
||||
SUBMITTING: 财务提交中\n(终态保护)
|
||||
DONE: 终态\n(终态保护)
|
||||
ERROR: 错误状态\n(可强制提交)
|
||||
|
||||
note right of EXTRACTING
|
||||
LLM 提取 + validator 校验\n最多 3 次重试
|
||||
end note
|
||||
```
|
||||
|
||||
### 7.2 前端 processState 状态机
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> idle
|
||||
|
||||
idle --> processing: startProcess()
|
||||
|
||||
processing --> awaiting_supplement: done事件\nresult.waiting_for_supplement
|
||||
processing --> done: done事件\nresult.ok
|
||||
|
||||
awaiting_supplement --> processing: 补充文件或文字
|
||||
awaiting_supplement --> submitting: 强制提交
|
||||
|
||||
submitting --> done: done事件
|
||||
|
||||
done: 流程结束
|
||||
idle: 初始状态
|
||||
processing: 处理中
|
||||
awaiting_supplement: 等待补充
|
||||
submitting: 提交中
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、SSE 事件类型完整参考
|
||||
|
||||
### 8.1 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_error` | `{type, message}` | 提取失败 |
|
||||
| `agent_max_rounds` | `{type, message}` | 达到最大轮次 |
|
||||
|
||||
### 8.2 文件进度事件 (file_events.log)
|
||||
|
||||
| 事件类型 | 数据结构 | 触发条件 |
|
||||
|---|---|---|
|
||||
| `file_progress` | `{type, file, status, summary?, error?}` | 文件处理状态变更 |
|
||||
|
||||
`status` 取值: `processing` / `done` / `cached` / `error`
|
||||
|
||||
### 8.3 LLM 流式事件 (llm_stream.log)
|
||||
|
||||
| 事件类型 | 数据结构 | 触发条件 |
|
||||
|---|---|---|
|
||||
| `llm_stream` | `{type, phase, content?}` | LLM 输出流 |
|
||||
|
||||
`phase` 取值: `start` / `reasoning` / `chunk` / `end` / `error`
|
||||
|
||||
### 8.4 完成事件 (SSE 直接发送)
|
||||
|
||||
| 事件类型 | 数据结构 | 触发条件 |
|
||||
|---|---|---|
|
||||
| `done` | `{type, result: {...}}` | `result.json` 出现 |
|
||||
|
||||
---
|
||||
|
||||
## 九、常见问题排查清单
|
||||
|
||||
### 9.1 SSE 事件丢失
|
||||
|
||||
**症状**: 前端没有收到预期的 agent 事件
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查 `agent_events.log` 是否存在、是否有内容
|
||||
2. 检查 SSE 连接是否建立成功(浏览器 Network 面板)
|
||||
3. 确认 `sse_handler.install_log_collector()` 是否被调用
|
||||
4. 确认 `remove_log_collector()` 是否过早调用
|
||||
|
||||
### 9.2 提交流程中断
|
||||
|
||||
**症状**: 流程在某个中间状态卡住,没有 `done` 事件
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查 `result.json` 是否被写入
|
||||
2. 检查后台线程是否异常退出(查看 `session.log`)
|
||||
3. 确认 `finally` 块中的 `result.json` 写入逻辑是否执行
|
||||
4. 检查是否触发了 600 秒超时
|
||||
|
||||
### 9.3 状态不一致
|
||||
|
||||
**症状**: 前端 `processState` 和后端 `AgentState` 不匹配
|
||||
|
||||
**排查步骤**:
|
||||
1. 对比 `agent_events.log` 中的状态变更序列
|
||||
2. 检查前端是否正确处理了 `done` 事件
|
||||
3. 确认 SSE 连接是否在适当时机关闭和重建
|
||||
4. 检查 `App.agentEventSource` 引用是否正确清理
|
||||
|
||||
### 9.4 补充流程不触发
|
||||
|
||||
**症状**: 用户上传补充文件或输入文字后,没有重新分析
|
||||
|
||||
**排查步骤**:
|
||||
1. 确认 `processState` 是否为 `awaiting_supplement`
|
||||
2. 检查补充 API 是否返回 `{status: "started"}`
|
||||
3. 检查新 SSE 连接是否成功建立
|
||||
4. 确认 `add_supplement()` 或 `process_user_text_supplement()` 是否被调用
|
||||
|
||||
---
|
||||
|
||||
## 十、关键文件索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `src/web/static/js/process.js` | 主提交流程入口,SSE 事件分发 |
|
||||
| `src/web/static/js/agent.js` | Agent 事件处理,补充/强制提交逻辑 |
|
||||
| `src/web/static/js/state.js` | 全局状态管理 |
|
||||
| `src/web/routes.py` | 后端路由,后台线程启动 |
|
||||
| `src/agent/orchestrator.py` | Agent 调度器,状态机,校验循环 |
|
||||
| `src/web/sse_handler.py` | SSE 日志收集器 |
|
||||
| `src/web/pipeline_web.py` | 发票提取管道,财务提交 |
|
||||
Reference in New Issue
Block a user