【AI篇3】在Java项目中调用大模型API

随着人工智能技术的快速发展,大型语言模型(LLM)已经成为现代应用开发的重要组成部分。作为Java开发者,掌握如何高效、稳定地集成AI能力到我们的应用中,已经成为必备技能。本文将系统性地介绍在Java项目中调用大模型API的各种方法和最佳实践。

目录

一、基础知识铺垫

[1.1 什么是大模型API?](#1.1 什么是大模型API?)

[1.2 什么是流式处理?](#1.2 什么是流式处理?)

二、调用方式的系统对比

方式一:使用SDK(封装方式)

方式二:直接HTTP调用(原生方式)

三、结果输出方式的系统对比

方式一:非流式输出(同步输出)

方式二:流式输出(实时输出)

四、选择决策矩阵

五、调用API其他技巧

[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+ 内置 -->
<!-- 无需添加依赖 -->

三、结果输出方式的系统对比

方式一:非流式输出(同步输出)

特点

  • 一次性返回完整结果

  • 适用于短文本、批处理

  • 实现简单

适用场景

  1. 文本分类/情感分析

  2. 关键词提取

  3. 简短问答

  4. 批量翻译

  5. 代码片段生成

以下是一个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"

方式二:流式输出(实时输出)

特点

  • 逐字/逐片段返回结果

  • 实时反馈,用户体验好

  • 适用于长文本生成

适用场景

  1. 长篇文章创作

  2. 实时对话聊天

  3. 代码生成(逐行显示)

  4. 故事/小说续写

  5. 教学讲解

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())
        );
    }
}
相关推荐
NAGNIP3 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab4 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab4 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
哥不是小萝莉5 小时前
OpenClaw 架构设计全解析
ai
AngelPP8 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年8 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼8 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS8 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
warm3snow8 小时前
Claude Code 黑客马拉松:5 个获奖项目,没有一个是"纯码农"做的
ai·大模型·llm·agent·skill·mcp
程序员清风9 小时前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试