从用户发送一条消息到收到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 │
└─────────────────────────────────────────────────────────────────┘
入口文件: 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
入口文件: ConversationController.java
请求到达后端后:
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);
}
核心类: 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)
核心类: 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节点: 输出最终答案 │
└──────────────────────────────────┘
核心类: 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
防护时机: 贯穿整个请求生命周期
安全防护贯穿全链路:
输入阶段 处理阶段 输出阶段
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 输入扫描 │ │ 提示词隔离│ │ 输出扫描 │
│ InputScanner│ │ PromptIsolation│ │ OutputScanner│
│ │ │ │ │ │
│· 正则检测 │ │· XML包裹 │ │· 金丝雀检测│
│ (20+模式)│ │· 安全规则 │ │· 敏感模式 │
│· 关键词 │ │· 金丝雀令牌│ │· API Key │
│ (40+词条)│ │ │ │· 系统上下文│
│· 长度检查 │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
危险→拒绝 隔离用户输入 泄露→替换/拒绝
可疑→累计 防止越狱 敏感信息→脱敏
最终通过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/延迟 │ │ │ │ │ │─释放信号量 │ │ │ │ │ │ │─记录完成 │ │ │