Java调用大模型从HTTP到框架:底层实践与工程选型指南

发布时间:2026/10/16 4:19:16
Java调用大模型从HTTP到框架:底层实践与工程选型指南 1. 先聊聊为什么要在 Java 里接大模型先说一个现象最近两三年大模型几乎成了后端开发的标配外挂。你要是在 Java 团队里负责业务系统大概率会遇到这种需求——做一个智能客服、写一个代码审查助手、给内部知识库加一个问答入口甚至只是帮运营自动生成一段文案。需求听着不复杂但真正落地的第一关就卡住了怎么在 Java 代码里调用大模型很多人的第一反应是去查某个大模型平台的官方 SDK。可实际上大部分主流大模型平台的核心接口就是一个 HTTP 接口所谓 SDK 也不过是帮你封装了请求和响应。也就是说搞懂了 HTTP 调用你就搞懂了大模型调用的底层逻辑再往上走才会理解那些开发框架到底帮你省了什么事。这篇文章我就从实际开发的角度把 Java 调大模型这条路完整捋一遍。先带你从最原始的 HTTP 请求出发把请求头、请求体、流式响应这些底层细节掰碎了讲然后再说说为什么封装框架是必然趋势以及怎么在真实项目里选型落地。适合正在接触大模型开发、想搞清楚底层原理又不满足于照着文档复制粘贴的 Java 后端开发者。我是做后端出身的一开始也是拿 HttpClient 硬怼大模型接口后来才逐步迁移到框架。这条路踩过的坑不少但走完一遍之后你会发现自己不仅会调大模型了对 HTTP 协议、网络编程、并发控制的理解也能上一个台阶。2. 从零开始的大模型调用本质是一场 HTTP 交互2.1 大模型 API 到底长什么样先帮你把大模型调用这个听起来很高大上的事情去神秘化。你在代码里发出一个请求把你想要的任务描述Prompt和一些参数发给大模型服务端服务端经过推理之后返回文本结果。这个过程对 Java 程序来说跟你调用任何一个第三方 RESTful 接口没有任何区别。看一下最典型的请求结构。绝大多数大模型平台都采用 POST 方式请求头里带上认证信息请求体是 JSON 格式里面至少包含模型名和消息列表。我拿一个最常见的标准形态举例长这样{ model: some-model-name, messages: [ { role: user, content: 用一句话介绍 Java 中的函数式编程 } ], temperature: 0.7, max_tokens: 512 }对应的响应也很有规律非流式情况下通常是一个 JSON里面包含模型的回复内容、Token 使用量和请求 ID 等元数据{ id: chatcmpl-something, object: chat.completion, created: 1734567890, model: some-model-name, choices: [ { index: 0, message: { role: assistant, content: 函数式编程是一种以函数为基本构建块的编程范式…… } } ], usage: { prompt_tokens: 13, completion_tokens: 42, total_tokens: 55 } }理解这一点非常重要。因为一旦你明白了它就是一个标准的 JSON over HTTP 接口你就不会对调用大模型产生畏惧感后面不管你换什么平台、什么 SDK核心思路都是相通的。唯一有所不同的是各家在消息格式、参数命名、鉴权方式上会有细微差异但骨架永远是这个骨架。小提示不同的平台在角色命名上略有差异有的用systemuserassistant有的叫instruction。但基本都是三条系统设定角色、用户输入角色、模型回复角色。这个三元结构是一切对话式大模型应用的基础。2.2 为什么必须先从手动 HTTP 请求开始我见过不少刚接触大模型开发的同事跳过了 HTTP 阶段直接上框架。结果一旦遇到问题完全不知道底层发生了什么。比如响应突然截断了框架把异常吞掉后返回空字符串你在上层排查半天也不知道根因在哪。这就是跳过底层直接上封装的代价。我的建议是不管最终是否使用框架第一步一定要用最原始的手段把 HTTP 调用跑通一遍。这不只是为了熟悉 API更是为了建立问题定位的感觉。当你亲手写出请求、亲手打印出响应体、亲眼看到网络异常长什么样后面再用框架时你会自然地知道应该去哪里排查问题。而且说句实在话很多小型项目、脚本任务、内部工具根本不需要引入一个重型框架。直接写一个几十行的 HTTP 调用工具类就能稳定跑很久。框架是帮你省事的不是给你增加玄学的。3. 手动写 HTTP 调用的三种常用姿势3.1 Java 原生 HttpClient稳扎稳打的基础功Java 11 之后JDK 自带的java.net.http.HttpClient已经完全够用了。它在绝大多数场景下都不逊色于第三方 HTTP 库而且不需要引入额外依赖。用原生 HttpClient 调大模型的代码并不复杂核心逻辑三步走构建请求、发送请求、解析响应。我们先看一个最简单的非流式调用。下面这段代码用的是 JDK 17 的语法我把关键逻辑都加了注释import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class LlmHttpClient { private static final String API_URL https://your-llm-api.example.com/v1/chat/completions; private static final String API_KEY sk-your-api-key; private final HttpClient client HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); public String chat(String userMessage) throws Exception { // 1. 构造请求体 JSON String requestBody { model: some-model-name, messages: [ {role: system, content: 你是一个简洁的助手}, {role: user, content: %s} ], temperature: 0.7, max_tokens: 1024 } .formatted(userMessage); // 2. 构建 HTTP 请求 HttpRequest request HttpRequest.newBuilder() .uri(URI.create(API_URL)) .header(Content-Type, application/json) .header(Authorization, Bearer API_KEY) .timeout(Duration.ofSeconds(30)) .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); // 3. 发送请求并获取响应 HttpResponseString response client.send(request, HttpResponse.BodyHandlers.ofString()); // 4. 解析响应这里先用字符串返回后面再看 JSON 解析 if (response.statusCode() 200) { return response.body(); } else { throw new RuntimeException(调用失败状态码: response.statusCode() , 响应: response.body()); } } }这段代码有几个细节值得展开说一下。第一连接超时connectTimeout和请求超时timeout是两码事。connectTimeout只管 TCP 连接建立的时间timeout是整次请求的最长等待时间。大模型推理通常比较慢尤其在某些高负载时段一个请求等十几秒很正常所以请求超时建议设置得宽裕一些正常 30 秒起步。第二鉴权方式。现在主流大模型平台普遍采用Authorization: Bearer token这种形式。虽然有少部分平台用的是api-key这种自定义头但 Bearer Token 是事实标准你先把这个学会以后遇到特殊平台再灵活变通。第三JSON 序列化。上面代码里我直接用文本拼接构造 JSON这只是为了演示原理。在实际项目里千万不要手写 JSON推荐使用 Jackson 或者 Gson。手动拼 JSON 不仅容易出现转义错误还会造成潜在的注入风险。后面我会给一个更规范的写法。原生 HttpClient 还有一个好兄弟叫sendAsync也就是异步版本。它返回一个CompletableFuture你可以在等待大模型响应期间干别的事情。这在做并发请求、批量处理场景下非常有用。比如你要同时对多个话题做摘要用同步方式就是一个一个等用异步方式可以并发发出多个请求整体耗时能大幅缩短。3.2 OkHttp补偿了原生的几个痛点JDK 原生 HttpClient 虽然够用但用久了还是会觉得少点什么。最常见的一个痛点是原生 HttpClient 默认没有连接池的超时淘汰机制连接管理不如老牌 HTTP 库细致另外一个痛点是请求响应日志全靠自己手动打。这时候 OkHttp 是一个很成熟的选择。OkHttp 的优势主要体现在三方面连接池复用更高效、拦截器机制非常灵活、超时配置更精细。尤其是拦截器简直是调试大模型接口的神器。你可以写一个日志拦截器把每个请求的 URL、请求头、请求体、响应状态、响应耗时全部打印出来排查问题的时候一目了然。简单看一下 OkHttp 版本的调用方式import okhttp3.*; import com.fasterxml.jackson.databind.ObjectMapper; public class LlmOkHttpClient { private final OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .addInterceptor(new HttpLoggingInterceptor()) // 日志拦截器 .build(); private final ObjectMapper mapper new ObjectMapper(); public String chat(String userMessage) throws Exception { MapString, Object body new HashMap(); body.put(model, some-model-name); body.put(messages, List.of( Map.of(role, system, content, 你是一个简洁的助手), Map.of(role, user, content, userMessage) )); body.put(temperature, 0.7); body.put(max_tokens, 1024); String json mapper.writeValueAsString(body); Request request new Request.Builder() .url(https://your-llm-api.example.com/v1/chat/completions) .addHeader(Content-Type, application/json) .addHeader(Authorization, Bearer sk-your-api-key) .post(RequestBody.create(json, MediaType.parse(application/json; charsetutf-8))) .build(); try (Response response client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new RuntimeException(HTTP response.code() : response.body().string()); } return response.body().string(); } } }细心的你可能已经发现了OkHttp 版比原生版多了一个writeTimeout。为什么要单独设置写超时因为当你发送一个很大的消息体比如一次请求里塞了好几千 Token 的上下文写入环节也可能耗时。分三个超时维度分别设置控制粒度自然更精细。我在真实项目中用 OkHttp 还有一个体会连接池复用对大模型频繁调用场景帮助很大。每次请求都新建连接不仅慢还可能把服务端的连接数打满。OkHttp 默认会复用连接池长连接模式下整体性能提升非常明显。3.3 Spring 生态的 RestTemplate 与 WebClient如果你的项目本身已经是 Spring Boot 技术栈那大概率不希望再单独维护一套 HTTP 调用逻辑。这时候有两个选择同步场景用RestTemplate异步和 Reactive 场景用WebClient。RestTemplate 的用法最直观。你可以把它封装成一个配置类注入到 Spring 容器里设置好拦截器和超时时间然后业务代码里直接注入使用。不过要提醒一点RestTemplate 在较新版本的 Spring 中官方已经标记为维护模式新项目更推荐用RestClient或WebClient。RestClient 是 Spring 6.1 引入的新同步客户端风格上更现代。但网上存量资料最多的还是 RestTemplate初学者搜索时容易看到过时信息注意甄别。如果你要做的功能是那种用户发起一次对话请求后端转发给大模型再把回复返回给前端而且你的技术栈是 Spring WebFlux那 WebClient 是天然的选择。因为它可以把大模型接口的响应流式转发给前端实现类似 ChatGPT 那种一个字一个字蹦出来的效果。这一点上同步的 RestTemplate 比较难做好。Service public class LlmService { private final RestClient restClient; public LlmService(RestClient.Builder builder) { this.restClient builder .baseUrl(https://your-llm-api.example.com/v1/chat/completions) .defaultHeader(Authorization, Bearer sk-your-api-key) .build(); } public String chat(String userMessage) { MapString, Object requestBody Map.of( model, some-model-name, messages, List.of( Map.of(role, user, content, userMessage) ), temperature, 0.7 ); return restClient.post() .contentType(MediaType.APPLICATION_JSON) .body(requestBody) .retrieve() .body(String.class); } }RestClient 的写法非常简洁它把请求构建、发送、响应解析的 API 设计得比较统一。从我的使用感受看RestClient 相比 RestTemplate 最大的改进是异常处理更清晰Web 相关的语义更明确。现在新起的 Spring Boot 3.x 项目如果不需要 WebFlux我会优先用 RestClient。4. 手动调用开始痛苦的时候框架帮你解决了什么4.1 手动调用的四大痛点当你用 HTTP 方式调大模型超过一两个星期一定会在某个时刻产生一种感觉怎么每调一个新的接口都要重写一遍请求逻辑这其实就是该上框架的信号了。我总结下手动调用在真实项目里暴露出来的问题。第一个痛点是协议细节被反复处理。Token 计数、消息格式转换、JSON 解析、错误码映射每个大模型平台都有细微差别每个接口你都要重新处理这些琐碎的边界情况。你本应该把精力花在业务逻辑上结果被这些协议细节消耗了。第二个痛点是流式响应处理麻烦。大模型最实用的一种调用方式是流式输出即一边生成一边返回。如果你用原生 HttpClient 做流式读取你需要处理 SSEServer-Sent Events协议手动解析data:开头的行处理[DONE]结束标志还要管理好连接的生命周期。代码写起来又长又容易出错。第三个痛点是并发与重试机制缺失。大模型服务端经常因为限流、瞬时过载返回 429 或者 5xx。生产环境必须有重试机制和退避策略。但手动调用时这些逻辑要自己实现很容易被忽略或者写得太简单导致雪崩。第四个痛点是上下文管理靠手动拼字符串。对话类应用必须维护历史消息数组上一次的回复要拼接进下一次的请求里。当消息长度超过模型上下文窗口时还要做截断或摘要。手动维护这件事写出来的代码既繁琐又脆弱。4.2 Java 生态里的两类大模型框架Java 生态里做 AI 应用开发现在比较成熟的有两条路线。一条是基于现有大模型平台 API 的轻量级封装代表性的有 LangChain4j。它把大模型调用、Prompt 模板、对话历史管理、文档知识检索RAG这些能力做成了统一的 Java API。你只要定义一个ChatLanguageModel接口然后在配置里指定某个具体平台的实现业务代码就不需要关心底层 HTTP 细节了。LangChain4j 的设计非常接近 Java 开发者的习惯大量使用接口、Builder 模式和泛型没有引入那种生硬的概念。另一条是 Spring 官方在做的 Spring AI。Spring AI 的目标是把 AI 能力作为 Spring 生态的一等公民来支持你可以像使用JdbcTemplate一样使用 AI 模板类。它有一个明显的特色跟 Spring Boot 的配置体系深度绑定比如在application.yml里配置 API Key、模型名这些参数框架自动装配好相关的 Bean。对于资深的 Spring 开发者来说上手成本几乎为零。这里我不去争论哪个框架更好。我的观点是如果你已经深度使用 Spring Boot选 Spring AI 更契合如果你需要一个不绑定 Web 框架的轻量解决方案或者需要非常灵活的 prompt 模板和 RAG 能力LangChain4j 会更顺手。注意框架不是银弹。无论用哪个框架上面第一节讲的 HTTP 基础你依然需要掌握。因为框架的配置项、异常信息、调试日志里处处都是 HTTP 层的概念。不懂底层框架出了问题你根本无从下手。5. 实操手把手搭建一个可用的对话服务5.1 环境准备与依赖引入开始动手之前先把环境准备好。我这里假设你已经有 Java 17 以上的 JDK 和一个 Maven 项目然后准备一个大模型平台的 API Key。如果你手头没有也可以先用官方提供的测试接口跑通流程。无论选哪条路都需要先把基础依赖引入 Maven 工程。用原生 HttpClient 的方式不需要额外依赖只需要 Jackson 做 JSON 解析用 LangChain4j 的方式需要引入对应的模块依赖。以 LangChain4j 为例如果你用的某个国内大模型平台兼容 OpenAI 协议那么引入依赖如下dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-core/artifactId version1.0.0-beta1/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version1.0.0-beta1/version /dependency如果是 Spring AI引入方式更简单直接在 Spring Boot 项目里加官方 starter 即可。需要注意的是框架版本和大模型服务的协议版本之间偶尔会存在兼容性问题引入依赖后先跑一次最简单的调用确认链路是通的再开始写业务代码。5.2 底层 HTTP 直连的完整实现下面我给出一个完整的、可以直接复用的原生 HTTP 调用实现。这个实现比前面的例子要完善得多包含了 JSON 序列化、超时控制、非 200 状态的错误处理和简单的异常分类。package com.example.llm; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.CompletableFuture; /** * 基于 JDK HttpClient 的大模型调用封装 * 适合快速验证、内部工具使用。 */ public class LlmClient { private static final String AUTH_HEADER Authorization; private static final String BEARER_PREFIX Bearer ; private final String apiUrl; private final String apiKey; private final ObjectMapper objectMapper; private final HttpClient httpClient; public LlmClient(String apiUrl, String apiKey) { this.apiUrl apiUrl; this.apiKey apiKey; this.objectMapper new ObjectMapper(); this.httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); } /** * 构造对话请求体 */ private String buildChatRequest(String userMessage, String systemPrompt) throws Exception { ObjectNode root objectMapper.createObjectNode(); root.put(model, some-model-name); ArrayNode messages root.putArray(messages); if (systemPrompt ! null !systemPrompt.isBlank()) { ObjectNode sysMsg messages.addObject(); sysMsg.put(role, system); sysMsg.put(content, systemPrompt); } ObjectNode userMsg messages.addObject(); userMsg.put(role, user); userMsg.put(content, userMessage); root.put(temperature, 0.7); root.put(max_tokens, 1024); return objectMapper.writeValueAsString(root); } /** * 同步调用 */ public String chatSync(String userMessage, String systemPrompt) throws Exception { HttpRequest request buildRequest(buildChatRequest(userMessage, systemPrompt)); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); return extractContent(response); } /** * 异步调用 */ public CompletableFutureString chatAsync(String userMessage, String systemPrompt) { try { HttpRequest request buildRequest(buildChatRequest(userMessage, systemPrompt)); return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(this::extractContent); } catch (Exception e) { return CompletableFuture.failedFuture(e); } } private HttpRequest buildRequest(String requestBody) { return HttpRequest.newBuilder() .uri(URI.create(apiUrl)) .header(Content-Type, application/json) .header(AUTH_HEADER, BEARER_PREFIX apiKey) .timeout(Duration.ofSeconds(60)) .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); } /** * 从响应体中提取 assistant 返回的内容 */ private String extractContent(HttpResponseString response) { if (response.statusCode() ! 200) { throw new LlmApiException(HTTP response.statusCode() : response.body()); } try { JsonNode root objectMapper.readTree(response.body()); JsonNode contentNode root.path(choices).path(0).path(message).path(content); if (contentNode.isMissingNode()) { throw new LlmApiException(响应中未找到 content 字段: response.body()); } return contentNode.asText(); } catch (JsonProcessingException e) { throw new LlmApiException(响应解析失败: response.body(), e); } } }这个工具类放出来之后我自己在真实项目里也染了几个项目。几点具体的取舍使用 Jackson 的ObjectNode和ArrayNode来构建 JSON避免手写字符串拼接的各种隐患。提取内容的逻辑统一封装在extractContent方法里上层业务不需要关心 JSON 结构。以后如果换了平台只需要改这一个方法。同步和异步方法都提供了。如果一次要调多个模型建议用异步版本加CompletableFuture.allOf做并发聚合。5.3 用框架封装的对比实现同样的功能如果用 LangChain4j 来实现代码量会显著减少。核心代码如下package com.example.llm; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.message.chat.ChatMessage; import dev.langchain4j.message.chat.UserChatMessage; import dev.langchain4j.message.chat.SystemChatMessage; import java.time.Duration; import java.util.List; public class LangChain4jExample { public void chatExample() { // 创建模型实例 ChatLanguageModel model OpenAiChatModel.builder() .apiKey(sk-your-api-key) .baseUrl(https://your-llm-api.example.com/v1) .modelName(some-model-name) .temperature(0.7) .timeout(Duration.ofSeconds(60)) .build(); // 构造对话消息 ListChatMessage messages List.of( new SystemChatMessage(你是一个 Java 开发助手回答要简洁专业。), new UserChatMessage(解释一下虚引用PhantomReference在内存管理中的作用) ); // 调用模型 String response model.chat(messages); System.out.println(response); } }注意这里我用的 LangChain4j 类名是简化举例不同版本 API 命名可能会有差异实际使用以你引入版本的官方文档为准。重点是感受框架思路你不再需要关心 JSON 构建、HTTP 状态码判断、content 字段提取这些细节了只需要关心业务本身——用什么样的消息去调用模型得到什么结果。如果你用 Spring AI那更贴近 Spring 开发者的习惯。典型写法是这样的Service public class AiAssistantService { private final ChatClient chatClient; public AiAssistantService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String ask(String question) { return chatClient.prompt() .systemMessage(你是一个专业的 Java 技术顾问回答问题时请给出代码示例。) .userMessage(question) .call() .content(); } }这段代码可读性非常高。尤其是ChatClient名字起得非常好你可以把它理解成 Spring 对 JDBC 的封装——底层具体怎么连数据库你不用管你只用JdbcTemplate执行业务 SQL 就行。Spring AI 的ChatClient就是大模型版的JdbcTemplate。5.4 流式响应从尾到头打通关键环节流式响应是大模型应用和普通 HTTP 接口最大的区别。普通接口你等全部结果返回然后整块展示大模型调用流式响应让结果像打字机一样逐字出现用户体验完全不同。底层流式原理是这样的大模型服务端返回的响应头里带有Content-Type: text/event-stream响应体按 SSE 格式分块返回。每个数据块以data:开头内容是一段 JSON最后有一个[DONE]标志结束。用 OkHttp 实现流式读取的代码结构是回调式的。你需要在 onResponse 回调里从 response body 打开一个字符流逐行读取。这里有个很关键的经验如果你按字节流读取再自己拼接字符串很容易出现中文乱码。正确做法是包一个 BufferedReader每次都按行读取。用 LangChain4j 做流式更省心。你只需要注册一个流式处理监听器框架帮你把 SSE 解析和行分割的工作做了model.chat(Java 有哪些最佳实践, new StreamingResponseHandlerAiMessage() { Override public void onNext(String token) { // 每生成一部分内容回调这个方法 System.out.print(token); } Override public void onComplete(ResponseAiMessage response) { // 全部生成完成 System.out.println(\n--- 完成 ---); } Override public void onError(Throwable error) { // 错误处理 error.printStackTrace(); } });流式响应写起来不难但有一个非常容易踩的坑连接管理的生命周期。流式响应可能会持续几十秒如果中间网络抖动连接断了而你的代码里没有对onError做处理用户端就会一直转圈没有任何提示。我的习惯是在onError里把连接中断这种事件及时反馈给前端而不是默默吞掉异常。6. 生产环境绕不开的痛点常见问题速查6.1 连接超时和读取超时怎么设置大模型接口是出了名的慢接口。第一轮交互因为要加载模型可能就要等 10 秒以上。我在最开始的项目里把所有超时都设成了 5 秒结果线上频繁报超时一开始还以为是网络问题折腾了两天才发现是超时设置太激进。我的建议是分场景设置场景连接超时读取超时聊天/对话类5~10 秒60 秒以上流式输出5~10 秒不设或设很长简单分类/改写5 秒30 秒批量离线任务10 秒120 秒以上读写超时的本质是在等待服务端推理。尤其流式输出模型生成一句话可能需要 20-30 秒如果你设置 30 秒读取超时可能正好卡在最后几个字上就断了。稳妥的做法是流式场景不设置 readTimeout而是通过其他手段做心跳检测。6.2 响应内容被截断怎么办有一次我调大模型做长文本摘要返回的内容总是在大约 500 字左右戛然而止。排查后发现问题出在max_tokens参数——我把这个参数理解成了最大输入长度实际上它是生成部分的 Token 上限。Token 这个单位也需要解释一下。Token 不等同于汉字1 个汉字大约相当于 1.5 到 2 个 Token英文一个单词通常约 1 到 2 个 Token。如果你设置max_tokens512实际能生成的中文文本大约只有 300 字左右。做长文本任务时要么把这个参数调大要么分批生成再拼接。另外还要注意不同的模型有各自的上下文窗口限制比如某些模型是 32K Token你要把输入的 Prompt 和生成的内容加在一起考虑超了就会报错。错误信息通常类似context length exceeded。6.3 上下文无限增长导致请求越来越慢对话应用中常见的一个问题是随着对话轮数增加消息数组越来越长每一次请求都携带全部历史消息导致请求体越来越大、处理越来越慢甚至直接超过上下文窗口。解决办法通常是滑动窗口或者摘要压缩。滑动窗口是指只保留最近 N 轮消息更早的消息丢弃。摘要压缩是把很早之前的整段对话大模型做一个摘要以后请求只带摘要再加最近几轮。这里有个小技巧判断一条消息是否需要保留可以按 Token 数量而不是轮数。比如设定历史消息总 Token 超过 2000 就裁剪最老的这样一个固定阈值规则实现简单、效果也不差。6.4 限流报错429和重试策略大模型平台普遍有限流策略。当你的应用请求频率过高服务端会返回 429 状态码。我在一次活动页中踩过这个坑用户一多并发请求上来瞬间把配额打满所有用户都报错。处理限流的常规做法是退避重试。所谓退避是指每次重试之前等待时间逐渐变长比如第一次等 1 秒、第二次等 2 秒、第三次等 4 秒最多重试 3 次。这个策略能避免在服务端还没恢复时持续压榨。Spring 的Retryable注解配合Recover就能实现也有现成的库如 Resilience4j 可以用。还有一个值得留意的点重试只适合幂等请求。大模型调用整体上属于幂等的因为同样的 Prompt 生成的结果虽然可能有随机性但不会产生副作用。然而如果用户是在做支付、下单等环节的文案生成你重试时要小心重复扣费虽然概率很低但第三方平台按 Token 计费时重试即重复计费。这种场景我会建议人工介入而不是无限自动重试。6.5 JSON 响应解析失败的通用解法大模型返回的格式偶尔会不符合规范。尤其是你用 JSON Mode 让模型输出结构化数据时模型可能夹杂一些 Markdown 标记比如把 JSON 包在代码块里或者因为 Token 截断导致 JSON 不完整。解决方案列表让模型输出的消息里明确说只输出 JSON不要包含任何其他文本或 Markdown 标记。在代码里对响应做一层清洗比如剔除首尾的json 和标记。用 Jackson 解析失败时的 lenient 配置容忍部分错误。如果截断了可以捕获异常后重新以更小生成长度请求一次。对关键 JSON 字段解析失败时给出默认值而不是让整个流程崩掉。7. 最后说几句实在的这篇文章从最底层的 HTTP 请求讲起一直聊到了 LangChain4j 和 Spring AI 这类开发框架。核心想表达的其实是一句话大模型调用并不神秘它就是一个 HTTP 接口框架的价值在于帮你处理重复琐碎的事情而不是替你解决逻辑问题。我自己的成长路径正好是反过来的一上来先用了框架等遇到框架解决不了的问题时才回头补 HTTP 层的东西。当时有个项目要对接一个比较小众的平台框架根本没有现成的实现类我被迫自己看它的 API 文档用 OkHttp 写了适配。写完那一刻才突然理解了框架内部是怎么工作的。从那以后我养成了一个习惯任何新技术先看它最原始的形态再思考如何抽象和封装。后面如果你想继续深入我建议沿着三条线走。第一条线是流式输出你要自己用 OkHttp 实现一个 SSE 客户端彻底搞懂它的事件格式。第二条线是函数调用Function Calling学会让模型在输出中请求调用你的业务工具。第三条线是 RAG做一个简单的向量检索问答系统会让你对模型生成 知识检索的完整链路有更扎实的体会。最后分享一个小技巧在自己电脑上调试大模型接口时强烈建议用 curl 先手测一遍确认网络和参数没问题再去写 Java 代码。不然你很可能花半天时间排错最后发现是某个参数名拼写错了。先把底层链路摸透再让工具帮你飞起来。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询