API ВЕГА

Индекс документации

Получите полный индекс документации по адресу: https://api.vega.chat/docs/llms.txt
Используйте этот файл, чтобы узнать о всех доступных страницах перед дальнейшим изучением.

api guides

Потоковая передача

MD версия

export const Template = ({children, data}) => { const replace = s => s.replace(/{{(\w+)}}/g, (_, k) => (k in data) ? data[k] : {{${k}}}); const leafText = node => typeof node === 'string' ? node : node?.$$typeof && typeof node.props?.children === 'string' ? node.props.children : null; const collapseTokens = nodes => { const out = []; let i = 0; while (i < nodes.length) { const ta = leafText(nodes[i]); const tb = leafText(nodes[i + 1]); const tc = leafText(nodes[i + 2]); if (ta != null && tb != null && tc != null) { const m = (ta + tb + tc).match(/^([\s\S]){{(\w+)}}([\s\S])$/); if (m && (m[2] in data)) { out.push(m[1] + data[m[2]] + m[3]); i += 3; continue; } } out.push(nodes[i]); i++; } return out; }; const process = node => { if (typeof node === 'string') return replace(node); if (Array.isArray(node)) return collapseTokens(node.map(process)); if (node && typeof node === 'object') { if (node.$$typeof) return { ...node, props: process(node.props) }; return Object.fromEntries(Object.entries(node).map(([k, v]) => [k, process(v)])); } return node; }; return <>{process(children)}</>; };

export const Model = { GPT_4_Omni: 'openai/gpt-4o' };

export const API_KEY_REF = '<OPENROUTER_API_KEY>';

API ВЕГА позволяет потоково передавать ответы от любой модели. Это полезно при создании чат‑интерфейсов или других приложений, где UI должен обновляться по мере генерации ответа моделью.

Чтобы включить потоковую передачу, можно установить параметр stream в true в вашем запросе. Модель будет передавать ответ клиенту частями, а не возвращать весь ответ сразу.

Ниже приведён пример того, как потоково передать ответ и обработать его:

<Template data={{ API_KEY_REF, MODEL: Model.GPT_4_Omni }}

```typescript title="TypeScript SDK" expandable lines theme={null} import { OpenRouter } from '@openrouter/sdk'; const openRouter = new OpenRouter({ apiKey: '{{API_KEY_REF}}', }); const question = 'How would you build the tallest building ever?'; const stream = await openRouter.chat.send({ model: '{{MODEL}}', messages: [{ role: 'user', content: question }], stream: true, }); for await (const chunk of stream) { const content = chunk.choices?.[0]?.delta?.content; if (content) { console.log(content); } // Final chunk includes usage stats if (chunk.usage) { console.log('Usage:', chunk.usage); } } ``` ```python Python expandable lines theme={null} import requests import json question = "How would you build the tallest building ever?" url = "https://openrouter.ai/api/v1/chat/completions" headers = { "Authorization": f"Bearer {{API_KEY_REF}}", "Content-Type": "application/json" } payload = { "model": "{{MODEL}}", "messages": [{"role": "user", "content": question}], "stream": True } buffer = "" with requests.post(url, headers=headers, json=payload, stream=True) as r: for chunk in r.iter_content(chunk_size=1024, decode_unicode=True): buffer += chunk while True: try: # Find the next complete SSE line line_end = buffer.find('\n') if line_end == -1: break line = buffer[:line_end].strip() buffer = buffer[line_end + 1:] # Skip SSE comments (lines starting with ":"), e.g. the # ": OPENROUTER PROCESSING" keep-alive — they are not JSON if line.startswith(':'): continue if line.startswith('data: '): data = line[6:] if data == '[DONE]': break try: data_obj = json.loads(data) content = data_obj["choices"][0]["delta"].get("content") if content: print(content, end="", flush=True) except json.JSONDecodeError: pass except Exception: break ``` ```typescript title="TypeScript (fetch)" expandable lines theme={null} const question = 'How would you build the tallest building ever?'; const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { Authorization: `Bearer ${API_KEY_REF}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: '{{MODEL}}', messages: [{ role: 'user', content: question }], stream: true, }), }); const reader = response.body?.getReader(); if (!reader) { throw new Error('Response body is not readable'); } const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; // Append new chunk to buffer buffer += decoder.decode(value, { stream: true }); // Process complete lines from buffer while (true) { const lineEnd = buffer.indexOf('\n'); if (lineEnd === -1) break; const line = buffer.slice(0, lineEnd).trim(); buffer = buffer.slice(lineEnd + 1); // Skip SSE comments (lines starting with ":"), e.g. the // ": OPENROUTER PROCESSING" keep-alive — they are not JSON if (line.startsWith(':')) continue; if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') break; try { const parsed = JSON.parse(data); const content = parsed.choices[0].delta.content; if (content) { console.log(content); } } catch (e) { // Ignore invalid JSON } } } } } finally { reader.cancel(); } ``` </Template>

