use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
User,
Assistant,
Tool,
}
impl Role {
pub fn as_str(&self) -> &'static str {
match self {
Role::System => "system",
Role::User => "user",
Role::Assistant => "assistant",
Role::Tool => "tool",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: Role,
pub content: MessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl ChatMessage {
pub fn system(content: impl Into<String>) -> Self {
Self {
role: Role::System,
content: MessageContent::Text(content.into()),
tool_call_id: None,
name: None,
}
}
pub fn user(content: impl Into<String>) -> Self {
Self {
role: Role::User,
content: MessageContent::Text(content.into()),
tool_call_id: None,
name: None,
}
}
pub fn assistant(content: impl Into<String>) -> Self {
Self {
role: Role::Assistant,
content: MessageContent::Text(content.into()),
tool_call_id: None,
name: None,
}
}
pub fn assistant_with_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
Self {
role: Role::Assistant,
content: MessageContent::ToolCalls(tool_calls),
tool_call_id: None,
name: None,
}
}
pub fn tool_result(
tool_call_id: impl Into<String>,
tool_name: impl Into<String>,
result: impl Into<String>,
) -> Self {
Self {
role: Role::Tool,
content: MessageContent::Text(result.into()),
tool_call_id: Some(tool_call_id.into()),
name: Some(tool_name.into()),
}
}
pub fn text(&self) -> Option<&str> {
match &self.content {
MessageContent::Text(t) => Some(t),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
ToolCalls(Vec<ToolCall>),
Blocks(Vec<ContentBlock>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text {
text: String,
},
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
ToolResult {
tool_use_id: String,
content: String,
#[serde(default)]
is_error: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
impl ToolDefinition {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TokenLogprob {
pub token: String,
pub logprob: f64,
pub bytes: Vec<u8>,
pub top_alternatives: Vec<TopTokenAlternative>,
}
impl TokenLogprob {
pub fn probability(&self) -> f64 {
self.logprob.exp()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TopTokenAlternative {
pub token: String,
pub logprob: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum StopReason {
#[default]
Stop,
MaxTokens,
ToolUse,
ContentFilter,
Other(String),
}
impl StopReason {
pub(crate) fn from_str(s: &str) -> Self {
match s {
"stop" | "end_turn" => StopReason::Stop,
"length" | "max_tokens" => StopReason::MaxTokens,
"tool_calls" | "tool_use" => StopReason::ToolUse,
"content_filter" => StopReason::ContentFilter,
other => StopReason::Other(other.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResponseFormat {
Text,
JsonObject,
JsonSchema {
schema: serde_json::Value,
name: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LlmApiResult {
pub generated_text: String,
pub input_tokens_count: u64,
pub output_tokens_count: u64,
pub stop_reason: StopReason,
pub model_used: String,
pub logprobs: Vec<TokenLogprob>,
pub tool_calls: Vec<ToolCall>,
pub reasoning_content: Option<String>,
}
impl LlmApiResult {
pub fn mean_logprob(&self) -> Option<f64> {
if self.logprobs.is_empty() {
return None;
}
let sum: f64 = self.logprobs.iter().map(|t| t.logprob).sum();
Some(sum / self.logprobs.len() as f64)
}
pub fn mean_probability(&self) -> Option<f64> {
self.mean_logprob().map(f64::exp)
}
pub fn min_token_probability(&self) -> Option<f64> {
self.logprobs
.iter()
.map(|t| t.logprob)
.reduce(f64::min)
.map(f64::exp)
}
}
#[derive(Debug, Clone)]
pub struct BatchRequest {
pub custom_id: String,
pub messages: Vec<ChatMessage>,
pub tools: Option<Vec<ToolDefinition>>,
pub system_prompt: Option<String>,
}
impl BatchRequest {
pub fn new(custom_id: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
Self {
custom_id: custom_id.into(),
messages,
tools: None,
system_prompt: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchHandle {
pub batch_id: String,
pub provider: String,
pub status: BatchStatus,
pub output_file_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BatchStatus {
Validating,
InProgress,
Finalizing,
Completed,
Failed,
Expired,
Cancelling,
Cancelled,
}
impl BatchStatus {
pub fn is_terminal(&self) -> bool {
matches!(
self,
BatchStatus::Completed | BatchStatus::Failed | BatchStatus::Expired | BatchStatus::Cancelled
)
}
pub(crate) fn from_str(s: &str) -> Self {
match s {
"validating" => BatchStatus::Validating,
"in_progress" => BatchStatus::InProgress,
"finalizing" => BatchStatus::Finalizing,
"completed" => BatchStatus::Completed,
"failed" => BatchStatus::Failed,
"expired" => BatchStatus::Expired,
"cancelling" => BatchStatus::Cancelling,
"cancelled" => BatchStatus::Cancelled,
_ => BatchStatus::InProgress,
}
}
}
#[derive(Debug, Clone)]
pub struct BatchRequestResult {
pub custom_id: String,
pub result: Result<LlmApiResult, crate::error::SamvadSetuError>,
}