diff --git a/Dockerfile b/Dockerfile index e28cf48..8368384 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,13 @@ RUN apt update && apt install make sudo -y ENV AGENT_USER="agent" ENV WORKSPACE_DIR="/workspace/" +ENV INTERNAL_DATA_DIR="/internal_data/" RUN useradd --shell /bin/bash $AGENT_USER \ && mkdir -p $WORKSPACE_DIR /home/$AGENT_USER \ && chown -R agent:agent $WORKSPACE_DIR /home/$AGENT_USER +RUN mkdir -p $INTERNAL_DATA_DIR \ + && chown -R root:root $INTERNAL_DATA_DIR \ + && chmod o-rwx $INTERNAL_DATA_DIR FROM base AS builder diff --git a/docker-compose.yml b/docker-compose.yml index d533db9..7447f94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - ./src:/app/src - ${AGENT_API_PATH}:/agent_api/ - ./data/workspace:/workspace/ + - ./data/internal:/internal_data/ ports: - "8000:8000" env_file: diff --git a/pyproject.toml b/pyproject.toml index 70c241c..ec1686f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,4 +11,6 @@ dependencies = [ "langchain-openai>=1.1.12", "composio>=0.11.5", "composio-langchain>=0.11.5", + "langgraph-checkpoint-sqlite>=3.0.3", + "aiosqlite>=0.22.1", ] diff --git a/src/agent/base.py b/src/agent/base.py index 09051b5..b5665a1 100644 --- a/src/agent/base.py +++ b/src/agent/base.py @@ -6,8 +6,10 @@ from langgraph.checkpoint.memory import MemorySaver from langgraph.graph.state import CompiledStateGraph from composio import Composio from composio_langchain import LangchainProvider +from langgraph.checkpoint.sqlite import SqliteSaver from src.agent.tools import send_file, execute_shell +from src.agent.checkpointer import get_active_checkpointer class Agent(CompiledStateGraph): @@ -38,14 +40,16 @@ def create_agent() -> Agent: } ) + checkpointer = get_active_checkpointer() + # noinspection PyTypeChecker # create_deep_agent возвращает CompiledStateGraph, но ниже мы его дополняем так, чтобы он соответствовал сигнатуре Agent agent: Agent = create_deep_agent( model=model, system_prompt="You are a helpful assistant. Use Composio tools to take action when needed.", - checkpointer=MemorySaver(), tools=tools + [send_file, execute_shell], backend=backend, + checkpointer=checkpointer, permissions=[ FilesystemPermission( operations=["read", "write"], diff --git a/src/agent/checkpointer.py b/src/agent/checkpointer.py new file mode 100644 index 0000000..474b3bc --- /dev/null +++ b/src/agent/checkpointer.py @@ -0,0 +1,27 @@ +from contextlib import asynccontextmanager +import os +from typing import AsyncIterable +from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver +from pathlib import Path + + +_instance: AsyncSqliteSaver | None = None + + +def get_active_checkpointer() -> AsyncSqliteSaver: + if not _instance: + raise RuntimeError("Checkpointer not initialized") + return _instance + + +@asynccontextmanager +async def create_checkpointer() -> AsyncIterable[AsyncSqliteSaver]: + global _instance + + internal_data_dir = os.environ["INTERNAL_DATA_DIR"] + filepath = Path(internal_data_dir) / "checkpoint.sqlite" + async with AsyncSqliteSaver.from_conn_string(filepath) as saver: + _instance = saver + yield saver + _instance = None + diff --git a/src/main.py b/src/main.py index 9a5e83c..299a1bd 100644 --- a/src/main.py +++ b/src/main.py @@ -4,12 +4,14 @@ from fastapi import FastAPI from src.api.external import router as ws_router from src.agent import AgentService +from src.agent.checkpointer import create_checkpointer @asynccontextmanager async def lifespan(app: FastAPI): - AgentService() # инициализируем синглтон - yield + async with create_checkpointer(): + AgentService() # инициализируем синглтон + yield app = FastAPI(lifespan=lifespan) diff --git a/uv.lock b/uv.lock index cbf3be0..d91015c 100644 --- a/uv.lock +++ b/uv.lock @@ -7,24 +7,37 @@ name = "agent" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "aiosqlite" }, { name = "composio" }, { name = "composio-langchain" }, { name = "deepagents" }, { name = "fastapi" }, { name = "langchain-openai" }, + { name = "langgraph-checkpoint-sqlite" }, { name = "uvicorn", extra = ["standard"] }, ] [package.metadata] requires-dist = [ + { name = "aiosqlite", specifier = ">=0.22.1" }, { name = "composio", specifier = ">=0.11.5" }, { name = "composio-langchain", specifier = ">=0.11.5" }, { name = "deepagents", specifier = ">=0.5.0" }, { name = "fastapi", specifier = ">=0.135.3" }, { name = "langchain-openai", specifier = ">=1.1.12" }, + { name = "langgraph-checkpoint-sqlite", specifier = ">=3.0.3" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" }, ] +[[package]] +name = "aiosqlite" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -632,6 +645,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" }, ] +[[package]] +name = "langgraph-checkpoint-sqlite" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiosqlite" }, + { name = "langgraph-checkpoint" }, + { name = "sqlite-vec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/61/40b7f8f29d6de92406e668c35265f409f57064907e31eae84ab3f2a3e3e1/langgraph_checkpoint_sqlite-3.0.3.tar.gz", hash = "sha256:438c234d37dabda979218954c9c6eb1db73bee6492c2f1d3a00552fe23fa34ed", size = 123876, upload-time = "2026-01-19T00:38:44.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/d8/84ef22ee1cc485c4910df450108fd5e246497379522b3c6cfba896f71bf6/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl", hash = "sha256:02eb683a79aa6fcda7cd4de43861062a5d160dbbb990ef8a9fd76c979998a952", size = 33593, upload-time = "2026-01-19T00:38:43.288Z" }, +] + [[package]] name = "langgraph-prebuilt" version = "1.0.11" @@ -955,6 +982,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sqlite-vec" +version = "0.1.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/85/9fad0045d8e7c8df3e0fa5a56c630e8e15ad6e5ca2e6106fceb666aa6638/sqlite_vec-0.1.9-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:1b62a7f0a060d9475575d4e599bbf94a13d85af896bc1ce86ee80d1b5b48e5fb", size = 131171, upload-time = "2026-03-31T08:02:31.717Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3d/3677e0cd2f92e5ebc43cd29fbf565b75582bff1ccfa0b8327c7508e1084f/sqlite_vec-0.1.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d52e30513bae4cc9778ddbf6145610434081be4c3afe57cd877893bad9f6b6c", size = 165434, upload-time = "2026-03-31T08:02:32.712Z" }, + { url = "https://files.pythonhosted.org/packages/00/d4/f2b936d3bdc38eadcbd2a87875815db36430fab0363182ba5d12cd8e0b51/sqlite_vec-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e921e592f24a5f9a18f590b6ddd530eb637e2d474e3b1972f9bbeb773aa3cb9", size = 160076, upload-time = "2026-03-31T08:02:33.796Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ad/6afd073b0f817b3e03f9e37ad626ae341805891f23c74b5292818f49ac63/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:1515727990b49e79bcaf75fdee2ffc7d461f8b66905013231251f1c8938e7786", size = 163388, upload-time = "2026-03-31T08:02:34.888Z" }, + { url = "https://files.pythonhosted.org/packages/42/89/81b2907cda14e566b9bf215e2ad82fc9b349edf07d2010756ffdb902f328/sqlite_vec-0.1.9-py3-none-win_amd64.whl", hash = "sha256:4a28dc12fa4b53d7b1dced22da2488fade444e96b5d16fd2d698cd670675cf32", size = 292804, upload-time = "2026-03-31T08:02:36.035Z" }, +] + [[package]] name = "starlette" version = "1.0.0"