use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Usage {
pub prompt_tokens: u64,
pub completion_tokens: u64,
}
impl Usage {
pub fn from_response(val: &Value) -> Self {
let u = match val.get("usage") {
Some(u) => u,
None => return Usage::default(),
};
Usage {
prompt_tokens: u
.get("prompt_tokens")
.or_else(|| u.get("input_tokens"))
.and_then(|v| v.as_u64())
.unwrap_or(0),
completion_tokens: u
.get("completion_tokens")
.or_else(|| u.get("output_tokens"))
.and_then(|v| v.as_u64())
.unwrap_or(0),
}
}
}
#[async_trait]
pub trait LlmCall: Send + Sync {
async fn call(&self, messages: Vec<Message>) -> Result<String, String>;
async fn call_with_tools(
&self,
messages: Vec<Value>,
tools: &[ToolDef],
) -> Result<(CallResult, Usage), String> {
let simple: Vec<Message> = messages
.into_iter()
.filter_map(|m| {
let role = m.get("role")?.as_str()?;
let content = m.get("content")?.as_str()?.to_string();
let role = match role {
"system" => MessageRole::System,
"user" => MessageRole::User,
"assistant" => MessageRole::Assistant,
_ => return None,
};
Some(Message { role, content })
})
.collect();
let _ = tools; let text = self.call(simple).await?;
Ok((CallResult::Text(text), Usage::default()))
}
}
#[derive(Debug, Clone)]
pub struct Message {
pub role: MessageRole,
pub content: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageRole {
System,
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDef {
pub name: String,
pub description: String,
pub parameters: Value,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone)]
pub enum CallResult {
Text(String),
ToolCalls(Vec<ToolCall>),
}