Поиск в интернете
Server-side инструмент — модель сама ищет в интернете и пишет ответ с источниками. Никакого ручного исполнения у вас на стороне.
В отличие от обычных функций (Вызов инструментов), которые исполняете вы, веб-поиск — это server-side инструмент: вы добавляете его в tools[], модель сама вызывает поиск, получает результаты, формирует ответ и присылает его одним финальным сообщением. Вашему клиенту ничего исполнять не нужно — никаких лишних round-trip'ов и role: "tool" сообщений.
Работает с большинством моделей. Под капотом наш слой маршрутизации выбирает поисковый движок — у части моделей он встроен в саму модель, у части подключается отдельно. Для вас разница только в наборе источников, которые попадут в ответ.
Минимальный пример
Передайте в tools[] элемент с типом hubris:web_search (это наш namespace для server-side инструментов — отличает их от обычных function-tool):
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "user", "content": "Что произошло на рынке российских облигаций на прошлой неделе?"}
],
"tools": [
{ "type": "hubris:web_search" }
]
}'Ответ — обычный chat-ответ, но с дополнительным полем annotations[], где перечислены источники:
{
"id": "chatcmpl-...",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "На прошлой неделе ставка ОФЗ выросла до 16.3% на длинных бумагах...",
"annotations": [
{
"type": "url_citation",
"url_citation": {
"url": "https://www.cbr.ru/press/event/?id=12345",
"title": "ЦБ РФ — Информация о денежно-кредитной политике",
"content": "Совет директоров Банка России 17 мая 2026 года...",
"start_index": 42,
"end_index": 87
}
}
]
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 4521,
"completion_tokens": 318,
"total_tokens": 4839
}
}tool_calls остаётся пустым — поиск отрабатывается полностью на стороне нашего слоя маршрутизации, наружу не вылезает.
Параметры поиска
Можно тонко настроить поведение:
{
"type": "hubris:web_search",
"parameters": {
"max_results": 5,
"search_context_size": "medium"
}
}| Параметр | Значения | Описание |
|---|---|---|
max_results | 1–10 (по умолчанию 5) | Сколько источников использовать для составления ответа |
search_context_size | "low" / "medium" / "high" | Сколько контекста из каждого источника подгружать в промпт. Больше — точнее ответ, но дороже |
Все параметры опциональны.
Аннотации
annotations[] приходит на сообщении в формате:
{
"type": "url_citation",
"url_citation": {
"url": "https://...",
"title": "Заголовок страницы",
"content": "Релевантный фрагмент текста источника",
"start_index": 42,
"end_index": 87
}
}start_index / end_index — позиция (в символах) в message.content, к которой относится цитата. Удобно для подсветки источников рядом с текстом в UI.
Стриминг
stream: true работает. Особенность: annotations[] приходит полным массивом сразу в первом чанке (внутри delta.annotations), ещё до самого текста. Дальше идут обычные текстовые delta.content чанки.
data: {"choices":[{"index":0,"delta":{"role":"assistant","annotations":[{"type":"url_citation","url_citation":{...}}]}}]}
data: {"choices":[{"index":0,"delta":{"content":"На прошлой "}}]}
data: {"choices":[{"index":0,"delta":{"content":"неделе ставка ОФЗ "}}]}
...
data: {"choices":[{"index":0,"finish_reason":"stop"}]}
data: [DONE]Это удобно: в UI можно сразу показать «по N источникам» — а текст подгружать по мере прихода.
OpenAI SDK
В официальных SDK OpenAI поле tools поддерживается, но проверки типа стандартные function — поэтому server-tool придётся передавать через extra_body (Python) или приведением типов (TypeScript).
Python:
from openai import OpenAI
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Курс доллара ЦБ на сегодня?"}],
extra_body={
"tools": [
{"type": "hubris:web_search", "parameters": {"max_results": 3}}
]
},
)
msg = resp.choices[0].message
print(msg.content)
annotations = msg.model_extra.get("annotations", [])
for ann in annotations:
cit = ann["url_citation"]
print(f" - {cit['title']}: {cit['url']}")TypeScript:
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const resp = await client.chat.completions.create({
model: 'openai/gpt-4o-mini',
messages: [{ role: 'user', content: 'Курс доллара ЦБ на сегодня?' }],
tools: [
{ type: 'hubris:web_search', parameters: { max_results: 3 } },
],
} as any);
const msg = resp.choices[0].message;
console.log(msg.content);
const annotations = (msg as any).annotations ?? [];
for (const ann of annotations) {
console.log(` - ${ann.url_citation.title}: ${ann.url_citation.url}`);
}Что важно знать
prompt_tokensбудут раздуты. Результаты поиска инжектятся в промпт — это нормально, что на коротком запросе вернутся 3–10 тысяч prompt-токенов. Ответ модели опирается на эти источники.tool_callsпустой. Не пытайтесь обработать веб-поиск как обычный function-call — поиск отрабатывает полностью на нашей стороне.- Совмещение с обычными функциями работает. В одном
tools[]можно передать и server-tool, и свои function-tools — модель выберет, что вызывать. /v1/responsesсо стримингом — известное ограничение. В non-stream ответе/v1/responsesэлемент вoutput[]приходит с типомhubris:web_search. В streaming-режиме на этом эндпоинте имя пока не нормализуется обратно — в SSE-событияхresponse.output_item.added/response.output_item.doneпрефикс типа отличается от того, что вы отправили в запросе. Будет исправлено. На/v1/chat/completions(любой режим) и на non-stream/v1/responsesвсё корректно.
Биллинг
Веб-поиск тарифицируется не только токенами:
- Токены — обычным образом (prompt + completion). С учётом раздутого prompt после инжекта результатов.
- Плата за поиск — фиксированная сумма за запрос (зависит от модели и
search_context_size).
В большинстве случаев бо́льшая часть стоимости запроса — это именно плата за поиск, не токены. Итоговая сумма по запросу видна в разделе «Расходы».
Списание происходит после успешного ответа. Транзакция атомарна — баланс, usage_log и движение по балансу пишутся одной БД-транзакцией.
Что дальше
- Вызов инструментов — для функций, которые исполняете вы сами.
- Структурированный вывод — заставить ответ соответствовать JSON-схеме (работает вместе с веб-поиском).
- POST /v1/chat/completions — полная схема.
- Расходы — детализация запросов и стоимости.
Обновлено: