01 - 对话完整执行过程

从用户发送一条消息到收到AI回复,中间经历了什么?

一、整体流程概览

当用户在前端输入一条消息并发送时,请求会经历以下完整链路:

用户输入 "请帮我分析这份简历"
    │
    ▼
┌─────────────────────────────────────────────────────────────────┐
│  1. 前端 (Vue 3)                                                │
│     ChatPanel.vue → chatApi.sendMessage(conversationId, text)  │
│     建立 SSE EventSource 连接,监听实时事件流                    │
└─────────────────────┬───────────────────────────────────────────┘
                      │ HTTP POST + SSE
                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. API层 (hub-api)                                             │
│     ConversationController.send()                               │
│     → JWT认证 → 参数校验 → 调用Agent编排层                      │
└─────────────────────┬───────────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  3. Agent编排层 (hub-agent-core)                                │
│     AgentTaskOrchestrator.execute(agent, request)               │
│     → 熔断检查 → 限流检查 → 超时设置 → 提交到线程池            │
└─────────────────────┬───────────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. Agent执行层 (hub-llm-core / hub-agents)                    │
│     AbstractLlmAgent.chat(request)                              │
│     → 构建系统提示词 → 安全包装 → 历史管理 → 工具注入          │
│     → ReAct推理循环 (LLM → 工具调用 → 分支判断 → 循环/结束)    │
└─────────────────────┬───────────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  5. LLM调用层                                                   │
│     ModelRegistry → PooledChatModel → TrackedChatModel          │
│     → 密钥池轮转 → 实际LLM API调用 → 流式响应                 │
└─────────────────────┬───────────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  6. 安全防护层 (hub-prompt-guard)                               │
│     输入扫描 → 提示词隔离(金丝雀令牌) → 输出扫描 → 消毒        │
└─────────────────────┬───────────────────────────────────────────┘
                      │ SSE事件流
                      ▼
┌─────────────────────────────────────────────────────────────────┐
│  7. 响应返回                                                    │
│     SSE事件 → 前端实时渲染 → 完成                               │
│     事件类型: react/final_answer/tool/thinking/done             │
└─────────────────────────────────────────────────────────────────┘

二、详细步骤分解

1

前端发起请求

入口文件: ChatPanel.vue

用户在聊天面板输入消息后,前端通过 chatApi.sendMessage() 发送POST请求到 /api/conversations/{id}/messages,同时建立SSE连接接收流式响应。

// 前端发起请求的关键逻辑
const response = await fetch(`/api/conversations/${convId}/messages`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ content: userInput })
});

// 处理SSE流式响应
const reader = response.body.getReader();
// 逐chunk解析SSE事件: react, final_answer, tool, thinking, done
2

API层 — Controller接收与认证

入口文件: ConversationController.java

请求到达后端后:

  1. JWT认证:JwtAuthenticationFilter 从请求头提取Token,验证签名和有效期
  2. 参数校验:检查conversationId是否存在、用户是否有权限
  3. Agent路由:根据会话关联的agentId,从AgentRegistry查找对应的Agent实例
  4. 构建请求:将HTTP参数封装为 AgentRequest 对象
// Controller核心逻辑
@PostMapping("/conversations/{id}/messages")
public Flux<ServerSentEvent<String>> sendMessage(@PathVariable Long id, @RequestBody MessageRequest req) {
    ConversationEntity conv = conversationService.getById(id);
    Agent agent = agentRegistry.getAgent(conv.getAgentId());
    AgentRequest agentReq = AgentRequest.builder()
        .userId(currentUserId).conversationId(id)
        .message(req.getContent()).modelId(req.getModelId())
        .build();
    return orchestrator.execute(agent, agentReq);
}
3

Agent编排层 — 任务调度与控制

核心类: AgentTaskOrchestrator

编排器是整个系统的"交通指挥",负责:

