Spring AI: подключение Claude API к OpenAI провайдеру
Spring AI работает с Claude и GPT через OpenAI-модуль. Главное — правильно указать base-url: в Spring AI 1.x и 2.x он разный.
4 мин · · разработка
Spring AI позволяет вызывать Claude и GPT из Java-приложения через привычные ChatClient и ChatModel, а к Zukko он подключается через OpenAI-модуль: шлюз говорит на OpenAI-совместимом API. Почти все проблемы при настройке сводятся к одному свойству — spring.ai.openai.base-url. Ниже разберём, какое значение нужно в вашей версии Spring AI, и покажем рабочий код: конфиг, вызов модели, стриминг и обработку ошибок.
Какой base-url указывать: зависит от версии Spring AI
Поведение OpenAI-модуля поменялось между ветками 1.x и 2.x, и это видно по исходникам и документации Spring AI:
| Версия Spring AI | Как строится адрес запроса | Что указать в base-url |
|---|---|---|
| 1.0.x и 1.1.x | base-url + completions-path (по умолчанию /v1/chat/completions) | https://api.zukko.pro |
| 2.0.x и новее | под капотом официальный SDK openai-java, он добавляет к базе /chat/completions; базовый адрес по умолчанию — https://api.openai.com/v1 | https://api.zukko.pro/v1 |
Итог должен быть один и тот же: https://api.zukko.pro/v1/chat/completions. Если в логах видите 404, посмотрите, куда ушёл запрос: путь /v1/v1/chat/completions значит, что в 1.x лишний /v1 в базе, а путь /chat/completions без /v1 — что в 2.x его не хватает.
Свою версию смотрите в pom.xml или build.gradle по артефакту spring-ai-bom. Spring AI 2.0.x работает со Spring Boot 4.0 и 4.1, ветка 1.x — со Spring Boot 3.
Зависимости
Maven, на примере Spring AI 2.0:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
</dependencies>Для Gradle — implementation platform("org.springframework.ai:spring-ai-bom:2.0.1") и implementation "org.springframework.ai:spring-ai-starter-model-openai".
Настройка application.yml
Для Spring AI 2.x:
spring:
ai:
openai:
api-key: ${ZUKKO_API_KEY}
base-url: https://api.zukko.pro/v1
timeout: 120s
chat:
model: claude-sonnet-5
max-tokens: 1024Для Spring AI 1.x свойства модели лежат под chat.options, а база — без /v1:
spring:
ai:
openai:
api-key: ${ZUKKO_API_KEY}
base-url: https://api.zukko.pro
chat:
options:
model: claude-sonnet-5
max-tokens: 1024Ключ берётся из переменной окружения ZUKKO_API_KEY — не храните его в репозитории. Модель можно поставить любую из доступных: claude-opus-5-5, gpt-5.6-sol, gpt-5.6-luna, glm-5.3, glm-5.3-flash. Шлюз сам конвертирует запрос в нужный формат, поэтому Claude через OpenAI-модуль работает так же, как GPT.
ChatClient: самый короткий путь
ChatClient.Builder Spring Boot создаёт автоматически по настройкам выше:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ChatApplication {
public static void main(String[] args) {
SpringApplication.run(ChatApplication.class, args);
}
@BeanChatModel и параметры на конкретный запрос
Если для одного эндпоинта нужна другая модель или лимит, передайте OpenAiChatOptions вместе с промптом — они перекроют значения из конфига:
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatModel chatModel;
public ChatController(ChatModel chatModel) {Текст ответа достаётся через getOutput().getText(): метод getContent() из ранних milestone-версий в 1.0 и 2.x уже не используется.
Стриминг ответа
ChatClient умеет отдавать ответ по частям как Flux<String>, а Spring превращает его в Server-Sent Events:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
public class StreamController {
private final ChatClient chatClient;
public StreamController(ChatClient.Builder builder) {
this.chatClient = builder.build();Обработка ошибок
Шлюз отвечает кодами 401 (неверный ключ), 402 (не хватает баланса), 403 (модель не разрешена для ключа), 413 (слишком большой запрос), 429 (слишком часто) и 503 (провайдер недоступен). В Spring AI 2.x ошибки HTTP приходят как исключения SDK openai-java — наследники com.openai.errors.OpenAIServiceException с методом statusCode():
import com.openai.errors.OpenAIServiceException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class AiErrorHandler {
@ExceptionHandler(OpenAIServiceException.class)
public ResponseEntity<String> handle(OpenAIServiceException e) {
String message = switch (e.statusCode()) {
case 401 -> "Неверный API-ключ";
case 402 -> "Недостаточно средств на балансе";
case 403 -> "Модель не разрешена для этого ключа";
case 429 -> "Слишком много запросов, повторите позже";
case 503 -> "Провайдер модели временно недоступен";
default -> "Ошибка API: " + e.getMessage();
};
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(message);
}
}Клиенту лучше отдавать 502, а не исходный 401: для него это ошибка вашего сервера, а не его авторизации. Повторы при сбоях SDK делает сам, число попыток задаёт свойство spring.ai.openai.max-retries. В Spring AI 1.x вместо исключений SDK используются NonTransientAiException (ошибки 4xx, без повторов) и TransientAiException (временные сбои) из пакета org.springframework.ai.retry.
Без Spring AI: java.net.http
Если фреймворк не нужен, хватит встроенного HttpClient (Java 11+) и Gson для JSON:
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class ZukkoClient {
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))Заголовок X-Zukko-Cost-Micros — стоимость запроса в микродолларах; подробнее о нём — в разделе о лимитах ключей и кэше. HttpClient потокобезопасен: создайте его один раз и переиспользуйте.
Частые вопросы
Почему 404, хотя ключ правильный?
Почти всегда дело в base-url. Spring AI 1.x сам дописывает /v1/chat/completions, поэтому база — https://api.zukko.pro. Spring AI 2.x дописывает только /chat/completions, поэтому база — https://api.zukko.pro/v1. Сверьтесь с таблицей в начале статьи и с описанием Chat Completions.
Где создать API-ключ?
В кабинете, на странице API-ключей. Полный ключ показывается один раз. Для сервиса на Spring удобно завести отдельный ключ с дневным лимитом и списком разрешённых моделей.
Как сменить модель?
Глобально — свойством spring.ai.openai.chat.model (2.x) или spring.ai.openai.chat.options.model (1.x), для отдельного запроса — через OpenAiChatOptions.builder().model("..."). Актуальный список моделей — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Какая модель подойдёт вашей задаче, проверьте на наборе типовых запросов из своего приложения.
Можно ли подключить Claude через Anthropic-модуль Spring AI?
Да, у Spring AI есть отдельный Anthropic-модуль, а Zukko принимает и Anthropic-формат (/v1/messages). Но настройки адреса у этого модуля свои — сверьтесь с документацией Spring AI для вашей версии. Через OpenAI-модуль все модели Zukko работают с одним и тем же конфигом, что проще.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.