Claude Code и Anthropic SDK
AgentAPI отдаёт Anthropic-совместимый эндпоинт
POST /v1/ai/messages с настоящим Anthropic SSE. Но путь
содержит сегмент /ai, и это создаёт нюанс, который надо понимать до настройки.
Почему нельзя просто подменить base URL
Anthropic SDK и Claude Code дописывают к базовому URL фиксированный путь /v1/messages.
Нужный адрес — /v1/**ai**/messages, где лишний сегмент стоит посередине. Подставить
его через базовый URL невозможно:
flowchart TD
A["ANTHROPIC_BASE_URL=<br/>https://api.agentapi.ru"] --> A1["→ /v1/messages"] --> A2["404 ❌"]
B["ANTHROPIC_BASE_URL=<br/>https://api.agentapi.ru/ai"] --> B1["→ /ai/v1/messages"] --> B2["404 ❌"]
C["Нужно попасть в"] --> C1["/v1/ai/messages ✅"]
Поэтому ниже — три рабочих варианта в порядке простоты.
Вариант 1: прямые HTTP-запросы (работает сразу)
Если вы пишете код сами, а не подключаете готовый клиент, — просто вызывайте эндпоинт по полному пути. Никаких обходных путей не нужно.
curl https://api.agentapi.ru/v1/ai/messages \
-H "x-api-key: agentapi-ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3.5-sonnet",
"max_tokens": 300,
"system": "Отвечай кратко.",
"messages": [{"role": "user", "content": "Привет!"}]
}'
import os, requests
response = requests.post(
"https://api.agentapi.ru/v1/ai/messages",
headers={"x-api-key": os.environ["AGENTAPI_KEY"]},
json={
"model": "anthropic/claude-3.5-sonnet",
"max_tokens": 300,
"messages": [{"role": "user", "content": "Привет!"}],
},
timeout=120,
)
data = response.json()
print(data["content"][0]["text"])
Формат ответа — Messages.
Вариант 2: Anthropic SDK с явным путём
SDK можно оставить ради типов и обработки ошибок, обойдя сборку пути через
низкоуровневый post:
import Anthropic from "@anthropic-ai/sdk";
import type { Message } from "@anthropic-ai/sdk/resources/messages";
const client = new Anthropic({
apiKey: process.env.AGENTAPI_KEY,
baseURL: "https://api.agentapi.ru",
});
// Путь указывается целиком, поэтому `/v1/messages` не подставляется автоматически.
const message = await client.post<Message>("/v1/ai/messages", {
body: {
model: "anthropic/claude-3.5-sonnet",
max_tokens: 300,
messages: [{ role: "user", content: "Привет!" }],
},
});
console.log(message.content);
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["AGENTAPI_KEY"],
base_url="https://api.agentapi.ru",
)
message = client.post(
"/v1/ai/messages",
body={
"model": "anthropic/claude-3.5-sonnet",
"max_tokens": 300,
"messages": [{"role": "user", "content": "Привет!"}],
},
cast_to=object,
)
print(message)
Штатные вызовы client.messages.create(...) при этом не заработают — они собирают
путь сами. Учтите и то, что post — низкоуровневый метод: его сигнатура может меняться
между версиями SDK.
Вариант 3: Claude Code через локальный прокси
Claude Code настраивается только переменной ANTHROPIC_BASE_URL и жёстко дописывает
/v1/messages, поэтому напрямую подключить его нельзя. Нужен локальный прокси,
переписывающий путь.
Минимальный вариант на Node.js (без зависимостей):
// agentapi-proxy.mjs — запуск: node agentapi-proxy.mjs
import { createServer } from "node:http";
const UPSTREAM = "https://api.agentapi.ru";
const API_KEY = process.env.AGENTAPI_KEY;
if (!API_KEY) throw new Error("Задайте AGENTAPI_KEY");
createServer(async (req, res) => {
// /v1/messages -> /v1/ai/messages, остальное проксируем как есть.
const path = req.url.startsWith("/v1/messages")
? req.url.replace("/v1/messages", "/v1/ai/messages")
: req.url;
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const upstream = await fetch(`${UPSTREAM}${path}`, {
method: req.method,
headers: {
"content-type": "application/json",
"x-api-key": API_KEY,
},
body: chunks.length ? Buffer.concat(chunks) : undefined,
});
res.writeHead(upstream.status, {
"content-type":
upstream.headers.get("content-type") ?? "application/json",
"cache-control": "no-cache",
});
// Ответ может быть SSE, поэтому передаём его потоком, а не целиком.
if (upstream.body) {
for await (const chunk of upstream.body) res.write(chunk);
}
res.end();
}).listen(8787, () => console.log("Прокси: http://localhost:8787"));
Запуск:
export AGENTAPI_KEY=agentapi-ВАШ_КЛЮЧ
node agentapi-proxy.mjs
В другом терминале:
export ANTHROPIC_BASE_URL=http://localhost:8787
export ANTHROPIC_AUTH_TOKEN=agentapi-ВАШ_КЛЮЧ
export ANTHROPIC_MODEL=anthropic/claude-3.5-sonnet
claude
Прокси слушает только localhost и подставляет ключ сам — не публикуйте его в сеть.
Альтернатива на LiteLLM
Если прокси уже есть в инфраструктуре, тот же результат даёт LiteLLM:
# litellm-config.yaml — запуск: litellm --config litellm-config.yaml
model_list:
- model_name: claude-3.5-sonnet
litellm_params:
model: openai/anthropic/claude-3.5-sonnet
api_base: https://api.agentapi.ru/v1/ai
api_key: os.environ/AGENTAPI_KEY
Здесь используется OpenAI-совместимый эндпоинт, который подставляется базовым URL без проблем, а Anthropic-формат для клиента обеспечивает сам LiteLLM.
Что было бы правильным решением
Нюанс с путём — следствие того, что внешний модуль смонтирован как /v1 + контроллер
ai. Достаточно добавить алиас POST /v1/messages на тот же обработчик, и Claude Code
с Anthropic SDK заработают без прокси. Сейчас такого алиаса нет — если он важен для вашего
сценария, напишите на agent@agentapi.ru.
Диагностика
| Симптом | Причина |
|---|---|
404 на /v1/messages | Ожидаемо: правильный путь — /v1/ai/messages |
401 Invalid API key format | В ANTHROPIC_AUTH_TOKEN не ключ agentapi-* |
| Claude Code не видит модель | В ANTHROPIC_MODEL нужен полный слаг: anthropic/claude-3.5-sonnet |
400 Negative or zero balance | Пополните баланс |