Ошибки
Любой вызов, который может упасть, возвращает 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")
}