Дополнительная информация

Для потоков SSE (Server-Sent Events) API ВЕГА иногда отправляет комментарии, чтобы предотвратить тайм‑ауты соединения. Такие комментарии выглядят так:

text
: OPENROUTER PROCESSING

Полезная нагрузка комментария может быть безопасно проигнорирована в соответствии со спецификацией SSE. Тем не менее, при желании её можно использовать для улучшения UX, например, показывая динамический индикатор загрузки.

Если вы парсите поток вручную, пропускайте строки, начинающиеся с : перед вызовом JSON.parse. Передача строки комментария вроде : OPENROUTER PROCESSING в JSON.parse вызывает ошибку, а необработанная ошибка приведёт к сбою цикла обработки потока. Приведённые выше фрагменты кода учитывают это.

Парсер, соответствующий спецификации, такой как eventsource-parser, обрабатывает комментарии, многострочные поля data: и буферизацию за вас:

typescript
import { createParser } from 'eventsource-parser'; const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'openai/gpt-4o', messages: [{ role: 'user', content: 'Hello' }], stream: true, }), }); // Errors that occur before streaming starts are plain JSON, not SSE if (!response.ok) { const error = await response.json(); throw new Error(error.error.message); } const parser = createParser({ onEvent(event) { if (event.data === '[DONE]') return; try { const chunk = JSON.parse(event.data); const content = chunk.choices?.[0]?.delta?.content; if (content) { console.log(content); } } catch { // Ignore invalid JSON } }, }); const reader = response.body!.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; parser.feed(decoder.decode(value, { stream: true })); }

Парсер обрабатывает только фрейминг SSE. Ошибки, возникающие в процессе генерации, всё равно приходят как обычные события data: с полем error. См. раздел Обработка ошибок во время потоковой передачи ниже.

Идентификатор генерации возвращается в заголовке ответа X-Generation-Id для всех конечных точек (chat completions, completions, responses и messages), что может быть полезно для отладки и корреляции запросов.

Некоторые реализации SSE‑клиентов могут некорректно разбирать полезную нагрузку согласно спецификации, что приводит к необработанной ошибке при попытке JSON.stringify не‑JSON данных. Мы рекомендуем использовать следующие клиенты:

Финальный блок использования (Chat Completions)

На конечной точке Chat Completions (/api/v1/chat/completions) каждый поток заканчивается дополнительным блоком, содержащим объект usage для запроса, отправляемым непосредственно перед сообщением [DONE]. Спецификация OpenAI выдаёт этот блок с пустым массивом choices, но многие клиенты падают, пытаясь обратиться к choices[0].delta. Поэтому API ВЕГА намеренно отклоняется: блок usage содержит один выбор с delta, не содержащим контента, но повторяющим finish_reason (и native_finish_reason) потока.

text
data: {"id":"gen-abc123",...,"choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]} data: {"id":"gen-abc123",...,"choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}],"usage":{...}} data: [DONE]

Это означает, что терминальный finish_reason появляется дважды: один раз в последнем блоке с контентом и ещё раз в блоке usage. Клиенты, валидирующие потоки, должны рассматривать блок usage как бухгалтерскую рамку, а не как второе терминальное событие.

Такая форма специфична для Chat Completions. Другие конечные точки следуют своим спецификациям: API Responses (/api/v1/responses) сообщает об использовании в событии response.completed, а API Messages (/api/v1/messages) — в событии message_delta перед message_stop.

Отмена потока

Запросы с потоковой передачей можно отменить, прервав соединение. Для поддерживаемых провайдеров это немедленно останавливает обработку модели и начисление оплаты.

Поддержка провайдеров

