Вложения
Сообщение пользователя может нести изображения и текстовые файлы рядом с текстом. Вложения, заданные путём, не читаются при сборке сообщения: они читаются во время отправки запроса, блок за блоком, поэтому большое изображение никогда не лежит в памяти целиком.
use svir::prelude::*;
fn attachments(photo: Vec<u8>) -> Message {
Message::user("Describe what you see.")
// A file: nothing is read until the request is sent.
.with(Image::path("chart.png"))
// An extension svir does not know needs its media type.
.with(Image::path("scan.tif").media_type("image/tiff"))
// Bytes already in memory always name theirs.
.with(Image::bytes(photo, "image/jpeg"))
// A text file, by path or from memory.
.with(TextFile::path("notes.md"))
.with(TextFile::text("query.sql", "select 1;"))
}
Изображения
Изображения передаются как base64 data URL.
- Медиатип берётся из расширения для
png,jpg,jpeg,gifиwebp. Для всего остального нужен.media_type(..), иначе отправка завершитсяErrorKind::Attachment. Image::bytes(data, media_type)принимает байты, уже лежащие в памяти, и всегда — их медиатип.- Модель должна уметь видеть. Текстовая модель отвергнет запрос или проигнорирует изображение; svir не может знать, какие модели это умеют.
Текстовые файлы
Текстовые файлы передаются как текст внутри сообщения, обёрнутый с их именем.
- Они должны быть в UTF-8.
TextFile— для текста: изображение отправляйте какImage, остальное сначала конвертируйте. - Модели сообщается имя файла.
.name(..)меняет его — например, чтобы спрятать локальный путь. TextFile::text(name, text)отправляет текст из памяти так, будто это файл.
Когда читаются файлы
При отправке запроса кодировщик измеряет каждый файл, поэтому точный
Content-Length тела известен до первого байта, а затем стримит файлы в тело.
- Файл, который отсутствует, не читается или изменился после измерения,
проваливает вызов с
ErrorKind::Attachment, а не сборку сообщения. - Чтобы измерить текстовый файл, его нужно прочитать целиком: длина после экранирования в JSON зависит от содержимого. Изображению хватает размера. Чтобы обойтись без этого чтения, объявите длину.
- Пути считаются относительно рабочего каталога процесса.
- Если в файл записали между измерением и отправкой, вызов завершится ошибкой, а не отправит тело, не совпадающее со своей длиной. Текстовый файл к тому же проверяется на UTF-8 по ходу отправки.
ClientBuilder::context_tokens(n) отклоняет запрос, который заведомо не
поместится, ещё до отправки — считая байты тела токенами. Для текста это
никогда не занижает оценку, но изображения завышает в разы: с вложенными
изображениями эту настройку лучше не включать.
Объявленная длина
Приложение, которое хранит файлы, — например, бэкенд чата — может измерить текстовый файл один раз, когда он приходит, и сохранить длину рядом с ним. Тогда при отправке до начала стриминга тела ничего не читается: узнаётся только размер файла.
use svir::prelude::*;
/// When the file arrives: its length once escaped into a JSON string.
fn measure(text: &[u8]) -> u64 {
svir::body::escaped_len(text)
}
/// When it is sent: the stored length, so the file is read only once.
fn attach(path: &str, escaped: u64) -> TextFile {
TextFile::path(path).escaped_len(escaped)
}
svir::body::escaped_len(bytes)считает так же, как экранирует svir. Экранирование побайтовое, поэтому длины кусков файла в сумме дают длину файла, даже если кусок заканчивается посреди символа: загрузку можно измерять по мере поступления. Проверки на UTF-8 здесь нет.- Длина, которой у файла быть не может, или которой у него не оказалось по
ходу стриминга, завершается
ErrorKind::Attachment; так же и файл не в UTF-8. Тело, не совпадающее со своимContent-Length, не уходит. - Текст в памяти (
TextFile::text) измеряется в любом случае; несовпадающая объявленная длина — ошибка при сборке тела. - Изображениям объявлять ничего не нужно: длина base64 следует из размера.
svir::body::escaped_lenне требует фич, так что код, который сохраняет файлы, может измерять их без клиента.
Сохранённые вложения
Запрос можно сериализовать; см. Диалоги. Вложение, заданное путём, сохраняется как путь, а заданное байтами — как base64. Объявленная длина после экранирования сохраняется вместе с файлом. Сохранённый путь должен снова существовать в момент отправки запроса.