diff --git a/.gitignore b/.gitignore index 7fc32c5..0c2c105 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,5 @@ generated/ *.db *.wav mlartifacts +*.sqlite +*.bin diff --git a/docs/references.md b/docs/references.md index 565558f..680366e 100644 --- a/docs/references.md +++ b/docs/references.md @@ -17,6 +17,10 @@ - [Custom UI for Deep Agents](https://github.com/langchain-ai/deep-agents-ui) - [How to deploy self-hosted standalone server](https://docs.langchain.com/langgraph-platform/deploy-standalone-server) - [「現場で活用するための AI エージェント実践入門」リポジトリ](https://github.com/masamasa59/genai-agent-advanced-book) +- [Add and manage memory](https://docs.langchain.com/oss/python/langgraph/add-memory) +- [Persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/) +- [Chatbot with message summarization & external DB memory](https://github.com/langchain-ai/langchain-academy/blob/main/module-2/chatbot-external-memory.ipynb) +- [LangGraph の会話履歴を SQLite に保持しよう](https://www.creationline.com/tech-blog/chatgpt-ai/75797) ### LangChain diff --git a/pyproject.toml b/pyproject.toml index 9ebb2b8..1b963d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "langchain-text-splitters>=0.3.9", "langfuse>=3.6.2", "langgraph>=0.6.2", + "langgraph-checkpoint-sqlite>=2.0.11", "langgraph-supervisor>=0.0.29", "mlflow>=3.4.0", "openai-whisper>=20250625", diff --git a/template_langgraph/agents/chat_with_tools_agent/agent.py b/template_langgraph/agents/chat_with_tools_agent/agent.py index 7d88c14..85d77db 100644 --- a/template_langgraph/agents/chat_with_tools_agent/agent.py +++ b/template_langgraph/agents/chat_with_tools_agent/agent.py @@ -50,9 +50,16 @@ def __call__(self, inputs: dict): class ChatWithToolsAgent: - def __init__(self, tools=get_default_tools()): + def __init__( + self, + tools=get_default_tools(), + checkpointer=None, + store=None, + ): self.llm = AzureOpenAiWrapper().chat_model self.tools = tools + self.checkpointer = checkpointer + self.store = store def create_graph(self): """Create the main graph for the agent.""" @@ -83,6 +90,8 @@ def create_graph(self): # Compile the graph return workflow.compile( name=ChatWithToolsAgent.__name__, + checkpointer=self.checkpointer, + store=self.store, ) def chat_with_tools(self, state: AgentState) -> AgentState: diff --git a/template_langgraph/services/streamlits/pages/chat_with_tools_agent.py b/template_langgraph/services/streamlits/pages/chat_with_tools_agent.py index 98c52b8..56d5100 100644 --- a/template_langgraph/services/streamlits/pages/chat_with_tools_agent.py +++ b/template_langgraph/services/streamlits/pages/chat_with_tools_agent.py @@ -1,5 +1,7 @@ import os +import sqlite3 import tempfile +import uuid from base64 import b64encode from dataclasses import dataclass @@ -9,6 +11,8 @@ StreamlitCallbackHandler, ) from langfuse.langchain import CallbackHandler +from langgraph.checkpoint.sqlite import SqliteSaver +from langgraph.store.sqlite import SqliteStore from template_langgraph.agents.chat_with_tools_agent.agent import ( AgentState, @@ -18,6 +22,10 @@ from template_langgraph.speeches.tts import TtsWrapper from template_langgraph.tools.common import get_default_tools +checkpoints_conn = sqlite3.connect("checkpoints.sqlite", check_same_thread=False) +store_conn = sqlite3.connect("store.sqlite", check_same_thread=False) +thread_id = str(uuid.uuid4()) + def image_to_base64(image_bytes: bytes) -> str: return b64encode(image_bytes).decode("utf-8") @@ -68,7 +76,15 @@ def ensure_agent_graph(selected_tools: list) -> None: signature = tuple(tool.name for tool in selected_tools) graph_signature = st.session_state.get("graph_tools_signature") if "graph" not in st.session_state or graph_signature != signature: - st.session_state["graph"] = ChatWithToolsAgent(tools=selected_tools).create_graph() + st.session_state["graph"] = ChatWithToolsAgent( + tools=selected_tools, + checkpointer=SqliteSaver( + conn=checkpoints_conn, + ), + store=SqliteStore( + conn=store_conn, + ), + ).create_graph() st.session_state["graph_tools_signature"] = signature @@ -296,12 +312,18 @@ def build_graph_messages() -> list: def invoke_agent(graph_messages: list) -> AgentState: return st.session_state["graph"].invoke( - {"messages": graph_messages}, + { + "messages": graph_messages, + }, { "callbacks": [ StreamlitCallbackHandler(st.container()), CallbackHandler(), - ] + ], + "configurable": { + "thread_id": thread_id, + "user_id": "user_1", + }, }, ) diff --git a/uv.lock b/uv.lock index 3799b5e..2a2e167 100644 --- a/uv.lock +++ b/uv.lock @@ -95,6 +95,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + [[package]] name = "alembic" version = "1.16.5" @@ -2530,6 +2542,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/f2/06bf5addf8ee664291e1b9ffa1f28fc9d97e59806dc7de5aea9844cbf335/langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60", size = 45763, upload-time = "2025-10-07T17:45:16.19Z" }, ] +[[package]] +name = "langgraph-checkpoint-sqlite" +version = "2.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiosqlite" }, + { name = "langgraph-checkpoint" }, + { name = "sqlite-vec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/aa/5f9e9de74a6d0a9b77c703db0068d0f0cdc8dbc2e9b292ae95f4de115a44/langgraph_checkpoint_sqlite-2.0.11.tar.gz", hash = "sha256:e9337204c27b01a29edff65c1ecb7da0ca8ac7f1bd66b405617459043ac6c3ed", size = 109749, upload-time = "2025-07-25T17:32:07.773Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/d4/c56f6b0e8c8211791c9954bef0edaef3dc2e118cf33800be44c7b90432bd/langgraph_checkpoint_sqlite-2.0.11-py3-none-any.whl", hash = "sha256:11c40d93225ce99fa2800332c97b16280addf9f15274def32c4d547955290d3f", size = 31191, upload-time = "2025-07-25T17:32:06.355Z" }, +] + [[package]] name = "langgraph-cli" version = "0.4.3" @@ -5148,6 +5174,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, ] +[[package]] +name = "sqlite-vec" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ed/aabc328f29ee6814033d008ec43e44f2c595447d9cccd5f2aabe60df2933/sqlite_vec-0.1.6-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:77491bcaa6d496f2acb5cc0d0ff0b8964434f141523c121e313f9a7d8088dee3", size = 164075, upload-time = "2024-11-20T16:40:29.847Z" }, + { url = "https://files.pythonhosted.org/packages/a7/57/05604e509a129b22e303758bfa062c19afb020557d5e19b008c64016704e/sqlite_vec-0.1.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fdca35f7ee3243668a055255d4dee4dea7eed5a06da8cad409f89facf4595361", size = 165242, upload-time = "2024-11-20T16:40:31.206Z" }, + { url = "https://files.pythonhosted.org/packages/f2/48/dbb2cc4e5bad88c89c7bb296e2d0a8df58aab9edc75853728c361eefc24f/sqlite_vec-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0519d9cd96164cd2e08e8eed225197f9cd2f0be82cb04567692a0a4be02da3", size = 103704, upload-time = "2024-11-20T16:40:33.729Z" }, + { url = "https://files.pythonhosted.org/packages/80/76/97f33b1a2446f6ae55e59b33869bed4eafaf59b7f4c662c8d9491b6a714a/sqlite_vec-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:823b0493add80d7fe82ab0fe25df7c0703f4752941aee1c7b2b02cec9656cb24", size = 151556, upload-time = "2024-11-20T16:40:35.387Z" }, + { url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540, upload-time = "2024-11-20T16:40:37.296Z" }, +] + [[package]] name = "sqlparse" version = "0.5.3" @@ -5274,6 +5312,7 @@ dependencies = [ { name = "langchain-text-splitters" }, { name = "langfuse" }, { name = "langgraph" }, + { name = "langgraph-checkpoint-sqlite" }, { name = "langgraph-supervisor" }, { name = "mlflow" }, { name = "openai", extra = ["realtime"] }, @@ -5334,6 +5373,7 @@ requires-dist = [ { name = "langchain-text-splitters", specifier = ">=0.3.9" }, { name = "langfuse", specifier = ">=3.6.2" }, { name = "langgraph", specifier = ">=0.6.2" }, + { name = "langgraph-checkpoint-sqlite", specifier = ">=2.0.11" }, { name = "langgraph-supervisor", specifier = ">=0.0.29" }, { name = "mlflow", specifier = ">=3.4.0" }, { name = "openai", extras = ["realtime"], specifier = ">=1.98.0" },