1use engram::embedding::{EmbeddingProvider, MockEmbeddingProvider};
16use engram::embedding_ollama::OllamaEmbeddingProvider;
17use engram::llm::{LlmClient, MockLlmClient};
18use engram::llm_anthropic::AnthropicLlmClient;
19use engram::llm_command::CommandLlmClient;
20use engram::llm_google::GoogleLlmClient;
21use engram::llm_ollama::OllamaLlmClient;
22use engram::llm_openai::OpenAiLlmClient;
23
24#[derive(Clone, Debug)]
26pub enum LlmBackend {
27 Mock,
30 Ollama { base_url: String, model: String },
33 OpenAiCompatible {
38 base_url: String,
39 api_key: String,
40 model: String,
41 },
42 Anthropic {
44 base_url: String,
45 api_key: String,
46 model: String,
47 },
48 Google {
50 base_url: String,
51 api_key: String,
52 model: String,
53 },
54 Command { command: String, timeout_secs: u64 },
58}
59
60impl LlmBackend {
61 pub fn build(&self) -> Box<dyn LlmClient> {
64 match self {
65 Self::Mock => Box::new(MockLlmClient::new(vec![serde_json::json!({"facts": []})])),
66 Self::Ollama { base_url, model } => Box::new(OllamaLlmClient::with_config(
67 base_url.clone(),
68 model.clone(),
69 )),
70 Self::OpenAiCompatible {
71 base_url,
72 api_key,
73 model,
74 } => Box::new(OpenAiLlmClient::with_config(
75 base_url.clone(),
76 api_key.clone(),
77 model.clone(),
78 )),
79 Self::Anthropic {
80 base_url,
81 api_key,
82 model,
83 } => Box::new(AnthropicLlmClient::with_config(
84 base_url.clone(),
85 api_key.clone(),
86 model.clone(),
87 )),
88 Self::Google {
89 base_url,
90 api_key,
91 model,
92 } => Box::new(GoogleLlmClient::with_config(
93 base_url.clone(),
94 api_key.clone(),
95 model.clone(),
96 )),
97 Self::Command {
98 command,
99 timeout_secs,
100 } => Box::new(CommandLlmClient::new(command.clone()).with_timeout(*timeout_secs)),
101 }
102 }
103
104 pub fn describe(&self) -> String {
107 match self {
108 Self::Mock => "mock (returns empty facts)".to_string(),
109 Self::Ollama { base_url, model } => format!("ollama {model} at {base_url}"),
110 Self::OpenAiCompatible {
111 base_url, model, ..
112 } => format!("openai-compatible {model} at {base_url}"),
113 Self::Anthropic {
114 base_url, model, ..
115 } => format!("anthropic {model} at {base_url}"),
116 Self::Google {
117 base_url, model, ..
118 } => format!("google {model} at {base_url}"),
119 Self::Command {
120 command,
121 timeout_secs,
122 } => {
123 let shown: String = command.chars().take(60).collect();
124 format!("command `{shown}` (timeout={timeout_secs}s)")
125 }
126 }
127 }
128}
129
130#[derive(Clone, Debug)]
132pub enum EmbeddingBackend {
133 Mock { dims: usize },
135 Ollama {
137 base_url: String,
138 model: String,
139 dims: usize,
140 },
141}
142
143impl EmbeddingBackend {
144 pub fn build(&self) -> Box<dyn EmbeddingProvider> {
146 match self {
147 Self::Mock { dims } => Box::new(MockEmbeddingProvider::new(*dims)),
148 Self::Ollama {
149 base_url,
150 model,
151 dims,
152 } => Box::new(OllamaEmbeddingProvider::with_config(base_url, model, *dims)),
153 }
154 }
155
156 pub fn dimensions(&self) -> usize {
157 match self {
158 Self::Mock { dims } => *dims,
159 Self::Ollama { dims, .. } => *dims,
160 }
161 }
162
163 pub fn describe(&self) -> String {
164 match self {
165 Self::Mock { dims } => format!("mock ({dims}d)"),
166 Self::Ollama {
167 base_url,
168 model,
169 dims,
170 } => format!("ollama {model} at {base_url} ({dims}d)"),
171 }
172 }
173}
174
175#[derive(Clone, Debug)]
178pub struct BackendConfig {
179 pub llm: LlmBackend,
180 pub embedding: EmbeddingBackend,
181}
182
183impl BackendConfig {
184 pub fn mock() -> Self {
186 Self {
187 llm: LlmBackend::Mock,
188 embedding: EmbeddingBackend::Mock { dims: 64 },
189 }
190 }
191
192 pub fn is_mock(&self) -> bool {
194 matches!(self.llm, LlmBackend::Mock)
195 || matches!(self.embedding, EmbeddingBackend::Mock { .. })
196 }
197}