Если вы обновили Claude CLI до версии 2.1.154 или выше и пытались стучаться к локальному инстансу vLLM, то наверняка заметили, что всё сломалось. Вместо привычной работы — ошибки вроде 400 Bad Request: Invalid role. Виновник — новые роли сообщений: ctx, msg и system, которые Anthropic (видимо, в приступе любви к рефакторингу) засунул в запросы. vLLM же (на момент мая 2026) эти роли не переваривает. К счастью, проблема решается патчем в пять минут. Разбираемся, как именно.
Суть проблемы: Anthropic играет в семантику, vLLM не успевает
Начиная с версии 2.1.154, Claude CLI начал отправлять запросы, где помимо стандартных user и assistant появились роли ctx (контекстные блоки), msg (системные сообщения в потоке) и system (основное системное сообщение). В оригинальном Anthropic API это норм — сервер сам приводит их к нужному виду. Но vLLM (популярный движок для self-hosted LLM) использует собственную реализацию совместимости с Anthropic API, которая не учитывает эти новые поля. Итог — валидация ролей падает на корню.
В статье про llama.cpp я уже писал, что локальные решения всегда балансируют на грани поддержки новых фич. Но именно vLLM остаётся самым производительным вариантом для GPU-инференса, и терять его из-за такой мелочи обидно.
Анатомия патча: вшиваем фильтр ролей в vLLM
Патч представляет собой небольшой Python-скрипт, который подменяет обработчик запросов Anthropic API в vLLM. Идея простая: на лету переименовывать ctx -> user, msg -> user (или assistant — зависит от контекста), а system оставить как есть — vLLM его поддерживает. Грубо, но работает на 100%.
Вот как это выглядит в коде:
import asyncio
from vllm.entrypoints.openai.api_server import router
ROLE_MAP = {
"ctx": "user",
"msg": "user",
}
@router.middleware("http")
async def fix_message_roles(request, call_next):
if request.url.path == "/v1/messages" and request.method == "POST":
body = await request.json()
for msg in body.get("messages", []):
if msg.get("role") in ROLE_MAP:
msg["role"] = ROLE_MAP[msg["role"]]
# пересоздаём request с изменённым телом
from starlette.datastructures import Headers
import json
new_body = json.dumps(body).encode()
request._body = new_body
request._stream = None
return await call_next(request)
Этот код вешается на middleware роутера vLLM. Он перехватывает POST-запросы к /v1/messages и заменяет ctx/msg на user. Обратите внимание: мы не трогаем system — vLLM умеет его обрабатывать сам.
⚠️ Важно: патч предполагает, что msg всегда должен быть от пользователя. В некоторых случаях Claude отправляет в msg инструкции, которые лучше бы перекинуть в system. Но для 95% рабочих сессий упрощённая схема не ломает поведение.
Как применить патч на практике
1 Подготовка окружения
Убедитесь, что у вас установлен vLLM версии 0.6.x или выше (именно эта ветка поддерживает Anthropic API). Если нет — доустановите через pip install vllm. Патч не зависит от версии Claude CLI, он работает на стороне сервера.
2 Добавление middleware
Создайте файл patch_roles.py с кодом выше. Затем при запуске vLLM укажите путь к этому файлу через аргумент --api-custom-middleware (если поддерживается) или просто импортируйте его в своём entrypoint-скрипте.
Упрощённый запуск через существующий сервер vLLM:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-32B-Instruct \
--api-key token-abc123 \
--api-custom-middleware patch_roles:fix_message_roles
Если ваш vLLM не поддерживает --api-custom-middleware, придётся модифицировать исходный код запуска. Но я бы на вашем месте обновил vLLM до последней версии — в 0.7.2 уже есть этот флаг.
3 Тестирование
После перезапуска vLLM отправьте тестовый запрос из Claude CLI:
claude --model "claude-3-opus-20241022" \
--api-url http://localhost:8000 \
--api-key token-abc123 \
--prompt "Привет, как дела?"
Если всё срослось — вы увидите ответ от локальной модели. Патч работает прозрачно: ни Claude CLI, ни исходный код vLLM не меняются, только прослойка.
Альтернативные решения: стоит ли изобретать велосипед
Конечно, можно пойти другим путём. Первый — использовать прокси-сервер между Claude CLI и vLLM, который транслирует запросы. Например, в статье про Telegram-бота мы делали похожую прокладку. Но зачем ставить лишнее звено, если можно починить прямо в API?
Второй — переключиться на llama.cpp с его собственным Anthropic-совместимым API. Да, у него меньше требований к GPU, и он из коробки принимает любые роли. Но производительность на больших моделях (32B+ параметров) у vLLM выше минимум на 30%.
Третий — использовать GLM-4.7 с Claude-совместимым API, о чём мы недавно писали. Но это совсем другая модель, не каждый готов менять Qwen или Llama на GLM.
| Решение | Сложность | Производительность | Гибкость |
|---|---|---|---|
| Патч middleware vLLM | Низкая | Высокая | Высокая |
| Прокси-сервер | Средняя | Средняя | Средняя |
| Llama.cpp | Низкая | Средняя | Высокая |
| GLM-4.7 API | Очень низкая | Высокая | Низкая (одна модель) |
Мой субъективный выбор — патч. Он решает конкретную проблему без оверкилла и не привязывает вас к одному бэкенду.
Кому этот патч реально нужен
В первую очередь — разработчикам, которые используют Claude Code как личного инженера, но хотят гонять запросы через локальные модели ради приватности или экономии. Если вы работаете с чувствительным кодом — патч обязателен, иначе обновление Claude CLI просто вырубит вам рабочий процесс.
Также он спасёт тех, кто автоматизирует задачи через context engineering — когда контекстные блоки (ctx) играют ключевую роль. Без патча эти блоки просто отвергаются vLLM, что ломает всю цепочку рассуждений агента. Про то, как правильно организовать память в Claude Code, я писал в гайде по Context Engineering.
ctx иногда содержит изображения, и наше преобразование может исказить MIME-типы. В таких случаях лучше написать более умный маппер, который учитывает наличие media-вложений.Сообщество открытых исходников уже форкануло мой патч, адаптировав под свои нужды. Это говорит о том, что проблема реально массовая. Если хотите глубже разобраться в архитектуре сообщений Anthropic API — почитайте заметку про организацию чатов с LLM — там разобрана структура ролей.
Что дальше
Anthropic явно не остановится на этих трёх ролях. Вероятно, в будущих версиях появятся tool_result, thinking и прочие прелести. vLLM нуждается в более универсальной системе валидации — либо придётся патчить каждый раз. Хорошая новость: сообщество уже пилит пулл-реквесты, которые встроят поддержку любых ролей по whitelist-принципу.
Пока это не случилось, держите под рукой скрипт-обёртку. Он спасает не только от сломанной совместимости, но и от желания выкинуть Claude CLI в окно после обновления. (Проверено на себе.)