Перейти к основному содержимому

Запросы

Request говорит, на что модель должна ответить: какая модель, системный промпт, диалог на данный момент, инструменты, которые она может вызвать, и несколько параметров. Он собирается через Request::new(model) и цепочку методов.

use svir::prelude::*;

fn request(question: &str) -> Request {
Request::new("qwen3-27b")
.system("Answer in one paragraph.")
.reasoning(Effort::Low)
.max_tokens(2048)
.temperature(0.2)
.user(question)
}

Билдер​

Каждый метод принимает self и возвращает его, поэтому запрос собирается одной цепочкой или переприсваивается по мере роста: request = request.user(line);. Вызов метода с отброшенным результатом ничего не меняет.

МетодЧто делает
system(text)Системный промпт. Один на запрос; повторный вызов заменяет его
user(text)Добавляет сообщение пользователя с этим текстом
message(message)Добавляет сообщение, собранное через Message
assistant(completion)Добавляет ответ модели — с текстом, вызовами инструментов и рассуждениями
tool_result(call_id, content)Добавляет результат одного вызова инструмента
tool_results(results)Добавляет результаты, по сообщению на каждый
tool(tool) / tools(&toolbox)Описывает инструменты, которые модель может вызвать; см. Инструменты
tool_choice(choice)Может ли или должна ли модель вызвать инструмент; см. Инструменты
response_format(format)Ответ в JSON или в JSON по схеме; см. Структурированный вывод
reasoning(effort)Насколько глубоко модели рассуждать
max_tokens(n)Максимум генерируемых токенов. На большинстве серверов рассуждения тоже в него входят
temperature(t)Температура сэмплирования
include_usage(bool)Переопределяет умолчание клиента (включено) — запрашивать ли расход токенов
send_reasoning(bool)Отправляет рассуждения из прошлых ответов обратно (по умолчанию выключено)

Неустановленное не отправляется: запрос без max_tokens или temperature оставляет их на усмотрение сервера, а выбор инструмента или формат ответа, оставленные по умолчанию, тоже не отправляются.

Поля открыты для чтения (request.model, request.messages) — этим пользуется слой. Request реализует Clone и сериализуется; см. Диалоги.

Системный промпт — поле, а не сообщение

Role — это User, Assistant или Tool. Системной роли нет: каждый протокол кладёт системный промпт в своё место, поэтому он живёт в Request::system, а размещает его адаптер.

Сообщения и части​

Message — это роль и её части по порядку. request.user(text) — сокращение для частого случая; соберите Message, когда в реплике больше, чем текст.

use svir::prelude::*;

fn compare() -> Request {
let question = Message::user("What changed between these two?")
.with(Image::path("before.png"))
.with(Image::path("after.png"))
.with(TextFile::path("diff.patch"));

Request::new("qwen3-27b").message(question)
}
КонструкторНазначение
Message::user(text)Реплика пользователя. Пустой текст не добавляет части, поэтому Message::user("") с изображениями несёт только изображения
Message::assistant(text)Реплика модели, восстановленная из сохранённого текста. Для живого ответа используйте request.assistant(done)
Message::tool_result(result)Один результат инструмента
.with(part)Добавляет часть: &str, String, Image, TextFile, ToolCall, ToolResult, Reasoning

Какие части допустимы для роли, проверяется при отправке запроса. Нарушение — ErrorKind::Unsupported: ничего не выбрасывается молча.

РольЧасти
UserТекст, изображения, текстовые файлы
AssistantТекст, рассуждения, вызовы инструментов
ToolТолько результаты инструментов

Изображения и текстовые файлы описаны во Вложениях.

Глубина рассуждений​

Effort — это Off, Low, Medium, High или XHigh. Если не задано, ничего не отправляется и выбор остаётся за сервером. Off просит не рассуждать; послушается ли модель — её дело.

use svir::prelude::*;

fn think_hard(question: &str) -> Request {
Request::new("qwen3-27b")
.reasoning(Effort::High)
// Reasoning spends the output budget too; leave room for the answer.
.max_tokens(8192)
.user(question)
}

Некоторые серверы сразу отвергают поле reasoning_effort. Клиент справляется с этим сам: запрос отправляется ещё раз без поля, один раз, и сервер запоминается. См. обработку совместимости.

Сами рассуждения приходят как Event::Reasoning, пока ответ стримится, и в Completion::reasoning в конце; см. Чтение ответа.

Чего запрос сказать не может​

В svir 0.1 нет стоп-последовательностей, и запрашивается один вариант ответа. Ответ всегда приходит потоком; complete собирает его. Если серверу нужно поле, которого нет в таблице выше, — svir 0.1 его не передаёт.