Skip to main content

Providers

Providers are the AI backends that power ClawDesk's agent responses. ClawDesk supports five providers out of the box through a unified trait-based abstraction, with automatic fallback and capability negotiation.

Supported Providers​

ProviderModelsStreamingTool UseVisionEmbeddings
AnthropicClaude 4 Opus, Sonnet, Haiku; Claude 3.5✅✅✅❌
OpenAIGPT-4o, GPT-4 Turbo, GPT-3.5, o1, o3✅✅✅✅
OllamaLlama 3, Mistral, Phi, Qwen, any GGUF✅✅✅✅
GeminiGemini 2.0, 1.5 Pro, 1.5 Flash✅✅✅✅
BedrockClaude, Llama, Mistral, Titan (via AWS)✅✅✅✅

Provider Trait​

All providers implement the core Provider trait:

#[async_trait]
pub trait Provider: Send + Sync + 'static {
/// Unique identifier for this provider
fn id(&self) -> &str;

/// List available models
async fn list_models(&self) -> Result<Vec<ModelInfo>>;

/// Send a request and get a complete response
async fn send(&self, request: Request) -> Result<Response>;

/// Send a request and get a streaming response
async fn send_stream(
&self,
request: Request,
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>>;

/// Query provider capabilities for a specific model
fn capabilities(&self, model: &str) -> ModelCapabilities;
}

pub struct ModelCapabilities {
pub max_tokens: u32,
pub supports_tools: bool,
pub supports_vision: bool,
pub supports_streaming: bool,
pub supports_system_prompt: bool,
pub context_window: u32,
}

Configuration​

Anthropic​

[providers.anthropic]
api_key = "${CLAWDESK_ANTHROPIC_API_KEY}"
default_model = "claude-sonnet-4-20250514"
max_retries = 3
timeout_secs = 60
base_url = "https://api.anthropic.com" # optional, for proxies

# Model-specific overrides
[providers.anthropic.models.claude-sonnet-4-20250514]
max_tokens = 8192
temperature = 0.7

[providers.anthropic.models.claude-3-5-haiku-20241022]
max_tokens = 4096
temperature = 0.5
tip

Use environment variable interpolation for API keys. Never store keys directly in config files that might be committed to version control:

api_key = "${CLAWDESK_ANTHROPIC_API_KEY}"

OpenAI​

[providers.openai]
api_key = "${CLAWDESK_OPENAI_API_KEY}"
default_model = "gpt-4o"
organization = "org-..." # optional
max_retries = 3
timeout_secs = 60
base_url = "https://api.openai.com/v1" # optional, for Azure or proxies

[providers.openai.models.gpt-4o]
max_tokens = 4096
temperature = 0.7

Azure OpenAI​

[providers.openai]
api_key = "${AZURE_OPENAI_API_KEY}"
base_url = "https://my-resource.openai.azure.com/openai/deployments/gpt-4o"
api_version = "2024-02-15-preview"
azure = true

Ollama​

[providers.ollama]
base_url = "http://localhost:11434" # default Ollama address
default_model = "llama3.1:70b"
timeout_secs = 120 # local models may be slower

# GPU configuration
[providers.ollama.options]
num_gpu = 1
num_ctx = 8192
info

Ollama must be running separately. Install it from ollama.com and pull models before use:

ollama pull llama3.1:70b
ollama pull nomic-embed-text # for embeddings

Gemini​

[providers.gemini]
api_key = "${CLAWDESK_GEMINI_API_KEY}"
default_model = "gemini-2.0-flash"
max_retries = 3
timeout_secs = 60

[providers.gemini.safety_settings]
harassment = "BLOCK_MEDIUM_AND_ABOVE"
hate_speech = "BLOCK_MEDIUM_AND_ABOVE"
sexually_explicit = "BLOCK_ONLY_HIGH"
dangerous_content = "BLOCK_MEDIUM_AND_ABOVE"

Bedrock​

[providers.bedrock]
region = "${AWS_REGION}" # or "us-east-1"
default_model = "anthropic.claude-sonnet-4-20250514-v2:0"
# Uses AWS credential chain: env vars → config file → IAM role
# Optionally specify a profile:
profile = "clawdesk"

[providers.bedrock.models."anthropic.claude-sonnet-4-20250514-v2:0"]
max_tokens = 4096
warning

Bedrock requires appropriate IAM permissions. Ensure your role/user has bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream permissions.


Provider Registry​

The ProviderRegistry manages all configured providers and handles model resolution:

pub struct ProviderRegistry {
providers: HashMap<String, Arc<dyn Provider>>,
default_provider: String,
}

impl ProviderRegistry {
/// Get a provider by name
pub fn get(&self, name: &str) -> Option<&Arc<dyn Provider>>;

/// Get the default provider
pub fn default(&self) -> &Arc<dyn Provider>;

/// List all registered providers
pub fn list(&self) -> Vec<&str>;

/// Find a provider that supports a specific model
pub fn find_for_model(&self, model: &str) -> Option<&Arc<dyn Provider>>;

/// Register a custom provider
pub fn register(&mut self, name: &str, provider: Arc<dyn Provider>);
}

Capability Negotiation​

When the agent pipeline sends a request, the Negotiator ensures the request is compatible with the target model's capabilities:

pub struct Negotiator;

