diff --git a/.env.example b/.env.example index 4332b87..be0ed2f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ PROVIDER_URL=http://localhost:8000/v1 PROVIDER_API_KEY=your-api-key PROVIDER_MODEL=gpt-4 +COMPOSIO_API_KEY=your-api-key +AGENT_ID=user-12345 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d6d46d1..f039ec1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,4 +9,6 @@ dependencies = [ "uvicorn[standard]>=0.34.0", "deepagents>=0.1.0", "langchain-openai>=1.1.12", + "composio>=0.11.5", + "composio-langchain>=0.11.5" ] diff --git a/src/agent/base.py b/src/agent/base.py index d85775f..012d3e9 100644 --- a/src/agent/base.py +++ b/src/agent/base.py @@ -1,8 +1,9 @@ import os - from deepagents import create_deep_agent from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver +from composio import Composio +from composio_langchain import LangchainProvider from src.agent.backends import IsolatedShellBackend from src.agent.tools import send_file @@ -15,6 +16,11 @@ def create_agent(): api_key=os.environ["PROVIDER_API_KEY"], ) + composio_user_id = os.environ["AGENT_ID"] + composio = Composio(provider=LangchainProvider()) + session = composio.create(user_id=composio_user_id) + tools = session.tools() + workspace_dir = os.environ["WORKSPACE_DIR"] agent_user = os.environ.get("AGENT_USER", "agent") @@ -26,8 +32,8 @@ def create_agent(): return create_deep_agent( model=model, - tools=[send_file], - system_prompt="You are a helpful assistant.", + system_prompt="You are a helpful assistant. Use Composio tools to take action when needed.", checkpointer=MemorySaver(), + tools=tools + [send_file], backend=backend, ) diff --git a/src/agent/service.py b/src/agent/service.py index 90c4663..7e5395c 100644 --- a/src/agent/service.py +++ b/src/agent/service.py @@ -114,8 +114,16 @@ class AgentService: # 2. Инструмент завершил работу и вернул результат elif kind == "on_tool_end": + result = event["data"].get("output") + + """# Перехватываем ссылку на авторизацию Composio v3 + if result and "connect.composio.dev" in str(result): + yield MsgEventTextChunk( + text=f"\n⚠️ Для выполнения действия требуется авторизация. Перейдите по ссылке: {result}\n") + else:""" yield MsgEventToolResult( - tool_name=event["name"], result=event["data"].get("output") + tool_name=event["name"], + result=str(result) # Страховка от ошибки сериализации JSON ) # 3. Кастомные события (send_file и др.) diff --git a/src/api/external.py b/src/api/external.py index d0d9445..3abf844 100644 --- a/src/api/external.py +++ b/src/api/external.py @@ -45,4 +45,3 @@ async def process_message(ws: WebSocket, chat: AgentChat, msg): case MsgUserMessage(): async for chunk in chat.astream(msg.text): await ws.send_text(chunk.model_dump_json()) - await ws.send_text(MsgEventEnd(tokens_used=0).model_dump_json()) diff --git a/uv.lock b/uv.lock index 22446ee..f360545 100644 --- a/uv.lock +++ b/uv.lock @@ -7,6 +7,8 @@ name = "agent" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "composio" }, + { name = "composio-langchain" }, { name = "deepagents" }, { name = "fastapi" }, { name = "langchain-openai" }, @@ -15,6 +17,8 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "composio", specifier = ">=0.11.5" }, + { name = "composio-langchain", specifier = ">=0.11.5" }, { name = "deepagents", specifier = ">=0.1.0" }, { name = "fastapi", specifier = ">=0.135.3" }, { name = "langchain-openai", specifier = ">=1.1.12" }, @@ -183,6 +187,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "composio" +version = "0.11.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "composio-client" }, + { name = "json-schema-to-pydantic" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "pysher" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/9e/d9ac27e293a3c60c823bd4e45fd2552b214d9291a60dade3e4e3f22fa77f/composio-0.11.5.tar.gz", hash = "sha256:524bc722b76af00b5ff4f4a88424f123aa41c77e84b336399464cfa9ad27ed3a", size = 171371, upload-time = "2026-04-10T07:59:42.952Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/cb/afca4cd94abf1c52060a78bca1cc2b1229e47f1b535c5aad5ba65320cc86/composio-0.11.5-py3-none-any.whl", hash = "sha256:5ba13a4d68508d9f93873810f37c71238897ccf3af5d951dad8a5f1bc229030b", size = 117125, upload-time = "2026-04-10T07:59:28.316Z" }, +] + +[[package]] +name = "composio-client" +version = "1.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/11/88d09721f03431deb8b055fe39cedb79697e5a72d4342fa033d277b1eb70/composio_client-1.33.0.tar.gz", hash = "sha256:3b92400383ca2454a361e3ad8d55e2b447b47ed3c4cd4910f61466e5933554c1", size = 225669, upload-time = "2026-04-10T01:35:31.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/bc/83efc07964e39109c4471f8873dda98cd16137c8e4e2d4456a8a5d8f9c42/composio_client-1.33.0-py3-none-any.whl", hash = "sha256:8c01f096772272398760f5c553b3444b5706e346b294856f613b092f1d3afd6b", size = 252699, upload-time = "2026-04-10T01:35:30.003Z" }, +] + +[[package]] +name = "composio-langchain" +version = "0.11.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "composio" }, + { name = "langchain" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/13/fdf3a471044369f89b33c9ba917a9bfc4dafbe6f44547866449e71655b4b/composio_langchain-0.11.5.tar.gz", hash = "sha256:1eccea5eff9255241c1894b30b90b050cde876e5a028ef210e316865efc3181c", size = 2678, upload-time = "2026-04-10T07:59:50.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d4/8cc58a41a45a2a12180e6a7d042206b2f4426e7a052b163b3620cf022d0f/composio_langchain-0.11.5-py3-none-any.whl", hash = "sha256:527539ae3bc41813b860911927d1aa1697ec0e1f6052f9a8311aa98c6e05a6f6", size = 2964, upload-time = "2026-04-10T07:59:36.936Z" }, +] + [[package]] name = "cryptography" version = "46.0.6" @@ -428,6 +479,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, ] +[[package]] +name = "json-schema-to-pydantic" +version = "0.4.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/d8/423895b918706c80db1cee679c13fbe810200b9a9d9a9442c7a58d35c3f2/json_schema_to_pydantic-0.4.11.tar.gz", hash = "sha256:35448ed711a28dd33396b095c8492939b4925aa30eb31942e9b8e08d04279465", size = 56597, upload-time = "2026-03-09T20:53:55.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/64/7cfeb8c6d2a5e73e0f8d732032aa62be9a7724c04beb461d677de0b4beb3/json_schema_to_pydantic-0.4.11-py3-none-any.whl", hash = "sha256:da2ccc39d070ee03dbcf0517d16720e3e33f7aa8d61257ace09af8c51bd46348", size = 17842, upload-time = "2026-03-09T20:53:54.576Z" }, +] + [[package]] name = "jsonpatch" version = "1.33" @@ -757,6 +820,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, ] +[[package]] +name = "pysher" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/a0/d0638470df605ce266991fb04f74c69ab1bed3b90ac3838e9c3c8b69b66a/Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988", size = 9071, upload-time = "2022-10-10T13:41:09.936Z" } + [[package]] name = "python-dotenv" version = "1.2.2" @@ -1069,6 +1142,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, ] +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + [[package]] name = "websockets" version = "16.0"