# Gemma 4 26B A4B — внутренний LLM API Инструкция для агентов, пишущих код против этого сервиса. Читай целиком до генерации кода: раздел GOTCHAS описывает поведение, которое ломает наивную интеграцию. ## ENDPOINT ``` BASE_URL = https://localllm.stc-ai.ru # только https, http отвечает 301 API_PATH = /v1 # OpenAI Chat Completions MODEL_ID = gemma-4-26b AUTH = Authorization: Bearer # ключ выдаётся на сервис, не общий ``` Протокол полностью совместим с OpenAI Chat Completions. Любой OpenAI-клиент работает после подмены `base_url` и `api_key`. Полная схема: `GET /openapi.json` (без авторизации). Доступно: `/v1/chat/completions`, `/v1/completions`, `/v1/models`, `/v1/embeddings` нет. Streaming (`"stream": true`) поддерживается, буферизация на прокси отключена. ## RULES — применять всегда 1. ВСЕГДА передавай `"chat_template_kwargs": {"enable_thinking": true}`. По умолчанию `false`. Без этого модель не рассуждает и решает хуже в разы на задачах с логикой, арифметикой и многошаговым разбором. 2. ВСЕГДА ставь сэмплинг `temperature=1.0, top_p=0.95, top_k=64`. Это опубликованные Google значения для этой модели. Дефолты OpenAI-клиентов (обычно `temperature=0.7` или `1.0` без `top_k`) дают худшее качество. `temperature=1.0` здесь — норма, а не «больше креатива». 3. ВСЕГДА ставь `max_tokens >= 2000` (для задач со схемой — 3000). Рассуждения расходуют тот же лимит: 500–2500 токенов ДО начала ответа. При нехватке получишь `finish_reason: "length"` и пустой либо обрезанный `content`. 4. ЧИТАЙ рассуждения из `message.reasoning`, ответ — из `message.content`. Это разные поля. `content` НЕ содержит рассуждений. 5. НИКОГДА не парси JSON регулярками из `content`. При использовании `response_format` он гарантированно валиден — просто `json.loads`. ## MINIMAL WORKING REQUEST ```bash curl https://localllm.stc-ai.ru/v1/chat/completions \ -H "Authorization: Bearer $LLM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gemma-4-26b", "messages": [{"role": "user", "content": "..."}], "chat_template_kwargs": {"enable_thinking": true}, "temperature": 1.0, "top_p": 0.95, "top_k": 64, "max_tokens": 3000 }' ``` ## PYTHON ```python from openai import OpenAI client = OpenAI(base_url="https://localllm.stc-ai.ru/v1", api_key=LLM_API_KEY) resp = client.chat.completions.create( model="gemma-4-26b", messages=[{"role": "user", "content": prompt}], temperature=1.0, top_p=0.95, max_tokens=3000, extra_body={ "top_k": 64, "chat_template_kwargs": {"enable_thinking": True}, }, ) answer = resp.choices[0].message.content reasoning = getattr(resp.choices[0].message, "reasoning", None) ``` `top_k` не входит в стандартную схему OpenAI — передавай через `extra_body`, иначе клиент отбросит параметр или упадёт на валидации. ## STRUCTURED JSON OUTPUT Схема применяется на уровне генерации: сервер запрещает токены, ломающие её. Невалидный JSON получить невозможно. Совместимо с `enable_thinking`: рассуждения идут в `reasoning`, схема применяется только к `content`. ```python resp = client.chat.completions.create( model="gemma-4-26b", messages=[{"role": "user", "content": text}], response_format={ "type": "json_schema", "json_schema": { "name": "ticket", "schema": { "type": "object", "properties": { "category": {"type": "string", "enum": ["auth","billing","bug","other"]}, "urgency": {"type": "integer", "minimum": 1, "maximum": 5}, "refund_needed": {"type": "boolean"}, "summary": {"type": "string"}, }, "required": ["category","urgency","refund_needed","summary"], "additionalProperties": False, }, }, }, temperature=1.0, top_p=0.95, max_tokens=3000, extra_body={"top_k": 64, "chat_template_kwargs": {"enable_thinking": True}}, ) data = json.loads(resp.choices[0].message.content) # безопасно ``` Требования к схеме: - `strict: true` (как у OpenAI) НЕ нужен — схема применяется всегда. - Перечисляй все поля в `required` и ставь `additionalProperties: false`, иначе модель добавит ключи от себя. - Держи схему плоской. Движок ограничений (xgrammar) не принимает глубокие `oneOf`, рекурсивные `$ref`, экзотические `pattern`. Ошибка приходит как HTTP 400. - Проверяй новую схему одним запросом до выката. ## GOTCHAS — проверено на этом сервере ### 1. Схема заставляет модель ошибиться в ЗНАЧЕНИИ Симптом: JSON валиден, но число противоречит тексту в соседнем поле. Причина: настоящий ответ не помещается в объявленный тип, генерация подгоняет его под ближайший допустимый. Ошибки не будет — молча вернётся неверное значение. ```json // схема требовала "urgency": {"type": "integer"}, модель посчитала 5.5 {"apples": 3, "explanation": "3 + 2,5 = 5,5"} ``` Что делать: тип должен вмещать реальность. - `number` вместо `integer`, если возможна дробь - `["string","null"]` там, где ответа может не быть - отдельное поле `confidence` или `uncertain: boolean` вместо принуждения к выбору - не ставь `minimum`/`maximum`, если данные могут выйти за диапазон ### 2. Пустой content при включённом рассуждении Симптом: `finish_reason == "length"`, `content` пустой или оборван. Причина: лимит израсходован на рассуждения. Что делать: `max_tokens >= 2000`. В коде проверяй `finish_reason` перед парсингом: ```python if resp.choices[0].finish_reason == "length": raise RuntimeError("ответ обрезан, увеличь max_tokens") ``` ### 3. Рассуждения на английском при русском запросе Модель думает на удобном ей языке независимо от языка запроса. На качество итогового ответа это не влияет. Если `reasoning` показывается пользователю — задай язык в system-промпте. Иначе игнорируй. ### 4. Модель может ошибиться в категории Классификация не идеальна: на коротком запросе без описания категорий возможен неверный выбор из `enum`. Что делать: описывай значения `enum` в промпте явно (что означает каждое), а не полагайся на имена полей. ### 5. Сервис на одной физической карте При её недоступности замолкает весь endpoint — деградации не будет, будет отказ. В коде: таймаут (рекомендуется 120 с), ретрай с backoff, запасной путь на своей стороне. Не считай сервис высокодоступным. ## LIMITS ``` max_model_len 16384 токена # промпт + рассуждения + ответ concurrency_ok 8 # без деградации; дальше растёт очередь rate_limit 10 запр/с # на IP, всплеск до 20; сверх — HTTP 429 throughput_1 135 tok/s throughput_4 334 tok/s throughput_8 523 tok/s latency_p50_c8 6.5 s # запрос со схемой и рассуждением latency_max_c8 9.1 s images не включены # модель мультимодальна, приём отключён audio не поддерживается embeddings нет ``` Контекст рассчитывай так: промпт + до 2500 токенов рассуждений + ответ. Для промптов длиннее ~10 000 токенов уменьшай `max_tokens` либо режь вход. ## КОДЫ ОТВЕТА Перед моделью стоит шлюз учёта: часть ответов приходит от него, а не от vLLM. ``` 200 успех 401 ключ неизвестен или не передан # шлюз, до модели не дошло 403 ключ отключён или отозван # шлюз, до модели не дошло 404 модель не существует (проверь model) # vLLM 429 превышен лимит запросов # nginx, до шлюза не дошло 502 модель недоступна # шлюз не достучался до vLLM ``` 401 и 403 стоит различать: первый значит «ключа нет в системе», второй — «ключ есть, но им запрещено пользоваться». Во втором случае перевыпуск не поможет, нужно разбираться с администратором. На 429 делай backoff, а не немедленный повтор: лимит скользящий, повтор в ту же секунду снова упрётся в него. Каждый запрос учитывается по ключу: сервис, токены, задержка и причина завершения видны в админке. Ключ одного сервиса не надо переиспользовать в другом — это единственное, что разделяет статистику. ## PRE-DEPLOY CHECKLIST - [ ] `enable_thinking: true` передаётся - [ ] `temperature=1.0, top_p=0.95, top_k=64` - [ ] `max_tokens >= 2000` - [ ] `finish_reason` проверяется до парсинга - [ ] схема прогнана живым запросом, а не только валидатором - [ ] типы в схеме допускают реальные значения (см. GOTCHAS 1) - [ ] таймаут и ретрай настроены, на 429 — backoff - [ ] ключ в переменной окружения, не в коде