#[cfg(feature = "anthropic")]
pub mod anthropic;
#[cfg(feature = "bedrock")]
pub mod bedrock;
pub mod config;
#[cfg(feature = "gemini")]
pub mod gemini;
#[cfg(any(
feature = "openai",
feature = "gemini",
feature = "ollama",
feature = "bedrock",
feature = "anthropic"
))]
pub(crate) mod http;
#[cfg(feature = "ollama")]
pub mod ollama;
#[cfg(feature = "openai")]
pub mod openai;
use std::pin::Pin;
use std::time::Duration;
use async_trait::async_trait;
use futures::Stream;
use crate::error::KovaError;
use crate::models::{
ConversationMessage, InferenceConfig, ModelInfo, ModelResponse, StreamEvent, ToolDefinition,
};
#[derive(Debug, Clone, PartialEq)]
pub struct RetryConfig {
pub max_retries: u32,
pub initial_backoff: Duration,
pub max_backoff: Duration,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_retries: 2,
initial_backoff: Duration::from_millis(500),
max_backoff: Duration::from_secs(10),
}
}
}
impl RetryConfig {
pub fn disabled() -> Self {
Self {
max_retries: 0,
..Self::default()
}
}
pub(crate) fn backoff(&self, attempt: u32) -> Duration {
self.initial_backoff
.saturating_mul(2u32.saturating_pow(attempt))
.min(self.max_backoff)
}
}
#[async_trait]
pub trait LlmProvider: Send + Sync {
async fn count_tokens(
&self,
messages: &[ConversationMessage],
tools: &[ToolDefinition],
) -> Result<u32, KovaError> {
Ok(heuristic_count_tokens(messages, tools))
}
async fn chat_completion(
&self,
messages: &[ConversationMessage],
tools: &[ToolDefinition],
config: &InferenceConfig,
) -> Result<ModelResponse, KovaError>;
async fn chat_completion_stream(
&self,
messages: &[ConversationMessage],
tools: &[ToolDefinition],
config: &InferenceConfig,
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, KovaError>> + Send>>, KovaError>;
async fn list_models(&self) -> Result<Vec<ModelInfo>, KovaError>;
}
pub fn heuristic_count_tokens(
messages: &[crate::models::ConversationMessage],
tools: &[crate::models::ToolDefinition],
) -> u32 {
use crate::models::ContentBlock;
let mut chars: usize = 0;
for msg in messages {
chars += 8; for block in &msg.content {
chars += match block {
ContentBlock::Text { text } => text.len(),
ContentBlock::ToolUse { name, input, .. } => {
name.len() + serde_json::to_string(input).map(|s| s.len()).unwrap_or(0) + 16
}
ContentBlock::ToolResult { content, .. } => content.len() + 16,
ContentBlock::Thinking { thinking, .. } => thinking.len(),
};
}
}
for tool in tools {
chars += tool.name.len()
+ tool.description.len()
+ serde_json::to_string(&tool.parameters)
.map(|s| s.len())
.unwrap_or(0)
+ 16;
}
(chars / 4) as u32
}