随着人工智能技术的快速发展,大型语言模型(LLM)已经成为现代应用开发的重要组成部分。作为Java开发者,掌握如何高效、稳定地集成AI能力到我们的应用中,已经成为必备技能。本文将系统性地介绍在Java项目中调用大模型API的各种方法和最佳实践。
目录
[1.1 什么是大模型API?](#1.1 什么是大模型API?)
[1.2 什么是流式处理?](#1.2 什么是流式处理?)
[5.1 HTTP连接池管理](#5.1 HTTP连接池管理)
[5.2 异步处理&重试机制](#5.2 异步处理&重试机制)
一、基础知识铺垫
1.1 什么是大模型API?
大模型API是各大AI厂商提供的编程接口,允许开发者通过网络请求的方式访问其训练好的大型语言模型。这些API通常遵循RESTful或WebSocket协议,支持文本生成、对话、代码生成等多种功能。
核心特点:
-
预训练模型:基于海量数据训练,具备通用语言理解能力
-
即用性:无需本地部署,通过API即可使用
-
按需付费:通常按token数量计费
-
持续更新:模型不断优化和迭代
1.2 什么是流式处理?
流式处理(Streaming)是指数据以连续流的形式处理,而不是等待所有数据就绪后再处理。在大模型API中,流式输出意味着模型生成的内容会逐字或逐片段地实时返回。
相应的,非流式处理就是等待模型完全产出结果后整体返回。
技术实现对比
流式输出发实现方式有如下几种
|---------------|--------------------|-----------|-------------|
| 技术 | 原理 | 优点 | 缺点 |
| SSE | Server-Sent Events | 简单,HTTP协议 | 单向,仅服务器推客户端 |
| WebSocket | 全双工通信 | 双向实时通信 | 复杂,需要专门协议 |
| 长轮询 | 客户端轮询 | 兼容性好 | 效率低,延迟高 |
流式/非流式输出方式的对比
|-----------|---------------|---------------|
| 特性 | 非流式输出 | 流式输出 |
| 响应方式 | 一次性返回完整结果 | 逐字/逐片段返回 |
| 等待时间 | 长(需等待完整生成) | 短(即时看到开头) |
| 用户体验 | 需要等待,然后看到完整内容 | 即时反馈,类似"打字效果" |
| 网络要求 | 低(单次请求) | 较高(长连接) |
| 实现复杂度 | 简单 | 复杂 |
| 适用场景 | 短文本、批量处理 | 长文本、对话交互 |
二、 调用方式的系统对比
方式一:使用SDK(封装方式)
优点
-
开发效率高,API封装完善
-
错误处理机制健全
-
支持高级功能(流式、函数调用等)
-
版本兼容性好
缺点
-
依赖特定库,增加项目体积
-
灵活性受限
-
更新依赖较频繁
导入SDK包需要我们在maven工程中的pom文件中导入相关的依赖,以下几个主流的SDK包
java
<!-- OpenAI -->
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>
<version>0.18.2</version>
</dependency>
<!-- 百度文心 -->
<dependency>
<groupId>com.baidubce</groupId>
<artifactId>qianfan-sdk-java</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 阿里通义 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-cloud-sdk</artifactId>
<version>2.0.0</version>
</dependency>
方式二:直接HTTP调用(原生方式)
优点
-
零依赖或最小依赖
-
完全控制请求/响应流程
-
适用于多厂商统一接口
-
学习成本低(理解HTTP即可)
缺点
-
需要手动处理序列化/反序列化
-
错误处理需要自己实现
-
流式处理较复杂
使用HTTP调用,需要我们导入HTTP客户端的相关依赖
java
<!-- OkHttp(推荐) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- Java 11+ 内置 -->
<!-- 无需添加依赖 -->
三、结果输出方式的系统对比
方式一:非流式输出(同步输出)
特点
-
一次性返回完整结果
-
适用于短文本、批处理
-
实现简单
适用场景
-
文本分类/情感分析
-
关键词提取
-
简短问答
-
批量翻译
-
代码片段生成
以下是一个SDK实现示例
java
// 1. OpenAI非流式调用
public class NonStreamSDKExample {
public String callOpenAI(String prompt) {
//输入api-Key
OpenAiService service = new OpenAiService("your-api-key");
//ChatCompletionRequest 是 OpenAI官方Java SDK 中的核心请求类,用于构建调用Chat Completions API的请求参数。
ChatCompletionRequest request = ChatCompletionRequest.builder()
.model("gpt-3.5-turbo")
.messages(List.of(new ChatMessage("user", prompt)))
.maxTokens(1000)
.temperature(0.7)
.stream(false) // 明确关闭流式
.build();
ChatCompletionResult result = service.createChatCompletion(request);
return result.getChoices().get(0).getMessage().getContent();
}
// 2. 百度文心非流式
public String callERNIE(String prompt) {
Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, apiKey, secretKey);
ChatResponse response = qianfan.chatCompletion()
.model("ERNIE-Bot")
.addMessage("user", prompt)
.temperature(0.7f)
.execute();
return response.getResult();
}
}
HTTP实现方式,http需要构造请求体
java
// HTTP非流式调用
public class NonStreamHttpExample {
private static final ObjectMapper mapper = new ObjectMapper();
private static final OkHttpClient client = new OkHttpClient();
//这里方法入参目标网址,apiKey和调用参数
public String callAI(String endpoint, String apiKey, String prompt) throws IOException {
// 构建请求体
Map<String, Object> requestBody = Map.of(
"model", "gpt-3.5-turbo",
"messages", List.of(Map.of(
"role", "user",
"content", prompt
)),
"max_tokens", 1000,
"temperature", 0.7,
"stream", false // 关键参数
);
String json = mapper.writeValueAsString(requestBody);
Request request = new Request.Builder()
.url(endpoint)
.post(RequestBody.create(json, MediaType.get("application/json")))
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Content-Type", "application/json")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response.code());
}
String responseBody = response.body().string();
JsonNode root = mapper.readTree(responseBody);
// 统一响应解析
return extractContent(root);
}
}
//这是一段将不同厂商的API响应标准化为统一的Java对象,屏蔽底层API差异。
private String extractContent(JsonNode root) {
// 适配不同厂商的响应格式
// 1. 检查是否是OpenAI格式
if (root.has("choices")) {
// OpenAI格式: {"choices":[{"message":{"content":"响应内容"}}]}
return root.path("choices").get(0)
.path("message")
.path("content")
.asText();//转为字符串
// 2. 检查是否是百度文心格式
} else if (root.has("result")) {
// 百度文心格式: {"result":"响应内容"}
return root.path("result").asText();
// 3. 检查是否是阿里通义格式
} else if (root.has("output")) {
// 阿里通义格式: {"output":{"text":"响应内容"}}
return root.path("output").path("text").asText();
}
throw new RuntimeException("无法解析响应格式");
}
}
path() vs get() 的区别, get()方法:如果字段不存在,返回null,path()方法:如果字段不存在,返回"MissingNode"
方式二:流式输出(实时输出)
特点
-
逐字/逐片段返回结果
-
实时反馈,用户体验好
-
适用于长文本生成
适用场景
-
长篇文章创作
-
实时对话聊天
-
代码生成(逐行显示)
-
故事/小说续写
-
教学讲解
SDK实现示例
java
// 1. OpenAI流式调用
public class StreamSDKExample {
public void streamOpenAI(String prompt, Consumer<String> chunkConsumer) {
OpenAiService service = new OpenAiService("your-api-key");
ChatCompletionRequest request = ChatCompletionRequest.builder()
.model("gpt-3.5-turbo")
.messages(List.of(new ChatMessage("user", prompt)))
.maxTokens(2000)
.temperature(0.7)
.stream(true) // 启用流式
.build();
// 处理流数据
service.streamChatCompletion(request) //// 返回Stream<ChatCompletionChunk>
.forEach(chunk -> { //chunk 是流式响应中的一个数据块(Chunk)
// 每个chunk是一个ChatCompletionChunk对象
String content = chunk.getChoices().get(0)
.getMessage().getContent();
if (content != null) {
chunkConsumer.accept(content);
}
});
}
// 2. 百度文心流式
public void streamERNIE(String prompt, Consumer<String> chunkConsumer) {
Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, apiKey, secretKey);
qianfan.chatCompletion()
.model("ERNIE-Bot")
.addMessage("user", prompt)
.incrementalOutput(true) // 启用增量输出
.executeStream(new ChatResponseHandler() {
@Override
public void onPartialResponse(String partialText) {
//调用者定义chunkConsumer的具体实现
chunkConsumer.accept(partialText);
}
@Override
public void onComplete(String fullText) {
System.out.println("生成完成,总长度: " + fullText.length());
}
});
}
}
HTTP实现示例
java
// HTTP流式调用(SSE协议)
public class StreamHttpExample {
private static final ObjectMapper mapper = new ObjectMapper();
public void streamAI(String endpoint, String apiKey,
String prompt, Consumer<String> chunkConsumer) throws IOException {
//stream = true
//此处请求体json的构建是使用字符串拼接是简化写法,可以构造Map
String requestBody = String.format(
"{\"model\":\"gpt-3.5-turbo\",\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],\"stream\":true}",
prompt.replace("\"", "\\\"")
);
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(0, TimeUnit.SECONDS) // 无限读取超时
.build();
Request request = new Request.Builder()
.url(endpoint)
.post(RequestBody.create(requestBody, MediaType.get("application/json")))
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("Accept", "text/event-stream")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response);
}
//读取SSE流式响应
// BufferedSource是OkHttp的缓冲数据源,try-with-resources确保流自动关闭
try (BufferedSource source = response.body().source()) {
while (!source.exhausted()) { // 循环直到数据源耗尽
String line = source.readUtf8Line();
if (line == null || line.isEmpty()) continue;
//SSE协议格式
//data: {"choices":[{"delta":{"content":"你"}}]}
//data: {"choices":[{"delta":{"content":"好"}}]}
//data: [DONE] // 结束标记
if (line.startsWith("data: ")) {
String data = line.substring(6); //去掉前缀
if (data.equals("[DONE]")) {
break;
}
try {
JsonNode json = mapper.readTree(data);
JsonNode choices = json.get("choices");
if (choices != null && choices.size() > 0) {
JsonNode delta = choices.get(0).get("delta");
if (delta != null && delta.has("content")) {
String content = delta.get("content").asText();
chunkConsumer.accept(content);
}
}
} catch (Exception e) {
// 忽略解析错误,继续读取
}
}
}
}
}
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
});
}
}
四、选择决策矩阵
|------------|------------|------------|
| 项目需求 | 推荐方案 | 理由 |
| 快速原型开发 | SDK + 非流式 | 开发最快,代码简洁 |
| 实时聊天应用 | SDK + 流式 | 用户体验好,功能完善 |
| 微服务架构 | HTTP + 非流式 | 依赖少,适合分布式 |
| 多厂商支持 | HTTP + 双模式 | 统一接口,灵活切换 |
| 性能敏感 | HTTP + 非流式 | 控制最细,性能最优 |
| 企业级应用 | 统一架构 + 双模式 | 可扩展,易维护 |
五、调用API其他技巧
5.1 HTTP连接池管理
大模型调用需要考虑连接池优化:
java
@Configuration
public class HttpClientConfig {
@Bean
public CloseableHttpClient httpClient() {
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
// 设置最大连接数
connectionManager.setMaxTotal(100);
// 设置每个路由的最大连接数
connectionManager.setDefaultMaxPerRoute(20);
// 设置连接超时
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000) // 连接超时5秒
.setSocketTimeout(30000) // 读取超时30秒
.setConnectionRequestTimeout(2000) // 请求超时2秒
.build();
return HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();
}
}
5.2 异步处理& 重试机制
大模型API调用通常是IO密集型操作,同步调用会阻塞线程,影响系统吞吐量。
调用CompletableFuture
java
public class AsyncAIService {
public CompletableFuture<String> callWithRetry(String prompt, int maxRetries) {
// 使用CompletableFuture.supplyAsync创建一个异步任务
return CompletableFuture.supplyAsync(() -> {
int retryCount = 0;
// 循环直到成功或达到最大重试次数
while (retryCount <= maxRetries) {
try {
return callAI(prompt);
} catch (Exception e) {
retryCount++;
if (retryCount > maxRetries) {
throw new RuntimeException("重试次数超限", e);
}
// 指数退避策略:等待时间随重试次数增加而增加
// 第1次重试等待1秒,第2次等待2秒,第3次等待3秒
try {
Thread.sleep(1000 * retryCount); // 指数退避
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("中断", ie);
}
}
}
throw new RuntimeException("调用失败");
});
}
// 并行调用多个模型/并行执行多个请求
public CompletableFuture<List<String>> callMultipleModels(List<String> prompts) {
List<CompletableFuture<String>> futures = prompts.stream()
.map(prompt -> CompletableFuture.supplyAsync(() -> callAI(prompt)))
.collect(Collectors.toList());
return CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
).thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
}