apollo/providers/
traits.rs1use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeSet;
7
8use crate::tools::ToolSpec;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ChatMessage {
13 pub role: String,
14 pub content: String,
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub tool_use_id: Option<String>,
18}
19
20impl ChatMessage {
21 pub fn system(content: impl Into<String>) -> Self {
22 Self {
23 role: "system".into(),
24 content: content.into(),
25 tool_use_id: None,
26 }
27 }
28 pub fn user(content: impl Into<String>) -> Self {
29 Self {
30 role: "user".into(),
31 content: content.into(),
32 tool_use_id: None,
33 }
34 }
35 pub fn assistant(content: impl Into<String>) -> Self {
36 Self {
37 role: "assistant".into(),
38 content: content.into(),
39 tool_use_id: None,
40 }
41 }
42 pub fn tool_result(id: impl Into<String>, content: impl Into<String>) -> Self {
43 Self {
44 role: "tool_result".into(),
45 content: content.into(),
46 tool_use_id: Some(id.into()),
47 }
48 }
49 pub fn is_tool_result(&self) -> bool {
50 self.role == "tool_result"
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ToolCall {
57 pub id: String,
58 pub name: String,
59 pub arguments: String,
60}
61
62#[derive(Debug, Clone, Default)]
64pub struct ChatResponse {
65 pub text: Option<String>,
66 pub tool_calls: Vec<ToolCall>,
67 pub usage: Option<Usage>,
68}
69
70#[derive(Debug, Clone, Default)]
71pub struct Usage {
72 pub input_tokens: u32,
73 pub output_tokens: u32,
74}
75
76#[derive(Debug, Clone, Default, Serialize, Deserialize)]
81pub struct ModelInfo {
82 pub id: String,
83 pub provider: String,
84 pub display_name: String,
85 #[serde(default)]
86 pub description: Option<String>,
87 #[serde(default)]
88 pub capabilities: BTreeSet<String>,
89 #[serde(default)]
90 pub input_modalities: Vec<String>,
91 #[serde(default)]
92 pub output_modalities: Vec<String>,
93 #[serde(default)]
94 pub supported_parameters: BTreeSet<String>,
95 #[serde(default)]
96 pub context_window: Option<u64>,
97 #[serde(default)]
98 pub max_output_tokens: Option<u64>,
99 #[serde(default)]
100 pub pricing: Option<ModelPricing>,
101}
102
103#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104pub struct ModelPricing {
105 pub input_per_token: Option<f64>,
106 pub output_per_token: Option<f64>,
107 pub request: Option<f64>,
108 pub image_input: Option<f64>,
109 pub reasoning: Option<f64>,
110 pub cache_read: Option<f64>,
111 pub cache_write: Option<f64>,
112}
113
114impl ChatResponse {
115 pub fn has_tool_calls(&self) -> bool {
116 !self.tool_calls.is_empty()
117 }
118 pub fn text_or_empty(&self) -> &str {
119 self.text.as_deref().unwrap_or("")
120 }
121}
122
123#[derive(Debug, Clone, Copy)]
125pub struct ChatRequest<'a> {
126 pub messages: &'a [ChatMessage],
127 pub tools: Option<&'a [ToolSpec]>,
128 pub model: &'a str,
129 pub temperature: f64,
130 pub max_tokens: Option<u32>,
131}
132
133#[derive(Debug, Clone, Default)]
135pub struct ProviderCapabilities {
136 pub native_tools: bool,
138 pub streaming: bool,
140 pub vision: bool,
142 pub max_context: u32,
144 pub native_web_search: bool,
147}
148
149#[async_trait]
152pub trait Provider: Send + Sync {
153 fn name(&self) -> &str;
155
156 fn capabilities(&self) -> ProviderCapabilities {
158 ProviderCapabilities::default()
159 }
160
161 async fn list_models(&self) -> anyhow::Result<Vec<ModelInfo>> {
167 Ok(Vec::new())
168 }
169
170 async fn chat(&self, request: &ChatRequest<'_>) -> anyhow::Result<ChatResponse>;
172
173 async fn simple_chat(&self, message: &str, model: &str) -> anyhow::Result<String> {
175 let messages = [ChatMessage::user(message)];
176 let request = ChatRequest {
177 messages: &messages,
178 tools: None,
179 model,
180 temperature: 0.7,
181 max_tokens: None,
182 };
183 let response = self.chat(&request).await?;
184 Ok(response.text.unwrap_or_default())
185 }
186}