use crate::extractor::ExtractorWrapper;
use crate::providers::{
AnthropicClientBuilder, CerebrasClientBuilder, ChatTemplate, LlmConfigLike,
LmStudioClientBuilder, MinimaxClientBuilder, MlxLmClientBuilder, OllamaClientBuilder,
OpenAIClientBuilder, OpenRouterClientBuilder, ProviderType, ProviderTypeError,
ZaiClientBuilder,
};
use thiserror::Error;
use tracing::{debug, info};
#[derive(Error, Debug)]
pub enum ExtractorBuildError {
#[error("Provider type error: {0}")]
ProviderType(#[from] ProviderTypeError),
}
pub fn build_extractor<C: LlmConfigLike>(
config: &C,
) -> Result<ExtractorWrapper, ExtractorBuildError> {
let provider_type = ProviderType::from_str(config.provider())?;
let api_key = config.api_key().unwrap_or("none");
info!(
"Building extractor: provider={}, model={}, url={}",
provider_type,
config.model(),
config.url()
);
match provider_type {
ProviderType::OpenRouter => {
debug!("Creating OpenRouter extractor client");
let mut builder = OpenRouterClientBuilder::new(api_key).base_url(config.url());
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::OpenRouter(client))
}
ProviderType::OpenAI => {
debug!("Creating OpenAI extractor client");
let mut builder = OpenAIClientBuilder::new(api_key).base_url(config.url());
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::OpenAI(client))
}
ProviderType::Anthropic => {
debug!("Creating Anthropic extractor client");
let mut builder = AnthropicClientBuilder::new(api_key).base_url(config.url());
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::Anthropic(client))
}
ProviderType::Minimax => {
debug!("Creating MiniMax extractor client");
let mut builder = MinimaxClientBuilder::new(api_key).base_url(config.url());
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::Minimax(client))
}
ProviderType::Ollama => {
debug!("Creating native Ollama extractor client");
let mut builder = OllamaClientBuilder::new()
.base_url(config.url())
.api_key(api_key);
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::Ollama(client))
}
ProviderType::Cerebras => {
debug!("Creating Cerebras extractor client");
let mut builder = CerebrasClientBuilder::new(api_key).base_url(config.url());
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::Cerebras(client))
}
ProviderType::Zai => {
debug!("Creating ZAI extractor client");
let mut builder = ZaiClientBuilder::new(api_key).base_url(config.url());
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::Zai(client))
}
ProviderType::MlxLm => {
let mut builder = MlxLmClientBuilder::new().base_url(config.url());
builder = match config.chat_template() {
Some("minimax") => builder.chat_template(ChatTemplate::Minimax),
Some("chatml") | Some("iquest") | Some("qwen") => {
builder.chat_template(ChatTemplate::ChatML)
}
Some("qwen35") | Some("qwen3.5") => builder.chat_template(ChatTemplate::Qwen35),
Some("glm") => builder.chat_template(ChatTemplate::GLM),
Some("openai") => builder.chat_template(ChatTemplate::OpenAI),
None => builder.auto_chat_template(config.model()), Some(other) => {
debug!(
"Unknown chat_template '{}', auto-detecting from model name",
other
);
builder.auto_chat_template(config.model())
}
};
debug!("Creating MLX-LM extractor client");
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let client = builder.build();
Ok(ExtractorWrapper::MlxLm(client))
}
ProviderType::LmStudio => {
let mut builder = LmStudioClientBuilder::new().base_url(config.url());
debug!("Creating LMStudio extractor client");
if let Some(temp) = config.temperature() {
builder = builder.temperature(temp);
}
let repeat_penalty = config.repeat_penalty().unwrap_or(1.1);
builder = builder.repeat_penalty(repeat_penalty);
if let Some(ctx_size) = config.repetition_context_size() {
builder = builder.repetition_context_size(ctx_size);
}
let client = builder.build();
Ok(ExtractorWrapper::LmStudio(client))
}
}
}