impl Negotiator {
pub fn negotiate(
request: &mut Request,
capabilities: &ModelCapabilities,
) -> NegotiationResult {
let mut warnings = Vec::new();

if !capabilities.supports_tools && request.has_tools() {
request.strip_tools();
warnings.push("Tools stripped: model does not support tool use");
}

if !capabilities.supports_vision && request.has_images() {
request.convert_images_to_text();
warnings.push("Images converted: model does not support vision");
}

if request.token_count() > capabilities.context_window {
request.truncate_history(capabilities.context_window);
warnings.push("History truncated to fit context window");
}

NegotiationResult { warnings }
}
}

Fallback System​

ClawDesk implements a Finite State Machine (FSM) for provider fallback when errors occur:

Fallback States​

StateDescription
IdleNo fallback in progress
SelectingChoosing the next provider from the fallback order
AttemptingSending the request to the selected provider
SucceededProvider responded successfully
RetryingRetrying the same provider after a transient error
ExhaustedMax retries reached for the current provider
AbortedAll providers failed, returning an error to the user

Error Classification​

The fallback system classifies errors to decide whether to retry or fail over:

Error TypeActionExamples
TransientRetry same providerRate limit (429), server error (500-503), timeout
PermanentFail over to nextAuth error (401), invalid model (404), content filter
FatalAbort immediatelyNetwork unreachable, invalid request format

Configuration​

[agent.failover]
enabled = true

# Provider priority order
fallback_order = ["anthropic", "openai", "gemini", "ollama"]

# Per-provider retry settings
max_retries_per_provider = 3
retry_delay_ms = 1000
backoff_factor = 2.0
max_retry_delay_ms = 30000

# Model mapping for fallbacks
[agent.failover.model_map]
"claude-sonnet-4-20250514" = { openai = "gpt-4o", gemini = "gemini-2.0-flash", ollama = "llama3.1:70b" }
"claude-3-5-haiku-20241022" = { openai = "gpt-4o-mini", gemini = "gemini-2.0-flash", ollama = "llama3.1:8b" }
info

The model_map ensures that when falling back to a different provider, ClawDesk selects an equivalent model. Without a mapping, the fallback provider's default model is used.


Model Selection​

Via CLI​

# Specify provider and model
clawdesk message -P anthropic -m claude-sonnet-4-20250514 "Hello"

# Use default provider with a specific model
clawdesk message -m gpt-4o "Hello"

# List available models
clawdesk config get providers

Via API​

# List all available models
curl http://localhost:1420/api/v1/models

# OpenAI-compatible model listing
curl http://localhost:1420/v1/models

Per-Channel Model Selection​

[agent.channel_overrides.tg_support]
provider = "anthropic"
model = "claude-sonnet-4-20250514"

[agent.channel_overrides.dc_community]
provider = "ollama"
model = "llama3.1:70b"

Custom Provider​

Implement the Provider trait to add a custom provider:

use clawdesk_agents::providers::{Provider, Request, Response, ModelInfo};

pub struct MyProvider {
api_key: String,
client: reqwest::Client,
}

#[async_trait]
impl Provider for MyProvider {
fn id(&self) -> &str {
"my_provider"
}

async fn list_models(&self) -> Result<Vec<ModelInfo>> {
Ok(vec![
ModelInfo {
id: "my-model-v1".into(),
name: "My Model v1".into(),
context_window: 32_768,
max_output_tokens: 4_096,
},
])
}

async fn send(&self, request: Request) -> Result<Response> {
// Transform request to your API format
let api_request = transform_request(&request);

// Call your API
let api_response = self.client
.post("https://api.my-provider.com/v1/chat")
.bearer_auth(&self.api_key)
.json(&api_request)
.send()
.await?;

// Transform response back
Ok(transform_response(api_response.json().await?))
}

async fn send_stream(
&self,
request: Request,
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
// Implement streaming...
todo!()
}

fn capabilities(&self, _model: &str) -> ModelCapabilities {
ModelCapabilities {
max_tokens: 4096,
supports_tools: true,
supports_vision: false,
supports_streaming: true,
supports_system_prompt: true,
context_window: 32_768,
}
}
}

Register in the provider registry:

let mut registry = ProviderRegistry::new();
registry.register("my_provider", Arc::new(MyProvider::new(api_key)));

Monitoring​

Provider Metrics​

MetricTypeDescription
clawdesk_provider_requests_totalCounterTotal requests by provider and model
clawdesk_provider_errors_totalCounterErrors by provider, model, and error type
clawdesk_provider_latency_secondsHistogramRequest latency by provider
clawdesk_provider_tokens_totalCounterTokens consumed (input + output)
clawdesk_provider_fallbacks_totalCounterFallback events
clawdesk_provider_cost_dollarsCounterEstimated cost in USD

Cost Tracking​

[providers.cost_tracking]
enabled = true
warn_threshold_daily = 50.00 # USD
hard_limit_daily = 100.00 # USD — stops requests
alert_channel = "tg_admin" # send alerts to this channel
# View cost summary
clawdesk config get providers.cost_tracking

# Example output:
# Today: $12.34 (anthropic: $8.21, openai: $4.13)
# This month: $234.56
# Budget remaining: $765.44

Troubleshooting​

ProblemSolution
"Provider not found"Check provider name in config, run clawdesk doctor
"Model not available"Verify model ID, check clawdesk config get providers.<name>
"Rate limited"Reduce request frequency or upgrade API tier
"Context window exceeded"Reduce history.max_tokens or use a model with larger context
Fallback not workingVerify failover.enabled = true and fallback_order is set
Ollama timeoutIncrease timeout_secs, check GPU availability
Bedrock auth failureVerify AWS credentials and IAM permissions