Agent 对话总览与 SSE 流式交互.md 32 KB

Agent 对话总览与 SSE 流式交互

本文引用的文件

  • chatApi.ts
  • sse.ts
  • chatStream.ts
  • types.ts
  • constants.ts
  • AgentChatController.java
  • AgentChatService.java
  • AgentChatServiceImpl.java
  • AgentChatSession.java
  • AgentMessage.java

目录

  1. 引言
  2. 项目结构定位
  3. 核心组件与职责分工
  4. 架构总览
  5. SSE 流式消息链路
  6. 会话与消息数据模型
  7. 典型调用示例
  8. 依赖关系分析
  9. 性能与内存特性
  10. 常见问题排查
  11. 结论

引言

本文面向 Agent 对话模块的开发者与维护者,说明前端如何发起对话、后端 AgentChatController 与 AgentChatService 的职责边界,以及从前端到后端的完整 SSE(Server-Sent Events)流式交互链路。重点包括:

  • 前端通过 chatApi.ts 暴露接口,由 sse.ts 使用 fetch + ReadableStream 处理事件流。
  • chatStream.ts 作为 Pinia store,负责会话生命周期、乐观插入用户消息、增量聚合助手回复、工具调用时序和 UI 渲染。
  • 后端控制器仅做请求路由,真正逻辑在 AgentChatServiceImpl:校验工作空间、建立会话级互斥、驱动 Agent 流事件、按事件类型生成 SSE 帧、持久化消息并发送心跳。
  • 解释 AgentChatSession、AgentMessage、Insight 会话在对话生命周期中的关系,并给出“选择模型后发送文本”“查看工具调用日志”“中断或重连 SSE”等典型流程。

项目结构定位

本功能横跨前后端两个子工程:

层级 关键路径 职责
前端 API 层 ai-electron/frontend/src/ai/api/chatApi.ts 封装会话、消息、图谱复原及流式对话入口;将解析器转发给 sse.ts
前端 SSE 实现 ai-electron/frontend/src/ai/api/sse.ts 通用 SSE 帧解析器、fetch 流读取、未登录/非 SSE 响应处理、中止信号透传
前端状态管理 ai-electron/frontend/src/ai/store/chatStream.ts 会话列表、消息列表、流式对话状态机、工具步骤与思考过程累积、UI 富内容块增量扫描
前端类型与常量 ai-electron/frontend/src/ai/api/types.ts、ai-electron/frontend/src/ai/utils/constants.ts 会话/消息/工具/Insight 契约、getChatStreamUrl() 地址拼接
后端控制器 ai-server/.../controller/AgentChatController.java /chat 下 REST 路由;/chat/stream 返回 Flux<ServerSentEvent<String>>
后端服务接口 ai-server/.../service/AgentChatService.java 会话 CRUD、消息 CRUD、流式对话抽象
后端服务实现 ai-server/.../service/impl/AgentChatServiceImpl.java 工作空间校验、会话锁、Agent 流事件映射、SSE 帧组装、心跳、落库
后端实体 AgentChatSession.java、AgentMessage.java 数据库表 agent_chat_session、agent_message 的字段映射
graph TB
    subgraph "前端"
        CA["chatApi.ts<br/>API 入口"]
        ST["sse.ts<br/>SSE 解析与 fetch 流"]
        CS["chatStream.ts<br/>Pinia 状态与流式状态机"]
        TP["types.ts / constants.ts<br/>类型与地址"]
    end

    subgraph "后端"
        AC["AgentChatController.java<br/>/chat 路由"]
        AS["AgentChatService.java<br/>服务接口"]
        ASI["AgentChatServiceImpl.java<br/>流式逻辑实现"]
        DB["agent_chat_session / agent_message<br/>PostgreSQL JSONB"]
    end

    CA --> ST
    CA --> CS
    CS --> CA
    ST --> TP
    AC --> AS
    AS --> ASI
    ASI --> DB
    ST --> AC

