# ChatDelta
[](https://crates.io/crates/chatdelta)
[](https://docs.rs/chatdelta)
[](https://opensource.org/licenses/MIT)
A unified Rust library for connecting to multiple AI APIs (OpenAI, Google Gemini, Anthropic Claude) with a common interface. Supports parallel execution, conversations, streaming, retry logic, and extensive configuration options.
## Features
- **Unified Interface**: Single trait (`AiClient`) for all AI providers
- **Multiple Providers**: OpenAI ChatGPT, Google Gemini, Anthropic Claude
- **Conversation Support**: Multi-turn conversations with message history
- **Streaming Responses**: Real-time streaming support (where available)
- **Parallel Execution**: Run multiple AI models concurrently
- **Builder Pattern**: Fluent configuration with `ClientConfig::builder()`
- **Advanced Error Handling**: Detailed error types with specific categories
- **Retry Logic**: Configurable retry attempts with exponential backoff
- **Async/Await**: Built with tokio for efficient async operations
- **Type Safety**: Full Rust type safety with comprehensive error handling
## Quick Start
Add this to your `Cargo.toml`:
```toml
[dependencies]
chatdelta = "0.4"
tokio = { version = "1", features = ["full"] }
```
## Usage
### Basic Example
```rust
use chatdelta::{AiClient, ClientConfig, create_client};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::builder()
.timeout(Duration::from_secs(30))
.retries(3)
.temperature(0.7)
.max_tokens(1024)
.build();
let client = create_client("openai", "your-api-key", "gpt-4o", config)?;
let response = client.send_prompt("Hello, world!").await?;
println!("{}", response);
Ok(())
}
```
### Conversation Example
```rust
use chatdelta::{AiClient, ClientConfig, Conversation, create_client};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::builder()
.system_message("You are a helpful assistant")
.temperature(0.7)
.build();
let client = create_client("anthropic", "your-api-key", "claude-3-5-sonnet-20241022", config)?;
let mut conversation = Conversation::new();
conversation.add_user("What's the capital of France?");
conversation.add_assistant("The capital of France is Paris.");
conversation.add_user("What's its population?");
let response = client.send_conversation(&conversation).await?;
println!("{}", response);
Ok(())
}
```
### Parallel Execution
```rust
use chatdelta::{create_client, execute_parallel, ClientConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::builder()
.retries(2)
.temperature(0.7)
.build();
let clients = vec![
create_client("openai", "openai-key", "gpt-4o", config.clone())?,
create_client("anthropic", "claude-key", "claude-3-5-sonnet-20241022", config.clone())?,
create_client("google", "gemini-key", "gemini-1.5-pro", config)?,
];
let results = execute_parallel(clients, "Explain quantum computing").await;
for (name, result) in results {
match result {
Ok(response) => println!("{}: {}", name, response),
Err(e) => eprintln!("{} failed: {}", name, e),
}
}
Ok(())
}
```
### Streaming Responses
```rust
use chatdelta::{AiClient, ClientConfig, create_client};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::builder()
.temperature(0.8)
.build();
let client = create_client("openai", "your-api-key", "gpt-4o", config)?;
if client.supports_streaming() {
let mut stream = client.stream_prompt("Tell me a story").await?;
while let Some(chunk) = stream.next().await {
match chunk {
Ok(chunk) => {
print!("{}", chunk.content);
if chunk.finished {
println!("\n[Stream finished]");
break;
}
}
Err(e) => eprintln!("Stream error: {}", e),
}
}
} else {
println!("Client doesn't support streaming");
}
Ok(())
}
```
## Supported Providers
### OpenAI
- Provider: `"openai"`, `"gpt"`, or `"chatgpt"`
- Models: `"gpt-4"`, `"gpt-3.5-turbo"`, etc.
- API Key: OpenAI API key
### Google Gemini
- Provider: `"google"` or `"gemini"`
- Models: `"gemini-1.5-pro"`, `"gemini-1.5-flash"`, etc.
- API Key: Google AI API key
### Anthropic Claude
- Provider: `"anthropic"` or `"claude"`
- Models: `"claude-3-5-sonnet-20241022"`, `"claude-3-haiku-20240307"`, etc.
- API Key: Anthropic API key
## Configuration
The `ClientConfig` supports extensive configuration through a builder pattern:
```rust
use chatdelta::ClientConfig;
use std::time::Duration;
let config = ClientConfig::builder()
.timeout(Duration::from_secs(60)) // Request timeout
.retries(3) // Number of retry attempts
.temperature(0.8) // Response creativity (0.0-2.0)
.max_tokens(2048) // Maximum response length
.top_p(0.9) // Top-p sampling (0.0-1.0)
.frequency_penalty(0.1) // Frequency penalty (-2.0 to 2.0)
.presence_penalty(0.1) // Presence penalty (-2.0 to 2.0)
.system_message("You are a helpful assistant") // System message for conversation context
.build();
```
### Configuration Options
| `timeout` | HTTP request timeout | 30 seconds | All |
| `retries` | Number of retry attempts | 0 | All |
| `temperature` | Response creativity (0.0-2.0) | None | All |
| `max_tokens` | Maximum response length | 1024 | All |
| `top_p` | Top-p sampling (0.0-1.0) | None | OpenAI |
| `frequency_penalty` | Frequency penalty (-2.0 to 2.0) | None | OpenAI |
| `presence_penalty` | Presence penalty (-2.0 to 2.0) | None | OpenAI |
| `system_message` | System message for conversations | None | All |
## Error Handling
The library provides comprehensive error handling through the `ClientError` enum with detailed error types:
```rust
use chatdelta::{ClientError, ApiErrorType, NetworkErrorType};
match result {
Err(ClientError::Network(net_err)) => {
match net_err.error_type {
NetworkErrorType::Timeout => println!("Request timed out"),
NetworkErrorType::ConnectionFailed => println!("Connection failed"),
_ => println!("Network error: {}", net_err.message),
}
}
Err(ClientError::Api(api_err)) => {
match api_err.error_type {
ApiErrorType::RateLimit => println!("Rate limit exceeded"),
ApiErrorType::QuotaExceeded => println!("API quota exceeded"),
ApiErrorType::InvalidModel => println!("Invalid model specified"),
_ => println!("API error: {}", api_err.message),
}
}
Err(ClientError::Authentication(auth_err)) => {
println!("Authentication failed: {}", auth_err.message);
}
Err(ClientError::Configuration(config_err)) => {
println!("Configuration error: {}", config_err.message);
}
Err(ClientError::Parse(parse_err)) => {
println!("Parse error: {}", parse_err.message);
}
Err(ClientError::Stream(stream_err)) => {
println!("Stream error: {}", stream_err.message);
}
Ok(response) => println!("Success: {}", response),
}
```
### Error Categories
- **Network**: Connection issues, timeouts, DNS resolution failures
- **API**: Rate limits, quota exceeded, invalid models, server errors
- **Authentication**: Invalid API keys, expired tokens, insufficient permissions
- **Configuration**: Invalid parameters, missing required fields
- **Parse**: JSON parsing errors, missing response fields
- **Stream**: Streaming-specific errors, connection lost, invalid chunks
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Contributing
We welcome contributions! To get started, clone the repository and install the Rust toolchain. Before opening a pull request, run the following commands:
```bash
# Check formatting
cargo fmt -- --check
# Run the linter
cargo clippy -- -D warnings
# Execute tests
cargo test
```
This project uses GitHub Actions to run the same checks automatically on every pull request.