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

Начало работы

svir — маленький компонуемый Rust SDK для общения с большими языковыми моделями: протокол обмена между вашим приложением и сервером модели — и ничего лишнего.

Он говорит на OpenAI-совместимом Chat Completions, всегда потоком — так, как его отдают LM Studio, llama.cpp, vLLM, mlx-lm, Azure OpenAI и облачные эндпоинты.

Предварительная версия

svir находится в стадии preview. Публичный API ещё может меняться между релизами 0.x. Что изменилось — в журнале изменений. Этот сайт описывает svir 0.1.5.

Установка​

svir требует Rust 1.85 или новее (edition 2024). Если Rust ещё не установлен, поставьте его через rustup.

cargo add svir
cargo add tokio --features macros,rt-multi-thread

Или вручную:

Cargo.toml
[dependencies]
svir = "0.1.5"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Фичи по умолчанию дают клиент и HTTPS. Остальные подключаются явно; см. Фичи и TLS.

Первый вызов​

Запустите любой OpenAI-совместимый сервер. LM Studio по умолчанию слушает http://127.0.0.1:1234; вместо qwen3-27b подставьте ID модели, которая на нём есть.

src/main.rs
use svir::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Error> {
let client = Client::openai("http://127.0.0.1:1234").build()?;
let request = Request::new("qwen3-27b")
.system("Be precise.")
.user("Why do rivers meander?");

// The whole answer.
let answer = client.complete(&request).await?;
println!("{}", answer.text.trim());

// The same answer as it arrives.
let mut stream = client.stream(&request).await?;
while let Some(event) = stream.next().await {
match event? {
Event::Text(piece) => print!("{piece}"),
Event::Completed(done) => println!("\n{:?}", done.usage),
_ => {}
}
}

Ok(())
}

Что здесь видно — и что повторяет любая программа на svir:

  • Client::openai(url) возвращает билдер; build() проверяет URL и ключ и возвращает клиента. URL — это база сервера, с /v1 или без.
  • Модель — это ID, который перечисляет сервер, а не название семейства. Узнать, что есть на сервере, можно через client.list_models().
  • Клиент не хранит диалог: диалог — это запрос, и владеете им вы.
  • complete возвращает готовый ответ. stream отдаёт события по мере прихода; последнее из них, Event::Completed, несёт весь ответ.
  • Drop потока отменяет запрос.

Что он умеет​

Ядро — это протокол:

  • Типы: сообщения с частями — текстом, изображениями и файлами; описания, вызовы и результаты инструментов; обязательный или запрещённый вызов инструмента; ответ в JSON или в JSON по схеме; расход токенов; причины завершения; один типизированный тип ошибки.
  • Кодировщик: тело запроса, которое стримится с диска вместе с вложениями, с точным Content-Length, известным до первого байта.
  • Декодер: разбор SSE, вызовы инструментов, собранные из дельт, рассуждения из reasoning_content, reasoning или inline-тегов <think>, расход токенов, ошибки внутри потока и жёсткие лимиты. Строгий по умолчанию, мягкий по запросу.
  • Транспорт: HTTP с необязательной Bearer-аутентификацией, типизированным разбором статусов, таймаутами и отменой через drop.
  • Совместимость: сервер, который отвергает необязательные поля вроде reasoning_effort, распознаётся один раз и запоминается. То, чему ответ должен соответствовать, — выбор инструмента или формат ответа, — никогда не отбрасывается.

Поверх ядра, по желанию:

  • Слои: middleware вокруг каждого вызова, со встроенными Retry, Timeout и Trace.
  • Инструменты: трейт Toolbox для всего, что описывает инструменты модели и отвечает на её вызовы, и Tools — простой реестр типизированных обработчиков. Без макросов.

Намеренно не входит: цикл агента, история сессий, хранилище и MCP. Это территория приложения. svir даёт ему детали, а страницы этого сайта показывают те несколько строк, которые нужны для каждой.

Принципы​

  • Протокол в ядре, строительные блоки сверху. svir встаёт под чат-бэкенд и под движок агента, и никому из них не приходится под него прогибаться.
  • Ничего не теряется молча. ID вызовов инструментов, рассуждения и служебные данные провайдера сохраняются при передаче туда и обратно. То, что адаптер не может представить, — явная ошибка, а не выброшенное поле.
  • Сначала целиком, потом исполнение. Вызов инструмента, пришедший частично, — только данные для отображения.
  • Строго и с лимитами по умолчанию. Неизвестный ввод — ошибка, если вы не попросили мягкий режим. У байтов, событий и вызовов инструментов всегда есть лимиты, и упереться в лимит — типизированный исход.
  • Секреты не попадают в записи. Ключи никогда не оказываются в ошибках, событиях и логах.
  • Тесты без модели. Транспорт спрятан за одним трейтом, поэтому код, который вызывает модель, можно тестировать вообще без сервера.

Что дальше​

В каталоге примеров по одной короткой программе на каждый способ использования svir, а справочник API — на docs.rs.