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

Ошибки

Любой вызов, который может упасть, возвращает svir::Error: один тип, у которого есть вид (kind), по которому можно действовать. Сопоставляйте по виду, а не по тексту.

МетодЧто даёт
kind()ErrorKind: что пошло не так — в терминах, по которым код может действовать
is_retryable()Может ли тот же запрос успешно выполниться позже
retry_after()Сколько сервер попросил подождать, если сказал (не больше 30 с)
is_unsent()Запрос так и не дошёл до сервера: соединение не удалось установить
status()HTTP-статус неуспешного ответа; None для любого другого сбоя
detail()Короткое описание, написанное svir
server_message()Собственные слова сервера, если он что-то прислал
source()Исходная ошибка, через std::error::Error

Display — это описание вида и detail. В нём никогда нет API-ключа, URL запроса, заголовков или сообщения сервера, поэтому его безопасно логировать.

Виды​

ВидПовторяемоКогда возникаетЧто делать
TransportдаСервер недоступен; HTTP 500, 502, 503Проверить, что сервер запущен; повторить с паузой
TimeoutдаПрошёл дедлайн подключения, простоя или слоя; HTTP 408, 504Повторить или увеличить дедлайн для медленной локальной модели
RateLimitedдаHTTP 429Подождать retry_after(), если он есть, и повторить
TruncatedStreamдаПоток закончился до конца ответаОтправить запрос снова, если частичный ответ можно выбросить
AuthenticationнетHTTP 401, 403Исправить ключ
ContextOverflowнетЗапрос не помещается в контекст моделиСократить диалог или убрать вложения
ContentFilterнетКонтент-фильтр сервера заблокировал промптИзменить промпт: тот же заблокируют повторно, и проверка будет оплачена ещё раз
ServerнетСервер сообщил о сбое внутри потокаПрочитать server_message(); подробности — в логах сервера
ProtocolнетНекорректный или несогласованный ответСообщить об этом, приложив сырой поток
UnsupportedнетТо, что svir не может представить; обязательный вызов инструмента, который запрос сделать не может; неожиданный статус; ответ, который не является event streamСм. Диагностику
ResponseLimitнетОтвет превысил заданный лимитОсознанно поднять Limits
AttachmentнетФайл не читается, не то, чем назван, или изменилсяИсправить файл или его медиатип
ConfigнетНеприемлемый URL, источник ключа или заголовокИсправить вызов билдера

ErrorKind помечен #[non_exhaustive]: match нужна ветка по умолчанию. Переполнение контекста распознаётся по коду, типу или сообщению ошибки — говорит ли о нём сервер в ответе с ошибкой или внутри потока со статусом 200. Заблокированный промпт распознаётся только по коду ошибки content_filter, который Azure OpenAI присылает со статусом 400. Ответ, остановленный фильтром, — не ошибка, а FinishReason::ContentFilter, и отказ отвечать — тоже не ошибка, а FinishReason::Refusal.

Обработка​

use std::time::Duration;

use svir::prelude::*;

enum Next {
Answer(Completion),
RetryAfter(Duration),
Shorten,
GiveUp(String),
}

async fn ask(client: &Client, request: &Request) -> Next {
let error = match client.complete(request).await {
Ok(done) => return Next::Answer(done),
Err(error) => error,
};

match error.kind() {
ErrorKind::ContextOverflow => Next::Shorten,
ErrorKind::RateLimited => {
Next::RetryAfter(error.retry_after().unwrap_or(Duration::from_secs(5)))
}
_ if error.is_retryable() => Next::RetryAfter(Duration::from_secs(1)),
// `Display` is safe to log and to show.
_ => Next::GiveUp(error.to_string()),
}
}
  • Сопоставляйте по виду, а не по тексту Display или detail(): текст может меняться между релизами, а виды — часть API.
  • complete возвращает либо весь ответ, либо ошибку, поэтому повтор там чистый. Со stream сбой после первого события означает, что пользователь уже видел часть ответа: повтор — продуктовое решение, а не поведение по умолчанию.
  • Для повторов до начала ответа предпочитайте слой Retry самописному циклу.
  • Возвращайте из своих функций svir::Error или конвертируйте его, сохраняя вид.

Собственное сообщение сервера​

error.server_message() — то, что сказал сервер: error.message из JSON-тела, тело обычным текстом или сообщение внутри потока, обрезанное до 4 КиБ.

Оно намеренно не попадает в Display и Debug. Сообщение сервера может цитировать промпт, имя файла или внутренний адрес, а им не место в логах. Показывайте его человеку, который сделал запрос, а логируйте error.to_string().

use svir::prelude::*;

fn explain(error: &Error) -> String {
match error.server_message() {
Some(said) => format!("{error} (the server said: {said})"),
None => error.to_string(),
}
}

Собственные ошибки​

Код вокруг svir — например, цикл инструментов или слой — может завершиться собственной ошибкой svir, собранной из вида и detail:

use svir::prelude::*;

fn gave_up() -> Error {
Error::new(ErrorKind::Unsupported).with_detail("the model kept calling tools")
}