a3s_code_core/llm/
factory.rs1use super::anthropic::AnthropicClient;
4use super::openai::OpenAiClient;
5use super::types::SecretString;
6use super::LlmClient;
7use crate::retry::RetryConfig;
8use std::sync::Arc;
9
10#[derive(Clone, Default)]
12pub struct LlmConfig {
13 pub provider: String,
14 pub model: String,
15 pub api_key: SecretString,
16 pub base_url: Option<String>,
17 pub retry_config: Option<RetryConfig>,
18 pub temperature: Option<f32>,
20 pub max_tokens: Option<usize>,
22 pub thinking_budget: Option<usize>,
24}
25
26impl std::fmt::Debug for LlmConfig {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.debug_struct("LlmConfig")
29 .field("provider", &self.provider)
30 .field("model", &self.model)
31 .field("api_key", &"[REDACTED]")
32 .field("base_url", &self.base_url)
33 .field("retry_config", &self.retry_config)
34 .field("temperature", &self.temperature)
35 .field("max_tokens", &self.max_tokens)
36 .field("thinking_budget", &self.thinking_budget)
37 .finish()
38 }
39}
40
41impl LlmConfig {
42 pub fn new(
43 provider: impl Into<String>,
44 model: impl Into<String>,
45 api_key: impl Into<String>,
46 ) -> Self {
47 Self {
48 provider: provider.into(),
49 model: model.into(),
50 api_key: SecretString::new(api_key.into()),
51 base_url: None,
52 retry_config: None,
53 temperature: None,
54 max_tokens: None,
55 thinking_budget: None,
56 }
57 }
58
59 pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
60 self.base_url = Some(base_url.into());
61 self
62 }
63
64 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
65 self.retry_config = Some(retry_config);
66 self
67 }
68
69 pub fn with_temperature(mut self, temperature: f32) -> Self {
70 self.temperature = Some(temperature);
71 self
72 }
73
74 pub fn with_max_tokens(mut self, max_tokens: usize) -> Self {
75 self.max_tokens = Some(max_tokens);
76 self
77 }
78
79 pub fn with_thinking_budget(mut self, budget: usize) -> Self {
80 self.thinking_budget = Some(budget);
81 self
82 }
83}
84
85pub fn create_client_with_config(config: LlmConfig) -> Arc<dyn LlmClient> {
87 let retry = config.retry_config.unwrap_or_default();
88 let api_key = config.api_key.expose().to_string();
89
90 match config.provider.as_str() {
91 "anthropic" | "claude" => {
92 let mut client = AnthropicClient::new(api_key, config.model).with_retry_config(retry);
93 if let Some(base_url) = config.base_url {
94 client = client.with_base_url(base_url);
95 }
96 if let Some(temp) = config.temperature {
97 client = client.with_temperature(temp);
98 }
99 if let Some(max) = config.max_tokens {
100 client = client.with_max_tokens(max);
101 }
102 if let Some(budget) = config.thinking_budget {
103 client = client.with_thinking_budget(budget);
104 }
105 Arc::new(client)
106 }
107 "openai" | "gpt" => {
108 let mut client = OpenAiClient::new(api_key, config.model).with_retry_config(retry);
109 if let Some(base_url) = config.base_url {
110 client = client.with_base_url(base_url);
111 }
112 if let Some(temp) = config.temperature {
113 client = client.with_temperature(temp);
114 }
115 if let Some(max) = config.max_tokens {
116 client = client.with_max_tokens(max);
117 }
118 Arc::new(client)
119 }
120 _ => {
122 tracing::info!(
123 "Using OpenAI-compatible client for provider '{}'",
124 config.provider
125 );
126 let mut client = OpenAiClient::new(api_key, config.model).with_retry_config(retry);
127 if let Some(base_url) = config.base_url {
128 client = client.with_base_url(base_url);
129 }
130 if let Some(temp) = config.temperature {
131 client = client.with_temperature(temp);
132 }
133 if let Some(max) = config.max_tokens {
134 client = client.with_max_tokens(max);
135 }
136 Arc::new(client)
137 }
138 }
139}