← 返回首页

JAVA调用大模型API

·32 分钟阅读·

一.OpenAI接口协议

OpenAI接口协议是什么:

类似于HTTP是WEB世界的通用协议,OpenAI的Chat Completions API已经成为大模型API领域的事实标准之一。

OpenAI接口协议就类似于大模型世界的普通话. 无论是DeepSeek,Qwen,GLM,GPT,Claude Code,Gemini,都支持OpenAI接口协议.

在切换模型或者平台时,很多情况下只需要修改:

baseURL
apiKey
model

核心调用逻辑基本不用重写。


二.请求格式详解

调用大模型API,本质上就是发送一个HTTP POST请求,请求体通常是一个JSON

下面是一个企业知识库问答助手的请求示例。

{
    "model": "Qwen/Qwen3-32B",
    "messages": [
        {
            "role": "system",
	        "content": "你是一个企业知识库问答助手,只回答公司制度、报销流程、项目问题。"
        },
        {
            "role": "user",
            "content": "公司的年假可以拆分使用吗?"
        }
    ],
    "temperature": 0.1,
    "max_tokens": 512,
    "stream": false
}

这个JSON中包含几个关键字段

  • model:指定要调用的模型
  • messages:对话消息的数组
  • temperature:控制回答的随机性
  • max_tokens:限制模型输出的最大长度
  • stream:是否开启流式返回

下面来进行逐个说明


1.model:指定要调用的模型

model 字段用于告诉平台:这次请求要调用哪个模型。 不同大模型的模型ID格式可能不同,推荐去模型官方文档查找.


2.messages:对话消息数组

messages 是整个请求中最核心的字段。

它是一个数组,数组中的每条消息都包含两个属性:

role
content

其中:

  • role 表示这条消息的角色;
  • content 表示这条消息的内容。

模型并不是只看用户当前输入的这一句话,而是会读取整个 messages 数组。你可以把它理解为一段完整的对话记录。

模型会基于这段对话上下文生成回答。

![[Pasted image 20260512151612.png]]


3.角色机制

messages 数组中的每条消息都有一个 role。常见角色有三种:

  • system
  • user
  • assistant

3.1system:系统角色

system 消息用于定义模型的行为规则,相当于给模型一份“工作说明书”。

例如:

{
    "role": "system",
    "content": "你是一个企业知识库问答助手,只回答公司制度、报销流程、项目规范相关的问题。"
}

这个系统消息告诉模型:

  • 你的身份是企业知识库问答助手;
  • 你的回答范围是公司制度、报销流程、项目规范;
  • 不相关的问题应该尽量避免回答。

比如用户问:

今晚吃什么?

模型就应该意识到这个问题不属于企业知识库问答范围.


3.2 user:用户角色

user 消息表示用户输入的问题。

例如:

{
    "role": "user",
    "content": "公司的年假可以拆分使用吗?"
}

这就是用户真正想问的问题。


3.3 assistant:助手角色

assistant 消息表示模型之前的回答,通常用于构建多轮对话上下文。

例如,一段多轮对话可以这样组织:

{
    "messages": [
        {"role": "system", "content": "你是一个企业知识库问答助手"},
        {"role": "user", "content": "公司的年假可以拆分使用吗?"},
        {"role": "assistant", "content": "可以。根据公司制度,年假支持按半天或整天为单位拆分使用,具体以审批系统中的可用余额为准。"},
        {"role": "user", "content": "那需要提前几天申请?"}
    ]
}

模型看到这段上下文后,就能理解最后一句:

那需要提前几天申请?

问的是“年假申请需要提前几天”,而不是其他流程。

如果你只发送最后一句“那需要提前几天申请?”,模型就很难判断用户到底在问年假、报销、出差,还是其他审批流程。


4.大模型没有自动记忆,多轮对话需要手动传历史

这里有一个非常重要的点:

大模型 API 的每次调用都是独立的。

模型不会自动记住上一次 API 调用的内容。所谓多轮对话,本质上是开发者在每次请求中,把历史消息一起放进 messages 数组里,让模型看到完整上下文。

也就是说,多轮对话并不是模型自己“记住了”,而是你每次都把历史对话重新发给了模型。

这也是为什么对话越长,消耗的 Token 越多:

因为每次请求都要把历史消息重新发送一遍


5. system消息会影响模型的回答方式

同一个用户问题,如果 system 消息不同,模型的回答风格也会发生明显变化。

