【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())
        );
    }
}
相关推荐
未若君雅裁1 小时前
JVM高级篇总结笔记
java·jvm·笔记
豐儀麟阁贵1 小时前
9.4字符串操作
java·linux·服务器·开发语言
碧海银沙音频科技研究院1 小时前
面向图像分类的自监督/对比学习辅助的知识蒸馏-类别对比蒸馏(Category Contrastive Distillation, CCD)
人工智能
KubeSphere 云原生1 小时前
云原生周刊:K8s 成为人工智能的新动力引擎
人工智能·云原生·kubernetes
数峦云数字孪生三维可视化1 小时前
魔观3DS智慧工厂数字孪生立体监测系统:让数字孪生“立体可感”的智能中枢
大数据·人工智能·物联网·信息可视化·数字孪生
新诺韦尔API1 小时前
手机在网状态查询接口对接详细流程
大数据·网络·智能手机·api
武子康1 小时前
Java-181 OSS 实战指南:Bucket/外链/防盗链/计费与常见坑
java·大数据·分布式·oss·云存储·fastdfs·ali
聆风吟º1 小时前
【Spring Boot 报错已解决】告别“Whitelabel Error Page”:Spring Boot 404报错的排查指南
java·spring boot·后端
(; ̄ェ ̄)。1 小时前
机器学习入门(二),KNN近邻算法
人工智能·机器学习·近邻算法