use everruns_provider::{ToolCall, ToolDefinition};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: MessageRole,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
pub model: String,
pub temperature: Option<f32>,
pub max_tokens: Option<u32>,
pub system_prompt: Option<String>,
#[serde(default)]
pub tools: Vec<ToolDefinition>,
}
#[derive(Debug, Clone)]
pub enum LlmStreamEvent {
TextDelta(String),
ToolCalls(Vec<ToolCall>),
Done(CompletionMetadata),
Error(String),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CompletionMetadata {
pub total_tokens: Option<u32>,
pub prompt_tokens: Option<u32>,
pub completion_tokens: Option<u32>,
pub model: String,
pub finish_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ChatRequest {
pub model: String,
pub messages: Vec<OpenAiMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<OpenAiTool>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiMessage {
pub role: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<OpenAiToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiTool {
pub r#type: String,
pub function: OpenAiFunction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiFunction {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiToolCall {
pub id: String,
pub r#type: String,
pub function: OpenAiFunctionCall,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiFunctionCall {
pub name: String,
pub arguments: String,
}
impl ChatMessage {
pub fn to_openai(&self) -> OpenAiMessage {
let role = match self.role {
MessageRole::System => "system",
MessageRole::User => "user",
MessageRole::Assistant => "assistant",
MessageRole::Tool => "tool",
};
OpenAiMessage {
role: role.to_string(),
content: Some(self.content.clone()),
tool_calls: self.tool_calls.as_ref().map(|calls| {
calls
.iter()
.map(|tc| OpenAiToolCall {
id: tc.id.clone(),
r#type: "function".to_string(),
function: OpenAiFunctionCall {
name: tc.name.clone(),
arguments: serde_json::to_string(&tc.arguments).unwrap_or_default(),
},
})
.collect()
}),
tool_call_id: self.tool_call_id.clone(),
}
}
}
impl LlmConfig {
pub fn tools_to_openai(&self) -> Vec<OpenAiTool> {
self.tools
.iter()
.map(|tool| {
let (name, description, parameters) = match tool {
ToolDefinition::Builtin(builtin) => {
(&builtin.name, &builtin.description, &builtin.parameters)
}
ToolDefinition::ClientSide(client) => {
(&client.name, &client.description, &client.parameters)
}
};
OpenAiTool {
r#type: "function".to_string(),
function: OpenAiFunction {
name: name.clone(),
description: description.clone(),
parameters: parameters.clone(),
},
}
})
.collect()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiModelsResponse {
pub data: Vec<OpenAiModelInfo>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiModelInfo {
pub id: String,
pub created: i64,
pub owned_by: String,
}
impl OpenAiModelInfo {
pub fn is_chat_model(&self) -> bool {
let id = self.id.as_str();
if id.starts_with("text-embedding")
|| id.starts_with("dall-e")
|| id.starts_with("tts-")
|| id.starts_with("whisper")
|| id.starts_with("davinci")
|| id.starts_with("babbage")
|| id.starts_with("omni-moderation")
|| id.starts_with("sora-")
|| id.starts_with("gpt-image")
|| id.starts_with("codex-")
|| id.contains("-transcribe")
|| id.contains("-realtime")
|| id.contains("-audio")
|| id.contains("-tts")
{
return false;
}
id.starts_with("gpt-")
|| id.starts_with("o1")
|| id.starts_with("o3")
|| id.starts_with("o4")
|| id.starts_with("chatgpt-")
}
}