Server端Grpc调用Client(Agent应用)

当 Server 通过 gRPC Streaming 将 /chat/dispatch 请求推送到 Agent 的 1790 端口时,ClientGrpcServer 接收并交给 ClientRequestDispatcher.streamingHandler()。

整体理解:不是"推送",而是"Server 作为 gRPC 客户端主动调用 Agent"
这里的关键在于理解 gRPC Server Streaming 模式的语义:
Server(指 Server 端应用)作为 gRPC Client,主动向 Agent 的 gRPC Server 发起调用
Server Streaming 表示:Server 发一个请求,Agent 可以返回多个响应(流式)
所以 "推送" 实际上是 Server 作为 gRPC 客户端,调用 Agent gRPC 服务端的 Server Streaming 接口
 

第一步:Agent 启动时,Server 预先建立 gRPC Channel
Agent 启动后通过心跳注册到 Server。在 ClientInstanceManager.registerOrUpdate() 中:

public void registerOrUpdate(ClientRegistration registration) {
    liveInstances.compute(key, (k, existing) -> {
        if (existing == null) {
            // 新客户端注册:创建 ClientInstanceInfo
            ClientInstanceInfo info = new ClientInstanceInfo(
                    registration.getAppId(), registration.getHostId(),
                    registration.getHostIp(), registration.getGrpcPort());
            // ★ 关键:创建到 Agent gRPC 端口的 ManagedChannel
            info.setChannel(createChannel(info.getHostIp(), info.getGrpcPort()));
            return info;
        }
        // 已有实例:确保 channel 存活
        ensureChannelAlive(existing);
        return existing;
    });
}

createChannel() 方法创建一个到 Agent 的 gRPC 通道:

private ManagedChannel createChannel(String hostIp, int grpcPort) {
    return NettyChannelBuilder.forAddress(hostIp, grpcPort)  // Agent 的 IP + 端口(如 1790)
            .usePlaintext()
            .keepAliveTime(30, TimeUnit.SECONDS)
            .keepAliveTimeout(10, TimeUnit.SECONDS)
            .idleTimeout(5, TimeUnit.MINUTES)
            .maxInboundMessageSize(10 * 1024 * 1024)
            .build();
}

这个 ManagedChannel 被保存在 ClientInstanceInfo.channel 字段中,是一个长连接,后续所有对该 Agent 的 gRPC 调用都复用这个 channel。

第二步:ContextCollectorHandler 选择目标 Agent 实例
ContextCollectorHandler.handle() 在责任链 Order(75) 执行:

public void handle(AgentChatContext ctx) {
    String appId = agent.getAppId();
    
    // 获取该 appId 下所有存活的 Agent 实例
    List<ClientInstanceInfo> candidates = instanceManager.getAliveInstances(appId);
    
    // 按路由策略选择一个(默认"最小负载"策略)
    ClientRouteStrategy routeStrategy = routeStrategyManager.get(routeKey);
    ClientInstanceInfo target = routeStrategy.select(candidates, ctx.getConversationId());
    
    // ★ 存入上下文,后续 LlmCallHandler 使用
    ctx.setTargetClient(target);
}

此时 target 对象里就包含了之前心跳注册时创建的 ManagedChannel。

第三步:LlmCallHandler 通过 Channel 发起 gRPC Server Streaming 调用
LlmCallHandler.handle() 在责任链 Order(80) 执行,这是实际发起 gRPC 调用的地方:

public void handle(AgentChatContext ctx) {
    // 1. 组装请求体(包含 Agent 配置、模型配置、历史消息、MCP、Skill 等全部上下文)
    ChatDispatchRequest request = buildDispatchRequest(ctx);
    String dispatchBody = JsonUtil.toJsonString(request);

    // 2. ★ 关键调用:通过 target client 的 channel 发起 Server Streaming 调用
    GrpcChannelUtil.sendServerStreaming(
            ctx.getTargetClient().getChannel(),      // ← 第一步建立的到 Agent 1790 端口的 channel
            UriConstants.CHAT_DISPATCH,               // ← URI: "/chat/dispatch"
            dispatchBody,                             // ← 请求体 JSON
            Map.of(),                                 // ← 空 headers
            new ChatStreamObserver(ctx, ...));         // ← 流式响应观察者
}

第四步:GrpcChannelUtil 底层如何发送
GrpcChannelUtil.sendServerStreaming() 的实现:

public static void sendServerStreaming(ManagedChannel channel, String uri, String body,
                                       Map<String, String> headers,
                                       StreamObserver<GrpcSnailAiResult> responseObserver) {
    // 1. 构建 Protobuf 请求
    GrpcSnailAiRequest request = buildRequest(uri, body, headers);
    // request.metadata.uri = "/chat/dispatch"
    // request.body = "{...ChatDispatchRequest JSON...}"
    // request.reqId = 自增 ID

    // 2. 构建 MethodDescriptor(描述 gRPC 方法元数据)
    MethodDescriptor<GrpcSnailAiRequest, GrpcSnailAiResult> md =
            buildMethodDescriptor(
                    MethodDescriptor.MethodType.SERVER_STREAMING,  // ★ Server Streaming 模式
                    "ServerStreamingRequest",                       // 服务名
                    "serverStreamingRequest");                      // 方法名
    // 最终 fullMethodName = "ServerStreamingRequest/serverStreamingRequest"

    // 3. ★ 发起异步 Server Streaming 调用
    ClientCalls.asyncServerStreamingCall(
            channel.newCall(md, CallOptions.DEFAULT),  // 通过 channel 创建 call
            request,                                    // 请求消息
            responseObserver);                          // 回调观察者
}

