Java 项目接入 OpenAI API 完整方案
Spring Boot 项目要接大模型,Spring AI 和官方 openai-java SDK 该选哪个?这篇给出两套可直接落地的配置与代码,讲清 base-url 拼接、流式输出、超时重试和 429 处理,附常见报错对照。
Java 侧接大模型现在有两条成熟路线:Spring AI 提供声明式封装,官方 openai-java SDK 轻量直接。两条路线都能改 base_url 指向兼容网关,但踩坑点不一样——Spring AI 会因为多拼一段 /v1 直接 404,官方 SDK 则是版本升级后类名变了,照着旧博客抄必然编译不过。
这篇给两套能直接跑的最小实现,再补上生产环境必须处理的三件事:超时、重试、流式。
先说结论
- 已有 Spring Boot 3.x 工程、想要和现有 Bean 体系融合:用 Spring AI,配置写在
application.yml - 只想发个 HTTP 请求、不想引入整个 AI 抽象层:用官方 openai-java SDK,依赖更少
base-url的写法两者相反:Spring AI 写到域名(它自己拼/v1),官方 SDK 要写到/v1- 流式输出必须用 SSE,中间任何一层网关缓冲都会让"流式"变成一次性返回
两条路线怎么选
| 对比项 | Spring AI | openai-java SDK |
|---|---|---|
| 依赖体积 | 较大,带自动配置与 advisor 链 | 小,只含 HTTP 与模型类 |
| 配置方式 | application.yml 声明式 | 代码里 builder 构造 |
| 多模型切换 | 换 model 属性即可 | 换 model 字符串 |
| 流式支持 | Flux 直接对接 | 回调式 Stream 处理 |
| 适合场景 | 新项目、统一治理 | 老项目、单点调用 |
两者不冲突,同一工程里也能共存。日常维护角度看,选一个就好,避免两套超时配置互相打架。
路线一:Spring AI
依赖(Spring Boot 3.3+):
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
配置:
spring:
ai:
openai:
# 注意:这里只写到域名,Spring AI 会自动拼接 /v1/chat/completions
base-url: https://api.zaimod.com
api-key: ${ZAIMOD_API_KEY}
chat:
options:
model: gpt-4.1-mini
temperature: 0.3
调用就是一个普通 Bean:
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是严谨的技术助手,回答控制在 200 字内。")
.build();
}
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatClient.prompt()
.user(q)
.call()
.content();
}
}
流式版本把 call() 换成 stream(),返回 Flux<String>,Spring WebFlux 会直接按 SSE 推给前端:
@GetMapping(value = "/ask/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String q) {
return chatClient.prompt().user(q).stream().content();
}
路线二:官方 openai-java SDK
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>4.0.0</version>
</dependency>
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
public class Demo {
public static void main(String[] args) {
OpenAIClient client = OpenAIOkHttpClient.builder()
.apiKey(System.getenv("ZAIMOD_API_KEY"))
// SDK 不会再补 /v1,必须写全
.baseUrl("https://api.zaimod.com/v1")
.build();
ChatCompletion resp = client.chat().completions().create(
ChatCompletionCreateParams.builder()
.model("gpt-4o-mini")
.addUserMessage("用一句话说明什么是 token")
.maxCompletionTokens(256)
.build());
resp.choices().forEach(c -> c.message().content()
.ifPresent(System.out::println));
}
}
两个细节容易忽略:content() 在 Java SDK 里是 Optional,模型只返回工具调用时它是空的;maxCompletionTokens 在新版本取代了 maxTokens,写旧字段会编译报错。
超时、重试、限流
默认超时对非流式请求太短,长回答会中途断开。建议按调用类型分开配:
| 场景 | 连接超时 | 读超时 | 重试策略 |
|---|---|---|---|
| 短问答、分类 | 3s | 30s | 2 次,指数退避 |
| 长文生成 | 5s | 180s | 1 次,仅对 5xx 和超时 |
| 流式 | 5s | 不设总超时 | 不自动重试,由前端续传 |
429 不要盲目重试。并发超限时立刻重试只会把窗口打得更满,正确做法是退避加抖动:
int attempt = 0;
while (true) {
try {
return callApi();
} catch (RateLimitException e) {
if (++attempt > 3) throw e;
long backoff = (long) (Math.pow(2, attempt) * 500) + ThreadLocalRandom.current().nextLong(300);
Thread.sleep(backoff);
}
}
完整的限流成因与排查顺序见 大模型 API 429 限流怎么解,错误码全集见 大模型 API 错误码速查。
三个工程实践
不要每次调用都 new 一个 client。 OpenAIClient 内部持有连接池,应当做成单例 Bean,否则高并发下连接数会爆。
日志里绝不能出现完整 Key。 统一在拦截器里脱敏成 sk-****abcd;Key 泄露后的处置流程见 API Key 泄露应急处理。
把模型名放进配置中心而不是常量。 换模型时改配置即可,配合灰度可以先放 5% 流量到新模型,比对延迟和成本再全量。
常见问题
Q:Spring AI 报 404,路径里出现两次 /v1 怎么办?
把 base-url 改成不带 /v1 的域名。若必须带路径前缀(比如走公司网关),用 spring.ai.openai.chat.base-url 单独覆盖,让对话和嵌入各走各的地址。
Q:能用 JDK 8 吗?
Spring AI 要求 Spring Boot 3.x,即 JDK 17 起。JDK 8 项目建议直接用 OkHttp 手写请求,反而比引 SDK 省事;协议本身只是 HTTP POST,见 OpenAI 兼容协议详解。
Q:流式输出在 Nginx 后面变成一次性返回?
关掉响应缓冲:proxy_buffering off;,并确认 X-Accel-Buffering: no 已透传。前端 SSE 的写法参考 SSE 流式输出从 curl 到前端。
Q:一个 Key 能同时跑多个服务吗?
可以,但建议按服务拆 Key,便于定位超量来源和单独吊销。额度与计费口径在 价格速查 里按模型列出。