Skip to content

Commit 3b85c83

Browse files
authored
Avoid repeated runtime calls to get_type_hints (langchain-ai#4888)
2 parents 08f88ce + d661d52 commit 3b85c83

File tree

3 files changed

+32
-6
lines changed

3 files changed

+32
-6
lines changed

libs/langgraph/langgraph/graph/state.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@
7272
)
7373
from langgraph.store.base import BaseStore
7474
from langgraph.types import All, CachePolicy, Checkpointer, Command, RetryPolicy, Send
75-
from langgraph.utils.fields import get_field_default, get_update_as_tuples
75+
from langgraph.utils.fields import (
76+
get_cached_annotated_keys,
77+
get_field_default,
78+
get_update_as_tuples,
79+
)
7680
from langgraph.utils.pydantic import create_model
7781
from langgraph.utils.runnable import RunnableLike, coerce_to_runnable
7882

@@ -885,7 +889,7 @@ def _get_updates(
885889
else:
886890
updates.extend(_get_updates(i) or ())
887891
return updates
888-
elif (t := type(input)) and get_type_hints(t):
892+
elif (t := type(input)) and get_cached_annotated_keys(t):
889893
return get_update_as_tuples(input, output_keys)
890894
else:
891895
msg = create_error_message(

libs/langgraph/langgraph/types.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
TypeVar,
1515
Union,
1616
cast,
17-
get_type_hints,
1817
)
1918

2019
from langchain_core.runnables import Runnable, RunnableConfig
@@ -23,7 +22,7 @@
2322

2423
from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointMetadata
2524
from langgraph.utils.cache import default_cache_key
26-
from langgraph.utils.fields import get_update_as_tuples
25+
from langgraph.utils.fields import get_cached_annotated_keys, get_update_as_tuples
2726

2827
if TYPE_CHECKING:
2928
from langgraph.pregel.protocol import PregelProtocol
@@ -350,8 +349,8 @@ def _update_as_tuples(self) -> Sequence[tuple[str, Any]]:
350349
for t in self.update
351350
):
352351
return self.update
353-
elif hints := get_type_hints(type(self.update)):
354-
return get_update_as_tuples(self.update, tuple(hints.keys()))
352+
elif keys := get_cached_annotated_keys(type(self.update)):
353+
return get_update_as_tuples(self.update, keys)
355354
elif self.update is not None:
356355
return [("__root__", self.update)]
357356
else:

libs/langgraph/langgraph/utils/fields.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import dataclasses
2+
import types
3+
import weakref
24
from collections.abc import Generator, Sequence
35
from typing import Annotated, Any, Optional, Union, get_type_hints
46

@@ -178,3 +180,24 @@ def get_update_as_tuples(input: Any, keys: Sequence[str]) -> list[tuple[str, Any
178180
or (keep is not None and k in keep)
179181
)
180182
]
183+
184+
185+
ANNOTATED_KEYS_CACHE: weakref.WeakKeyDictionary[type[Any], tuple[str, ...]] = (
186+
weakref.WeakKeyDictionary()
187+
)
188+
189+
190+
def get_cached_annotated_keys(obj: type[Any]) -> tuple[str, ...]:
191+
"""Return cached annotated keys for a Python class."""
192+
if obj in ANNOTATED_KEYS_CACHE:
193+
return ANNOTATED_KEYS_CACHE[obj]
194+
if isinstance(obj, type):
195+
keys: list[str] = []
196+
for base in reversed(obj.__mro__):
197+
ann = base.__dict__.get("__annotations__")
198+
if ann is None or isinstance(ann, types.GetSetDescriptorType):
199+
continue
200+
keys.extend(ann.keys())
201+
return ANNOTATED_KEYS_CACHE.setdefault(obj, tuple(keys))
202+
else:
203+
raise TypeError(f"Expected a type, got {type(obj)}. ")

0 commit comments

Comments
 (0)