这里就是"推送"的本质:ClientCalls.asyncServerStreamingCall() 是 gRPC Java 标准 API,它通过已有的 ManagedChannel(TCP 长连接到 Agent 的 1790 端口)发送一个 HTTP/2 请求,帧类型为 SERVER_STREAMING,告诉对端:"我要调用你的 ServerStreamingRequest/serverStreamingRequest 方法,URI 是 /chat/dispatch,请流式返回结果"。

第五步:Agent 端 ClientGrpcServer 接收请求
Agent 启动时 ClientGrpcServer.start() 注册了 Streaming 服务定义:

public void start() throws Exception {
    server = NettyServerBuilder.forPort(port)   // 1790 端口
            .addService(GrpcServiceDefinitionBuilder.createUnaryServiceDefinition(unaryHandler))
            .addService(GrpcServiceDefinitionBuilder.createServerStreamingServiceDefinition(streamingHandler))
            //                                              ↑ 注册 Server Streaming 服务
            .build().start();
}

createServerStreamingServiceDefinition() 注册的方法全名恰好是 "ServerStreamingRequest/serverStreamingRequest",与 Server 端调用的完全匹配:

MethodDescriptor.MethodType.SERVER_STREAMING,
"ServerStreamingRequest",     // 服务名
"serverStreamingRequest"      // 方法名

当 gRPC 请求到达 1790 端口时,Netty 根据方法名路由到 streamingHandler,即 ClientRequestDispatcher.streamingHandler():

public ServerCalls.ServerStreamingMethod<...> streamingHandler() {
    return (request, observer) -> {
        String uri = request.getMetadata().getUri();  // "/chat/dispatch"
        grpcRequestDispatcher.dispatchStreaming(       // 按 URI 路由
                uri, headers, request.getBody(), request.getReqId(), observer);
    };
}

当 gRPC 请求到达 1790 端口时,Netty 根据方法名路由到 streamingHandler,即 ClientRequestDispatcher.streamingHandler():

public ServerCalls.ServerStreamingMethod<...> streamingHandler() {
    return (request, observer) -> {
        String uri = request.getMetadata().getUri();  // "/chat/dispatch"
        grpcRequestDispatcher.dispatchStreaming(       // 按 URI 路由
                uri, headers, request.getBody(), request.getReqId(), observer);
    };
}

第六步:流式响应回传
Agent 处理过程中,每个 LLM 输出的文本 chunk 通过 observer.onNext() 沿同一条 gRPC 流回传。Server 端的 ChatStreamObserver 接收:

public void onNext(GrpcSnailAiResult result) {
    ChatStreamResponse data = JsonUtil.parseObject(result.getData(), ChatStreamResponse.class);
    switch (data.getType()) {
        case TYPE_TEXT      -> handleText(data);       // 转发给前端 SSE
        case TYPE_THINKING  -> handleThinking(data);   // 转发思考过程
        case TYPE_COMPLETION-> handleCompletion(data); // 持久化 + 关闭流
        case TYPE_ERROR     -> handleError(data);      // 转发错误
    }
}

总结:完整调用链路图

┌─ Server (端口 8900) ──────────────────────────────────────────────┐
│                                                                    │
│  LlmCallHandler.handle()                                          │
│    │                                                               │
│    ├─ buildDispatchRequest() → ChatDispatchRequest JSON            │
│    │                                                               │
│    └─ GrpcChannelUtil.sendServerStreaming(                         │
│          targetClient.getChannel(),  ←── 心跳时建立的 TCP 长连接    │
│          "/chat/dispatch",                                         │
│          dispatchBody,                                             │
│          new ChatStreamObserver(...))  ←── 异步回调观察者           │
│               │                                                    │
│               │  ClientCalls.asyncServerStreamingCall()            │
│               │  HTTP/2 帧: ServerStreamingRequest/serverStreamingRequest │
│               │                                                    │
└───────────────┼────────────────────────────────────────────────────┘
                │  TCP (Netty, HTTP/2)
                ▼
┌─ Agent (端口 1790) ───────────────────────────────────────────────┐
│                                                                    │
│  ClientGrpcServer (NettyServerBuilder)                            │
│    │  匹配方法名 "ServerStreamingRequest/serverStreamingRequest"   │
│    ▼                                                               │
│  ClientRequestDispatcher.streamingHandler()                       │
│    │  提取 URI = "/chat/dispatch"                                 │
│    ▼                                                               │
│  GrpcRequestDispatcher.dispatchStreaming()                        │
│    │  遍历 streamingHandlers, 匹配 supports("/chat/dispatch")     │
│    ▼                                                               │
│  ChatDispatchStreamingHandler.handle()                            │
│    │  解析 ChatDispatchRequest                                    │
│    ▼                                                               │
│  ChatSessionRuntime.execute() → ClientChatExecutor → LLM 调用     │
│    │                                                               │
│    └─ 每个 chunk → observer.onNext() ──────────── 流式回传 ──→ Server │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

核心要点:"推送"本质上是 Server 作为 gRPC 客户端,通过心跳时预先建立的 ManagedChannel(TCP 长连接),使用 ClientCalls.asyncServerStreamingCall() 异步调用 Agent 的 Server Streaming 接口。这不是传统意义上的"推送"(如 WebSocket),而是标准的 gRPC 请求-流式响应模式。

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