Caching does not take function code into consideration #894

Closed
opened 2026-02-20 17:42:16 -05:00 by yindo · 1 comment
Owner

Originally created by @pascalwhoop on GitHub (Aug 4, 2025).

Originally assigned to: @casparb on GitHub.

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Description

A function cache should consider the function's codebase as a paramter as well. Since the function may be changed completely but the cache still returns the old values.

Joblib Memory already solved this a while ago and while it's not perfect, it's certainly more mature. I suggest to use this instead.
https://joblib.readthedocs.io/en/latest/auto_examples/memory_basic_usage.html

System Info

Originally created by @pascalwhoop on GitHub (Aug 4, 2025). Originally assigned to: @casparb on GitHub. ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/). - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Description A function cache should consider the function's codebase as a paramter as well. Since the function may be changed completely but the cache still returns the old values. Joblib Memory already solved this a while ago and while it's not perfect, it's certainly more mature. I suggest to use this instead. https://joblib.readthedocs.io/en/latest/auto_examples/memory_basic_usage.html ### System Info -
yindo added the bugpending labels 2026-02-20 17:42:16 -05:00
yindo closed this issue 2026-02-20 17:42:16 -05:00
Author
Owner

@casparb commented on GitHub (Sep 16, 2025):

Hi @pascalwhoop, you can configure a custom CachePolicy.key_func to read your function's body:

from typing import TypedDict

from langgraph.cache.memory import InMemoryCache
from langgraph.graph import StateGraph
from langgraph.types import CachePolicy

class State(TypedDict):
    x: int
    result: int

# v1
def expensive_node(state: State) -> State:
    print('NOT CACHED v1')
    return {"result": state["x"] * 2}

cache = InMemoryCache() 

graph = (
    StateGraph(State)
    .add_node(expensive_node, cache_policy=CachePolicy(key_func=cache_key))
    .set_entry_point("expensive_node")
    .set_finish_point("expensive_node")
    .compile(cache=cache)  # NOTE: no checkpointer here
)

print("run #1:")
print(graph.invoke({"x": 5}, stream_mode="updates"))

# v2: different body
def expensive_node(state: State) -> State:
    print('NOT CACHED v2')
    return {"result": state["x"] * 3}

graph2 = (
    StateGraph(State)
    .add_node(expensive_node, cache_policy=CachePolicy(key_func=cache_key))
    .set_entry_point("expensive_node")
    .set_finish_point("expensive_node")
    .compile(cache=cache)
)

print("\nrun #2:")
print(graph2.invoke({"x": 5}, stream_mode="updates"))

You'll see that the cache won't be read from:

run #1:
NOT CACHED v1
[{'expensive_node': {'result': 10}}]

run #2:
NOT CACHED v2
[{'expensive_node': {'result': 15}}]
@casparb commented on GitHub (Sep 16, 2025): Hi @pascalwhoop, you can configure a custom [`CachePolicy.key_func`](https://langchain-ai.github.io/langgraph/reference/types/?h=key_func#langgraph.types.CachePolicy.key_func) to read your function's body: ```python from typing import TypedDict from langgraph.cache.memory import InMemoryCache from langgraph.graph import StateGraph from langgraph.types import CachePolicy class State(TypedDict): x: int result: int # v1 def expensive_node(state: State) -> State: print('NOT CACHED v1') return {"result": state["x"] * 2} cache = InMemoryCache() graph = ( StateGraph(State) .add_node(expensive_node, cache_policy=CachePolicy(key_func=cache_key)) .set_entry_point("expensive_node") .set_finish_point("expensive_node") .compile(cache=cache) # NOTE: no checkpointer here ) print("run #1:") print(graph.invoke({"x": 5}, stream_mode="updates")) # v2: different body def expensive_node(state: State) -> State: print('NOT CACHED v2') return {"result": state["x"] * 3} graph2 = ( StateGraph(State) .add_node(expensive_node, cache_policy=CachePolicy(key_func=cache_key)) .set_entry_point("expensive_node") .set_finish_point("expensive_node") .compile(cache=cache) ) print("\nrun #2:") print(graph2.invoke({"x": 5}, stream_mode="updates")) ``` You'll see that the cache won't be read from: ``` run #1: NOT CACHED v1 [{'expensive_node': {'result': 10}}] run #2: NOT CACHED v2 [{'expensive_node': {'result': 15}}] ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#894