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 包括:

image                                                                                                                                                                                                                 结论: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 分发。

posted @ 2026-09-22 16:15  雨也飘柔  阅读(2)  评论(0)    收藏  举报