图表来源

  • chatApi.ts:1-80
  • sse.ts:1-222
  • chatStream.ts:1-598
  • AgentChatController.java:1-170
  • AgentChatService.java:1-83
  • AgentChatServiceImpl.java:345-599

章节来源

  • chatApi.ts:1-80
  • sse.ts:1-222
  • chatStream.ts:1-598
  • AgentChatController.java:1-170
  • AgentChatService.java:1-83

核心组件与职责分工

前端:chatApi.ts

chatApi.ts 是聊天相关 HTTP 接口的统一入口。它不直接实现 SSE 细节,而是把流式能力转发给 sse.ts:

  • 会话操作:创建、列表、详情、重命名、删除、置顶。
  • 消息操作:查询、发送、删除、收藏。
  • 图谱复原:根据历史工具入参中的查询条件重新拉取全量关系图,避免数据库存大 JSON。
  • 流式对话:导出 createParser、streamSse、streamChat,保持调用方 chatStream.ts 零改动。

关键设计点:

  • 非流式接口走 defHttp(即 aiHttp)。
  • 流式对话不走 axios,因为 axios 无法逐帧读取 ReadableStream。
  • 注释明确区分“普通发送消息”和“流式对话”。

章节来源

  • chatApi.ts:1-80

前端:sse.ts

sse.ts 是通用 SSE 基础设施,同时供 AI 数据分析与 Insight 研判复用。其职责包括:

  1. 帧解析器 createParser

    • 维护缓冲区,支持 \n\n 与 \r\n\r\n 分隔。
    • 忽略注释行(以 : 开头)。
    • 合并多行 data: 字段。
    • 对 JSON 载荷按 event 分发到 onIntent、onThinking、onToolCall、onToolInput、onToolResult、onHeartbeat、onGraphData、onInsight、onToken、onDone、onError。
    • 非 JSON 载荷兜底为 token,避免丢字。
    • 提供 flush(),防止最后一帧没有结尾空行时丢失末事件。
  2. streamSse 连接层

    • 使用 fetch 发起 POST,携带 x-token、Accept: text/event-stream。
    • 若后端返回非 SSE(例如 200 + code=401 的 JSON),先尝试解析错误消息并触发未登录跳转。
    • 若 HTTP 非 ok,尽量解析 body 中错误信息。
    • 使用 response.body.getReader() 逐块解码并喂给解析器。
    • 捕获 AbortError 透传给调用方;其他异常转 transportError。
  3. streamChat 适配器

    • 统一调用 getChatStreamUrl(),即 /chat/stream。
    • 保留旧调用方式,内部委托 streamSse。

章节来源

  • sse.ts:1-222
  • constants.ts:20-28

前端:chatStream.ts

chatStream.ts 是整个对话 UI 的核心状态机。它不是简单的网络回调包装,而是承担了:

  • 初始化工作空间、加载会话列表。
  • 新建会话、选中会话、加载历史消息。
  • 历史消息中缺失的关系图按需复原。
  • 后端消息反序列化为前端渲染态:工具步骤、思考过程、附件徽标、富内容块。
  • 流式对话生命周期:乐观插入用户消息 → 占位助手消息 → 增量 token → 工具调用时序 → done/error → 收尾刷新会话元信息。
  • 节流富内容块增量扫描,避免长回答 O(n²) 卡顿。
  • 单进程内只允许一路流式请求,使用模块作用域的 abortController。

重要状态字段:

字段 含义
workspaceId 当前案件/工作空间
sessions 会话列表
activeSessionId 当前选中会话
messages 当前会话消息数组
streaming 是否正在流式输出
pendingExpert 来自 Insight 页面的预选研判方案
thinking 模型思考正文
trace 思考与工具的真实到达顺序链
toolSteps 工具调用步骤列表
intent / suggestions 意图识别与下一步引导
lastActivityAt / lastHeartbeatAt 用于区分“模型在输出”和“连接断掉”

