langchain - ✅(Solved) Fix Bug: RunnableWithFallbacks.__getattr__ raises NameError on methods with unresolvable forward-ref annotations [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36579Fetched 2026-04-08 03:00:58
View on GitHub
Comments
1
Participants
1
Timeline
4
Reactions
0
Participants
Timeline (top)
cross-referenced ×2commented ×1labeled ×1

Error Message

File ".../langchain_core/runnables/fallbacks.py", line 626, in getattr if _returns_runnable(attr): File ".../langchain_core/runnables/fallbacks.py", line 652, in _returns_runnable return_type = typing.get_type_hints(attr).get("return") File ".../typing.py", line 2315, in get_type_hints hints[name] = _eval_type(value, globalns, localns, type_params) File ".../typing.py", line 947, in _evaluate eval(self.forward_code, globalns, localns), File "<string>", line 1, in <module> NameError: name 'Runnable' is not defined

Fix Action

Fix / Workaround

  • This is a bug, not a usage question.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version.
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example.

Workaround (currently in use)

PR fix notes

PR #36580: fix(core): handle unresolvable forward refs in RunnableWithFallbacks.getattr

Description (problem / solution / changelog)

Fixes #36579

RunnableWithFallbacks.__getattr__ calls _returns_runnable(attr) which calls typing.get_type_hints(attr) to decide whether the attribute should be re-wrapped to propagate fallbacks. When the introspected method's return annotation is a string forward reference that can't be resolved against the function's __globals__, get_type_hints raises NameError and the entire __getattr__ lookup crashes.

This makes chain.with_fallbacks([fallback]).with_structured_output(schema) unusable for any chat model whose with_structured_output is inherited from a base class whose module doesn't have all of the annotation's forward refs in scope (hit in production with langchain-xai's ChatXAI).

Fix

Catch NameError, TypeError, and AttributeError from typing.get_type_hints in _returns_runnable and default to False. The attribute is then returned bare instead of being treated as a Runnable-returning method. Methods that genuinely return Runnable and have resolvable annotations are unaffected — the only behavioral loss is automatic fallback re-wrapping for methods we can't introspect, which is the right safe default when we can't determine the return type.

Test

Adds test_fallbacks_getattr_unresolvable_annotation which constructs a fake chat model whose method has a string forward reference to a name that can't be resolved ("ThisClassDoesNotExistAnywhere"), wraps it with fallbacks, and asserts that __getattr__ returns the bare method instead of raising.

Verified to fail before the fix with the same NameError pattern as the production crash, and pass after.

How verified

From libs/core:

  • make format — clean
  • make lint (ruff + mypy) — clean
  • make test on tests/unit_tests/runnables/test_fallbacks.py — 17/17 passing (16 existing + 1 new regression test)

Changed files

  • libs/core/langchain_core/runnables/fallbacks.py (modified, +19/-1)
  • libs/core/tests/unit_tests/runnables/test_fallbacks.py (modified, +68/-0)

Code Example

from langchain_xai import ChatXAI  # or any chat model whose with_structured_output annotation has unresolvable forward refs
from pydantic import BaseModel

class TestSchema(BaseModel):
    field: str

primary = ChatXAI(model="grok-4-1-fast-non-reasoning")
fallback = ChatXAI(model="grok-4-1-fast-non-reasoning")
chain_with_fallbacks = primary.with_fallbacks([fallback])

# This crashes:
chain = chain_with_fallbacks.with_structured_output(TestSchema)

---

File ".../langchain_core/runnables/fallbacks.py", line 626, in __getattr__
    if _returns_runnable(attr):
File ".../langchain_core/runnables/fallbacks.py", line 652, in _returns_runnable
    return_type = typing.get_type_hints(attr).get("return")
File ".../typing.py", line 2315, in get_type_hints
    hints[name] = _eval_type(value, globalns, localns, type_params)
File ".../typing.py", line 947, in _evaluate
    eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
NameError: name 'Runnable' is not defined

---

primary_with_so = primary.with_structured_output(schema)
fallback_with_so = fallback.with_structured_output(schema)
chain = primary_with_so.with_fallbacks([fallback_with_so])

---

def _returns_runnable(attr: Any) -> bool:
    if not callable(attr):
        return False
    try:
        return_type = typing.get_type_hints(attr).get("return")
    except (NameError, TypeError, AttributeError):
        return False
    return bool(return_type and _is_runnable_type(return_type))
RAW_BUFFERClick to expand / collapse

Checked other resources

  • This is a bug, not a usage question.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version.
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example.

Package

langchain-core

Bug summary

Calling .with_structured_output(schema) on a RunnableWithFallbacks (e.g. a chat model wrapped with .with_fallbacks([fallback])) crashes with NameError: name 'Runnable' is not defined inside langchain_core/runnables/fallbacks.py:_returns_runnable.

The crash happens when _returns_runnable calls typing.get_type_hints(attr) on the bound method of the underlying chat model. get_type_hints evaluates string forward references against the function's __globals__ (the module where the method was defined). If that module doesn't have the referenced name in scope — which happens when a chat model inherits a method from a base class whose module uses forward refs that aren't importable from there — get_type_hints raises NameError and the entire __getattr__ lookup blows up.

This makes it impossible to use the otherwise-supported pattern of binding structured output to a fallback chain.

Reproducer

from langchain_xai import ChatXAI  # or any chat model whose with_structured_output annotation has unresolvable forward refs
from pydantic import BaseModel

class TestSchema(BaseModel):
    field: str

primary = ChatXAI(model="grok-4-1-fast-non-reasoning")
fallback = ChatXAI(model="grok-4-1-fast-non-reasoning")
chain_with_fallbacks = primary.with_fallbacks([fallback])

# This crashes:
chain = chain_with_fallbacks.with_structured_output(TestSchema)

Stack trace (truncated):

File ".../langchain_core/runnables/fallbacks.py", line 626, in __getattr__
    if _returns_runnable(attr):
File ".../langchain_core/runnables/fallbacks.py", line 652, in _returns_runnable
    return_type = typing.get_type_hints(attr).get("return")
File ".../typing.py", line 2315, in get_type_hints
    hints[name] = _eval_type(value, globalns, localns, type_params)
File ".../typing.py", line 947, in _evaluate
    eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
NameError: name 'Runnable' is not defined

Expected behavior

chain_with_fallbacks.with_structured_output(schema) should return a working structured-output chain (or at minimum, return the bare method without raising). Methods that genuinely return a Runnable and have resolvable annotations should still be re-wrapped to propagate fallbacks.

Actual behavior

__getattr__ raises NameError and the call site is left with no usable chain.

Workaround (currently in use)

Bind structured output to the primary and the fallback individually before composing the fallback chain:

primary_with_so = primary.with_structured_output(schema)
fallback_with_so = fallback.with_structured_output(schema)
chain = primary_with_so.with_fallbacks([fallback_with_so])

This works but is awkward — it forces callers to know about the failure mode of RunnableWithFallbacks.__getattr__ and reach into the underlying runnables.

Proposed fix

Make _returns_runnable defensive against get_type_hints failures. When the return type can't be determined (NameError from unresolvable forward refs, TypeError from unsupported callables, AttributeError from missing __annotations__), default to False and return the bare attribute. This preserves callability and only loses the automatic fallback re-wrapping for the introspection failure case — which is the safe default when we genuinely can't tell.

def _returns_runnable(attr: Any) -> bool:
    if not callable(attr):
        return False
    try:
        return_type = typing.get_type_hints(attr).get("return")
    except (NameError, TypeError, AttributeError):
        return False
    return bool(return_type and _is_runnable_type(return_type))

I have a branch with this fix plus a regression test (test_fallbacks_getattr_unresolvable_annotation) ready, verified to fail before the fix and pass after, with all 17 existing fallbacks tests still passing and make format/make lint/make test all green:

https://github.com/untilhamza/langchain/tree/fix/runnable-fallbacks-introspection-nameerror

Happy to open a PR once assigned.

System Info

  • langchain-core: 1.2.16 (also reproduces on master)
  • Python: 3.12
  • OS: macOS

extent analysis

TL;DR

The proposed fix involves making the _returns_runnable function defensive against get_type_hints failures by defaulting to False and returning the bare attribute when the return type can't be determined.

Guidance

  • The issue arises from get_type_hints raising a NameError due to unresolvable forward references in the annotations of the underlying chat model.
  • To fix this, modify the _returns_runnable function to catch and handle exceptions raised by get_type_hints, such as NameError, TypeError, and AttributeError.
  • The proposed fix preserves callability and only loses automatic fallback re-wrapping for the introspection failure case.
  • Before applying the fix, verify that the issue is indeed caused by unresolvable forward references in the annotations.

Example

def _returns_runnable(attr: Any) -> bool:
    if not callable(attr):
        return False
    try:
        return_type = typing.get_type_hints(attr).get("return")
    except (NameError, TypeError, AttributeError):
        return False
    return bool(return_type and _is_runnable_type(return_type))

Notes

  • The fix is specific to the langchain-core package and may not apply to other packages or versions.
  • The proposed fix has been verified to work with the provided reproducer and has passed existing tests.

Recommendation

Apply the proposed workaround by modifying the _returns_runnable function to handle exceptions raised by get_type_hints, as this fix preserves callability and only loses automatic fallback re-wrapping for the introspection failure case.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

chain_with_fallbacks.with_structured_output(schema) should return a working structured-output chain (or at minimum, return the bare method without raising). Methods that genuinely return a Runnable and have resolvable annotations should still be re-wrapped to propagate fallbacks.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING