SQLiteSaver fails to pass type checking at graph compilation #173

Closed
opened 2026-02-20 17:30:21 -05:00 by yindo · 3 comments
Owner

Originally created by @streamnsight on GitHub (Aug 7, 2024).

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangGraph/LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangGraph/LangChain rather than my code.
  • I am sure this is better as an issue rather than a GitHub discussion, since this is a LangGraph bug and not a design question.

Example Code

from typing import List
from typing_extensions import TypedDict
from langgraph.graph import END, StateGraph, START
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.sqlite import SqliteSaver

workflow = StateGraph(dict)

# this works:
workflow.compile(checkpointer=MemorySaver())

# this doesn't work:
workflow.compile(checkpointer=SqliteSaver.from_conn_string(":memory:"))

Error Message and Stack Trace (if applicable)

---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[28], line 1
----> 1 workflow.compile(checkpointer=SqliteSaver.from_conn_string(":memory:"))

File ~/workspace/code/me/notebooks/.venv/lib/python3.11/site-packages/langgraph/graph/state.py:431, in StateGraph.compile(self, checkpointer, interrupt_before, interrupt_after, debug)
    411 output_channels = (
    412     "__root__"
    413     if len(self.schemas[self.output]) == 1
   (...)
    419     ]
    420 )
    421 stream_channels = (
    422     "__root__"
    423     if len(self.channels) == 1 and "__root__" in self.channels
   (...)
    428     ]
    429 )
--> 431 compiled = CompiledStateGraph(
    432     builder=self,
    433     config_type=self.config_schema,
    434     nodes={},
    435     channels={**self.channels, START: EphemeralValue(self.input)},
    436     input_channels=START,
    437     stream_mode="updates",
    438     output_channels=output_channels,
    439     stream_channels=stream_channels,
    440     checkpointer=checkpointer,
    441     interrupt_before_nodes=interrupt_before,
    442     interrupt_after_nodes=interrupt_after,
    443     auto_validate=False,
    444     debug=debug,
    445 )
    447 compiled.attach_node(START, None)
    448 for key, node in self.nodes.items():

File ~/workspace/code/me/notebooks/.venv/lib/python3.11/site-packages/pydantic/v1/main.py:341, in BaseModel.__init__(__pydantic_self__, **data)
    339 values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data)
    340 if validation_error:
--> 341     raise validation_error
    342 try:
    343     object_setattr(__pydantic_self__, '__dict__', values)

ValidationError: 1 validation error for CompiledStateGraph
checkpointer
  instance of BaseCheckpointSaver expected (type=type_error.arbitrary_type; expected_arbitrary_type=BaseCheckpointSaver)

Description

langchain==0.2.9
langchain-community==0.2.7
langchain-core==0.2.28
langchain-experimental==0.0.62
langchain-text-splitters==0.2.2
langdetect==1.0.9
langgraph==0.2.1
langgraph-checkpoint==1.0.2
langgraph-checkpoint-sqlite==1.0.0
langsmith==0.1.85

System Info

langchain==0.2.9
langchain-community==0.2.7
langchain-core==0.2.28
langchain-experimental==0.0.62
langchain-text-splitters==0.2.2
langdetect==1.0.9
langgraph==0.2.1
langgraph-checkpoint==1.0.2
langgraph-checkpoint-sqlite==1.0.0
langsmith==0.1.85

platform: linux
python: 3.9.18

System Information

OS: Linux
OS Version: #15-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 9 17:03:36 UTC 2024
Python Version: 3.9.18 (main, May 16 2024, 00:00:00)
[GCC 11.4.1 20231218 (Red Hat 11.4.1-3.0.1)]

Package Information

langchain_core: 0.2.29
langchain: 0.2.7
langchain_community: 0.2.7
langsmith: 0.1.90
langchain_text_splitters: 0.2.2
langgraph: 0.2.1

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.9.5
async-timeout: 4.0.3
dataclasses-json: 0.6.7
jsonpatch: 1.33
langgraph-checkpoint: 1.0.2
numpy: 1.26.4
orjson: 3.10.6
packaging: 24.1
pydantic: 2.8.2
PyYAML: 6.0.1
requests: 2.32.3
SQLAlchemy: 2.0.31
tenacity: 8.5.0
typing-extensions: 4.12.2
[root@colima src]#

