RunnableWithFallbacks.batch({ returnExceptions: true }) throws instead of returning errors
RunnableWithFallbacks.batch() throws Error: Not implemented. whenever returnExceptions: true is passed, even though the method's own type overloads declare that option as supported and return (RunOutput | Error)[].
Reproduction
@langchain/core 1.2.11, current main (778566e), Node v24.21.0.
import { RunnableLambda } from "@langchain/core/runnables";
const failing = RunnableLambda.from(() => {
throw new Error("primary model down");
});
const fallback = RunnableLambda.from((x: string) => `fallback result for ${x}`);
const withFallbacks = failing.withFallbacks([fallback]);
await withFallbacks.invoke("input-1");
// -> "fallback result for input-1" (fine)
await withFallbacks.batch(["input-1", "input-2"]);
// -> ["fallback result for input-1", "fallback result for input-2"] (fine)
await withFallbacks.batch(["input-1", "input-2"], undefined, { returnExceptions: true });
// -> throws: Error: Not implemented.Source: libs/langchain-core/src/runnables/base.ts, RunnableWithFallbacks.batch():
async batch(
inputs: RunInput[],
options?: Partial<RunnableConfig> | Partial<RunnableConfig>[],
batchOptions?: RunnableBatchOptions
): Promise<(RunOutput | Error)[]> {
if (batchOptions?.returnExceptions) {
throw new Error("Not implemented.");
}The three overload signatures immediately above this implementation (lines 3061-3074) advertise returnExceptions: true as a valid, typed input that resolves to (RunOutput | Error)[]. Nothing in the type signature indicates a runtime throw, so this fails silently until called: tsc accepts the call, and it blows up at runtime instead.
For comparison, calling failing.batch(["input-1", "input-2"], undefined, { returnExceptions: true }) directly on the plain RunnableLambda (no fallback wrapper) returns [Error, Error] instead of throwing. The base Runnable.batch() path handles the option; RunnableWithFallbacks.batch() is the one implementation that doesn't.
Why this matters for fallbacks specifically
withFallbacks() is the way to build multi-provider routing in this library: try model A, fall back to model B on failure. returnExceptions: true is what you reach for so one bad input in a batch doesn't abort the rest. That combination throws for every fallback runnable right now. The workaround is dropping down to a manual loop of individual invoke() calls with your own try/catch, giving up the batching this method exists to provide.
I hit this building model-routing and cost-optimization logic on @langchain/core fallbacks at Sagui.AI. We batch prompts through a primary/fallback pair and pass returnExceptions: true so one failed row doesn't take down the whole batch.
Prior art
I didn't find an existing issue or PR on this specific gap. #8533 was a different (mistaken) report that assumed the class didn't exist in TS at all; it was closed as not-planned once the reporter confirmed the class was already present, and didn't touch this returnExceptions behavior. This is on current main, not fixed by anything merged since.
Source: langchain-ai/langchainjs