use std::collections::BTreeMap;
use onnx_genai::StopSequence;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct EmbeddingRequest {
pub model: String,
pub input: EmbeddingInput,
#[serde(default)]
pub encoding_format: EmbeddingEncodingFormat,
#[serde(default)]
pub dimensions: Option<usize>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum EmbeddingInput {
String(String),
Strings(Vec<String>),
TokenArrays(Vec<Vec<u32>>),
}
#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum EmbeddingEncodingFormat {
#[default]
Float,
Base64,
}
#[derive(Debug, Serialize)]
pub struct EmbeddingResponse {
pub object: &'static str,
pub data: Vec<EmbeddingData>,
pub model: String,
pub usage: EmbeddingUsage,
}
#[derive(Debug, Serialize)]
pub struct EmbeddingData {
pub object: &'static str,
pub embedding: EmbeddingVector,
pub index: usize,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum EmbeddingVector {
Float(Vec<f32>),
Base64(String),
}
impl EmbeddingVector {
pub fn from_floats(values: Vec<f32>, format: EmbeddingEncodingFormat) -> Self {
match format {
EmbeddingEncodingFormat::Float => Self::Float(values),
EmbeddingEncodingFormat::Base64 => {
use base64::{Engine as _, engine::general_purpose::STANDARD};
let mut bytes = Vec::with_capacity(values.len() * size_of::<f32>());
for value in values {
bytes.extend_from_slice(&value.to_le_bytes());
}
Self::Base64(STANDARD.encode(bytes))
}
}
}
}
#[derive(Debug, Serialize)]
pub struct EmbeddingUsage {
pub prompt_tokens: usize,
pub total_tokens: usize,
}
#[derive(Debug, Deserialize)]
pub struct ChatCompletionRequest {
pub model: String,
pub messages: Vec<ChatMessage>,
#[serde(default = "default_max_tokens")]
pub max_tokens: usize,
#[serde(default = "default_temperature")]
pub temperature: f32,
#[serde(default = "default_top_p")]
pub top_p: f32,
#[serde(default)]
pub stream: bool,
#[serde(default)]
pub stop: Option<StopInput>,
#[serde(default)]
pub response_format: Option<ResponseFormat>,
#[serde(default)]
pub tools: Option<Vec<ChatTool>>,
#[serde(default)]
pub tool_choice: Option<ToolChoice>,
#[serde(default)]
pub logprobs: bool,
#[serde(default)]
pub top_logprobs: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub struct CompletionRequest {
pub model: String,
pub prompt: String,
#[serde(default)]
pub suffix: Option<String>,
#[serde(default = "default_max_tokens")]
pub max_tokens: usize,
#[serde(default = "default_temperature")]
pub temperature: f32,
#[serde(default = "default_top_p")]
pub top_p: f32,
#[serde(default)]
pub min_p: f32,
#[serde(default)]
pub frequency_penalty: f32,
#[serde(default)]
pub presence_penalty: f32,
#[serde(default)]
pub stream: bool,
#[serde(default)]
pub stop: Option<StopInput>,
#[serde(default)]
pub logprobs: Option<usize>,
}
impl ChatCompletionRequest {
pub(crate) fn wants_json_object(&self) -> bool {
matches!(
self.response_format.as_ref().map(|format| &format.kind),
Some(ResponseFormatType::JsonObject)
)
}
pub(crate) fn has_tool_context(&self) -> bool {
self.tools.as_ref().is_some_and(|tools| !tools.is_empty())
|| self.tool_choice.is_some()
|| self.messages.iter().any(|message| {
message
.tool_calls
.as_ref()
.is_some_and(|calls| !calls.is_empty())
|| message.tool_call_id.is_some()
|| message.role == "tool"
})
}
pub(crate) fn image_urls(&self) -> Vec<String> {
self.messages
.iter()
.filter_map(|message| message.content.as_ref())
.flat_map(ChatMessageContent::image_urls)
.map(ToString::to_string)
.collect()
}
pub(crate) fn input_audio(&self) -> Vec<InputAudio> {
self.messages
.iter()
.filter_map(|message| message.content.as_ref())
.flat_map(ChatMessageContent::input_audio)
.cloned()
.collect()
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ChatMessage {
pub role: String,
#[serde(default)]
pub content: Option<ChatMessageContent>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ChatMessageToolCall>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ChatMessageContent {
Text(String),
Parts(Vec<ChatMessageContentPart>),
}
impl ChatMessageContent {
pub(crate) fn text(&self) -> String {
match self {
Self::Text(text) => text.clone(),
Self::Parts(parts) => parts
.iter()
.filter_map(|part| match part {
ChatMessageContentPart::Text { text } => Some(text.as_str()),
ChatMessageContentPart::ImageUrl { .. }
| ChatMessageContentPart::InputAudio { .. } => None,
})
.collect::<Vec<_>>()
.join(""),
}
}
pub(crate) fn image_urls(&self) -> impl Iterator<Item = &str> {
match self {
Self::Text(_) => [].as_slice(),
Self::Parts(parts) => parts.as_slice(),
}
.iter()
.filter_map(|part| match part {
ChatMessageContentPart::ImageUrl { image_url } => Some(image_url.url.as_str()),
ChatMessageContentPart::Text { .. } | ChatMessageContentPart::InputAudio { .. } => None,
})
}
pub(crate) fn input_audio(&self) -> impl Iterator<Item = &InputAudio> {
match self {
Self::Text(_) => [].as_slice(),
Self::Parts(parts) => parts.as_slice(),
}
.iter()
.filter_map(|part| match part {
ChatMessageContentPart::InputAudio { input_audio } => Some(input_audio),
ChatMessageContentPart::Text { .. } | ChatMessageContentPart::ImageUrl { .. } => None,
})
}
}
impl From<String> for ChatMessageContent {
fn from(value: String) -> Self {
Self::Text(value)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ChatMessageContentPart {
Text { text: String },
ImageUrl { image_url: ImageUrl },
InputAudio { input_audio: InputAudio },
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ImageUrl {
pub url: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InputAudio {
pub data: String,
pub format: String,
}
#[derive(Debug, Serialize)]
pub struct AudioTranscriptionResponse {
pub text: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ChatMessageToolCall {
pub id: String,
#[serde(rename = "type")]
pub kind: String,
pub function: ChatMessageToolCallFunction,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ChatMessageToolCallFunction {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ChatTool {
#[serde(rename = "type")]
pub kind: String,
pub function: ChatToolFunction,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ChatToolFunction {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
Mode(ToolChoiceMode),
Specific(ToolChoiceSpecific),
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ToolChoiceMode {
Auto,
None,
Required,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ToolChoiceSpecific {
#[serde(rename = "type")]
pub kind: String,
pub function: ToolChoiceFunction,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ToolChoiceFunction {
pub name: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseFormat {
#[serde(rename = "type")]
pub kind: ResponseFormatType,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ResponseFormatType {
Text,
JsonObject,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum StopInput {
One(String),
Many(Vec<String>),
}
impl StopInput {
pub(crate) fn into_texts(self) -> Vec<String> {
match self {
Self::One(value) => vec![value],
Self::Many(values) => values,
}
}
pub(crate) fn into_sequences(self) -> Vec<StopSequence> {
self.into_texts()
.into_iter()
.map(StopSequence::Text)
.collect()
}
}
#[derive(Debug, Serialize)]
pub struct ChatCompletionResponse {
pub id: String,
pub object: &'static str,
pub created: u64,
pub model: String,
pub choices: Vec<ChatChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_token_count: Option<usize>,
}
#[derive(Debug, Serialize)]
pub struct ChatChoice {
pub index: usize,
pub message: ChatMessage,
pub finish_reason: &'static str,
pub logprobs: Option<ChatLogprobs>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ChatLogprobs {
pub content: Vec<ChatTokenLogprob>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ChatTokenLogprob {
pub token: String,
pub logprob: f32,
pub bytes: Vec<u8>,
pub top_logprobs: Vec<ChatTopLogprob>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ChatTopLogprob {
pub token: String,
pub logprob: f32,
pub bytes: Vec<u8>,
}
#[derive(Debug, Serialize)]
pub struct Usage {
pub prompt_tokens: usize,
pub completion_tokens: usize,
pub total_tokens: usize,
}
#[derive(Debug, Serialize)]
pub struct CompletionResponse {
pub id: String,
pub object: &'static str,
pub created: u64,
pub model: String,
pub choices: Vec<CompletionChoice>,
pub usage: Usage,
}
#[derive(Debug, Serialize)]
pub struct CompletionChoice {
pub text: String,
pub index: usize,
pub finish_reason: &'static str,
pub logprobs: Option<CompletionLogprobs>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CompletionLogprobs {
pub tokens: Vec<String>,
pub token_logprobs: Vec<f32>,
pub top_logprobs: Vec<BTreeMap<String, f32>>,
pub text_offset: Vec<usize>,
}
fn default_max_tokens() -> usize {
256
}
fn default_temperature() -> f32 {
1.0
}
fn default_top_p() -> f32 {
1.0
}