langchain-core LCEL chain with retries and fallbacks, tested offline: working versions (tested 2026-10-15)
Compose a prompt, a chat model and an output parser with with_retry and with_fallbacks, then test the failure paths offline with GenericFakeChatModel and a failing runnable.
passed5 passed · 0 failed2026-10-15 13:31 UTC- Status
- passed
- Last passed
- 2026-10-15 13:31 UTC
- Tests
- 5 / 5
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
langchain-core@1.6.10pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38742087302
The code
Exactly the files that passed. Your agent can pull them with get_recipe("langchain-core-runnable-fallbacks") (install), or see them on GitHub.
requirements.txt
langchain-core==1.6.10 pytest==9.1.1
chain.py
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import Runnable
prompt = ChatPromptTemplate.from_messages([
("system", "You are terse."),
("human", "{question}"),
])
def fake_model(text: str) -> GenericFakeChatModel:
# The messages iterator is consumed on every call, so build a fresh model per use.
return GenericFakeChatModel(messages=iter([AIMessage(content=text)]))
class Flaky:
"""Raises `exc` for the first `fail_times` calls, then delegates to `inner`."""
def __init__(self, inner: Runnable, fail_times: int, exc: type[Exception] = TimeoutError):
self.inner, self.fail_times, self.exc = inner, fail_times, exc
self.calls = 0
def __call__(self, value):
self.calls += 1
if self.calls <= self.fail_times:
raise self.exc("transient")
return self.inner.invoke(value)
def build_chain(primary: Runnable, backup: Runnable, attempts: int = 3):
# Retry happens first, on the primary only. The fallback runs once retries are exhausted.
retried = primary.with_retry(
retry_if_exception_type=(TimeoutError,),
stop_after_attempt=attempts,
wait_exponential_jitter=False,
)
guarded = retried.with_fallbacks([backup], exceptions_to_handle=(TimeoutError,))
return prompt | guarded | StrOutputParser()
tests/test_chain.py
import pytest
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableLambda
from chain import Flaky, build_chain, fake_model
INPUT = {"question": "hi"}
def test_happy_path_returns_text():
chain = build_chain(fake_model("pong"), fake_model("backup"))
assert chain.invoke(INPUT) == "pong"
def test_retry_recovers_before_fallback():
primary = Flaky(fake_model("primary"), fail_times=2)
chain = build_chain(RunnableLambda(primary), fake_model("backup"), attempts=3)
assert chain.invoke(INPUT) == "primary"
assert primary.calls == 3
def test_fallback_after_retries_exhausted():
primary = Flaky(fake_model("primary"), fail_times=99)
chain = build_chain(RunnableLambda(primary), fake_model("backup"), attempts=3)
assert chain.invoke(INPUT) == "backup"
assert primary.calls == 3
def test_fallback_receives_original_input_not_the_error():
seen = []
def capture(value):
seen.append(value)
return AIMessage(content="ok")
primary = Flaky(fake_model("primary"), fail_times=99)
chain = build_chain(RunnableLambda(primary), RunnableLambda(capture))
assert chain.invoke(INPUT) == "ok"
assert "hi" in seen[0].to_string()
def test_unlisted_exception_bypasses_retry_and_fallback():
primary = Flaky(fake_model("primary"), fail_times=99, exc=ValueError)
chain = build_chain(RunnableLambda(primary), fake_model("backup"))
with pytest.raises(ValueError):
chain.invoke(INPUT)
assert primary.calls == 1
pytest.ini
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.