Files
Auto-Finance/.agents/docs/plans/系统事件流全景图.md

15 KiB
Raw Blame History

系统事件流全景图

最后更新: 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 通信机制

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 时序图

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 结构(成功路径)

{
  "ok": true,
  "agent_ready": true,
  "submit_ok": true,
  "round": 1,
  "message": "信息完整,已自动提交到财务系统"
}

三、场景二:用户提交材料 → 需补充 → 用户上传文件

3.1 时序图

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 结构(需补充)

{
  "ok": true,
  "agent_ready": false,
  "agent_state": "awaiting_supplement",
  "round": 1,
  "waiting_for_supplement": true
}

四、场景三:用户提交材料 → 需补充 → 用户通过对话提供信息

4.1 时序图

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 时序图

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 事件结构

{
  "type": "agent_error",
  "message": "LLM 提取失败: ..."
}

6.3 agent_max_rounds 事件结构

{
  "type": "agent_max_rounds",
  "message": "已达到最大轮次 (5),请检查信息或强制提交"
}

七、状态机完整图

7.1 后端 AgentState 状态机

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 状态机

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 发票提取管道,财务提交