
裸调 LLM APIJava 的 HTTP 客户端就够框架三行代码就能跑通报文却从没看清过。哪天框架出问题日志里全是框架自己的词汇你连「正确的报文长什么样」都不知道。这篇把框架摘掉用 JDK 自带的 HttpClient 裸调一次模型接口tool_use 的原始 JSON 全部见光工单循环自己转一圈。做过一次裸调所有框架的文档就都变成了「翻译稿」。一次裸调的报文往返调模型接口就是一次普通的 HTTP POST。请求体分三块model 指定用哪个模型messages 是对话历史tools 是工具清单相当于接口文档。{model:claude-sonnet-4-5,messages:[{role:user,content:北京今天多少度}],tools:[{name:get_weather,description:查询指定城市的当前天气,input_schema:{type:object,properties:{city:{type:string}},required:[city]}}]}响应体里content 是一个块数组需要调用工具时其中会有一块 tool_use带着工具名和参数。{content:[{type:tool_use,id:toolu_01,name:get_weather,input:{city:北京}}],stop_reason:tool_use}字段名按主流 API 的公开文档口径整理Anthropic 的工具定义用 input_schema其他家叫 parameters形状相同核验 2026-10-25。模型 API你的 Java 代码模型 API你的 Java 代码你的代码执行 getWeather(北京)POST /messagesmessages toolscontent 含 tool_use要调 get_weatherPOST /messages追加 tool_resultcontent 是最终文字回答自己转一圈工单循环看懂报文之后循环的四步全是你写过的代码。第一步发请求。第二步解析响应看 stop_reason 是不是 tool_use是就取出工具名和参数。第三步执行工具也就是调用你自己的普通方法。第四步把结果包成 tool_result 塞进 messages 再发一次请求直到 stop_reason 不再是 tool_use。没有框架循环只有四个 HTTP 动作的厚度。框架替你做的其实是三件事报文的拼装、循环的托管、各家供应商差异的抹平。为什么值得裸一次排查的底牌。框架报错时你能从框架日志反推「原始报文应该长什么样」定位是配置错了还是供应商改了字段。换供应商的基准。裸调一遍 A 家再裸调一遍 B 家字段差异当场对比选型不再靠评测软文。性能的真相。一次工单循环实际是两次以上的 HTTP 往返延迟的账单在裸调里看得最清楚这是调优的起点。亲手跑下面用一段 Java 拼请求报文、发请求、解析响应。没有配置 AI_API_KEY 时走演示模式打印报文并解析一份模拟响应形状和真实响应一致。直接运行java BareLlmCall.javaJDK 11。// 运行java BareLlmCall.java// 裸调一次模型接口。无 AI_API_KEY 时演示模式打印并解析报文。importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;publicclassBareLlmCall{staticfinalStringREQUEST_BODY { model: claude-sonnet-4-5, messages: [ {role: user, content: 北京今天多少度} ], tools: [ { name: get_weather, description: 查询指定城市的当前天气, input_schema: { type: object, properties: {city: {type: string}}, required: [city] } } ] } ;staticfinalStringMOCK_RESPONSE { content: [ { type: tool_use, id: toolu_01, name: get_weather, input: {city: 北京} } ], stop_reason: tool_use } ;publicstaticvoidmain(String[]args)throwsException{StringapiKeySystem.getenv(AI_API_KEY);Stringresponse;if(apiKeynull){System.out.println( 演示模式这就是将要发出的请求体 );System.out.println(REQUEST_BODY);responseMOCK_RESPONSE;}else{HttpRequestrequestHttpRequest.newBuilder().uri(URI.create(https://api.example.com/v1/messages)).header(x-api-key,apiKey).header(anthropic-version,2023-06-01).header(content-type,application/json).POST(HttpRequest.BodyPublishers.ofString(REQUEST_BODY)).build();responseHttpClient.newHttpClient().send(request,HttpResponse.BodyHandlers.ofString()).body();}System.out.println( 响应体 );System.out.println(response);// 演示级解析看懂形状为目的生产请用 JSON 库if(response.contains(\tool_use\)){Stringtoolextract(response,\name\:,,);Stringcityextract(response,\city\:,});System.out.println(循环第二步发现工单工具tool.trim()参数 citycity.replace(\,).trim());System.out.println(循环第三步执行 getWeather(city.replace(\,).trim()));System.out.println(循环第四步把结果包成 tool_result追加进 messages 再发一次。);}else{System.out.println(stop_reason 不是 tool_use这就是最终回答。);}}staticStringextract(Stringbody,Stringkey,StringendMark){intibody.indexOf(key);if(i0){return?;}intsikey.length();intebody.indexOf(endMark,s);returnbody.substring(s,e0?e:body.length());}}记住一句话框架是翻译稿报文才是原文原文只在裸调里见过一次就再也不会忘。适用边界这一篇管非流式的最小调用。流式响应的 SSE 报文、各家字段的完整对照表、以及鉴权头的差异Bearer 与 x-api-key是另外的题。演示的字符串解析只为看形状生产代码请用 JSON 库。报文按 2026-10-25 的主流 API 文档口径整理字段名以你用的供应商为准。跑完把演示模式的报文存下来它就是你以后排查一切框架问题的对照基准。