Originally created by @streamnsight on GitHub (Aug 7, 2024). ### Checked other resources - [X] I added a very descriptive title to this issue. - [X] I searched the [LangGraph](https://langchain-ai.github.io/langgraph/)/LangChain documentation with the integrated search. - [X] I used the GitHub search to find a similar question and didn't find it. - [X] I am sure that this is a bug in LangGraph/LangChain rather than my code. - [X] I am sure this is better as an issue [rather than a GitHub discussion](https://github.com/langchain-ai/langgraph/discussions/new/choose), since this is a LangGraph bug and not a design question. ### Example Code ```python from typing import List from typing_extensions import TypedDict from langgraph.graph import END, StateGraph, START from langgraph.checkpoint.memory import MemorySaver from langgraph.checkpoint.sqlite import SqliteSaver workflow = StateGraph(dict) # this works: workflow.compile(checkpointer=MemorySaver()) # this doesn't work: workflow.compile(checkpointer=SqliteSaver.from_conn_string(":memory:")) ``` ### Error Message and Stack Trace (if applicable) ```shell --------------------------------------------------------------------------- ValidationError Traceback (most recent call last) Cell In[28], line 1 ----> 1 workflow.compile(checkpointer=SqliteSaver.from_conn_string(":memory:")) File ~/workspace/code/me/notebooks/.venv/lib/python3.11/site-packages/langgraph/graph/state.py:431, in StateGraph.compile(self, checkpointer, interrupt_before, interrupt_after, debug) 411 output_channels = ( 412 "__root__" 413 if len(self.schemas[self.output]) == 1 (...) 419 ] 420 ) 421 stream_channels = ( 422 "__root__" 423 if len(self.channels) == 1 and "__root__" in self.channels (...) 428 ] 429 ) --> 431 compiled = CompiledStateGraph( 432 builder=self, 433 config_type=self.config_schema, 434 nodes={}, 435 channels={**self.channels, START: EphemeralValue(self.input)}, 436 input_channels=START, 437 stream_mode="updates", 438 output_channels=output_channels, 439 stream_channels=stream_channels, 440 checkpointer=checkpointer, 441 interrupt_before_nodes=interrupt_before, 442 interrupt_after_nodes=interrupt_after, 443 auto_validate=False, 444 debug=debug, 445 ) 447 compiled.attach_node(START, None) 448 for key, node in self.nodes.items(): File ~/workspace/code/me/notebooks/.venv/lib/python3.11/site-packages/pydantic/v1/main.py:341, in BaseModel.__init__(__pydantic_self__, **data) 339 values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data) 340 if validation_error: --> 341 raise validation_error 342 try: 343 object_setattr(__pydantic_self__, '__dict__', values) ValidationError: 1 validation error for CompiledStateGraph checkpointer instance of BaseCheckpointSaver expected (type=type_error.arbitrary_type; expected_arbitrary_type=BaseCheckpointSaver) ``` ### Description langchain==0.2.9 langchain-community==0.2.7 langchain-core==0.2.28 langchain-experimental==0.0.62 langchain-text-splitters==0.2.2 langdetect==1.0.9 langgraph==0.2.1 langgraph-checkpoint==1.0.2 langgraph-checkpoint-sqlite==1.0.0 langsmith==0.1.85 ### System Info langchain==0.2.9 langchain-community==0.2.7 langchain-core==0.2.28 langchain-experimental==0.0.62 langchain-text-splitters==0.2.2 langdetect==1.0.9 langgraph==0.2.1 langgraph-checkpoint==1.0.2 langgraph-checkpoint-sqlite==1.0.0 langsmith==0.1.85 platform: linux python: 3.9.18 System Information ------------------ > OS: Linux > OS Version: #15-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 9 17:03:36 UTC 2024 > Python Version: 3.9.18 (main, May 16 2024, 00:00:00) [GCC 11.4.1 20231218 (Red Hat 11.4.1-3.0.1)] Package Information ------------------- > langchain_core: 0.2.29 > langchain: 0.2.7 > langchain_community: 0.2.7 > langsmith: 0.1.90 > langchain_text_splitters: 0.2.2 > langgraph: 0.2.1 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp: 3.9.5 > async-timeout: 4.0.3 > dataclasses-json: 0.6.7 > jsonpatch: 1.33 > langgraph-checkpoint: 1.0.2 > numpy: 1.26.4 > orjson: 3.10.6 > packaging: 24.1 > pydantic: 2.8.2 > PyYAML: 6.0.1 > requests: 2.32.3 > SQLAlchemy: 2.0.31 > tenacity: 8.5.0 > typing-extensions: 4.12.2 [root@colima src]#
yindo closed this issue 2026-02-20 17:30:21 -05:00
Author
Owner

