Compare commits
No commits in common. "#22-local-subagents-and-skills" and "main" have entirely different histories.
#22-local-
...
main
9 changed files with 8 additions and 243 deletions
|
|
@ -8,4 +8,3 @@
|
|||
__pycache__/
|
||||
.env
|
||||
/data/
|
||||
/subagents/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
AGENT_ID=user-12345
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,7 +1,7 @@
|
|||
/data/
|
||||
/subagents/
|
||||
|
||||
.idea/
|
||||
workspace/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
53
README.md
53
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)
|
||||
73
SUBAGENTS.md
73
SUBAGENTS.md
|
|
@ -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
|
||||
```
|
||||
|
|
@ -23,7 +23,6 @@ services:
|
|||
- ${AGENT_API_PATH}:/agent_api/
|
||||
- ./data/workspace:/workspace/
|
||||
- ./data/internal:/internal_data/
|
||||
- ./subagents:/subagents
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue