use serde::{Deserialize, Serialize};
pub mod token_spec;
pub use token_spec::{CalcResizeError, ImageTokenSpec, ResizeResult};
pub const IMAGE_SPECIAL_TOKEN: &str = "<|image|>";
pub const IMAGE_SPECIAL_TOKEN_ID: u32 = 129264;
pub const MAX_IMAGE_URL_LEN: usize = 8192;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageMediaType {
Jpeg,
Png,
Gif,
Webp,
}
impl ImageMediaType {
pub const fn mime(self) -> &'static str {
match self {
Self::Jpeg => "image/jpeg",
Self::Png => "image/png",
Self::Gif => "image/gif",
Self::Webp => "image/webp",
}
}
pub fn from_mime(mime: &str) -> Option<Self> {
let mime = mime.split(';').next().unwrap_or_default().trim();
let (kind, subtype) = mime.split_once('/')?;
if !kind.eq_ignore_ascii_case("image") {
return None;
}
if subtype.eq_ignore_ascii_case("jpeg") || subtype.eq_ignore_ascii_case("jpg") {
return Some(Self::Jpeg);
}
if subtype.eq_ignore_ascii_case("png") {
return Some(Self::Png);
}
if subtype.eq_ignore_ascii_case("gif") {
return Some(Self::Gif);
}
if subtype.eq_ignore_ascii_case("webp") {
return Some(Self::Webp);
}
None
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ImageDetail {
Low,
#[default]
High,
Original,
Auto,
}
impl ImageDetail {
pub const fn is_low(self) -> bool {
matches!(self, Self::Low)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImageSource {
DataUrl {
data_url: String,
detail: ImageDetail,
},
Url { url: String, detail: ImageDetail },
Bytes { data: Vec<u8>, detail: ImageDetail },
}
impl ImageSource {
pub const fn detail(&self) -> ImageDetail {
match self {
Self::DataUrl { detail, .. }
| Self::Url { detail, .. }
| Self::Bytes { detail, .. } => *detail,
}
}
}
pub fn is_http_url(url: &str) -> bool {
url.get(..4)
.is_some_and(|scheme| scheme.eq_ignore_ascii_case("http"))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageInfo {
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MultiModalData {
pub images: Vec<ImageInfo>,
}
impl MultiModalData {
pub fn is_empty(&self) -> bool {
self.images.is_empty()
}
pub fn image_token_adjustment(&self) -> Result<usize, CalcResizeError> {
let spec = ImageTokenSpec::v41();
let mut adjustment = 0usize;
for image in &self.images {
adjustment += spec.calc_token_len(image.width as usize, image.height as usize)? - 1;
}
Ok(adjustment)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_adjustment_counts_one_placeholder_per_image() {
let spec = ImageTokenSpec::v41();
let data = MultiModalData {
images: vec![
ImageInfo {
data: Vec::new(),
width: 1920,
height: 1080,
},
ImageInfo {
data: Vec::new(),
width: 640,
height: 480,
},
],
};
let expected =
spec.calc_token_len(1920, 1080).unwrap() + spec.calc_token_len(640, 480).unwrap() - 2;
assert_eq!(data.image_token_adjustment().unwrap(), expected);
assert!(MultiModalData::default().image_token_adjustment().unwrap() == 0);
}
}