chat请求链路设计
一、GrpcRequestDispatcher 在 Server 端的角色
SnailAiServerGrpcServer 是 Server 端的 gRPC 服务器(监听 1789 端口),它接收来自 Agent(Client)的回调请求,而非用户的聊天请求。核心逻辑在 handleUnaryRequest 方法:
private void handleUnaryRequest(GrpcSnailAiRequest request, StreamObserver<GrpcSnailAiResult> observer) {
String uri = request.getMetadata().getUri();
// 委托给 GrpcRequestDispatcher 按 URI 路由到具体 Handler
GrpcSnailAiResult result = grpcRequestDispatcher.dispatchUnary(
request.getReqId(), uri, headers, request.getBody());
...
}
grpcRequestDispatcher 的作用就是一个 URI 路由器:它持有所有 GrpcRequestHandler 的列表,遍历找到 supports(uri) 匹配的 handler 来处理请求。
在 Server 端,GrpcRequestDispatcher 注册的 Unary Handler 包括:
结论:Server 端的 GrpcRequestDispatcher 与用户的 chat 请求无直接关系。它处理的是 Agent 主动回调 Server 的请求(心跳、保存对话、RAG 搜索等)。
二、同一个类,两个角色 —— GrpcRequestDispatcher 是 Server/Client 共用的
关键在 GrpcRequestDispatcher 的注释:gRPC 请求统一分发器 — Server / Client 共用,按 supports(uri) 路由。
Spring 自动注入所有 GrpcRequestHandler 和 GrpcStreamingRequestHandler 实现到这两个 List:
private final List<GrpcRequestHandler> unaryHandlers;
private final List<GrpcStreamingRequestHandler> streamingHandlers;
在 Agent(Client)端,GrpcRequestDispatcher 被注入到 ClientRequestDispatcher,后者是它的包装器:
public class ClientRequestDispatcher {
private final GrpcRequestDispatcher grpcRequestDispatcher;
// Unary 请求处理(如 /ping)
public ServerCalls.UnaryMethod<...> unaryHandler() { ... }
// Server-Streaming 请求处理(如 /chat/dispatch) ← chat 走这里
public ServerCalls.ServerStreamingMethod<...> streamingHandler() { ... }
}
三、Agent 端 Chat 请求的完整执行链路
下面结合代码,一步步分析每次 chat 请求在 Agent 端的完整执行流程:
第 1 步:Client gRPC 服务接收请求
SnailAiAgentAutoConfiguration.onApplicationReady() 在容器就绪后启动 Client gRPC 服务:
clientGrpcServer = new ClientGrpcServer(
props.getPort(), // 1790 端口
dispatcher.unaryHandler(), // Unary 路由
dispatcher.streamingHandler()); // Streaming 路由 ← chat 走这
clientGrpcServer.start();
当 Server 通过 gRPC Streaming 将 /chat/dispatch 请求推送到 Agent 的 1790 端口时,ClientGrpcServer 接收并交给 ClientRequestDispatcher.streamingHandler()。
第 2 步:GrpcRequestDispatcher 按 URI 路由
ClientRequestDispatcher.streamingHandler() 委托给 GrpcRequestDispatcher.dispatchStreaming():
grpcRequestDispatcher.dispatchStreaming(uri, headers, request.getBody(), request.getReqId(), observer);
GrpcRequestDispatcher.dispatchStreaming() 遍历所有 GrpcStreamingRequestHandler,找到 supports("/chat/dispatch") 的 handler —— 即 ChatDispatchStreamingHandler。
第 3 步:ChatDispatchStreamingHandler 处理 Chat 分发
public void handle(GrpcHandlerRequest request, StreamObserver<GrpcSnailAiResult> observer) {
ChatDispatchRequest dispatchRequest = parseDispatchRequest(request.getBody());
chatSessionRuntime.execute(ChatSessionRequest.builder()
.dispatchRequest(dispatchRequest)
.textConsumer(text -> handleTextChunk(...)) // 文本分片 → 回传 Server
.thinkingConsumer(thinking -> handleThinkingChunk(...)) // 思考过程 → 回传
.completionConsumer(completion -> handleCompletion(...)) // 完成 → 回传 + 关闭流
.errorConsumer(error -> handleError(...)) // 错误 → 回传
.build());
}
第 4 步:ChatSessionRuntime 编排执行
ChatSessionRuntime.execute():
public void execute(ChatSessionRequest request) {
activeChatCounter.increment(); // 活跃聊天数 +1
ToolRuntime.ToolResolution resolution = toolRuntime.resolve(request.getDispatchRequest());
// ↑ 解析工具:Skill、RAG、基础工具等
chatExecutor.executeStream(ClientChatExecutionRequest.builder()
.dispatchRequest(request.getDispatchRequest())
.tools(resolution.getTools()) // 解析出的工具列表
.messageDeltaConsumer(request.getTextConsumer())
.thinkingDeltaConsumer(request.getThinkingConsumer())
.completionConsumer(...)
.errorConsumer(...)
.build());
}
第 5 步:ClientChatExecutor 执行 LLM 调用
ClientChatExecutor.executeStream() → streamResponses():
public Flux<ChatResponse> streamResponses(...) {
ChatClient chatClient = chatClientFactory.build(...); // 构建 Spring AI ChatClient
Prompt prompt = promptFactory.build(dispatchRequest); // 构建 Prompt
return chatClient.prompt(prompt)
.advisors(a -> a
.param(ClientAdvisorKeys.DISPATCH, dispatchRequest)
.param(ClientAdvisorKeys.STREAM_STATE, state)
.param(ClientAdvisorKeys.CHUNK_CONSUMER, ...)
.param(ClientAdvisorKeys.THINKING_CONSUMER, ...))
.stream().chatResponse(); // 流式调用 LLM
}
这里通过 ChatClientFactory 构建带有 Advisor 链的 ChatClient,Advisor 包括:
InterceptorChainAdvisor — 拦截器链(日志等)
TokenUsageCollectorAdvisor — Token 用量采集
ThinkingCollectorAdvisor — 思考过程采集
StreamChunkForwarderAdvisor — 流分片转发(将每个 chunk 推送给 consumer → 回传 Server)
四、总结:完整数据流
用户浏览器 → HTTP → Server (AgentController)
→ Handler 责任链 (Order 10~60: 订阅检查/Agent解析/模型解析/MCP/Skill/上下文)
→ LlmCallHandler (Order 80) 发起 gRPC Streaming 调用
→ ──── gRPC 网络 ────→
Agent ClientGrpcServer (1790端口)
→ ClientRequestDispatcher.streamingHandler()
→ GrpcRequestDispatcher.dispatchStreaming() ← 按 URI 路由
→ ChatDispatchStreamingHandler.handle() ← 匹配 /chat/dispatch
→ ChatSessionRuntime.execute() ← 解析工具 + 管理生命周期
→ ClientChatExecutor.executeStream() ← Spring AI ChatClient 流式调 LLM
→ ──── 流式回传 ────→
每个 text/thinking chunk 通过 observer.onNext() → Server → 浏览器 SSE
所以 GrpcRequestDispatcher 在 Agent 端 确实参与了每一次 chat 请求的执行 —— 它是 Agent gRPC 服务内部的 URI 路由器,将 /chat/dispatch 请求分发到 ChatDispatchStreamingHandler。而在 Server 端,同一个类的实例处理的是 Agent 的回调请求(心跳、对话保存等),不直接参与 chat 分发。

浙公网安备 33010602011771号