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

Вложения

Сообщение пользователя может нести изображения и текстовые файлы рядом с текстом. Вложения, заданные путём, не читаются при сборке сообщения: они читаются во время отправки запроса, блок за блоком, поэтому большое изображение никогда не лежит в памяти целиком.

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. Объявленная длина после экранирования сохраняется вместе с файлом. Сохранённый путь должен снова существовать в момент отправки запроса.