章节来源

  • chatStream.ts:1-598

后端:AgentChatController.java

控制器只承担路由和参数绑定职责,不包含业务编排逻辑:

  • 会话 CRUD:/chat/sessions、标题更新、删除、置顶。
  • 消息 CRUD:/chat/messages、收藏。
  • 图谱复原:/chat/graph/restore。
  • 附件上传:/chat/attachments。
  • 流式对话:POST /chat/stream,返回 Flux<ServerSentEvent<String>>。

控制器注释明确描述了 SSE 事件顺序:

  1. tool_call
  2. tool_input
  3. tool_result
  4. token
  5. done
  6. error

章节来源

  • AgentChatController.java:1-170

后端:AgentChatService.java

该接口定义了聊天域的所有对外方法,包括:

  • 会话:创建、列表、详情、标题、删除、置顶。
  • 消息:查询、发送、删除、收藏。
  • 图谱复原。
  • 流式对话:stream(ChatRequestDTO req)。

它不关心具体事件如何映射,只声明契约。

章节来源

  • AgentChatService.java:1-83

后端:AgentChatServiceImpl.java

这是流式对话的实际实现。核心行为:

  • 校验消息是否为空。
  • 延迟解析会话 ID 与上下文,异常转为 error 帧。
  • 会话级互斥:同一会话同一时刻只允许一条流,抢占失败返回“正在回复中”。
  • 获取工作空间并校验访问权限。
  • 构建 Agent 与 RuntimeContext。
  • 订阅 HarnessAgent.streamEvents,将 Agent 事件映射为 SSE 帧。
  • 独立心跳流:每 10 秒发送一次 heartbeat。
  • done 时持久化完整助手消息;中断时持久化部分消息。
  • 错误时持久化错误消息。

章节来源

  • AgentChatServiceImpl.java:345-599

架构总览

下图展示一次完整的 Agent 对话请求从前端到后端的控制流和数据流:

sequenceDiagram
    participant UI as "聊天页面"
    participant Store as "chatStream.ts"
    participant Api as "chatApi.ts"
    participant Sse as "sse.ts"
    participant Ctrl as "AgentChatController.java"
    participant Svc as "AgentChatServiceImpl.java"
    participant Db as "agent_chat_session / agent_message"

    UI->>Store: "选择模型并发送文本"
    Store->>Store: "乐观插入用户消息与占位助手消息"
    Store->>Api: "streamChat(params)"
    Api->>Sse: "streamSse(getChatStreamUrl(), params, handlers)"
    Sse->>Ctrl: "POST /chat/stream"
    Ctrl->>Svc: "stream(req)"
    Svc->>Db: "校验会话与工作空间"
    Svc->>Svc: "会话级互斥检查"
    Svc->>Svc: "启动 Agent 流事件"

    loop "SSE 事件流"
        Svc-->>Sse: "tool_call / tool_input / tool_result / token / thinking / heartbeat"
        Sse-->>Store: "对应 on* 回调"
        Store->>Store: "累积 content / toolSteps / trace / thinking"
    end

    alt "正常完成"
        Svc-->>Sse: "done"
        Svc->>Db: "持久化助手消息"
    else "错误或中断"
        Svc-->>Sse: "error / 连接关闭"
        Svc->>Db: "持久化错误或部分消息"
    end

    Sse-->>Store: "finally 清理流状态"
    Store->>Store: "结束 streaming、刷新会话元信息"

图表来源

  • chatStream.ts:400-598
  • chatApi.ts:57-80
  • sse.ts:140-222
  • AgentChatController.java:140-170
  • AgentChatServiceImpl.java:345-599

SSE 流式消息链路

前端发起连接