Поддерживается

  • OpenAI, Azure, Anthropic
  • Fireworks, Mancer, Recursal
  • AnyScale, Lepton, OctoAI
  • Novita, DeepInfra, Together
  • Cohere, Hyperbolic, Infermatic
  • Avian, XAI, Cloudflare
  • SFCompute, Nineteen, Liquid
  • Friendli, Chutes, DeepSeek

В данный момент не поддерживается

  • AWS Bedrock, Groq, Modal
  • Google, Google AI Studio, Minimax
  • HuggingFace, Replicate, Perplexity
  • Mistral, AI21, Featherless
  • Lynn, Lambda, Reflection
  • SambaNova, Inflection, ZeroOneAI
  • AionLabs, Alibaba, Nebius
  • Kluster, Targon, InferenceNet

Чтобы реализовать отмену потока:

<Template data={{ API_KEY_REF, MODEL: Model.GPT_4_Omni }}

```typescript title="TypeScript SDK" expandable lines theme={null} import { OpenRouter } from '@openrouter/sdk'; const openRouter = new OpenRouter({ apiKey: '{{API_KEY_REF}}', }); const controller = new AbortController(); try { const stream = await openRouter.chat.send({ model: '{{MODEL}}', messages: [{ role: 'user', content: 'Write a story' }], stream: true, }, { signal: controller.signal, }); for await (const chunk of stream) { const content = chunk.choices?.[0]?.delta?.content; if (content) { console.log(content); } } } catch (error) { if (error.name === 'AbortError') { console.log('Stream cancelled'); } else { throw error; } } // To cancel the stream: controller.abort(); ``` ```python Python expandable lines theme={null} import requests from threading import Event, Thread def stream_with_cancellation(prompt: str, cancel_event: Event): with requests.Session() as session: response = session.post( "https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {{API_KEY_REF}}"}, json={"model": "{{MODEL}}", "messages": [{"role": "user", "content": prompt}], "stream": True}, stream=True ) try: for line in response.iter_lines(): if cancel_event.is_set(): response.close() return if line: print(line.decode(), end="", flush=True) finally: response.close() # Example usage: cancel_event = Event() stream_thread = Thread(target=lambda: stream_with_cancellation("Write a story", cancel_event)) stream_thread.start() # To cancel the stream: cancel_event.set() ``` ```typescript title="TypeScript (fetch)" expandable lines theme={null} const controller = new AbortController(); try { const response = await fetch( 'https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { Authorization: `Bearer ${{{API_KEY_REF}}}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: '{{MODEL}}', messages: [{ role: 'user', content: 'Write a story' }], stream: true, }), signal: controller.signal, }, ); // Process the stream... } catch (error) { if (error.name === 'AbortError') { console.log('Stream cancelled'); } else { throw error; } } // To cancel the stream: controller.abort(); ``` </Template>

Отмена работает только для потоковых запросов к поддерживаемым провайдерам. Для запросов без потоковой передачи или к неподдерживаемым провайдерам модель будет продолжать обработку, и вам будет начислена плата за полный ответ.

Обработка ошибок во время потоковой передачи

API ВЕГА обрабатывает ошибки по‑разному в зависимости от того, когда они возникают в процессе потоковой передачи:

Ошибки до отправки любых токенов

Если ошибка происходит до того, как какие‑либо токены были переданы клиенту, API ВЕГА возвращает стандартный JSON‑ответ об ошибке с соответствующим HTTP‑статусом. Это соответствует обычному формату ошибки:

json
{ "error": { "code": 400, "message": "Invalid model specified" } }

Распространённые коды статуса HTTP:

  • 400: Bad Request (некорректные параметры)
  • 401: Unauthorized (недействительный ключ API)
  • 402: Payment Required (недостаточно кредитов)
  • 429: Too Many Requests (превышен лимит запросов)
  • 502: Bad Gateway (ошибка провайдера)
  • 503: Service Unavailable (нет доступных провайдеров)

Ошибки после отправки токенов (mid‑stream)

Если ошибка происходит после того, как часть токенов уже передана клиенту, API ВЕГА не может изменить HTTP‑статус (который уже 200 OK). Вместо этого ошибка отправляется как событие Server‑Sent Event (SSE) с единой структурой:

text
data: {"id":"cmpl-abc123","object":"chat.completion.chunk","created":1234567890,"model":"openai/gpt-4o","provider":"openai","error":{"code":"server_error","message":"Provider disconnected unexpectedly"},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}

