1pub mod error;
10pub mod openai_compatible;
11pub mod rate_limiter;
12pub mod simple_model;
13pub mod simulated;
14pub mod span;
15pub mod strategies;
16pub mod stub;
17pub use error::LlmError;
18pub use openai_compatible::OpenAICompatibleModel;
19pub use rate_limiter::RateLimiter;
20pub use simple_model::SimpleOpenAIModel;
21pub use span::LlmRequestSpan;
22pub use stub::StubModel;
23
24use crate::agents::AgentConfig;
25use async_openai::types::{
26 ChatCompletionRequestMessage, ChatCompletionTool, ChatCompletionToolChoiceOption,
27 CreateChatCompletionResponse,
28};
29use async_trait::async_trait;
30use dyn_clone::DynClone;
31use std::fmt::Debug;
32
33#[derive(Debug, Clone)]
36pub struct RequestConfig {
37 pub messages: Vec<ChatCompletionRequestMessage>,
38 pub tools: Option<Vec<ChatCompletionTool>>,
39 pub tool_choice: Option<ChatCompletionToolChoiceOption>,
40 pub presence_penalty: Option<f32>,
41}
42
43pub struct ChatCompletionResult {
45 pub response: CreateChatCompletionResponse,
46 pub raw_request: String,
47 pub timing: TimingMetadata,
48 pub provider_backend: Option<String>,
49 pub shrink_info: Option<ShrinkInfo>,
51}
52
53pub struct TimingMetadata {
55 pub ttft_ms: Option<u64>,
56 pub generation_ms: Option<u64>,
57}
58
59#[derive(Debug, Clone)]
63pub struct ShrinkInfo {
64 pub floor_used: bool,
67 pub available_space: u32,
70 pub requested_max: u32,
71 pub floor: u32,
72 pub estimated_input: u32,
73 pub context_window: u32,
74}
75
76#[async_trait]
79pub trait AiModel: Send + Sync + DynClone + Debug {
80 async fn chat_completion(
84 &self,
85 agent: &AgentConfig,
86 request_config: RequestConfig,
87 ) -> Result<ChatCompletionResult, LlmError>;
88}
89
90dyn_clone::clone_trait_object!(AiModel);
92
93#[derive(Debug, Clone, Default)]
95pub struct RequestOverrides {
96 pub max_tokens: Option<u32>,
97}
98
99#[async_trait]
106pub trait ChatStrategy: Send + Sync {
107 async fn prepare_request(
109 &self,
110 agent: &AgentConfig,
111 request: &RequestConfig,
112 overrides: &RequestOverrides,
113 ) -> Result<serde_json::Value, LlmError>;
114
115 async fn parse_response(
117 &self,
118 response_body: &str,
119 ) -> Result<CreateChatCompletionResponse, LlmError>;
120
121 fn endpoint_suffix(&self) -> &str {
123 "/chat/completions"
124 }
125
126 fn supports_streaming(&self) -> bool {
128 true
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[derive(Debug)]
139 struct DefaultStrategy;
140
141 #[async_trait]
142 impl ChatStrategy for DefaultStrategy {
143 async fn prepare_request(
144 &self,
145 _agent: &AgentConfig,
146 _request: &RequestConfig,
147 _overrides: &RequestOverrides,
148 ) -> Result<serde_json::Value, LlmError> {
149 Ok(serde_json::json!({}))
150 }
151
152 async fn parse_response(
153 &self,
154 _response_body: &str,
155 ) -> Result<CreateChatCompletionResponse, LlmError> {
156 Err(LlmError::Other("not implemented".into()))
157 }
158 }
159
160 #[test]
161 fn chat_strategy_default_endpoint_suffix() {
162 let strategy = DefaultStrategy;
163 assert_eq!(strategy.endpoint_suffix(), "/chat/completions");
164 }
165
166 #[test]
167 fn chat_strategy_default_supports_streaming() {
168 let strategy = DefaultStrategy;
169 assert!(strategy.supports_streaming());
170 }
171
172 #[test]
173 fn request_overrides_default() {
174 let overrides = RequestOverrides::default();
175 assert!(overrides.max_tokens.is_none());
176 }
177}