use async_trait::async_trait;
use crate::chat::{ChatMessage, ChatProvider, ChatResponse, ChatRole, MessageType, Tool};
use crate::completion::{CompletionProvider, CompletionRequest, CompletionResponse};
use crate::embedding::EmbeddingProvider;
use crate::error::LLMError;
use crate::models::ModelsProvider;
use crate::stt::SpeechToTextProvider;
use crate::tts::TextToSpeechProvider;
use crate::{builder::ValidatorFn, LLMProvider};
pub struct ValidatedLLM {
inner: Box<dyn LLMProvider>,
validator: Box<ValidatorFn>,
attempts: usize,
}
impl ValidatedLLM {
pub fn new(inner: Box<dyn LLMProvider>, validator: Box<ValidatorFn>, attempts: usize) -> Self {
Self {
inner,
validator,
attempts,
}
}
}
impl LLMProvider for ValidatedLLM {}
#[async_trait]
impl ChatProvider for ValidatedLLM {
async fn chat_with_tools(
&self,
messages: &[ChatMessage],
tools: Option<&[Tool]>,
) -> Result<Box<dyn ChatResponse>, LLMError> {
let mut local_messages = messages.to_vec();
let mut remaining_attempts = self.attempts;
loop {
let response = match self.inner.chat_with_tools(&local_messages, tools).await {
Ok(resp) => resp,
Err(e) => return Err(e),
};
match (self.validator)(&response.text().unwrap_or_default()) {
Ok(()) => {
return Ok(response);
}
Err(err) => {
remaining_attempts -= 1;
if remaining_attempts == 0 {
return Err(LLMError::InvalidRequest(format!(
"Validation error after max attempts: {err}"
)));
}
log::debug!(
"Completion validation failed (attempts remaining: {remaining_attempts}). Reason: {err}"
);
log::debug!(
"Validation failed (attempt remaining: {remaining_attempts}). Reason: {err}"
);
local_messages.push(ChatMessage {
role: ChatRole::User,
message_type: MessageType::Text,
content: format!(
"Your previous output was invalid because: {err}\n\
Please try again and produce a valid response."
),
});
}
}
}
}
}
#[async_trait]
impl CompletionProvider for ValidatedLLM {
async fn complete(&self, req: &CompletionRequest) -> Result<CompletionResponse, LLMError> {
let mut remaining_attempts = self.attempts;
loop {
let response = match self.inner.complete(req).await {
Ok(resp) => resp,
Err(e) => return Err(e),
};
match (self.validator)(&response.text) {
Ok(()) => {
return Ok(response);
}
Err(err) => {
remaining_attempts -= 1;
if remaining_attempts == 0 {
return Err(LLMError::InvalidRequest(format!(
"Validation error after max attempts: {err}"
)));
}
}
}
}
}
}
#[async_trait]
impl EmbeddingProvider for ValidatedLLM {
async fn embed(&self, input: Vec<String>) -> Result<Vec<Vec<f32>>, LLMError> {
self.inner.embed(input).await
}
}
#[async_trait]
impl SpeechToTextProvider for ValidatedLLM {
async fn transcribe(&self, _audio: Vec<u8>) -> Result<String, LLMError> {
Err(LLMError::ProviderError(
"Speech to text not supported".to_string(),
))
}
}
#[async_trait]
impl TextToSpeechProvider for ValidatedLLM {
async fn speech(&self, _text: &str) -> Result<Vec<u8>, LLMError> {
Err(LLMError::ProviderError(
"Text to speech not supported".to_string(),
))
}
}
#[async_trait]
impl ModelsProvider for ValidatedLLM {}