前端调用链如下:

  1. 用户在聊天界面输入消息并点击发送。
  2. chatStream.ts 的 send 方法判断是否需要新建会话。
  3. 乐观插入本地用户消息和占位助手消息。
  4. 调用 chatApi.streamChat。
  5. chatApi.ts 转发到 sse.ts 的 streamChat。
  6. sse.ts 拼接 /chat/stream 地址并发起 fetch。
  7. 使用 TextDecoder + SseFrameParser 逐帧解析。
  8. 将事件分发给 chatStream.ts 注册的 handlers。

    flowchart TD
    Start["用户发送消息"] --> Draft{"是否存在有效会话?"}
    Draft -->|否| Create["创建会话"]
    Draft -->|是| UseExisting["复用现有会话"]
    Create --> InsertUser["乐观插入用户消息"]
    UseExisting --> InsertUser
    InsertUser --> InsertAssistant["插入占位助手消息"]
    InsertAssistant --> StreamApi["chatApi.streamChat"]
    StreamApi --> SseCore["sse.ts streamSse"]
    SseCore --> Fetch["fetch POST /chat/stream"]
    Fetch --> Parse["SseFrameParser 解析帧"]
    Parse --> Handlers["chatStream handlers 更新状态"]
    Handlers --> Render["UI 增量渲染"]
    

图表来源

  • chatStream.ts:400-598
  • chatApi.ts:57-80
  • sse.ts:140-222

章节来源

  • chatStream.ts:400-598
  • chatApi.ts:57-80
  • sse.ts:140-222

后端事件生产

后端 AgentChatServiceImpl 将 AgentScope 的 AgentEvent 转换为 SSE 帧:

Agent 阶段 SSE event 前端处理器 状态变化
工具开始 tool_call onToolCall 新增 ToolStep{status:'running'}
工具入参完成 tool_input onToolInput 写入 step.input
工具结果完成 tool_result onToolResult 写入 step.result、status='done'
模型 token token onToken 追加 assistant.content
思考开始 thinking.start onThinking 开启 thinkingActive,创建 trace think 节点
思考增量 thinking.delta onThinking 追加 thinking 与 trace.text
思考结束 thinking.end onThinking 关闭 thinkingActive
完成 done onDone 结束 streaming,持久化消息
错误 error onError 标记 messageType='error'
心跳 heartbeat onHeartbeat 更新 lastHeartbeatAt

心跳流与业务事件流是独立的:心跳通过 Flux.interval(10s) 生成,并在连接结束时停止。

章节来源

  • AgentChatServiceImpl.java:461-599

前端增量聚合与渲染

chatStream.ts 对每个事件的处理策略:

  • onIntent:保存意图识别结果。
  • onSuggestions:保存扁平建议与方向分组。
  • onThinking:分段累积思考,维护 thinkingActive 与 trace。
  • onToolCall:新增工具步骤,记录 trace tool 节点。
  • onToolInput:归一化工具入参文本。
  • onToolResult:写入结果并标记完成。
  • onToken:追加正文,并按 100ms 节流执行富内容块增量扫描。
  • onHeartbeat:仅更新时间戳,不影响 content。
  • onGraphData:回填 get_case_graph 的全量图谱。
  • onDone:结束消息 streaming,但整体 finally 仍会清理流状态。
  • onError / onTransportError:设置 messageType 为 error 或 interrupted。
  • finally:若收到连接关闭但未收到 done,标记为“连接已结束但未收到完成信号”。

章节来源

  • chatStream.ts:426-598

会话与消息数据模型

会话实体:AgentChatSession

字段 类型 含义
id Long 主键
agent_id Long 绑定的研判方案
model_id Long 会话默认模型
case_id Long 工作空间/案件
title String 会话标题
context JSONB 上下文
message_count Integer 消息数
total_tokens Long 累计 token
last_message_at LocalDateTime 最后消息时间
pinned Boolean 是否置顶
attachments JSONB 附件摘要
create_at LocalDateTime 创建时间
update_at LocalDateTime 更新时间

消息实体:AgentMessage