@raphavtorres commented on GitHub (Aug 7, 2024):

same here, I updated to:
langgraph==0.2.1
langchain==0.2.12
langgraph-checkpoint==1.0.2

from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string(":memory:")
graph = graph_builder.compile(
    checkpointer=memory,
    interrupt_before=["driver"],
)

ValidationError: 1 validation error for CompiledStateGraph
checkpointer
instance of BaseCheckpointSaver expected (type=type_error.arbitrary_type; expected_arbitrary_type=BaseCheckpointSaver)

@raphavtorres commented on GitHub (Aug 7, 2024): same here, I updated to: langgraph==0.2.1 langchain==0.2.12 langgraph-checkpoint==1.0.2 ```python from langgraph.checkpoint.sqlite import SqliteSaver memory = SqliteSaver.from_conn_string(":memory:") ``` ```python graph = graph_builder.compile( checkpointer=memory, interrupt_before=["driver"], ) ``` ValidationError: 1 validation error for CompiledStateGraph checkpointer instance of BaseCheckpointSaver expected (type=type_error.arbitrary_type; expected_arbitrary_type=BaseCheckpointSaver)
Author
Owner

@vbarda commented on GitHub (Aug 7, 2024):

@streamnsight from_conn_string is a context manager in 0.2.x, you can either compile the graph inside it or define connection directly:

with SqliteSaver.from_conn_string(":memory:") as checkpointer:
    workflow.compile(checkpointer=checkpointer)

or

import sqlite3
conn = sqlite3.connect(":memory:")
checkpointer = SqliteSaver(conn)
workflow.compile(checkpointer=checkpointer)
@vbarda commented on GitHub (Aug 7, 2024): @streamnsight `from_conn_string` is a context manager in `0.2.x`, you can either compile the graph inside it or define connection directly: ```python with SqliteSaver.from_conn_string(":memory:") as checkpointer: workflow.compile(checkpointer=checkpointer) ``` or ```python import sqlite3 conn = sqlite3.connect(":memory:") checkpointer = SqliteSaver(conn) workflow.compile(checkpointer=checkpointer) ```
Author
Owner

@robsyc commented on GitHub (Aug 23, 2024):

I think this question may come back more because the implementation used in the Deeplearning.ai course is now outdated...

Is this the correct procedure for setting up an agent class with tools and persistence? Code is based on what's provided in this thread and in the Deeplearning.ai / LangChain course notebook.

class AgentState(TypedDict):
    """Memory state of the agent."""
    messages: Annotated[list[AnyMessage], operator.add]

