use futures_util::FutureExt;
use std::fmt;
use std::future::Future;
use std::future::IntoFuture;
use std::panic::AssertUnwindSafe;
use std::panic::catch_unwind;
use std::sync::Arc;
use std::sync::Mutex;
use ferrin_spec::BoxFuture;
use ferrin_spec::LanguageModelRef;
use super::StreamErrorInfo;
use super::StreamEvent;
use super::pipeline;
use super::result::StreamTextResult;
use super::transforms::StreamTransform;
use crate::error::Error;
use crate::generate_text::config::CallConfig;
use crate::hooks::HookFn;
use crate::output::NoOutput;
use crate::output::Output;
use crate::output::OutputHandler;
use crate::telemetry::AbortEvent;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ErrorDecision {
#[default]
Continue,
Retry,
}
pub trait OnErrorFn: Send + Sync + 'static {
fn call(&self, error: StreamErrorInfo) -> BoxFuture<'static, ErrorDecision>;
}
impl<F, Fut> OnErrorFn for F
where
F: Fn(StreamErrorInfo) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ErrorDecision> + Send + 'static,
{
fn call(&self, error: StreamErrorInfo) -> BoxFuture<'static, ErrorDecision> {
Box::pin(self(error))
}
}
#[derive(Default)]
pub(crate) struct StreamConfig {
pub(crate) transforms: Vec<Arc<dyn StreamTransform>>,
pub(crate) include_raw_chunks: bool,
pub(crate) stream_retries: Option<u32>,
pub(crate) on_error: Option<Arc<dyn OnErrorFn>>,
handled_errors: Mutex<Vec<StreamErrorInfo>>,
}
impl StreamConfig {
pub(crate) fn mark_error_handled(&self, error: StreamErrorInfo) {
self.handled_errors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(error);
}
pub(crate) fn take_error_handled(&self, error: &StreamErrorInfo) -> bool {
let mut handled = self
.handled_errors
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(index) = handled.iter().position(|item| item == error) {
handled.remove(index);
true
} else {
false
}
}
pub(crate) async fn error_decision(&self, error: StreamErrorInfo) -> ErrorDecision {
let Some(callback) = &self.on_error else {
return ErrorDecision::Continue;
};
let Ok(future) = catch_unwind(AssertUnwindSafe(|| callback.call(error))) else {
return ErrorDecision::Continue;
};
AssertUnwindSafe(future)
.catch_unwind()
.await
.unwrap_or(ErrorDecision::Continue)
}
}
impl fmt::Debug for StreamConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StreamConfig")
.field("transforms", &self.transforms.len())
.field("include_raw_chunks", &self.include_raw_chunks)
.field("stream_retries", &self.stream_retries)
.field("on_error", &self.on_error.is_some())
.finish()
}
}
#[must_use]
pub fn stream_text(model: impl Into<LanguageModelRef>) -> StreamText<()> {
StreamText {
config: CallConfig::new(model.into()),
output: Arc::new(NoOutput),
stream: StreamConfig {
transforms: Vec::new(),
include_raw_chunks: false,
stream_retries: None,
on_error: None,
handled_errors: Mutex::new(Vec::new()),
},
}
}
pub struct StreamText<O> {
pub(crate) config: CallConfig,
pub(crate) output: Arc<dyn OutputHandler<O>>,
pub(crate) stream: StreamConfig,
}
crate::builder::impl_call_builder!(StreamText);
impl<O> StreamText<O> {
pub fn output<T>(self, output: Output<T>) -> StreamText<T> {
StreamText {
config: self.config,
output: output.handler(),
stream: self.stream,
}
}
#[must_use]
pub fn transform(mut self, transform: impl StreamTransform + 'static) -> Self {
self.stream.transforms.push(Arc::new(transform));
self
}
#[must_use]
pub fn include_raw_chunks(mut self) -> Self {
self.stream.include_raw_chunks = true;
self
}
#[must_use]
pub fn stream_retries(mut self, retries: u32) -> Self {
self.stream.stream_retries = Some(retries);
self
}
#[must_use]
pub fn on_chunk(mut self, f: impl HookFn<StreamEvent>) -> Self {
self.config.hooks.on_chunk.push(Arc::new(f));
self
}
#[must_use]
pub fn on_abort(mut self, f: impl HookFn<AbortEvent>) -> Self {
self.config.hooks.on_abort.push(Arc::new(f));
self
}
#[must_use]
pub fn on_error(mut self, f: impl OnErrorFn) -> Self {
self.stream.on_error = Some(Arc::new(f));
self
}
}
impl<O> fmt::Debug for StreamText<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StreamText")
.field("config", &self.config)
.field("stream", &self.stream)
.finish_non_exhaustive()
}
}
impl<O: Send + 'static> IntoFuture for StreamText<O> {
type Output = Result<StreamTextResult<O>, Error>;
type IntoFuture = BoxFuture<'static, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
self.output.validate_configuration()?;
pipeline::start(self.config, self.output, self.stream).await
})
}
}