use super::any::{into_runnable_any, RunnableAny};
use super::config::RunnableConfig;
use super::error::LcelError;
use super::runnable_trait::Runnable;
use async_trait::async_trait;
use futures_util::{Stream, StreamExt};
use std::any::Any;
use std::marker::PhantomData;
use std::pin::Pin;
pub struct RunnableSequence<I: Send + Sync + 'static, O: Send + Sync + 'static> {
steps: Vec<Box<dyn RunnableAny>>,
_marker: PhantomData<(I, O)>,
}
impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug
for RunnableSequence<I, O>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RunnableSequence")
.field("steps", &self.steps.len())
.field("input", &std::any::type_name::<I>())
.field("output", &std::any::type_name::<O>())
.finish()
}
}
impl<I: Send + Sync + 'static, O: Send + Sync + 'static> RunnableSequence<I, O> {
pub fn from_single<R>(runnable: R) -> Self
where
R: Runnable<I, O> + 'static,
R::Error: Into<LcelError>,
{
Self {
steps: vec![into_runnable_any(runnable)],
_marker: PhantomData,
}
}
pub fn from_pair<R1, R2, M>(first: R1, second: R2) -> RunnableSequence<I, O>
where
M: Send + Sync + 'static,
R1: Runnable<I, M> + 'static,
R1::Error: Into<LcelError>,
R2: Runnable<M, O> + 'static,
R2::Error: Into<LcelError>,
{
Self {
steps: vec![into_runnable_any(first), into_runnable_any(second)],
_marker: PhantomData,
}
}
pub fn pipe<O2, R>(self, other: R) -> RunnableSequence<I, O2>
where
O2: Send + Sync + 'static,
R: Runnable<O, O2> + Send + Sync + 'static,
R::Error: Into<LcelError>,
{
let mut steps = self.steps;
steps.push(into_runnable_any(other));
RunnableSequence {
steps,
_marker: PhantomData,
}
}
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn steps(&self) -> &[Box<dyn RunnableAny>] {
&self.steps
}
}
#[async_trait]
impl<I: Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O> for RunnableSequence<I, O> {
type Error = LcelError;
async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<O, LcelError> {
let mut current: Box<dyn Any + Send> = Box::new(input);
for step in &self.steps {
current = step.invoke_any(current, config.clone()).await?;
}
current.downcast::<O>().map(|b| *b).map_err(|_| {
LcelError::TypeMismatch(format!(
"final downcast failed: expected {}",
std::any::type_name::<O>()
))
})
}
async fn batch(
&self,
inputs: Vec<I>,
config: Option<RunnableConfig>,
) -> Result<Vec<O>, LcelError> {
let mut current: Vec<Box<dyn Any + Send>> = inputs
.into_iter()
.map(|i| Box::new(i) as Box<dyn Any + Send>)
.collect();
for step in &self.steps {
current = step.batch_any(current, config.clone()).await?;
}
current
.into_iter()
.map(|boxed| {
boxed.downcast::<O>().map(|b| *b).map_err(|_| {
LcelError::TypeMismatch(format!(
"batch final downcast: expected {}",
std::any::type_name::<O>()
))
})
})
.collect()
}
async fn stream(
&self,
input: I,
config: Option<RunnableConfig>,
) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
let input_stream: Pin<
Box<dyn Stream<Item = Result<Box<dyn Any + Send>, LcelError>> + Send>,
> = Box::pin(futures_util::stream::once(async {
Ok(Box::new(input) as Box<dyn Any + Send>)
}));
let mut current_stream = input_stream;
for step in &self.steps {
current_stream = step.transform_any(current_stream, config.clone()).await?;
}
let output_stream = current_stream.map(|result| {
result.and_then(|boxed| {
boxed.downcast::<O>().map(|b| *b).map_err(|_| {
LcelError::TypeMismatch(format!(
"stream final downcast: expected {}",
std::any::type_name::<O>()
))
})
})
});
Ok(Box::pin(output_stream))
}
async fn transform(
&self,
input: Pin<Box<dyn Stream<Item = Result<I, LcelError>> + Send>>,
config: Option<RunnableConfig>,
) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
let mut current_stream: Pin<
Box<dyn Stream<Item = Result<Box<dyn Any + Send>, LcelError>> + Send>,
> = Box::pin(input.map(|result| result.map(|item| Box::new(item) as Box<dyn Any + Send>)));
for step in &self.steps {
current_stream = step.transform_any(current_stream, config.clone()).await?;
}
let output_stream = current_stream.map(|result| {
result.and_then(|boxed| {
boxed.downcast::<O>().map(|b| *b).map_err(|_| {
LcelError::TypeMismatch(format!(
"transform final downcast: expected {}",
std::any::type_name::<O>()
))
})
})
});
Ok(Box::pin(output_stream))
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::StreamExt;
struct Double;
#[async_trait]
impl Runnable<i32, i32> for Double {
type Error = std::convert::Infallible;
async fn invoke(
&self,
input: i32,
_config: Option<RunnableConfig>,
) -> Result<i32, Self::Error> {
Ok(input * 2)
}
}
struct AddOne;
#[async_trait]
impl Runnable<i32, i32> for AddOne {
type Error = std::convert::Infallible;
async fn invoke(
&self,
input: i32,
_config: Option<RunnableConfig>,
) -> Result<i32, Self::Error> {
Ok(input + 1)
}
}
struct I32ToString;
#[async_trait]
impl Runnable<i32, String> for I32ToString {
type Error = std::convert::Infallible;
async fn invoke(
&self,
input: i32,
_config: Option<RunnableConfig>,
) -> Result<String, Self::Error> {
Ok(format!("value={}", input))
}
}
#[tokio::test]
async fn invoke_two_steps() {
let seq = RunnableSequence::from_pair(Double, AddOne);
let result = seq.invoke(5, None).await.unwrap();
assert_eq!(result, 11);
}
#[tokio::test]
async fn invoke_three_steps() {
let seq = RunnableSequence::from_pair(Double, AddOne).pipe(I32ToString);
let result = seq.invoke(3, None).await.unwrap();
assert_eq!(result, "value=7");
}
#[tokio::test]
async fn batch_works() {
let seq = RunnableSequence::from_pair(Double, AddOne);
let results = seq.batch(vec![1, 2, 3], None).await.unwrap();
assert_eq!(results, vec![3, 5, 7]);
}
#[tokio::test]
async fn stream_works() {
let seq = RunnableSequence::from_pair(Double, AddOne);
let mut stream = seq.stream(10, None).await.unwrap();
let result = stream.next().await.unwrap().unwrap();
assert_eq!(result, 21);
}
#[tokio::test]
async fn transform_works() {
let seq = RunnableSequence::from_pair(Double, AddOne);
let input = Box::pin(futures_util::stream::iter(vec![
Ok(1i32),
Ok(2i32),
Ok(3i32),
])) as Pin<Box<dyn Stream<Item = Result<i32, LcelError>> + Send>>;
let mut output = seq.transform(input, None).await.unwrap();
let result = output.next().await.unwrap().unwrap();
assert_eq!(result, 7);
}
#[tokio::test]
async fn from_single_works() {
let seq: RunnableSequence<i32, i32> = RunnableSequence::from_single(Double);
let result = seq.invoke(4, None).await.unwrap();
assert_eq!(result, 8);
}
#[tokio::test]
async fn pipe_on_sequence_works() {
let seq = RunnableSequence::from_single(Double)
.pipe(AddOne)
.pipe(I32ToString);
let result = seq.invoke(5, None).await.unwrap();
assert_eq!(result, "value=11"); }
#[tokio::test]
async fn len_and_empty() {
let seq = RunnableSequence::from_pair(Double, AddOne);
assert_eq!(seq.len(), 2);
assert!(!seq.is_empty());
}
}