mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
## Summary Port the agent generation pipeline from the external AgentGenerator service into local copilot tools, making the Claude Agent SDK itself handle validation, fixing, and block recommendation — no separate inner LLM calls needed. Key capabilities: - **Local agent generation**: Create, edit, and customize agents entirely within the SDK session - **Graph validation**: 9 validation checks (block existence, link references, type compatibility, IO blocks, etc.) - **Graph fixing**: 17+ auto-fix methods (ID repair, link rewiring, type conversion, credential stripping, dynamic block sink names, etc.) - **MCP tool blocks**: Guide and fixer support for MCPToolBlock nodes with proper dynamic input schema handling - **Sub-agent composition**: AgentExecutorBlock support with library agent schema enrichment - **Embedding fallback**: Falls back to OpenRouter for embeddings when `openai_internal_api_key` is unavailable - **Actionable error messages**: Excluded block types (MCP, Agent) return specific hints redirecting to the correct tool ### New Tools - `validate_agent_graph` — run 9 validation checks on agent JSON - `fix_agent_graph` — apply 17+ auto-fixes to agent JSON - `get_blocks_for_goal` — recommend blocks for a given goal (with optimized descriptions) ### Refactored Tools - `create_agent`, `edit_agent`, `customize_agent` — accept `agent_json` for local generation with shared fix→validate→save pipeline - `find_block` — added `include_schemas` parameter, excludes MCP/Agent blocks with actionable hints - `run_block` — actionable error messages for excluded block types - `find_library_agent` — enriched with `graph_version`, `input_schema`, `output_schema` for sub-agent composition ### Architecture - Split 2,558-line `validation.py` into `fixer.py`, `validator.py`, `helpers.py`, `pipeline.py` - Extracted shared `fix_validate_and_save()` pipeline (was duplicated across 3 tools) - Shared `OPENROUTER_BASE_URL` constant across codebase - Comprehensive test coverage: 78+ unit tests for fixer/validator, 8 run_block tests, 17 SDK compat tests ## Test plan - [x] `poetry run format` passes - [x] `poetry run pytest -s -vvv backend/copilot/` — all tests pass - [x] CI green on all Python versions (3.11, 3.12, 3.13) - [x] Manual E2E: copilot generates agents with correct IO blocks, links, and node structure - [x] Manual E2E: MCP tool blocks use bare field names for dynamic inputs - [x] Manual E2E: sub-agent composition with AgentExecutorBlock
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
"""CustomizeAgentTool - Customizes marketplace/template agents."""
|
|
|
|
import logging
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from backend.copilot.model import ChatSession
|
|
|
|
from .agent_generator.pipeline import fetch_library_agents, fix_validate_and_save
|
|
from .base import BaseTool
|
|
from .models import ErrorResponse, ToolResponseBase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CustomizeAgentTool(BaseTool):
|
|
"""Tool for customizing marketplace/template agents."""
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return "customize_agent"
|
|
|
|
@property
|
|
def description(self) -> str:
|
|
return (
|
|
"Customize a marketplace or template agent. Pass `agent_json` "
|
|
"with the complete customized agent JSON. The tool validates, "
|
|
"auto-fixes, and saves."
|
|
)
|
|
|
|
@property
|
|
def requires_auth(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def parameters(self) -> dict[str, Any]:
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_json": {
|
|
"type": "object",
|
|
"description": (
|
|
"Complete customized agent JSON to validate and save. "
|
|
"Optionally include 'name' and 'description'."
|
|
),
|
|
},
|
|
"library_agent_ids": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": (
|
|
"List of library agent IDs to use as building blocks."
|
|
),
|
|
},
|
|
"save": {
|
|
"type": "boolean",
|
|
"description": (
|
|
"Whether to save the customized agent. Default is true."
|
|
),
|
|
"default": True,
|
|
},
|
|
"folder_id": {
|
|
"type": "string",
|
|
"description": (
|
|
"Optional folder ID to save the agent into. "
|
|
"If not provided, the agent is saved at root level. "
|
|
"Use list_folders to find available folders."
|
|
),
|
|
},
|
|
},
|
|
"required": ["agent_json"],
|
|
}
|
|
|
|
async def _execute(
|
|
self,
|
|
user_id: str | None,
|
|
session: ChatSession,
|
|
**kwargs,
|
|
) -> ToolResponseBase:
|
|
agent_json: dict[str, Any] | None = kwargs.get("agent_json")
|
|
session_id = session.session_id if session else None
|
|
|
|
if not agent_json:
|
|
return ErrorResponse(
|
|
message=(
|
|
"Please provide agent_json with the complete customized agent graph."
|
|
),
|
|
error="missing_agent_json",
|
|
session_id=session_id,
|
|
)
|
|
|
|
save = kwargs.get("save", True)
|
|
library_agent_ids = kwargs.get("library_agent_ids", [])
|
|
folder_id: str | None = kwargs.get("folder_id")
|
|
|
|
nodes = agent_json.get("nodes", [])
|
|
if not nodes:
|
|
return ErrorResponse(
|
|
message="The agent JSON has no nodes.",
|
|
error="empty_agent",
|
|
session_id=session_id,
|
|
)
|
|
|
|
# Ensure top-level fields before the fixer pipeline
|
|
if "id" not in agent_json:
|
|
agent_json["id"] = str(uuid.uuid4())
|
|
agent_json.setdefault("version", 1)
|
|
agent_json.setdefault("is_active", True)
|
|
|
|
# Fetch library agents for AgentExecutorBlock validation
|
|
library_agents = await fetch_library_agents(user_id, library_agent_ids)
|
|
|
|
return await fix_validate_and_save(
|
|
agent_json,
|
|
user_id=user_id,
|
|
session_id=session_id,
|
|
save=save,
|
|
is_update=False,
|
|
default_name="Customized Agent",
|
|
library_agents=library_agents,
|
|
folder_id=folder_id,
|
|
)
|