字段 类型 含义
id Long 主键
session_id Long 所属会话
role String user / assistant / system
content String 正文
content_type String 内容类型
token_count Integer token 计数
metadata JSONB 思考过程、附件摘要、错误标记等
tool_events JSONB 工具调用事件序列
parent_id Long 父消息
message_type String text / error / interrupted
reply_to_message_id Long 回复目标
starred Boolean 是否收藏
create_at LocalDateTime 创建时间

三者关系

erDiagram
  AGENT_CHAT_SESSION {
    long id PK
    long agent_id
    long model_id
    long case_id
    string title
    jsonb context
    int message_count
    long total_tokens
    datetime last_message_at
    boolean pinned
    jsonb attachments
    datetime create_at
    datetime update_at
  }

  AGENT_MESSAGE {
    long id PK
    long session_id FK
    string role
    string content
    string content_type
    int token_count
    jsonb metadata
    jsonb tool_events
    long parent_id
    string message_type
    long reply_to_message_id
    boolean starred
    datetime create_at
  }

  INSIGHT_SESSION {
    long id PK
    string name
    jsonb context
    datetime create_at
    datetime update_at
  }

  AGENT_CHAT_SESSION ||--o{ AGENT_MESSAGE : "包含"

图表来源

  • AgentChatSession.java:1-58
  • AgentMessage.java:1-55

在对话生命周期中:

  • AgentChatSession 表示一次 Agent 对话会话,绑定工作空间、研判方案和模型。
  • AgentMessage 表示会话中的一条消息,可以是用户提问、助手回答、错误提示或中断结果。
  • Insight 会话是“持续联系 AI 研判”的业务会话,它与 Agent 对话共享同一套 SSE 事件协议,但额外支持 insight 帧;前端在 chatStream.ts 中通过 onInsight 处理。
  • 两者并非父子实体,而是不同业务场景下的会话载体:Agent 对话侧重交互式问答,Insight 侧重结构化研判任务。

章节来源

  • AgentChatSession.java:1-58
  • AgentMessage.java:1-55

典型调用示例

示例一:选择模型后发送文本消息

  1. 用户在聊天面板选择某个模型。
  2. 输入文本消息。
  3. chatStream.ts 的 send(text, modelId) 被调用。
  4. 若当前会话绑定的研判方案与预选不一致,则新建会话。
  5. 乐观插入用户消息和助手占位消息。
  6. 调用 streamChat({ sessionKey, message, modelId })。
  7. 后端解析 sessionKey 为会话 ID,校验工作空间,建立 Agent 流。
  8. 前端逐步累积 token 并渲染。
  9. 收到 done 后结束流式态,刷新会话标题、消息数和最后消息时间。

注意:modelId 在前端类型中要求按字符串传递,避免雪花 ID 精度丢失。

章节来源

  • chatStream.ts:400-598
  • types.ts:247-260
  • AgentChatServiceImpl.java:345-400

示例二:查看工具调用日志

工具调用日志保存在 AgentMessage.tool_events,前端将其反序列化为 ToolStep[]:

阶段 事件 ToolStep 变化
开始 tool_call 新增步骤,状态 running
入参 tool_input 填充 input
结果 tool_result 填充 result,状态 done
图谱 graph_data 回填 graphPayload

前端还会对历史消息中的 get_case_graph 调用进行图谱复原:如果数据库中只存了查询条件,则调用 /chat/graph/restore 拉取全量图。

章节来源

  • chatStream.ts:240-360
  • chatApi.ts:40-46
  • sse.ts:30-120

示例三:中断或重连 SSE

中断:

  1. 用户点击“停止生成”。
  2. chatStream.ts 调用 stop()。
  3. stop() 清除定时器并调用 abortController.abort()。
  4. sse.ts 捕获 AbortError 并向上抛出。
  5. chatStream.ts catch 分支忽略 AbortError,进入 finally。
  6. finally 中若未收到 done,且消息仍在 streaming,则标记为 interrupted。
  7. 后端可能已持久化部分消息,也可能尚未持久化,因此前端需要提示“以上为已接收内容,请重试”。

重连:

  • 前端当前实现不会自动重连 SSE。
  • 常见做法是让用户重新发送消息,从而重建 AbortController 和新的 fetch 连接。
  • 如果希望支持重连,应在 onTransportError 中暴露重连策略,而不是无限重试同一个 reader。

章节来源

  • chatStream.ts:550-598
  • sse.ts:201-222

依赖关系分析

前端模块依赖

graph LR
    ChatPage["聊天页面组件"] --> ChatStream["chatStream.ts"]
    ChatStream --> ChatApi["chatApi.ts"]
    ChatApi --> Sse["sse.ts"]
    Sse --> Constants["constants.ts"]
    ChatStream --> Types["types.ts"]
    Sse --> Types

图表来源

  • chatStream.ts:1-50
  • chatApi.ts:1-80
  • sse.ts:1-20
  • types.ts:240-260
  • constants.ts:20-28

后端模块依赖

graph LR
    Controller["AgentChatController"] --> Service["AgentChatService"]
    Service --> Impl["AgentChatServiceImpl"]
    Impl --> SessionMapper["AgentChatSessionMapper"]
    Impl --> MessageMapper["AgentMessageMapper"]
    Impl --> AgentService["AgentService"]
    Impl --> ModelService["AgentModelService"]
    Impl --> UsageStore["UsageStore"]
    Impl --> GraphService["GraphService"]
    Impl --> CaseAccessGuard["CaseAccessGuard"]

图表来源

  • AgentChatController.java:1-170
  • AgentChatService.java:1-83
  • AgentChatServiceImpl.java:1-120

章节来源

  • AgentChatController.java:1-170
  • AgentChatService.java:1-83
  • AgentChatServiceImpl.java:1-120

性能与内存特性

富内容块增量扫描

chatStream.ts 对每次 token 追加并不立即重新扫描全文,而是通过 RESAN_THROTTLE_MS 节流,默认约 100ms。这避免了长回答中反复扫描 Markdown/代码/ECharts/Insight 围栏导致的 O(n²) 成本。

已完成块会被冻结复用,进一步降低重复解析压力。

章节来源

  • chatStream.ts:1-30
  • chatStream.ts:450-520

SSE 缓冲区与解码器

sse.ts 维护一个字符串缓冲区,逐块拼接后再按 \n\n 切分。对于超大响应:

  • 缓冲区和消息 content 都会持续增长。
  • 浏览器侧 TextDecoder 以流模式解码,减少一次性转换开销。
  • 若后端一次推送 chunk 过大,可能造成单次解析耗时增加。

优化建议:

  • 控制后端单次 SSE data payload 大小。
  • 前端可对过长 response 做上限保护,避免极端情况导致内存暴涨。

章节来源

  • sse.ts:20-120
  • sse.ts:160-222

会话级互斥

AgentChatServiceImpl 使用 ConcurrentHashMap<Long, Semaphore> 保证同一会话只有一条流。这避免:

  • 双标签页并发写 Agent 记忆槽。
  • 多条流交错写 transcript。
  • 第一条对话完成后后续消息误报“正在回复中”。

注意:这是进程内锁,多实例部署时需要扩展为分布式锁。

章节来源

  • AgentChatServiceImpl.java:80-120
  • AgentChatServiceImpl.java:380-400

常见问题排查

SSE 断连与心跳

现象:

  • 模型长时间无输出,前端显示“正在输出”,但实际没有新 token。
  • 用户看到“连接已结束但未收到完成信号”。

原因:

  • 服务端模型推理慢,但 SSE 连接未断。
  • 中间代理、网关或防火墙提前关闭空闲连接。
  • 后端服务进程异常退出。

排查要点:

  1. 检查 lastHeartbeatAt 是否持续更新。
  2. 若心跳停止但 lastActivityAt 也很久没变,大概率是连接断了。
  3. 若心跳正常但无 token,可能是模型卡住或工具调用阻塞。
  4. 后端心跳间隔为 10 秒,前端不应仅凭“长时间无活动”就判定断连。

章节来源

  • AgentChatServiceImpl.java:593-599
  • chatStream.ts:460-598

token 过期或未登录

现象:

  • 前端收到 HTTP 200,但 Content-Type 不是 text/event-stream。
  • 出现“请求失败:服务端未返回事件流”或跳转到登录页。

原因:

  • 后端 sa-token 校验失败,返回普通 Result JSON,而非 SSE。
  • 前端先检测 Content-Type,再尝试解析 JSON 中的 code=401。

处理:

  • 前端会调用 handleUnauthorizedRedirect 跳转。
  • 用户重新登录后应重新发起对话。
  • 不要静默吞掉未登录错误。

章节来源

  • sse.ts:140-190

服务端 503 或其他非 SSE 响应

现象:

  • 前端显示“请求失败(HTTP 503)”或类似错误。
  • 工具日志不完整,助手消息可能被标记为 error。

原因:

  • 网关、负载均衡或后端服务不可用。
  • 上游 LLM 提供商返回异常。
  • 后端抛出未捕获异常。

处理:

  • sse.ts 会尝试读取响应体并提取 message 或 error。
  • 前端 store 将消息标记为 error,并保留已收到的内容。
  • 建议添加“重试”按钮,而不是自动无限重试。

章节来源

  • sse.ts:170-200
  • chatStream.ts:520-598

大响应导致内存增长

现象:

  • 长时间对话后,浏览器内存明显上升。
  • 超长工具结果或图谱导致渲染卡顿。

原因:

  • chatStream.ts 中 target.content 不断拼接 token。
  • sse.ts 的 buffer 累积未解析文本。
  • tool_steps 中 result 可能包含大量文本。
  • 历史消息恢复图谱时,全量 graph 可能很大。

缓解措施:

  • 前端限制单条消息最大长度,超出部分截断或折叠。
  • 对工具结果采用分页或懒加载。
  • 图谱数据优先展示元信息,全量图按需拉取。
  • 后端对工具结果长度做合理限制。

章节来源

  • chatStream.ts:450-520
  • sse.ts:20-120

中断后刷新看不到消息

现象:

  • 用户停止生成后刷新页面,助手消息消失。
  • 或者只看到半截内容。

原因:

  • 中断发生在后端持久化之前。
  • 前端只做了本地 optimistic 写入,未落库。
  • 后端在中断时尝试持久化部分消息,但可能失败或尚未完成。

处理:

  • 前端在 interrupted 状态下提示“请重试”。
  • 后端 persistPartialMessage 会写入 messageType=interrupted。
  • 若刷新后确实没有消息,属于后端落库失败或服务异常,应记录日志并提示用户。

章节来源

  • AgentChatServiceImpl.java:781-804
  • chatStream.ts:560-598

结论

Agent 对话与 SSE 流式交互是一个典型的前后端实时协作场景:

  • 前端 chatApi.ts 暴露稳定接口,sse.ts 处理低层流协议,chatStream.ts 承载业务状态机。
  • 后端 AgentChatController 只做路由,AgentChatServiceImpl 负责会话互斥、Agent 事件映射、SSE 帧生产和消息持久化。
  • SSE 事件不仅是 token 流,还承载工具调用、思考过程、意图识别、下一步建议和心跳。
  • 正确理解 AgentChatSession、AgentMessage 与 Insight 会话的区别,有助于避免把不同业务会话混为一谈。
  • 排障时应优先区分三类问题:认证问题、连接问题、后端业务问题。
  • 性能和内存方面,应关注长消息拼接、SSE 缓冲区、工具结果体积和图谱全量数据。