use crate::LlmError;
use crate::LlmModel;
use crate::Result as LlmResult;
use crate::catalog::{Provider, ReasoningEffortError};
use std::pin::Pin;
use tokio_stream::Stream;
use utils::ReasoningEffort;
use super::{Context, LlmResponse};
pub type LlmResponseStream = Pin<Box<dyn Stream<Item = LlmResult<LlmResponse>> + Send>>;
#[doc = include_str!("docs/streaming_model_provider.md")]
pub trait StreamingModelProvider: Send + Sync {
fn stream_response(&self, context: &Context) -> LlmResponseStream;
fn display_name(&self) -> String {
self.model()
.map_or_else(|| "Unknown model".to_string(), |model| display_name(model.provider_enum(), &model.model_id()))
}
fn context_window(&self) -> Option<u32> {
self.model().and_then(|model| model.context_window())
}
fn model(&self) -> Option<LlmModel> {
None
}
}
pub(crate) fn display_name(provider: Provider, model_id: &str) -> String {
if model_id.is_empty() {
provider.display_name().to_string()
} else {
format!("{} ({model_id})", provider.display_name())
}
}
pub(crate) fn validate_reasoning(context: &Context, model: Option<&LlmModel>) -> LlmResult<()> {
if context.reasoning_effort() != ReasoningEffort::Disabled {
return Ok(());
}
let model = model.ok_or_else(|| ReasoningEffortError::Unsupported {
model: "unknown".to_string(),
effort: ReasoningEffort::Disabled,
supported: Vec::new(),
})?;
if !model.supports_reasoning_off() {
model.validate_reasoning_effort(ReasoningEffort::Disabled)?;
}
if !model.supports_reasoning_off_transport() {
return Err(LlmError::UnsupportedDisableTransport { model: model.to_string() });
}
Ok(())
}
impl StreamingModelProvider for Box<dyn StreamingModelProvider> {
fn stream_response(&self, context: &Context) -> LlmResponseStream {
(**self).stream_response(context)
}
fn display_name(&self) -> String {
(**self).display_name()
}
fn context_window(&self) -> Option<u32> {
(**self).context_window()
}
fn model(&self) -> Option<LlmModel> {
(**self).model()
}
}
impl<T: StreamingModelProvider + ?Sized> StreamingModelProvider for std::sync::Arc<T> {
fn stream_response(&self, context: &Context) -> LlmResponseStream {
(**self).stream_response(context)
}
fn display_name(&self) -> String {
(**self).display_name()
}
fn context_window(&self) -> Option<u32> {
(**self).context_window()
}
fn model(&self) -> Option<LlmModel> {
(**self).model()
}
}