比如用户问题都是:

SpringBoot的自动装配底层原理是什么?

不同的 system 消息会得到不同风格的回答:

system消息用户问题模型回答风格
你是专业的技术顾问SpringBoot的自动装配底层原理是什么?客观解释底层原理
你是一个面试官SpringBoot的自动装配底层原理是什么?用面试追问的方式引导回答
你是一个面向初学者的 技术导师SpringBoot的自动装配底层原理是什么?用通俗类比解释概念

这就是 system 消息的作用:

它可以从根本上影响模型的身份、语气、回答边界和输出风格.


三. 常用请求参数说明

除了 modelmessages,还有几个常用参数需要掌握。

参数类型说明
temperaturefloat控制回答随机性,值越高回答越发散
max_tokensint控制模型最多生成多少个 Token
streamboolean是否启用流式返回

四.非流式返回相应格式详解

steam设置为false时,模型会返回一个完整的JSON

示例

{
    
    "model": "deepseek-v4-flash",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "可以。根据公司制度,年假通常支持按半天或整天为单位拆分使用,具体可用天数和申请规则以公司审批系统中的制度说明为准。"
            },
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 46,
        "completion_tokens": 58,
        "total_tokens": 104
    }
}

这里有几个关键字段


  1. choices choices是模型回答的数组,一般情况下只有一个choices[0]元素,除非你设置了参数要求模型一次生成多个回答
  2. choices[0].message 这是模型最终生成的回答,其中的content字段就是我们最终要展示给用户的内容
  3. finish_reason finish_reason 表示模型停止生成的原因。 常见值如下:
    • stop:标志正常结束,模型认为回答已经完整
    • length:达到长度上限:模型输出的token已经达到max_tokens
  4. usage用于统计token消耗

五、为什么很多厂商都兼容 OpenAI 协议

OpenAI 的 Chat Completions API 形成了较大的生态,很多框架、工具和教程都围绕这套协议展开。

例如:

  • LangChain;
  • Spring AI;
  • 各类命令行工具;
  • Postman 请求模板;
  • 各种开源示例项目。

因此,很多大模型平台都会提供 OpenAI 兼容接口,降低开发者迁移成本。


六.非流式调用示例

非流式调用是最简单的大模型 API 调用方式。

它的特点是:

客户端发送请求,等待模型生成完毕后,一次性拿到完整回答。