AgentTaskOrchestrator.execute(agent, request)
    │
    ├── ❌ 熔断检查 ── AgentCircuitBreaker.isOpen(agentId)?
    │                  如果该Agent近期失败率过高,直接拒绝
    │
    ├── ❌ 限流检查 ── UserRateLimiter.isOverLimit(userId)?
    │                  如果用户请求频率过高,直接拒绝
    │
    ├── 🪝 BEFORE Hook ── AgentHookPublisher.fire(BEFORE_AGENT_EXECUTION)
    │                     插件可在执行前拦截/修改请求
    │
    ├── ⏱️ 超时设置 ── max(agent.timeout, globalDefault)
    │
    ├── 📝 创建任务 ── AgentTask(PENDING → RUNNING)
    │                  持久化到数据库
    │
    └── 📤 提交执行 ── AgentExecutor.submit(task, callable)
                        │
                        ├── 获取全局信号量 (默认最大50并发)
                        ├── 获取Agent级信号量 (默认最大10并发)
                        ├── 传播SecurityContext到工作线程
                        └── 在线程池中执行 agent.chat(request)
4

Agent执行层 — ReAct推理循环

核心类: AbstractLlmAgent.chat() (2343行)

这是整个系统最核心的部分,执行流程如下:

AbstractLlmAgent.chat(request)
    │
    ├── 1️⃣ 构建系统提示词 (System Prompt)
    │   ├── 基础提示词 + 角色设定
    │   ├── 🛡️ PromptIsolationService 安全包装
    │   │   └── XML标签包裹 + 安全规则 + 金丝雀令牌注入
    │   ├── 🔧 工具描述注入 (MCP/Skill/Shell/NOS)
    │   └── 🪝 ON_SYSTEM_PROMPT Hook
    │
    ├── 2️⃣ 构建消息列表
    │   ├── SystemMessage (系统提示词)
    │   ├── 历史消息 (从数据库加载)
    │   ├── 上下文消息 (RAG检索结果,如有)
    │   └── UserMessage (当前用户输入)
    │
    ├── 3️⃣ 解析模型
    │   └── ModelRegistry → 降级感知路由 → TrackedChatModel
    │
    ├── 4️⃣ 选择执行模式
    │   ├── LEGACY: 传统for循环ReAct
    │   ├── GRAPH: 自研Graph状态机
    │   └── GRAPH_PARALLEL: SAA并行图引擎
    │
    └── 5️⃣ ReAct推理循环 (核心!)
        ┌──────────────────────────────────┐
        │  ┌────┐    ┌──────┐    ┌─────┐  │
        │  │ LLM │───→│ BRANCH │───→│ TOOL │──┐
        │  └──┬─┘    └──┬───┘    └─────┘  │
        │     ↑         │                   │
        │     │         ↓                   │
        │     │       ┌─────┐              │
        │     │       │ END │              │
        │     │       └─────┘              │
        │     └────────────────────────────┘
        │                                     │
        │  LLM节点: 调用大模型,获取响应       │
        │  BRANCH节点: 判断是否有工具调用       │
        │  TOOL节点: 执行工具,获取结果        │
        │  END节点: 输出最终答案               │
        └──────────────────────────────────┘
5

LLM调用层 — 模型调用与流式处理

核心类: ModelRegistry → PooledChatModel → TrackedChatModel

ModelRegistry.getModel(modelId)
    │
    ├── 查找Provider (智谱/通义千问/DeepSeek/MiniMax/GPT4Free)
    ├── 检查降级状态 → 如果已降级,透明切换到备用Provider
    └── 用TrackedChatModel装饰 (记录Token消耗、延迟)
        │
        ▼
PooledChatModel.call() / stream()
    │
    ├── ApiKeyPool.acquire() → 轮转/加权随机选择一个Key
    ├── 如果Key被限流(429) → 冷却Key → 尝试下一个Key
    ├── 如果所有Key耗尽 → DegradationManager熔断,路由到降级链
    └── 调用实际LLM API (流式SSE)
        │
        ├── AnthropicStreamingFixConfig 处理thinking类型
        ├── 每个chunk通过Flux推送给上层
        └── Token统计: inputTokens, outputTokens, cacheTokens
6

安全防护层 — 纵深防御

防护时机: 贯穿整个请求生命周期

