AgentAPI

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Пополните баланс