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

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

Streaming

MD версия
tsx
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 VEGA позволяет получать потоковые ответы от любой модели. Это полезно для создания чат-интерфейсов или других приложений, где пользовательский интерфейс должен обновляться по мере генерации ответа моделью.

Чтобы включить потоковую передачу, вы можете установить параметр 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 VEGA иногда отправляет комментарии, чтобы предотвратить тайм‑ауты соединения. Такие комментарии выглядят так:

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 данных. Мы рекомендуем использовать следующих клиентов:

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

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

Provider Support

Supported

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

Not Currently Supported

  • 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 VEGA обрабатывает ошибки по‑разному в зависимости от того, когда они происходят в процессе потоковой передачи:

Errors before any tokens are sent

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

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

Общие коды HTTP‑статусов включают:

  • 400: Некорректный запрос (неверные параметры)
  • 401: Неавторизован (недействительный ключ VEGA)
  • 402: Требуется оплата (недостаточно кредитов VEGA)
  • 429: Слишком много запросов (ограничение по частоте)
  • 502: Плохой шлюз (ошибка провайдера)
  • 503: Сервис недоступен (нет доступных провайдеров)

Errors after tokens have been sent (mid-stream)

Если ошибка происходит после того, как часть токенов уже была передана клиенту, API VEGA не может изменить 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"}]}

Характеристики ошибок в середине потока:

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

Code examples

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

<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-specific behavior

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

  • OpenAI Chat Completions API: Returns ErrorResponse directly if no chunks were processed, or includes error information in the response if some chunks were processed
  • OpenAI Responses API: May transform certain error codes (like context_length_exceeded) into a successful response with finish_reason: "length" instead of treating them as errors