автозагрузка субагентов
This commit is contained in:
parent
684848eda9
commit
40a56c9f11
6 changed files with 116 additions and 7 deletions
|
|
@ -8,3 +8,4 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.env
|
.env
|
||||||
/data/
|
/data/
|
||||||
|
/subagents/
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
/data/
|
/data/
|
||||||
|
/subagents/
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
workspace/
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ RUN apt update && apt install make sudo -y
|
||||||
ENV AGENT_USER="agent"
|
ENV AGENT_USER="agent"
|
||||||
ENV WORKSPACE_DIR="/workspace/"
|
ENV WORKSPACE_DIR="/workspace/"
|
||||||
ENV INTERNAL_DATA_DIR="/internal_data/"
|
ENV INTERNAL_DATA_DIR="/internal_data/"
|
||||||
|
ENV SUBAGENTS_DIR="/subagents/"
|
||||||
RUN useradd --shell /bin/bash $AGENT_USER \
|
RUN useradd --shell /bin/bash $AGENT_USER \
|
||||||
&& mkdir -p $WORKSPACE_DIR /home/$AGENT_USER \
|
&& mkdir -p $WORKSPACE_DIR /home/$AGENT_USER \
|
||||||
&& chown -R agent:agent $WORKSPACE_DIR /home/$AGENT_USER
|
&& chown -R agent:agent $WORKSPACE_DIR /home/$AGENT_USER
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ services:
|
||||||
- ${AGENT_API_PATH}:/agent_api/
|
- ${AGENT_API_PATH}:/agent_api/
|
||||||
- ./data/workspace:/workspace/
|
- ./data/workspace:/workspace/
|
||||||
- ./data/internal:/internal_data/
|
- ./data/internal:/internal_data/
|
||||||
|
- ./subagents:/subagents
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
env_file:
|
env_file:
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import os
|
import os
|
||||||
|
from typing import Any
|
||||||
from deepagents import create_deep_agent, FilesystemPermission
|
from deepagents import create_deep_agent, FilesystemPermission
|
||||||
from deepagents.backends import CompositeBackend, FilesystemBackend, StateBackend
|
from deepagents.backends import CompositeBackend, FilesystemBackend, StateBackend
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
|
||||||
from langgraph.graph.state import CompiledStateGraph
|
from langgraph.graph.state import CompiledStateGraph
|
||||||
from composio import Composio
|
from composio import Composio
|
||||||
from composio_langchain import LangchainProvider
|
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.tools import send_file, execute_shell
|
||||||
from src.agent.checkpointer import get_active_checkpointer
|
from src.agent.checkpointer import get_active_checkpointer
|
||||||
from src.core.logger import get_logger
|
from src.core.logger import get_logger
|
||||||
|
|
@ -41,16 +41,28 @@ def create_agent() -> Agent:
|
||||||
|
|
||||||
workspace_dir = os.environ["WORKSPACE_DIR"]
|
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(
|
backend = CompositeBackend(
|
||||||
default=StateBackend(),
|
default=StateBackend(),
|
||||||
routes={
|
routes=routes,
|
||||||
workspace_dir: FilesystemBackend(workspace_dir, virtual_mode=True),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
logger.debug(f"Configured CompositeBackend with workspace: {workspace_dir}")
|
logger.debug(f"Configured CompositeBackend with workspace: {workspace_dir}")
|
||||||
|
|
||||||
checkpointer = get_active_checkpointer()
|
checkpointer = get_active_checkpointer()
|
||||||
logger.debug(f"Retrieved checkpointer: {type(checkpointer).__name__}")
|
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
|
# noinspection PyTypeChecker
|
||||||
# create_deep_agent возвращает CompiledStateGraph, но ниже мы его дополняем так, чтобы он соответствовал сигнатуре Agent
|
# create_deep_agent возвращает CompiledStateGraph, но ниже мы его дополняем так, чтобы он соответствовал сигнатуре Agent
|
||||||
agent: Agent = create_deep_agent(
|
agent: Agent = create_deep_agent(
|
||||||
|
|
@ -59,12 +71,18 @@ def create_agent() -> Agent:
|
||||||
tools=tools + [send_file, execute_shell],
|
tools=tools + [send_file, execute_shell],
|
||||||
backend=backend,
|
backend=backend,
|
||||||
checkpointer=checkpointer,
|
checkpointer=checkpointer,
|
||||||
|
subagents=subagents if subagents else None,
|
||||||
permissions=[
|
permissions=[
|
||||||
FilesystemPermission(
|
FilesystemPermission(
|
||||||
operations=["read", "write"],
|
operations=["read", "write"],
|
||||||
paths=["/workspace/**"],
|
paths=["/workspace/**"],
|
||||||
mode="allow",
|
mode="allow",
|
||||||
),
|
),
|
||||||
|
FilesystemPermission(
|
||||||
|
operations=["read"],
|
||||||
|
paths=["/subagents/**"],
|
||||||
|
mode="allow",
|
||||||
|
),
|
||||||
FilesystemPermission(
|
FilesystemPermission(
|
||||||
operations=["read", "write"],
|
operations=["read", "write"],
|
||||||
paths=["/**"],
|
paths=["/**"],
|
||||||
|
|
@ -83,4 +101,3 @@ def create_agent() -> Agent:
|
||||||
logger.exception(f"Error creating agent: {e}")
|
logger.exception(f"Error creating agent: {e}")
|
||||||
raise
|
raise
|
||||||
return agent
|
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