Индекс документации
Получите полный индекс документации по адресу: https://api.vega.chat/docs/llms.txt
Используйте этот файл, чтобы узнать о всех доступных страницах перед дальнейшим изучением.
Streaming
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 иногда отправляет комментарии, чтобы предотвратить тайм‑ауты соединения. Такие комментарии выглядят так:
Полезную нагрузку комментария можно безопасно игнорировать в соответствии со спецификацией SSE. Тем не менее, при необходимости вы можете использовать её для улучшения UX, например, показывая динамический индикатор загрузки.
Если вы парсите поток вручную, пропускайте строки, начинающиеся с
:перед вызовомJSON.parse. Передача строки комментария вроде: OPENROUTER PROCESSINGвJSON.parseвызывает исключение, и если его не обработать, ваш цикл обработки потока завершится с ошибкой. Приведённые выше фрагменты кода учитывают это.
Парсер, соответствующий спецификации, такой как [eventsource-parser], обрабатывает комментарии, многострочные поля data: и буферизацию за вас:
Парсер обрабатывает только фрейминг 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‑статусом. Это соответствует стандартному формату ошибки:
Общие коды 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) с единой структурой:
Характеристики ошибок в середине потока:
- Ошибка появляется на верхнем уровне вместе со стандартными полями ответа (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
ErrorResponsedirectly 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 withfinish_reason: "length"instead of treating them as errors