Understanding the single pattern that powers most AI agent frameworks
The Runnable is the fundamental building block of our framework. It's a simple yet powerful abstraction that allows us to build complex AI systems from composable parts.
Think of Runnable as the "contract" that every component in the framework must follow. It's like LEGO blocks—each block has the same connection mechanism, which means any block can connect to any other block.
By the end of this lesson, you'll understand why frameworks like LangChain built everything around this single interface, and you'll implement your own Runnable components.
// Without a common interface,
// every component is different:
const llmResponse = await llm.generate(prompt);
const parsedOutput = parser.parse(llmResponse);
const memorySaved = memory.store(parsedOutput);
// Different methods: generate(),
// parse(), store()
// Hard to compose, hard to test,
// hard to maintain
// With Runnable, everything uses
// the same interface:
const result = await prompt
.pipe(llm)
.pipe(parser)
.pipe(memory)
.invoke(input);
// Same method everywhere: invoke()
// Easy to compose, test, and maintain
By the end of this lesson, you will:
Runnable class
invoke, stream, batch
A Runnable is any component that can:
That's it! But this simplicity is what makes it powerful.
invoke() - Single ExecutionRun once with one input, get one output.
const result = await runnable.invoke(input);
// Input: "Hello"
// Output: "Hello, World!"
Use case: Normal execution, when you have one thing to process.
stream() - Streaming ExecutionProcess input and receive output in chunks as it's generated.
for await (const chunk of runnable.stream(input)) {
console.log(chunk); // Print each piece as it arrives
}
// Output: "H", "e", "l", "l", "o", "..."
Use case: LLM text generation, where you want to show results in real-time.
batch() - Parallel ExecutionProcess multiple inputs at once.
const results = await runnable.batch([input1, input2, input3]);
// Input: ["Hello", "Hi", "Hey"]
// Output: ["Hello, World!", "Hi, World!", "Hey, World!"]
Use case: Processing many items efficiently.
The real power comes from combining Runnables:
const pipeline = runnableA.pipe(runnableB).pipe(runnableC);
Because everything is a Runnable, you can chain them together infinitely!
You might be wondering: "Why should I care about Runnable if I can just use LangChain directly?"
Here's the fascinating part: LangChain rebuilt their entire framework around Runnable in 2023. Before that, they had dozens of different interfaces—chains, agents, tools, each with their own methods. It was a mess.
run() methodexecute() methodcall() methodgenerate() methodinvoke()stream()batch()pipe()When LangChain introduced Runnable, they didn't just add a new feature—they fundamentally changed how the framework works. They realized that having one consistent interface was more powerful than having specialized interfaces for each component.
This is why you're learning Runnable first: it's the foundation that everything else is built on. Once you understand Runnable, you'll understand:
Fun fact: The Runnable pattern is so successful that other frameworks (like LlamaIndex) have adopted similar patterns. It's become the de facto standard for composable AI frameworks.
Each step processes the output of the previous step
prompt.pipe(llm).pipe(parser).pipe(memory).invoke(input)
Every Runnable follows the same pattern: Input → Process → Output. When you pipe them, the output of one becomes the input of the next. This creates a data transformation pipeline where each step does one thing well, and together they solve complex problems.
invoke(), stream(), and batch()_call()pipe() to chain Runnables together