class Agent:
    """Agent that uses an LLM model and tools to interact with the user."""
    def __init__(self, model, tools, system=""):
        """Initialize agent with LLM model, memory, available tools, and system prompt."""
        self.system = system
        graph = StateGraph(AgentState)              # initialize empty state graph
        graph.add_node("llm", self.call_openai)     # add LLM node (executes reasoning w/ call_openai method)
        graph.add_node("action", self.take_action)  # add action node (executes action w/ take_action method)
        graph.add_conditional_edges(                # conditional edge from LLM to action (if action exists)
            "llm",
            self.exists_action,
            {True: "action", False: END}
        )
            
        graph.add_edge("action", "llm")             # edge from action to LLM (adds observation)
        graph.set_entry_point("llm")                # LLM node is used as entry point for the graph
        
        with SqliteSaver.from_conn_string(":memory:") as checkpointer:
            self.graph = graph.compile(checkpointer=checkpointer)

        self.tools = {t.name: t for t in tools}     # dict mapping tool_name to tool itself
        self.model = model.bind_tools(tools)        # bind model to use the tools
    
    def exists_action(self, state: AgentState):
        """Check if the LLM model returned any tool calls."""
        result = state['messages'][-1]
        return len(result.tool_calls) > 0

    def call_openai(self, state: AgentState):
        """Call the LLM model with the current state."""
        messages = state['messages']
        if self.system:
            messages = [SystemMessage(content=self.system)] + messages
        message = self.model.invoke(messages)
        return {'messages': [message]}

    def take_action(self, state: AgentState):
        """Execute the action returned by the LLM model."""
        tool_calls = state['messages'][-1].tool_calls
        results = []
        for t in tool_calls:
            print(f"Calling: {t}")
            if not t['name'] in self.tools:      # check for bad tool name from LLM
                print("\n ....bad tool name....")
                result = "bad tool name, retry"  # instruct LLM to retry if bad
            else:
                result = self.tools[t['name']].invoke(t['args']) # tool observation result
            results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result)))
        print("Back to the model!")
        return {'messages': results}
@robsyc commented on GitHub (Aug 23, 2024): I think this question may come back more because the implementation used in the Deeplearning.ai course is now outdated... Is this the correct procedure for setting up an agent class with tools and persistence? Code is based on what's provided in this thread and in the [Deeplearning.ai / LangChain course](https://www.deeplearning.ai/short-courses/ai-agents-in-langgraph/) notebook. ```python class AgentState(TypedDict): """Memory state of the agent.""" messages: Annotated[list[AnyMessage], operator.add] class Agent: """Agent that uses an LLM model and tools to interact with the user.""" def __init__(self, model, tools, system=""): """Initialize agent with LLM model, memory, available tools, and system prompt.""" self.system = system graph = StateGraph(AgentState) # initialize empty state graph graph.add_node("llm", self.call_openai) # add LLM node (executes reasoning w/ call_openai method) graph.add_node("action", self.take_action) # add action node (executes action w/ take_action method) graph.add_conditional_edges( # conditional edge from LLM to action (if action exists) "llm", self.exists_action, {True: "action", False: END} ) graph.add_edge("action", "llm") # edge from action to LLM (adds observation) graph.set_entry_point("llm") # LLM node is used as entry point for the graph with SqliteSaver.from_conn_string(":memory:") as checkpointer: self.graph = graph.compile(checkpointer=checkpointer) self.tools = {t.name: t for t in tools} # dict mapping tool_name to tool itself self.model = model.bind_tools(tools) # bind model to use the tools def exists_action(self, state: AgentState): """Check if the LLM model returned any tool calls.""" result = state['messages'][-1] return len(result.tool_calls) > 0 def call_openai(self, state: AgentState): """Call the LLM model with the current state.""" messages = state['messages'] if self.system: messages = [SystemMessage(content=self.system)] + messages message = self.model.invoke(messages) return {'messages': [message]} def take_action(self, state: AgentState): """Execute the action returned by the LLM model.""" tool_calls = state['messages'][-1].tool_calls results = [] for t in tool_calls: print(f"Calling: {t}") if not t['name'] in self.tools: # check for bad tool name from LLM print("\n ....bad tool name....") result = "bad tool name, retry" # instruct LLM to retry if bad else: result = self.tools[t['name']].invoke(t['args']) # tool observation result results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result))) print("Back to the model!") return {'messages': results} ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#173