Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runnables (LCEL)

Synaptic implements LCEL (LangChain Expression Language) through the Runnable trait and a set of composable building blocks. Every component in an LCEL chain -- prompts, models, parsers, custom logic -- implements the same Runnable<I, O> interface, so they can be combined freely with a uniform API.

The Runnable trait

The Runnable<I, O> trait is defined in synaptic_core and provides three core methods:

MethodDescription
invoke(input, config)Execute on a single input, returning one output
batch(inputs, config)Execute on multiple inputs sequentially
stream(input, config)Return a RunnableOutputStream of incremental results

Every Runnable also has a boxed() method that wraps it into a BoxRunnable<I, O> -- a type-erased container that enables the | pipe operator for composition.

use synaptic::runnables::{Runnable, RunnableLambda, BoxRunnable};
use synaptic::core::RunnableConfig;

let step = RunnableLambda::new(|x: String| async move {
    Ok(x.to_uppercase())
});

let config = RunnableConfig::default();
let result = step.invoke("hello".to_string(), &config).await?;
assert_eq!(result, "HELLO");

BoxRunnable -- type-erased composition

BoxRunnable<I, O> is the key type for building chains. It wraps any Runnable<I, O> behind a trait object, which erases the concrete type. This is necessary because the | operator requires both sides to have known types at the call site.

BoxRunnable itself implements Runnable<I, O>, so boxed runnables compose seamlessly.

Building blocks

Synaptic provides the following LCEL building blocks:

TypePurpose
RunnableLambdaWraps an async closure as a runnable
RunnablePassthroughPasses input through unchanged
RunnableSequenceChains two runnables (created by | operator)
RunnableParallelRuns named branches concurrently, merges to JSON
RunnableBranchRoutes input by condition, with a default fallback
RunnableAssignMerges parallel branch results into the input JSON object
RunnablePickExtracts specific keys from a JSON object
RunnableWithFallbacksTries alternatives when the primary runnable fails
RunnableRetryRetries with exponential backoff on failure
RunnableEachMaps a runnable over each element in a Vec
RunnableGeneratorWraps a generator function for true streaming output

Tip: For standalone async functions, you can also use the #[chain] macro to generate a BoxRunnable factory. This avoids writing RunnableLambda::new(|x| async { ... }).boxed() by hand. See Procedural Macros.

Guides

  • Pipe Operator -- chain runnables with | to build sequential pipelines
  • Streaming -- consume incremental output through a chain
  • Parallel & Branch -- run branches concurrently or route by condition
  • Assign & Pick -- merge computed keys into JSON and extract specific fields
  • Fallbacks -- provide alternative runnables when the primary one fails
  • Bind -- attach config transforms to a runnable
  • Retry -- retry with exponential backoff on transient failures
  • Generator -- wrap a streaming generator function as a runnable
  • Each -- map a runnable over each element in a list