diff --git a/.dockerignore b/.dockerignore index 2197ab1..f83dc7f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,4 +7,4 @@ *.pyc __pycache__/ .env -workspace/ +/data/ diff --git a/.gitignore b/.gitignore index 7e2ecb5..6134988 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -workspace/ +/data/ .idea/ workspace/ diff --git a/docker-compose.yml b/docker-compose.yml index d639315..d533db9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: volumes: - ./src:/app/src - ${AGENT_API_PATH}:/agent_api/ - - ./workspace:/workspace/ + - ./data/workspace:/workspace/ ports: - "8000:8000" env_file: diff --git a/pyproject.toml b/pyproject.toml index f039ec1..70c241c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,8 @@ requires-python = ">=3.14" dependencies = [ "fastapi>=0.135.3", "uvicorn[standard]>=0.34.0", - "deepagents>=0.1.0", + "deepagents>=0.5.0", "langchain-openai>=1.1.12", "composio>=0.11.5", - "composio-langchain>=0.11.5" + "composio-langchain>=0.11.5", ] diff --git a/src/agent/backends/isolated_shell.py b/src/agent/backends/isolated_shell.py index 468fb1b..11eb43f 100644 --- a/src/agent/backends/isolated_shell.py +++ b/src/agent/backends/isolated_shell.py @@ -2,23 +2,49 @@ import os import pwd import subprocess from typing import Any - -from deepagents.backends.local_shell import LocalShellBackend +import uuid +from pathlib import Path +from deepagents.backends.local_shell import DEFAULT_EXECUTE_TIMEOUT +from deepagents.backends.protocol import SandboxBackendProtocol -class IsolatedShellBackend(LocalShellBackend): +class IsolatedShellBackend(SandboxBackendProtocol): """LocalShellBackend с изоляцией shell-команд через отдельного пользователя.""" def __init__( self, user: str, - **kwargs: Any, + root_dir: str, + timeout: int = DEFAULT_EXECUTE_TIMEOUT, + max_output_bytes: int = 100_000, + env: dict[str, str] | None = None, + inherit_env: bool = False, ): - super().__init__(**kwargs) + self._user = user self._uid = pwd.getpwnam(user).pw_uid # type: ignore[attr-defined] self._gid = pwd.getpwnam(user).pw_gid # type: ignore[attr-defined] + if timeout <= 0: + msg = f"timeout must be positive, got {timeout}" + raise ValueError(msg) + + # Store execution parameters + self._default_timeout = timeout + self._max_output_bytes = max_output_bytes + self.cwd = Path(root_dir).resolve() if root_dir else Path.cwd() + + # Build environment based on inherit_env setting + if inherit_env: + self._env = os.environ.copy() + if env is not None: + self._env.update(env) + else: + self._env = env if env is not None else {} + + # Generate unique sandbox ID + self._sandbox_id = f"local-{uuid.uuid4().hex[:8]}" + def execute( self, command: str, diff --git a/src/agent/base.py b/src/agent/base.py index 740b5ff..f51790a 100644 --- a/src/agent/base.py +++ b/src/agent/base.py @@ -1,5 +1,6 @@ import os -from deepagents import create_deep_agent +from deepagents import create_deep_agent, FilesystemPermission +from deepagents.backends import CompositeBackend, FilesystemBackend from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from langgraph.graph.state import CompiledStateGraph @@ -14,7 +15,7 @@ class Agent(CompiledStateGraph): """ Временный (надеюсь) костыль, чтобы дать доступ сервису к файловой системе агента. """ - backend: IsolatedShellBackend + backend: CompositeBackend def create_agent() -> Agent: @@ -32,10 +33,10 @@ def create_agent() -> Agent: workspace_dir = os.environ["WORKSPACE_DIR"] agent_user = os.environ.get("AGENT_USER", "agent") - backend = IsolatedShellBackend( - user=agent_user, - root_dir=workspace_dir, - virtual_mode=True, + backend = CompositeBackend( + routes={ + workspace_dir: FilesystemBackend(workspace_dir, virtual_mode=True), + } ) # noinspection PyTypeChecker @@ -46,6 +47,18 @@ def create_agent() -> Agent: checkpointer=MemorySaver(), tools=tools + [send_file], backend=backend, + permissions=[ + FilesystemPermission( + operations=["read", "write"], + paths=["/workspace/**"], + mode="allow", + ), + FilesystemPermission( + operations=["read", "write"], + paths=["/**"], + mode="deny" + ) + ] ) agent.backend = backend diff --git a/uv.lock b/uv.lock index f360545..cbf3be0 100644 --- a/uv.lock +++ b/uv.lock @@ -19,7 +19,7 @@ dependencies = [ requires-dist = [ { name = "composio", specifier = ">=0.11.5" }, { name = "composio-langchain", specifier = ">=0.11.5" }, - { name = "deepagents", specifier = ">=0.1.0" }, + { name = "deepagents", specifier = ">=0.5.0" }, { name = "fastapi", specifier = ">=0.135.3" }, { name = "langchain-openai", specifier = ">=1.1.12" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" }, @@ -289,18 +289,19 @@ wheels = [ [[package]] name = "deepagents" -version = "0.4.12" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain" }, { name = "langchain-anthropic" }, { name = "langchain-core" }, { name = "langchain-google-genai" }, + { name = "langsmith" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/0b2ccd5e4f40c1554145de17a7f3ee41994de1dda3ea36abe28600f1a3cf/deepagents-0.4.12.tar.gz", hash = "sha256:fc24a691e5cba00920ac4fa1d94f8147d6081fe513ed22bdba7da469288681c3", size = 91870, upload-time = "2026-03-20T14:54:29.904Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/c5/fbf36ff707f7ad4ff2d1590ba8c3b44622370955c74f0c84e4bb1b101d7d/deepagents-0.5.3.tar.gz", hash = "sha256:cbe63bd482c37d3aef883326f5dde70effcd489e67fc91834ffe7a8769796a5f", size = 122654, upload-time = "2026-04-15T13:06:34.875Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/77/63a5cb4e3a8871c4a52d600661a232c26b35f52931ee551c3adc38eeacf6/deepagents-0.4.12-py3-none-any.whl", hash = "sha256:76a272bac25607c5ef8c5adc876e391da945f1107b504686964dfdb6afdc1ebb", size = 104455, upload-time = "2026-03-20T14:54:28.786Z" }, + { url = "https://files.pythonhosted.org/packages/30/8f/40c91a29e4f094e5a1375e33309a3d0ca2e5204816d1dcdcd41ac38410d8/deepagents-0.5.3-py3-none-any.whl", hash = "sha256:f1f1c968f17a5bfb0a6d588d00c2e83264cdac0faa91f7b522892d5a2bd303cb", size = 138475, upload-time = "2026-04-15T13:06:33.593Z" }, ] [[package]] @@ -514,16 +515,16 @@ wheels = [ [[package]] name = "langchain" -version = "1.2.14" +version = "1.2.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/2b/0ca77ee988a9f1c1f1d923115d7c91221ab434067bc36f2f637201aeee81/langchain-1.2.14.tar.gz", hash = "sha256:fc5511e8f8af7efee9e5a144da4392d700d627b301d240470db97272940ad317", size = 574190, upload-time = "2026-03-31T13:50:37.398Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/3f/888a7099d2bd2917f8b0c3ffc7e347f1e664cf64267820b0b923c4f339fc/langchain-1.2.15.tar.gz", hash = "sha256:1717b6719daefae90b2728314a5e2a117ff916291e2862595b6c3d6fba33d652", size = 574732, upload-time = "2026-04-03T14:26:03.994Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/87/324ae5fd9993f024339a452fc89e3fd808bccde87ef95c8dafab3de023c0/langchain-1.2.14-py3-none-any.whl", hash = "sha256:96da6d7338d5a6fc41eb4ec0db83f7ef5d03bb5efd17bb269f34ba4378ebdb4d", size = 112715, upload-time = "2026-03-31T13:50:35.997Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e8/a3b8cb0005553f6a876865073c81ef93bd7c5b18381bcb9ba4013af96ebc/langchain-1.2.15-py3-none-any.whl", hash = "sha256:e349db349cb3e9550c4044077cf90a1717691756cc236438404b23500e615874", size = 112714, upload-time = "2026-04-03T14:26:02.557Z" }, ] [[package]] @@ -542,10 +543,11 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.25" +version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, + { name = "langchain-protocol" }, { name = "langsmith" }, { name = "packaging" }, { name = "pydantic" }, @@ -554,9 +556,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/2a/d65de24fc9b7989137253da8973f850f3e39b4ce3e0377bc8200d6b3c189/langchain_core-1.2.25.tar.gz", hash = "sha256:77e032b96509d0eb1f6875042fdf97b7e2334a815314700c6894d9d078909b9c", size = 842347, upload-time = "2026-04-02T22:39:11.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/03/7219502e8ca728d65eb44d7a3eb60239230742a70dbfc9241b9bfd61c4ab/langchain_core-1.3.2.tar.gz", hash = "sha256:fd7a50b2f28ba561fd9d7f5d2760bc9e06cf00cdf820a3ccafe88a94ffa8d5b7", size = 911813, upload-time = "2026-04-24T15:49:23.699Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/0e/7b31b0249f9b9b0fc7829d5b0ee484b8f8d43c78e376e9951e2ef3eac70c/langchain_core-1.2.25-py3-none-any.whl", hash = "sha256:0c05bf395aec6d2dfa14488fd006f7bcd0540e7e89287e04f92203532a82c828", size = 506866, upload-time = "2026-04-02T22:39:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d5/8fa4431007cbb7cfed7590f4d6a5dea3ad724f4174d248f6642ef5ce7d05/langchain_core-1.3.2-py3-none-any.whl", hash = "sha256:d44a66127f9f8db735bdfd0ab9661bccb47a97113cfd3f2d89c74864422b7274", size = 542390, upload-time = "2026-04-24T15:49:21.991Z" }, ] [[package]] @@ -588,9 +590,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/a6/68fb22e3604015e6f546fa1d3677d24378b482855ae74710cbf4aec44132/langchain_openai-1.1.12-py3-none-any.whl", hash = "sha256:da71ca3f2d18c16f7a2443cc306aa195ad2a07054335ac9b0626dcae02b6a0c5", size = 88487, upload-time = "2026-03-23T18:59:17.978Z" }, ] +[[package]] +name = "langchain-protocol" +version = "0.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/51/1157009b6f94e6e58be58fa8b620187d657909a8b36a6bf5b0c52a2711f6/langchain_protocol-0.0.12.tar.gz", hash = "sha256:5e14c434290a705c9510fdb1a83ecf7561a5e6e0dfd053930ade80dba069269f", size = 6408, upload-time = "2026-04-25T01:05:01.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/82/3431e3061c917439589fa88a6b23c9bc0e154cba0f05d2e895a68c76ff74/langchain_protocol-0.0.12-py3-none-any.whl", hash = "sha256:402b61f42d4139692528cf37226c367bb6efc8ff8165b29380accb0abfece7b2", size = 6639, upload-time = "2026-04-25T01:05:00.487Z" }, +] + [[package]] name = "langgraph" -version = "1.1.4" +version = "1.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -600,9 +614,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/ba/8a8f48ca1248ecff4844cb27247d10a85f05b4ac6b903298d36b2ca090fd/langgraph-1.1.4.tar.gz", hash = "sha256:c951a859f68a021c69a27500db4eafc1900fc7ac32a54f7fc31d277165d04bed", size = 545440, upload-time = "2026-03-31T12:56:45.344Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/9d9c65d5500a1ca7ea63d6d65aecfb248037018a74d7d4ef52e276bb4e4b/langgraph-1.1.9.tar.gz", hash = "sha256:bc5a49d5a5e71fda1f9c53c06c62f4caec9a95545b739d130a58b6ab3269e274", size = 560717, upload-time = "2026-04-21T13:43:06.809Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/74/22ea4734247b59e7c98e575e31a1f463366b084e0dc83cf63715b079ff28/langgraph-1.1.4-py3-none-any.whl", hash = "sha256:77ebe7ed44a2699f13696bf41f1dabe7b5fa8e6ad51e3597f2f175492e8f3656", size = 168190, upload-time = "2026-03-31T12:56:44.221Z" }, + { url = "https://files.pythonhosted.org/packages/16/58/0380420e66619d12c992c1f8cfda0c7a04e8f0fe8a84752245b9e7b1cba7/langgraph-1.1.9-py3-none-any.whl", hash = "sha256:7db13ceecde4ea643df6c097dcc9e534895dcd9fcc6500eeff2f2cde0fab16b2", size = 173744, upload-time = "2026-04-21T13:43:05.513Z" }, ] [[package]] @@ -620,15 +634,15 @@ wheels = [ [[package]] name = "langgraph-prebuilt" -version = "1.0.8" +version = "1.0.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/bb/0e0b3eb33b1f2f32f8810a49aa24b7d11a5b0ed45f679386095946a59557/langgraph_prebuilt-1.0.11.tar.gz", hash = "sha256:0e71545f706a134b6a80a2a56916562797b499e3e4ab6eed5ce89396ac03d322", size = 171759, upload-time = "2026-04-24T18:18:34.528Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/f4c574cb75ae9b8a474215d03a029ea723c919f65771ca1c82fe532d0297/langgraph_prebuilt-1.0.11-py3-none-any.whl", hash = "sha256:7afbaf5d64959e452976664c75bb8ec24098d3510cf9c205919baf443e7342ec", size = 36832, upload-time = "2026-04-24T18:18:33.586Z" }, ] [[package]]