Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dallay/corvus/llms.txt
Use this file to discover all available pages before exploring further.
Custom Provider
Build a custom provider to integrate any LLM backend with Corvus.
Overview
Providers implement the Provider trait from src/providers/traits.rs. The simplest implementation requires only chat_with_system().
Step 1: Create the Provider
Create src/providers/my_provider.rs:
use crate::providers::traits::Provider;
use anyhow::Result;
use async_trait::async_trait;
use reqwest::Client;
pub struct MyProvider {
api_key: String,
base_url: String,
client: Client,
}
impl MyProvider {
pub fn new(api_key: &str, base_url: Option<&str>) -> Self {
Self {
api_key: api_key.to_string(),
base_url: base_url.unwrap_or("https://api.example.com").to_string(),
client: Client::new(),
}
}
}
#[async_trait]
impl Provider for MyProvider {
async fn chat_with_system(
&self,
system_prompt: Option<&str>,
message: &str,
model: &str,
temperature: f64,
) -> Result<String> {
let url = format!("{}/v1/chat/completions", self.base_url);
let mut messages = Vec::new();
if let Some(sys) = system_prompt {
messages.push(serde_json::json!({
"role": "system",
"content": sys
}));
}
messages.push(serde_json::json!({
"role": "user",
"content": message
}));
let body = serde_json::json!({
"model": model,
"messages": messages,
"temperature": temperature,
});
let resp = self.client
.post(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.json(&body)
.send()
.await?
.json::<serde_json::Value>()
.await?;
resp["choices"][0]["message"]["content"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| anyhow::anyhow!("No content in response"))
}
}
Step 2: Register the Provider
Add to src/providers/mod.rs:
pub mod my_provider;
pub fn create_provider(name: &str, config: &Config) -> Result<Box<dyn Provider>> {
match name {
"my_provider" => Ok(Box::new(my_provider::MyProvider::new(
&config.api_key,
None,
))),
// ... existing providers
_ => Err(anyhow::anyhow!("Unknown provider: {}", name)),
}
}
Update ~/.corvus/config.toml:
api_key = "your-api-key"
default_provider = "my_provider"
default_model = "my-model-v1"
Step 4: Test
Add tests in src/providers/my_provider.rs:
#[cfg(test)]
mod tests {
use super::*
;
#[tokio::test]
async fn test_my_provider() {
let provider = MyProvider::new("test-key", None);
// Mock API call or use test endpoint
let result = provider.chat_with_system(
Some("You are helpful"),
"Hello",
"test-model",
0.7,
).await;
assert!(result.is_ok());
}
}
Run tests:
cargo test providers::my_provider
Advanced Features
If your provider supports native tool calling:
impl Provider for MyProvider {
fn capabilities(&self) -> ProviderCapabilities {
ProviderCapabilities {
native_tool_calling: true,
}
}
async fn chat_with_tools(
&self,
messages: &[ChatMessage],
tools: &[serde_json::Value],
model: &str,
temperature: f64,
) -> Result<ChatResponse> {
// Format tools for your API
// Parse tool_use from response
// Return ChatResponse with tool_calls
}
}
Streaming
For streaming support:
use futures_util::stream::BoxStream;
impl Provider for MyProvider {
fn supports_streaming(&self) -> bool {
true
}
fn stream_chat_with_system(
&self,
system_prompt: Option<&str>,
message: &str,
model: &str,
temperature: f64,
options: StreamOptions,
) -> BoxStream<'static, StreamResult<StreamChunk>> {
// Return async stream of chunks
}
}
Full Example
See examples/custom_provider.rs:19-59 for a complete Ollama integration.