use siumai::models;
use siumai::prelude::*;
use siumai::traits::ChatCapability;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🔍 Capability Detection - Feature detection and graceful degradation\n");
demonstrate_basic_capability_detection().await;
demonstrate_feature_availability().await;
demonstrate_graceful_feature_degradation().await;
demonstrate_provider_metadata().await;
demonstrate_adaptive_behavior().await;
println!("\n✅ Capability detection examples completed!");
Ok(())
}
async fn demonstrate_basic_capability_detection() {
println!("🔍 Basic Capability Detection:\n");
let providers = create_test_providers().await;
for (name, client) in providers {
println!(" Provider: {name}");
let capabilities = detect_capabilities(client.as_ref(), &name).await;
println!(" 📋 Detected Capabilities:");
for (feature, supported) in capabilities {
let status = if supported { "✅" } else { "❌" };
println!(" {status} {feature}");
}
println!();
}
}
async fn demonstrate_feature_availability() {
println!("🎯 Feature Availability Checking:\n");
let providers = create_test_providers().await;
let features_to_test = vec![
"streaming",
"vision",
"audio",
"tools",
"json_mode",
"thinking",
];
for feature in features_to_test {
println!(" Feature: {feature}");
for (name, client) in &providers {
let available = check_feature_availability(client.as_ref(), name, feature).await;
let status = if available { "✅" } else { "❌" };
println!(" {status} {name}");
}
println!();
}
}
async fn demonstrate_graceful_feature_degradation() {
println!("🎭 Graceful Feature Degradation:\n");
let providers = create_test_providers().await;
if let Some((name, client)) = providers.into_iter().next() {
println!(" Using provider: {name}");
println!(" Testing streaming with fallback:");
match try_streaming_with_fallback(client.as_ref()).await {
Ok(response) => {
println!(
" ✅ Got response: {}",
&response[..response.len().min(100)]
);
}
Err(e) => {
println!(" ❌ Failed: {e}");
}
}
println!("\n Testing vision with fallback:");
match try_vision_with_fallback(client.as_ref()).await {
Ok(response) => {
println!(
" ✅ Got response: {}",
&response[..response.len().min(100)]
);
}
Err(e) => {
println!(" ❌ Failed: {e}");
}
}
} else {
println!(" ⚠️ No providers available for testing");
}
println!();
}
async fn demonstrate_provider_metadata() {
println!("📊 Provider Metadata:\n");
let providers = create_test_providers().await;
for (name, client) in providers {
println!(" Provider: {name}");
let metadata = get_provider_metadata(client.as_ref(), &name).await;
println!(" 📋 Metadata:");
println!(" Type: {}", metadata.provider_type);
println!(" Model: {}", metadata.model);
println!(
" Max tokens: {}",
metadata.max_tokens.unwrap_or_default()
);
println!(" Context window: {}", metadata.context_window);
println!(
" Supports streaming: {}",
metadata.supports_streaming
);
println!(" Supports vision: {}", metadata.supports_vision);
println!(
" Cost per 1K tokens: ${:.4}",
metadata.cost_per_1k_tokens
);
println!();
}
}
async fn demonstrate_adaptive_behavior() {
println!("🤖 Adaptive Behavior:\n");
let providers = create_test_providers().await;
if let Some((name, client)) = providers.into_iter().next() {
println!(" Using provider: {name}");
let message = "Explain machine learning in simple terms";
match adaptive_chat(client.as_ref(), &name, message).await {
Ok(response) => {
println!(" ✅ Adaptive response received");
println!(" Response: {}", &response[..response.len().min(150)]);
}
Err(e) => {
println!(" ❌ Adaptive chat failed: {e}");
}
}
} else {
println!(" ⚠️ No providers available for testing");
}
println!();
}
async fn create_test_providers() -> Vec<(String, Box<dyn ChatCapability + Send + Sync>)> {
let mut providers = Vec::new();
if let Ok(api_key) = std::env::var("OPENAI_API_KEY")
&& let Ok(client) = LlmBuilder::new()
.openai()
.api_key(&api_key)
.model(models::openai::GPT_4O_MINI)
.build()
.await
{
providers.push((
"OpenAI".to_string(),
Box::new(client) as Box<dyn ChatCapability + Send + Sync>,
));
}
if let Ok(api_key) = std::env::var("ANTHROPIC_API_KEY")
&& let Ok(client) = LlmBuilder::new()
.anthropic()
.api_key(&api_key)
.model(models::anthropic::CLAUDE_HAIKU_3_5)
.build()
.await
{
providers.push((
"Anthropic".to_string(),
Box::new(client) as Box<dyn ChatCapability + Send + Sync>,
));
}
if let Ok(client) = LlmBuilder::new()
.ollama()
.base_url("http://localhost:11434")
.model("llama3.2")
.build()
.await
{
let test_messages = vec![user!("Hi")];
if client.chat(test_messages).await.is_ok() {
providers.push((
"Ollama".to_string(),
Box::new(client) as Box<dyn ChatCapability + Send + Sync>,
));
}
}
providers
}
async fn detect_capabilities(
client: &dyn ChatCapability,
provider_name: &str,
) -> HashMap<String, bool> {
let mut capabilities = HashMap::new();
capabilities.insert("Basic Chat".to_string(), true);
capabilities.insert(
"Streaming".to_string(),
test_streaming_support(client).await,
);
capabilities.insert(
"Vision".to_string(),
provider_supports_vision(provider_name),
);
capabilities.insert("Audio".to_string(), provider_supports_audio(provider_name));
capabilities.insert("Tools".to_string(), provider_supports_tools(provider_name));
capabilities.insert(
"JSON Mode".to_string(),
provider_supports_json_mode(provider_name),
);
capabilities.insert(
"Thinking".to_string(),
provider_supports_thinking(provider_name),
);
capabilities
}
async fn check_feature_availability(
client: &dyn ChatCapability,
provider_name: &str,
feature: &str,
) -> bool {
match feature {
"streaming" => test_streaming_support(client).await,
"vision" => provider_supports_vision(provider_name),
"audio" => provider_supports_audio(provider_name),
"tools" => provider_supports_tools(provider_name),
"json_mode" => provider_supports_json_mode(provider_name),
"thinking" => provider_supports_thinking(provider_name),
_ => false,
}
}
async fn test_streaming_support(_client: &dyn ChatCapability) -> bool {
true
}
fn provider_supports_vision(provider_name: &str) -> bool {
matches!(provider_name, "OpenAI" | "Anthropic")
}
fn provider_supports_audio(provider_name: &str) -> bool {
matches!(provider_name, "OpenAI")
}
fn provider_supports_tools(provider_name: &str) -> bool {
matches!(provider_name, "OpenAI" | "Anthropic")
}
fn provider_supports_json_mode(provider_name: &str) -> bool {
matches!(provider_name, "OpenAI" | "Anthropic")
}
fn provider_supports_thinking(provider_name: &str) -> bool {
matches!(provider_name, "Anthropic")
}
async fn try_streaming_with_fallback(client: &dyn ChatCapability) -> Result<String, LlmError> {
let messages = vec![user!("Count from 1 to 5")];
if let Ok(mut stream) = client.chat_stream(messages.clone(), None).await {
use futures_util::StreamExt;
let mut result = String::new();
while let Some(event) = stream.next().await {
match event? {
ChatStreamEvent::ContentDelta { delta, .. } => {
result.push_str(&delta);
}
ChatStreamEvent::StreamEnd { .. } => break,
_ => {}
}
}
Ok(format!("Streaming: {result}"))
} else {
let response = client.chat(messages).await?;
Ok(format!(
"Fallback: {}",
response.content_text().unwrap_or_default()
))
}
}
async fn try_vision_with_fallback(client: &dyn ChatCapability) -> Result<String, LlmError> {
let messages = vec![user!(
"Describe what you see in this image: [image would be here]"
)];
if let Ok(response) = client.chat(messages.clone()).await {
Ok(format!(
"Vision: {}",
response.content_text().unwrap_or_default()
))
} else {
let fallback_messages = vec![user!("Explain how image analysis works")];
let response = client.chat(fallback_messages).await?;
Ok(format!(
"Text fallback: {}",
response.content_text().unwrap_or_default()
))
}
}
#[derive(Debug)]
struct ProviderMetadata {
provider_type: String,
model: String,
max_tokens: Option<u32>,
context_window: u32,
supports_streaming: bool,
supports_vision: bool,
cost_per_1k_tokens: f64,
}
async fn get_provider_metadata(
_client: &dyn ChatCapability,
provider_name: &str,
) -> ProviderMetadata {
match provider_name {
"OpenAI" => ProviderMetadata {
provider_type: "Cloud API".to_string(),
model: models::openai::GPT_4O_MINI.to_string(),
max_tokens: Some(4096),
context_window: 128_000,
supports_streaming: true,
supports_vision: true,
cost_per_1k_tokens: 0.15,
},
"Anthropic" => ProviderMetadata {
provider_type: "Cloud API".to_string(),
model: models::anthropic::CLAUDE_HAIKU_3_5.to_string(),
max_tokens: Some(4096),
context_window: 200_000,
supports_streaming: true,
supports_vision: true,
cost_per_1k_tokens: 0.25,
},
"Ollama" => ProviderMetadata {
provider_type: "Local".to_string(),
model: "llama3.2".to_string(),
max_tokens: Some(2048),
context_window: 8192,
supports_streaming: true,
supports_vision: false,
cost_per_1k_tokens: 0.0,
},
_ => ProviderMetadata {
provider_type: "Unknown".to_string(),
model: "unknown".to_string(),
max_tokens: None,
context_window: 4096,
supports_streaming: false,
supports_vision: false,
cost_per_1k_tokens: 0.0,
},
}
}
async fn adaptive_chat(
client: &dyn ChatCapability,
provider_name: &str,
message: &str,
) -> Result<String, LlmError> {
let metadata = get_provider_metadata(client, provider_name).await;
let adjusted_message = if metadata.supports_vision {
format!("{message} (Note: This provider supports vision)")
} else {
message.to_string()
};
let messages = vec![user!(&adjusted_message)];
if metadata.supports_streaming {
if let Ok(mut stream) = client.chat_stream(messages.clone(), None).await {
use futures_util::StreamExt;
let mut result = String::new();
while let Some(event) = stream.next().await {
match event? {
ChatStreamEvent::ContentDelta { delta, .. } => {
result.push_str(&delta);
}
ChatStreamEvent::StreamEnd { .. } => break,
_ => {}
}
}
Ok(result)
} else {
let response = client.chat(messages).await?;
Ok(response.content_text().unwrap_or_default().to_string())
}
} else {
let response = client.chat(messages).await?;
Ok(response.content_text().unwrap_or_default().to_string())
}
}