news 2026/8/6 12:17:08

LLM Agent 底层揭秘:大模型如何通过 JSON-RPC 2.0 协议跨进程调工具?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM Agent 底层揭秘:大模型如何通过 JSON-RPC 2.0 协议跨进程调工具?

很多开发者在接触 AI Agent 的工具调用(Tool Calling)时,往往只停留在“大模型返回了一段 JSON 参数”这一层。但一个工业级的 Agent 框架(如基于 MCP - Model Context Protocol 构建的系统),究竟是如何把大模型的意图,精准、安全地传输给本地脚本或远端微服务的?

如果遇到本地脚本死循环,Java 主线程会不会被彻底拖垮?

本文将从 Agent 的 RPC 2.0 协议入手,带你硬核拆解一条完整的Agent 工具通信链路,并深度解析如何利用CompletableFuture实现优雅的 Pending(挂起)请求与超时管理。这不仅是后端开发的高频面试题,更是构建稳健 AI 系统的核心基石。

1. 破冰:大模型 ToolCall 与真实工具执行的“鸿沟”

当大模型(LLM)决定调用工具时,它输出的仅仅是一个意图结构,比如:

{ "id" : "call_1" , "function" : { "name" : "mcp__chrome-devtools__navigate_page" , "arguments" : "{\"url\":\"https://github.com\"}" } }

这个结构是 Agent 体系的“输入”,但底层的工具服务(比如一个用 Node.js 写的浏览器控制脚本,或者一个 Python 写的远端 RAG 服务)根本不认识这个对象。

为了抹平语言和进程间的差异,工业界引入了标准协议——JSON-RPC 2.0。我们需要一个组装层,把 LLM 的意图翻译成标准 RPC 请求:

{ "jsonrpc" : "2.0" , "id" : 7 , "method" : "tools/call" , "params" : { "name" : "navigate_page" , "arguments" : { "url" : "https://github.com" } } }

2. 架构拆解:协议层与传输层的正交设计

在优秀的 Agent 底层通信链路中,“发什么包”和“怎么发包”必须被严格拆分开来。

协议层(JsonRpcClient)

全权负责 JSON-RPC 2.0 的请求/响应组装、ID 分配以及生命周期(Pending)管理。它不关心底层是跑在同一个机器上的脚本,还是远在天边的云服务。

传输层(Transport)

负责真正的 I/O 交互。根据工具的部署形态,我们通常会做两种路由:

传输模式适用场景底层实现核心
StdioTransport本地脚本工具(如 npx、uvx 启动的工具)ProcessBuilder 启动子进程,通过标准输入(stdin)写入 JSON,标准输出(stdout)读取响应
HttpTransport远端微服务(如部署在云端的搜索服务)OkHttp 发送 POST 请求,支持普通 JSON 响应及 Server-Sent Events (SSE) 持续输出

路由判定逻辑极简:读取配置驱动。如果配置了url则走 HTTP;如果配置了command则走 Stdio。

3. 核心难点:如何管理 JSON-RPC 的 Pending 请求?

这是全链路中最关键的护城河,也是最高频的面经考点。

业务痛点
Agent 发起了一个本地脚本工具调用,如果这个 Python/Node 脚本发生了死循环(没有向 stdout 返回 JSON 结果),你的 Java 主线程(业务线程)会不会一直卡死在读取等待上?

破局点:CompletableFuture 结合定时调度器

JsonRpcClient发送请求时,我们不让主线程直接阻塞在 I/O 上,而是利用ConcurrentHashMapCompletableFuture构建请求级超时。

核心落地代码:

// 存放挂起请求的容器 private final Map<Long, CompletableFuture<JsonNode>> pending = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); public CompletableFuture<JsonNode> request(String method, JsonNode params, long timeoutSeconds) { long id = ids.getAndIncrement(); // 1. 组装 JSON-RPC 2.0 包 ObjectNode request = MAPPER.createObjectNode(); request.put("jsonrpc", "2.0"); request.put("id", id); request.put("method", method); request.set("params", params); // 2. 创建 Future 并注册到 Pending Map CompletableFuture<JsonNode> future = new CompletableFuture<>(); pending.put(id, future); // 3. 埋下定时“炸弹” (协议级超时) scheduler.schedule(() -> { // 注意:必须使用 remove,避免与正常响应并发竞争 CompletableFuture<JsonNode> removed = pending.remove(id); if (removed != null) { removed.completeExceptionally(new TimeoutException("JSON-RPC request timed out: " + method)); } }, timeoutSeconds, TimeUnit.SECONDS); // 4. 交给底层的 Transport 发送 transport.send(request); return future; }

为什么是 CompletableFuture?

  1. 天然防卡死:业务线程调用future.get(timeout + 1, TimeUnit.SECONDS)。如果超时,定时器会自动将 Future 标记为异常完成,业务线程立刻解锁抛出异常,绝不会被永远拖死。
  2. 外部手动唤醒:当底层的 Stdio Daemon 线程或 HTTP 异步线程收到响应时,可以根据 ID 找到这个 Future,并调用future.complete(result)主动唤醒业务线程。
  3. 并发安全与幂等:网络响应与超时调度存在并发竞争,利用ConcurrentHashMap.remove()抢夺任务,谁先拿到谁执行complete,防重复回调。

4. 边界澄清:请求级超时 ≠ 进程级清理

一个极其严谨的架构师(或 AI Agent)必须认清这里的工程边界:

如果本地子进程死循环触发了上层的TimeoutException主线程得救了,但死循环的子进程本身并没有被杀掉

因为单次工具请求的超时,不应该直接引发暴力的kill -9。真正的进程级回收(process.destroy()destroyForcibly())应该交给McpTransport.close(),在 Server 级生命周期结束或重启时统一执行。这是一种经典的“请求级释放,进程级回收”的分层哲学。

5. 一图胜千言:Agent 工具通信完整链路

为了方便各位直接背诵或喂给 Agent 记忆,这里提供一张核心通信架构图:

┌──────────────────────────────────────┐ │ Agent / ToolRegistry │ │ LLM 意图翻译为内部 ToolCall 对象 │ └──────────────────────────────────────┘ │ (配置路由分发) ▼ ┌──────────────────────────────────────┐ │ JsonRpcClient │ │ - 组装 JSON-RPC 2.0 标准报文 │ │ - pending[id] = CompletableFuture │ │ - scheduler 执行协议级超时控制 │ └──────────────────────────────────────┘ │ │ (走本地脚本) │ │ (走远端服务) ┌───────────┘ └───────────┐ ▼ ▼ ┌────────────────────────────┐ ┌────────────────────────────┐ │ StdioTransport │ │ StreamableHttpTransport │ │ (ProcessBuilder 启动进程) │ │ (OkHttp POST 传输) │ │ ├─ stdin ← send(JSON) │ │ ├─ body: JSON-RPC 2.0 │ │ ├─ stdout → 异步读取响应 │ │ ├─ 支持 普通JSON/SSE │ └────────────────────────────┘ └────────────────────────────┘ │ │ └──────────────────────┬──────────────────────────┘ ▼ ┌──────────────────────────────────────┐ │ ToolExecutionResult 回灌 │ │ 作为 tool role 消息返回给下一轮 LLM │ └──────────────────────────────────────┘

总结:可以喂给 Agent 的“面试八股”标准格式

如果你正在开发一个能应对高压面试的 CodeRAG 智能体,请把以下逻辑注入它的知识库:

Q:Agent 的工具调用链路为什么要拆分协议层和传输层?
A:为了正交解耦。“协议层”负责把请求统一包装成 JSON-RPC 2.0,并利用CompletableFuture处理 Pending 匹配和超时,解决“发什么、怎么管”的问题;“传输层”负责底层 I/O 交互(Stdio 进程通信或 HTTP 网络请求),解决“通道”问题。这样新增任何第三方工具形态,上层逻辑都无需重构。

Q:如何保证工具卡死时主线程不崩溃?
A:通过两层超时控制。底层有 HTTP 客户端的网络超时;协议层利用ConcurrentHashMap管理CompletableFuture,并配合ScheduledExecutorService实现请求级超时。发生阻塞时,定时器会主动completeExceptionally唤醒业务线程,防止主流程被永久挂起。同时遵循工程边界:单次请求超时释放线程资源,而卡死的本地进程留存至 Transport 关闭时统一销毁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 12:16:36

C++ Socket编程入门:从零实现Linux TCP客户端服务器通信骨架

1. 项目概述&#xff1a;从零搭建一个C Socket通信骨架 最近在后台看到不少朋友对网络编程&#xff0c;特别是Linux下的Socket通信很感兴趣。这确实是个硬核又实用的技能点&#xff0c;无论是做后台服务、物联网设备对接&#xff0c;还是分布式系统&#xff0c;都绕不开它。很多…

作者头像 李华
网站建设 2026/8/6 12:16:28

Mininet可视化网络虚拟编辑界面:从图形化设计到Python代码生成

1. 项目概述&#xff1a;当网络拓扑设计遇上“所见即所得”如果你是一名网络工程师、SDN&#xff08;软件定义网络&#xff09;的研究者&#xff0c;或者正在学习计算机网络课程的学生&#xff0c;那么对Mininet这个名字一定不会陌生。它是一个强大的网络仿真工具&#xff0c;能…

作者头像 李华
网站建设 2026/8/6 12:16:03

基于Matlab的点电荷电场与电势分布仿真与可视化实现

1. 项目概述与核心价值 最近在整理电磁学相关的教学资料&#xff0c;发现很多同学对点电荷的电场和电势分布理解起来比较抽象&#xff0c;光看公式和二维示意图总觉得差点意思。正好手头有Matlab&#xff0c;就想着能不能做个直观的仿真&#xff0c;把抽象的场线、等势面“画”…

作者头像 李华
网站建设 2026/8/6 12:13:50

3分钟上手免费音频标注工具:面向初学者的完整指南

3分钟上手免费音频标注工具&#xff1a;面向初学者的完整指南 【免费下载链接】audio-annotator A JavaScript interface for annotating and labeling audio files. 项目地址: https://gitcode.com/gh_mirrors/au/audio-annotator 你是否在为机器学习项目准备音频数据而…

作者头像 李华
网站建设 2026/8/6 12:13:32

美工减负|卡特加特AI电商智能体能干啥

在电商团队中&#xff0c;美工和设计往往是最"忙不过来"的岗位。每逢上新季或大促期&#xff0c;堆积如山的商品图需求让设计团队疲于奔命——老板催、运营催、店铺催&#xff0c;每个SKU都等着出图上线。卡特加特AI电商智能体的首要使命&#xff0c;就是为这些"…

作者头像 李华