安全防护贯穿全链路:

  输入阶段                    处理阶段                    输出阶段
  ┌──────────┐              ┌──────────┐              ┌──────────┐
  │ 输入扫描  │              │ 提示词隔离│              │ 输出扫描  │
  │ InputScanner│           │ PromptIsolation│         │ OutputScanner│
  │           │              │          │              │           │
  │· 正则检测 │              │· XML包裹 │              │· 金丝雀检测│
  │  (20+模式)│              │· 安全规则 │              │· 敏感模式 │
  │· 关键词   │              │· 金丝雀令牌│             │· API Key  │
  │  (40+词条)│              │          │              │· 系统上下文│
  │· 长度检查 │              │          │              │           │
  └──────────┘              └──────────┘              └──────────┘
       │                          │                         │
       ▼                          ▼                         ▼
   危险→拒绝                  隔离用户输入               泄露→替换/拒绝
   可疑→累计                 防止越狱                   敏感信息→脱敏
7

响应返回 — SSE流式事件

最终通过SSE(Server-Sent Events)将响应流式推送给前端:

SSE事件类型含义示例
react推理中间文本"我来分析一下这份简历..."
thinking思维链(扩展思考)模型内部推理过程
tool工具调用事件正在执行搜索工具...
final_answer最终答案完整的AI回复内容
done会话结束标记{}
guard_blocked安全拦截检测到注入攻击

三、完整时序图

用户          前端Vue        Controller      编排器         Agent         LLM引擎       Key池
 │              │              │              │              │              │            │
 │─输入消息───→│              │              │              │              │            │
 │              │─POST+SSE───→│              │              │              │            │
 │              │              │─JWT认证─────→│              │              │            │
 │              │              │              │─熔断检查───→│              │            │
 │              │              │              │─限流检查     │              │            │
 │              │              │              │─创建Task     │              │            │
 │              │              │              │─提交线程池──→│              │            │
 │              │              │              │              │─构建提示词   │            │
 │              │              │              │              │─安全包装     │            │
 │              │              │              │              │─选择模型────→│            │
 │              │              │              │              │              │─获取Key──→│
 │              │              │              │              │              │←──Key────│
 │              │              │              │              │              │─LLM API──→│
 │              │              │              │              │              │←─流式响应──│
 │              │              │              │              │←──SSE流─────│            │
 │              │              │              │              │─ReAct循环?  │            │
 │              │              │              │              │ 是→工具调用  │            │
 │              │              │              │              │ 否→最终答案  │            │
 │              │←──SSE事件流─────────────────────────────│              │            │
 │←──实时显示──│              │              │              │              │            │
 │              │              │              │─记录Token/延迟             │            │
 │              │              │              │─释放信号量   │              │            │
 │              │              │              │─记录完成     │              │            │

四、面试高频问题

Q: 一条消息从发到收到回复,经过了哪些关键步骤?
A: 前端发起POST+SSE请求 → JWT认证 → Agent编排器(熔断/限流/线程池) → Agent执行(构建提示词/安全包装/工具注入) → ReAct推理循环(LLM调用→工具执行→循环/结束) → SSE流式响应返回前端。关键亮点:双层信号量并发控制、活动超时+硬超时双层超时、5层安全防护贯穿全链路。
Q: ReAct循环是什么?为什么需要它?
A: ReAct(Reasoning + Acting)是一种让LLM"思考-行动-观察"交替进行的模式。LLM先思考应该做什么(Reasoning),然后执行一个工具调用(Acting),观察工具返回的结果(Observation),再继续思考。这比单次LLM调用更强大,因为Agent可以动态决定使用哪些工具、调用多少次,实现复杂任务的分步解决。本项目将ReAct建模为4节点Graph(LLM→BRANCH→TOOL/END),支持三种执行模式(传统/Graph/并行)。
Q: 流式响应是怎么实现的?
A: 使用SSE(Server-Sent Events)+ Spring WebFlux的Flux。LLM API返回流式响应(每个Token一个chunk),后端通过Flux操作符逐事件包装为ServerSentEvent推送。前端通过EventSource/fetch+ReadableStream实时接收。编排器还增加了活动超时机制:每收到一个SSE事件就重置超时计时器,这样长时间ReAct循环不会被误杀。
Q: 如何保证一条消息不会执行无限长时间?
A: 三层超时机制:1) 活动超时(默认120s),每个SSE事件重置;2) 硬超时(3倍活动超时),无论如何强制终止;3) Graph引擎maxSteps(默认64步),防止ReAct死循环。此外还有熔断器保护:如果Agent连续失败率超过阈值,直接熔断不再接受请求。