diff --git a/.dockerignore b/.dockerignore index a8124c6..f83dc7f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,3 @@ __pycache__/ .env /data/ -/subagents/ diff --git a/.env.example b/.env.example index 6ce661a..be0ed2f 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,4 @@ PROVIDER_URL=http://localhost:8000/v1 PROVIDER_API_KEY=your-api-key PROVIDER_MODEL=gpt-4 COMPOSIO_API_KEY=your-api-key -AGENT_ID=my_agent \ No newline at end of file +AGENT_ID=user-12345 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95db2a7..6134988 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ /data/ -/subagents/ .idea/ +workspace/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/Dockerfile b/Dockerfile index 36cc859..e731081 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,6 @@ RUN apt update && apt install make sudo -y ENV AGENT_USER="agent" ENV WORKSPACE_DIR="/workspace/" ENV INTERNAL_DATA_DIR="/internal_data/" -ENV SUBAGENTS_DIR="/subagents/" RUN useradd --shell /bin/bash $AGENT_USER \ && mkdir -p $WORKSPACE_DIR /home/$AGENT_USER \ && chown -R agent:agent $WORKSPACE_DIR /home/$AGENT_USER diff --git a/README.md b/README.md index 3e63a73..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,53 +0,0 @@ -# Lambda Agent Backend - -#### Это headless агент, предназначенный для запуска на удаленном сервере. Используется через [agent_api](https://git.lambda.coredump.ru/platform/agent_api) - -## Запуск -### 1. Agent_api -Для локальной разработки и запуска нужен [модуль с API](https://git.lambda.coredump.ru/platform/agent_api). -Склонируйте его в любую директорию: -```bash -git clone https://git.lambda.coredump.ru/platform/agent_api agent_api -``` - -Далее нужно добавить путь до этой директории в переменную окружения: -```bash -export AGENT_API_PATH=C:/Users/User/agent_api -``` - -### 2. Environment -Заполните файл `.env` по примеру `.env.example`. -- `COMPOSIO_API_KEY` заполняется, если нужно подключить внешние инструменты из сервиса [Composio.dev](https://composio.dev/) - -### 3. Make (опционально) -Чтобы было удобнее работать с проектом, желательно установить утилиту `make`. - -### 4. Запуск -Через **Make**: -```bash -make up-dev -``` - -Без **Make**: -```bash -docker compose --profile dev up -``` - -### 5. Подключение и использование -Подключиться к агенту можно через скрипт [manual.py](https://git.lambda.coredump.ru/platform/agent_api/src/branch/master/tests/manual.py) из репозитория `agent_api`. -В качестве `base_url` необходимо указать `ws://localhost:8000/`. - -Данные агента хранятся в директории `data`: -- `data/internal` - служебная информация: история чатов и т. д. -- `data/workspace` - рабочее пространство агента. Тут он сохраняет все файлы, -сюда же они попадают с поверхностей. - -#### Отправка файлов в агента: -- Необходимо вручную добавить файл в директорию `data/workspace` -- Перед отправкой сообщения через скрипт в `agent_api` запрашиваются вложения: -`Attachments (comma-separated, empty for none): `. Необходимо через запятую перечислить пути до файлов внутри директории `workspace`. -Например, файл `data/workspace/my_dir/file.txt` -> `my_dir/file.txt` - -## Субагенты -В директории `subagents` можно создавать собственных субагентов. -Подробнее в [SUBAGENTS.md](SUBAGENTS.md) \ No newline at end of file diff --git a/SUBAGENTS.md b/SUBAGENTS.md deleted file mode 100644 index b0781dc..0000000 --- a/SUBAGENTS.md +++ /dev/null @@ -1,73 +0,0 @@ -# SubAgents - -## Структура - -``` -subagents/ # В корне репозитория -├── media-agent/ -│ ├── SUBAGENT.md # Метаданные + системный промпт -│ ├── image-gen/ # Skill (формат Deep Agents) -│ │ └── SKILL.md -│ └── meme-maker/ -│ └── SKILL.md -└── researcher/ - ├── SUBAGENT.md - └── web-search/ - └── SKILL.md -``` - -Папка `subagents/` монтируется в `/subagents/` через `docker-compose`. - -## SUBAGENT.md - -```markdown ---- -name: media-agent -description: Генерирует медиа-контент. Использовать для создания картинок, мемов, видео. ---- - -Ты субагент для генерации медиа-контента. -Описание задач, инструментов, формата ответа... -``` - -**Поля:** -- `name` - уникальный ID (используется в `task()`) -- `description` - когда основной агент должен делегировать задачу этому субагенту - -Всё после закрывающего `---` улетает в системный промпт. - -## SKILL.md (навыки) - -```markdown ---- -name: image-gen -description: Генерация изображений по текстовому описанию ---- - -# Image Generation -## When to Use -... -## How to Use -... -``` - -Каждый навык - отдельная папка с файлом `SKILL.md`. Находятся на одном уровне с `SUBAGENT.md`. -В папке навыка могут находиться дополнительные скрипты. - -## Как добавить субагента - -1. Создать папку в `subagents/` с именем субагента -2. Добавить `SUBAGENT.md` с YAML frontmatter и промптом -3. (Опционально) добавить папки со скилами -4. Перезапустить контейнер - -## Инструменты - -Субагенты наследуют все тулзы основного агента (Composio + custom tools). - -## Docker Compose - -```yaml -volumes: - - ./subagents:/subagents -``` diff --git a/docker-compose.yml b/docker-compose.yml index 3bcaf63..fbf4585 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,6 @@ services: - ${AGENT_API_PATH}:/agent_api/ - ./data/workspace:/workspace/ - ./data/internal:/internal_data/ - - ./subagents:/subagents ports: - "8000:8000" env_file: diff --git a/src/agent/base.py b/src/agent/base.py index d7c6aa8..425c6ea 100644 --- a/src/agent/base.py +++ b/src/agent/base.py @@ -1,13 +1,13 @@ import os -from typing import Any from deepagents import create_deep_agent, FilesystemPermission from deepagents.backends import CompositeBackend, FilesystemBackend, StateBackend from langchain_openai import ChatOpenAI +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.subagents import load_subagents from src.agent.tools import send_file, execute_shell from src.agent.checkpointer import get_active_checkpointer from src.core.logger import get_logger @@ -41,28 +41,16 @@ def create_agent() -> Agent: workspace_dir = os.environ["WORKSPACE_DIR"] - routes: dict[str, Any] = { - workspace_dir: FilesystemBackend(workspace_dir, virtual_mode=True), - } - - subagents_dir = os.environ.get("SUBAGENTS_DIR") - if subagents_dir and os.path.isdir(subagents_dir): - routes["/subagents/"] = FilesystemBackend(subagents_dir, virtual_mode=True) - logger.debug(f"Mounted subagents directory at /subagents/: {subagents_dir}") - backend = CompositeBackend( default=StateBackend(), - routes=routes, + routes={ + workspace_dir: FilesystemBackend(workspace_dir, virtual_mode=True), + } ) logger.debug(f"Configured CompositeBackend with workspace: {workspace_dir}") checkpointer = get_active_checkpointer() logger.debug(f"Retrieved checkpointer: {type(checkpointer).__name__}") - - subagents = load_subagents() - subagents_log = f" with {len(subagents)} subagent(s)" if subagents else "" - logger.info(f"Creating agent{subagents_log}") - # noinspection PyTypeChecker # create_deep_agent возвращает CompiledStateGraph, но ниже мы его дополняем так, чтобы он соответствовал сигнатуре Agent agent: Agent = create_deep_agent( @@ -71,18 +59,12 @@ def create_agent() -> Agent: tools=tools + [send_file, execute_shell], backend=backend, checkpointer=checkpointer, - subagents=subagents if subagents else None, permissions=[ FilesystemPermission( operations=["read", "write"], paths=["/workspace/**"], mode="allow", ), - FilesystemPermission( - operations=["read"], - paths=["/subagents/**"], - mode="allow", - ), FilesystemPermission( operations=["read", "write"], paths=["/**"], @@ -101,3 +83,4 @@ def create_agent() -> Agent: logger.exception(f"Error creating agent: {e}") raise return agent + \ No newline at end of file diff --git a/src/agent/subagents.py b/src/agent/subagents.py deleted file mode 100644 index de4b147..0000000 --- a/src/agent/subagents.py +++ /dev/null @@ -1,89 +0,0 @@ -import os -import yaml -from typing import Any - -from src.core.logger import get_logger - -logger = get_logger(__name__) - -SUBAGENT_MD = "SUBAGENT.md" - - -def _parse_subagent_md(file_path: str) -> dict[str, Any] | None: - """Парсит SUBAGENT.md файл, извлекая YAML метаданные и системный промпт.""" - try: - with open(file_path, "r", encoding="utf-8") as f: - content = f.read() - except (OSError, IOError) as e: - logger.error(f"Failed to read {file_path}: {e}") - return None - - if not content.startswith("---"): - logger.warning( - f"Invalid SUBAGENT.md format in {file_path}: missing YAML frontmatter" - ) - return None - - # Находим закрывающий --- - end_marker = content.find("---", 3) - if end_marker == -1: - logger.warning( - f"Invalid SUBAGENT.md format in {file_path}: missing closing ---" - ) - return None - - yaml_content = content[3:end_marker].strip() - system_prompt = content[end_marker + 3 :].strip() - - try: - metadata = yaml.safe_load(yaml_content) or {} - except yaml.YAMLError as e: - logger.error(f"Failed to parse YAML in {file_path}: {e}") - return None - - name = metadata.get("name") - description = metadata.get("description") - - if not name or not description: - logger.warning(f"Missing required fields (name, description) in {file_path}") - return None - - return { - "name": name, - "description": description, - "system_prompt": system_prompt, - "skills": [f"/subagents/{name}/"], - } - - -def load_subagents() -> list[dict[str, Any]]: - """Загружает субагентов из директории SUBAGENTS_DIR.""" - subagents_dir = os.environ.get("SUBAGENTS_DIR") - if not subagents_dir: - logger.debug("SUBAGENTS_DIR not set, skipping subagent loading") - return [] - - if not os.path.isdir(subagents_dir): - logger.warning(f"SUBAGENTS_DIR '{subagents_dir}' is not a valid directory") - return [] - - subagents = [] - for entry in os.listdir(subagents_dir): - subagent_path = os.path.join(subagents_dir, entry) - if not os.path.isdir(subagent_path): - continue - - subagent_md_path = os.path.join(subagent_path, SUBAGENT_MD) - if not os.path.isfile(subagent_md_path): - logger.debug(f"No {SUBAGENT_MD} found in {subagent_path}, skipping") - continue - - subagent_spec = _parse_subagent_md(subagent_md_path) - if subagent_spec: - subagents.append(subagent_spec) - logger.info( - f"Loaded subagent '{subagent_spec['name']}': {subagent_spec['description'][:50]}..." - ) - - logger.info(f"Total subagents loaded: {len(subagents)}") - return subagents