автозагрузка субагентов
This commit is contained in:
parent
684848eda9
commit
40a56c9f11
6 changed files with 116 additions and 7 deletions
|
|
@ -8,3 +8,4 @@
|
|||
__pycache__/
|
||||
.env
|
||||
/data/
|
||||
/subagents/
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,7 +1,7 @@
|
|||
/data/
|
||||
/subagents/
|
||||
|
||||
.idea/
|
||||
workspace/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ 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,16 +41,28 @@ 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={
|
||||
workspace_dir: FilesystemBackend(workspace_dir, virtual_mode=True),
|
||||
}
|
||||
routes=routes,
|
||||
)
|
||||
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(
|
||||
|
|
@ -59,12 +71,18 @@ 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=["/**"],
|
||||
|
|
@ -83,4 +101,3 @@ def create_agent() -> Agent:
|
|||
logger.exception(f"Error creating agent: {e}")
|
||||
raise
|
||||
return agent
|
||||
|
||||
89
src/agent/subagents.py
Normal file
89
src/agent/subagents.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
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