就像调用普通 REST API 一样。


  1. 添加maven依赖

    pom.xml中添加以下依赖:```

<dependencies>
    <!-- OkHttp:HTTP 客户端 -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
    <!-- Gson:JSON 处理 -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.13.1</version>
    </dependency>
</dependencies>

这里使用 OkHttp,而不是 Spring 的 RestTemplate 或 WebClient,主要是因为 OkHttp 是纯 HTTP 客户端,不依赖 Spring 框架,代码更简洁,也方便在任意 Java 项目中使用

  1. 完整代码实现
import com.google.gson.Gson;  
import com.google.gson.JsonArray;  
import com.google.gson.JsonObject;  
import okhttp3.*;  
  
import java.io.IOException;  
import java.util.concurrent.TimeUnit;  
  
public class NonStreamingChat {  
  
       
       private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";  
          
       private static final String API_KEY = "sk-4c715b5fdae44f609df899975256a8fb";  
  
       public static void main(String[] args) throws IOException {  
          // 1. 构建请求体 JSON          
          JsonObject requestBody = new JsonObject();  
          requestBody.addProperty("model", "deepseek-v4-flash");  
          requestBody.addProperty("temperature", 0);  
          requestBody.addProperty("max_tokens", 1024);  
          requestBody.addProperty("stream", false);  
  
          // 构建 messages 数组  
          JsonArray messages = new JsonArray();  
  
          // system 消息:定义模型的行为规则  
          JsonObject systemMsg = new JsonObject();  
          systemMsg.addProperty("role", "system");  
          systemMsg.addProperty("content", "你是一个企业知识库问答助手,回答要简洁明了。");  
          messages.add(systemMsg);  
  
          // user 消息:用户的问题  
          JsonObject userMsg = new JsonObject();  
          userMsg.addProperty("role", "user");  
          userMsg.addProperty("content", "公司的年假可以拆分使用吗?");  
          messages.add(userMsg);  
  
          requestBody.add("messages", messages);  
  
          // 2. 创建 OkHttp 客户端(设置超时时间,大模型响应可能较慢)  
          OkHttpClient client = new OkHttpClient.Builder()  
                .connectTimeout(30, TimeUnit.SECONDS)  
                .readTimeout(60, TimeUnit.SECONDS)  
                .build();  
  
          // 3. 构建 HTTP 请求  
          Request request = new Request.Builder()  
                .url(API_URL)  
                .addHeader("Authorization", "Bearer " + API_KEY)  
                .addHeader("Content-Type", "application/json")  
                .post(RequestBody.create(  
                      requestBody.toString(),  
                      MediaType.parse("application/json")  
                ))  
                .build();  
  
          // 4. 发送请求并处理响应  
          try (Response response = client.newCall(request).execute()) {  
             if (!response.isSuccessful()) {  
                System.out.println("请求失败,状态码:" + response.code());  
                System.out.println("错误信息:" + response.body().string());  
                return;  
             }  
  
             // 5. 解析 JSON 响应  
             String responseBody = response.body().string();  
             Gson gson = new Gson();  
             JsonObject jsonResponse = gson.fromJson(responseBody, JsonObject.class);  
  
             // 提取模型的回答  
             String answer = jsonResponse  
                   .getAsJsonArray("choices")  
                   .get(0).getAsJsonObject()  
                   .getAsJsonObject("message")  
                   .get("content").getAsString();  
  
             // 提取 finish_reason             String finishReason = jsonResponse  
                   .getAsJsonArray("choices")  
                   .get(0).getAsJsonObject()  
                   .get("finish_reason").getAsString();  
  
             // 提取 Token 用量  
             JsonObject usage = jsonResponse.getAsJsonObject("usage");  
             int promptTokens = usage.get("prompt_tokens").getAsInt();  
             int completionTokens = usage.get("completion_tokens").getAsInt();  
             int totalTokens = usage.get("total_tokens").getAsInt();  
  
             // 6. 打印结果  
             System.out.println("=== 模型回答 ===");  
             System.out.println(answer);  
             System.out.println();  
             System.out.println("=== 调用信息 ===");  
             System.out.println("结束原因:" + finishReason);  
             System.out.println("输入 Token:" + promptTokens);  
             System.out.println("输出 Token:" + completionTokens);  
             System.out.println("总 Token:" + totalTokens);  
          }  
       }  
  
  
}

七.流式调用详解

1.为什么需要流式调用

非流式调用有一个体验问题:模型必须生成完整内容后,才会一次性返回结果。

如果回答比较短,这个问题不明显。

但如果回答比较长,比如用户问:

请总结一下公司研发流程文档中的代码评审规范

模型可能需要几秒甚至十几秒才能生成完整回答。在这段时间里,如果页面没有任何变化,用户很容易觉得系统卡住了。

流式调用就是为了解决这个问题。

开启流式调用后,模型每生成一小段内容,就会立刻推送给客户端。客户端收到一段就展示一段,用户看到的效果就是文字逐步出现。

这就是 ChatGPT、DeepSeek 网页端常见的“打字机效果”。

![[Pasted image 20260512162325.png]]


2. SSE协议简介

流式调用通常基于SSE,也就是Server-Sent Events,中文可以理解为服务端推送事件

普通HTTP请求是一问一答模式

客户端发送请求 - 服务端返回完整相应 - 连接关闭

SSE:

客户端发送请求 - 服务端持续推送数据块,保持连接 - 推送完成后关闭连接

每个数据块以data开头,当所有内容完毕后,服务端发送一个特殊标记:data:[DONE].

3. 流式响应的数据格式

流式相应和非流式相应的JSON结构有一个关键区别:

  • 非流式相应中,模型回答在choices[0].message
  • 流式响应中,每个数据块的增量在choices[0].delta里.

一个完整的流式相应的数据流大致如下

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"content":"可以"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"content":"的。"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"content":"根据"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"content":"公司"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{"content":"制度"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

解析时需要注意:

  1. 第一段数据中的 delta 可能包含 role: "assistant",表示助手开始回答。
  2. 中间数据块中的 delta.content 是模型新增生成的内容。
  3. 倒数第二个数据块中,delta 可能为空,finish_reason 变为 "stop"
  4. 最后一行data: [DONE]是结束标记,不是 JSON
  5. 数据块之间可能存在空行,解析时需要跳过。

要得到完整回答,需要把所有数据块中的delta.content接起来


八.流式调用完整代码实现

import com.google.gson.Gson;  
import com.google.gson.JsonArray;  
import com.google.gson.JsonElement;  
import com.google.gson.JsonObject;  
import okhttp3.*;  
  
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.concurrent.TimeUnit;  
  
public class StreamingChat {  
  
    private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";  
    private static final String API_KEY = "sk-4c715b5fdae44f609df899975256a8fb";  
  
    public static void main(String[] args) throws IOException {  
       // 1. 构建请求体(注意 stream 设为 true)  
       JsonObject requestBody = new JsonObject();  
       requestBody.addProperty("model", "deepseek-v4-flash");  
       requestBody.addProperty("temperature", 0.1);  
       requestBody.addProperty("max_tokens", 1024);  
       requestBody.addProperty("stream", true);  // 开启流式  
  
       JsonArray messages = new JsonArray();  
  
       JsonObject systemMsg = new JsonObject();  
       systemMsg.addProperty("role", "system");  
       systemMsg.addProperty("content", "你是一个企业知识库问答助手,回答要简洁明了。");  
       messages.add(systemMsg);  
  
       JsonObject userMsg = new JsonObject();  
       userMsg.addProperty("role", "user");  
       userMsg.addProperty("content", "请简单说明公司报销流程一般包括哪些步骤。");  
       messages.add(userMsg);  
  
       requestBody.add("messages", messages);  
  
       // 2. 创建 OkHttp 客户端  
       OkHttpClient client = new OkHttpClient.Builder()  
             .connectTimeout(30, TimeUnit.SECONDS)  
             .readTimeout(120, TimeUnit.SECONDS)  // 流式调用需要更长的读取超时  
             .build();  
  
       // 3. 构建请求  
       Request request = new Request.Builder()  
             .url(API_URL)  
             .addHeader("Authorization", "Bearer " + API_KEY)  
             .addHeader("Content-Type", "application/json")  
             .post(RequestBody.create(  
                   requestBody.toString(),  
                   MediaType.parse("application/json")  
             ))  
             .build();  
  
       // 4. 发送请求并逐行读取 SSE 响应  
       Gson gson = new Gson();  
       StringBuilder fullContent = new StringBuilder();  
  
       System.out.println("=== 模型回答(流式输出)===");  
  
       try (Response response = client.newCall(request).execute()) {  
          if (!response.isSuccessful()) {  
             System.out.println("请求失败,状态码:" + response.code());  
             System.out.println("错误信息:" + response.body().string());  
             return;  
          }  
  
          // 逐行读取响应体  
          BufferedReader reader = new BufferedReader(  
                new InputStreamReader(response.body().byteStream())  
          );  
  
          String line;  
          while ((line = reader.readLine()) != null) {  
             // 跳过空行  
             if (line.isEmpty()) {  
                continue;  
             }  
  
             // 每行以 "data: " 开头,去掉前缀  
             if (!line.startsWith("data: ")) {  
                continue;  
             }  
             String data = line.substring(6);  // 去掉 "data: " 前缀(6 个字符)  
  
             // 检查是否是结束标记  
             if ("[DONE]".equals(data)) {  
                break;  
             }  
  
             // 解析 JSON,提取增量内容  
             JsonObject chunk = gson.fromJson(data, JsonObject.class);  
             JsonArray choices = chunk.getAsJsonArray("choices");  
             if (choices != null && choices.size() > 0) {  
                JsonObject delta = choices.get(0).getAsJsonObject()  
                      .getAsJsonObject("delta");  
                if (delta != null && delta.has("content")) {  
                   JsonElement contentElement = delta.get("content");  
                   if (!contentElement.isJsonNull()) {  
                      String content = contentElement.getAsString();  
                      // 实时打印增量内容(不换行,模拟打字效果)  
                      System.out.print(content);  
                      fullContent.append(content);  
                   }  
                }  
             }  
          }  
       }  
  
       // 输出完毕,换行  
       System.out.println();  
       System.out.println();  
       System.out.println("=== 完整回答 ===");  
       System.out.println(fullContent);  
    }  
}

从最终内容来看,流式调用和非流式调用都能得到完整回答。

但用户体验不同:

  • 非流式:等待一段时间后,一次性看到完整回答;
  • 流式:几乎立刻看到内容开始输出。

在控制台中,逐字效果可能不是特别明显,因为网络传输可能会批量返回多个字符。但如果接入前端页面,用户看到的就是标准的打字机效果。