Ключевые характеристики ошибок mid‑stream:

  • Ошибка появляется на верхнем уровне вместе со стандартными полями ответа (id, object, created и т.д.)
  • В массиве choices присутствует finish_reason: "error" для корректного завершения потока
  • HTTP‑статус остаётся 200 OK, поскольку заголовки уже отправлены
  • После этого события поток завершается

Примеры кода

Ниже показано, как правильно обрабатывать оба типа ошибок в реализации потоковой передачи:

<Template data={{ API_KEY_REF, MODEL: Model.GPT_4_Omni }}

```typescript title="TypeScript SDK" expandable lines theme={null} import { OpenRouter } from '@openrouter/sdk'; const openRouter = new OpenRouter({ apiKey: '{{API_KEY_REF}}', }); async function streamWithErrorHandling(prompt: string) { try { const stream = await openRouter.chat.send({ model: '{{MODEL}}', messages: [{ role: 'user', content: prompt }], stream: true, }); for await (const chunk of stream) { // Check for errors in chunk if ('error' in chunk) { console.error(`Stream error: ${chunk.error.message}`); if (chunk.choices?.[0]?.finish_reason === 'error') { console.log('Stream terminated due to error'); } return; } // Process normal content const content = chunk.choices?.[0]?.delta?.content; if (content) { console.log(content); } } } catch (error) { // Handle pre-stream errors console.error(`Error: ${error.message}`); } } ``` ```python Python expandable lines theme={null} import requests import json async def stream_with_error_handling(prompt): response = requests.post( 'https://openrouter.ai/api/v1/chat/completions', headers={'Authorization': f'Bearer {{API_KEY_REF}}'}, json={ 'model': '{{MODEL}}', 'messages': [{'role': 'user', 'content': prompt}], 'stream': True }, stream=True ) # Check initial HTTP status for pre-stream errors if response.status_code != 200: error_data = response.json() print(f"Error: {error_data['error']['message']}") return # Process stream and handle mid-stream errors for line in response.iter_lines(): if line: line_text = line.decode('utf-8') if line_text.startswith('data: '): data = line_text[6:] if data == '[DONE]': break try: parsed = json.loads(data) # Check for mid-stream error if 'error' in parsed: print(f"Stream error: {parsed['error']['message']}") # Check finish_reason if needed if parsed.get('choices', [{}])[0].get('finish_reason') == 'error': print("Stream terminated due to error") break # Process normal content content = parsed['choices'][0]['delta'].get('content') if content: print(content, end='', flush=True) except json.JSONDecodeError: pass ``` ```typescript title="TypeScript (fetch)" expandable lines theme={null} async function streamWithErrorHandling(prompt: string) { const response = await fetch( 'https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${{{API_KEY_REF}}}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: '{{MODEL}}', messages: [{ role: 'user', content: prompt }], stream: true, }), } ); // Check initial HTTP status for pre-stream errors if (!response.ok) { const error = await response.json(); console.error(`Error: ${error.error.message}`); return; } const reader = response.body?.getReader(); if (!reader) throw new Error('No response body'); const decoder = new TextDecoder(); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); while (true) { const lineEnd = buffer.indexOf('\n'); if (lineEnd === -1) break; const line = buffer.slice(0, lineEnd).trim(); buffer = buffer.slice(lineEnd + 1); if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') return; try { const parsed = JSON.parse(data); // Check for mid-stream error if (parsed.error) { console.error(`Stream error: ${parsed.error.message}`); // Check finish_reason if needed if (parsed.choices?.[0]?.finish_reason === 'error') { console.log('Stream terminated due to error'); } return; } // Process normal content const content = parsed.choices[0].delta.content; if (content) { console.log(content); } } catch (e) { // Ignore parsing errors } } } } } finally { reader.cancel(); } } ``` </Template>

Поведение, специфичное для API

Разные конечные точки API могут немного по‑разному обрабатывать ошибки потоковой передачи:

  • OpenAI Chat Completions API: Возвращает ErrorResponse напрямую, если не было обработано ни одного чанка, либо включает информацию об ошибке в ответ, если часть чанков уже была отправлена.
  • OpenAI Responses API: Может преобразовывать некоторые коды ошибок (например, context_length_exceeded) в успешный ответ с finish_reason: "length" вместо того, чтобы рассматривать их как ошибки.