Files
AutoGPT/autogpt_platform/backend/backend/copilot/tools/fix_agent.py
Zamil Majdy 6491cb1e23 feat(copilot): local agent generation with validation, fixing, MCP & sub-agent support (#12238)
## 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
2026-03-09 16:10:22 +00:00

135 lines
4.7 KiB
Python

"""FixAgentGraphTool - Auto-fixes common agent JSON issues."""
import logging
from typing import Any
from backend.copilot.model import ChatSession
from .agent_generator.validation import AgentFixer, AgentValidator, get_blocks_as_dicts
from .base import BaseTool
from .models import ErrorResponse, FixResultResponse, ToolResponseBase
logger = logging.getLogger(__name__)
class FixAgentGraphTool(BaseTool):
"""Tool for auto-fixing common issues in agent JSON graphs."""
@property
def name(self) -> str:
return "fix_agent_graph"
@property
def description(self) -> str:
return (
"Auto-fix common issues in an agent JSON graph. Applies fixes for:\n"
"- Missing or invalid UUIDs on nodes and links\n"
"- StoreValueBlock prerequisites for ConditionBlock\n"
"- Double curly brace escaping in prompt templates\n"
"- AddToList/AddToDictionary prerequisite blocks\n"
"- CodeExecutionBlock output field naming\n"
"- Missing credentials configuration\n"
"- Node X coordinate spacing (800+ units apart)\n"
"- AI model default parameters\n"
"- Link static properties based on input schema\n"
"- Type mismatches (inserts conversion blocks)\n\n"
"Returns the fixed agent JSON plus a list of fixes applied. "
"After fixing, the agent is re-validated. If still invalid, "
"the remaining errors are included in the response."
)
@property
def requires_auth(self) -> bool:
return False
@property
def parameters(self) -> dict[str, Any]:
return {
"type": "object",
"properties": {
"agent_json": {
"type": "object",
"description": (
"The agent JSON to fix. Must contain 'nodes' and 'links' arrays."
),
},
},
"required": ["agent_json"],
}
async def _execute(
self,
user_id: str | None,
session: ChatSession,
**kwargs,
) -> ToolResponseBase:
agent_json = kwargs.get("agent_json")
session_id = session.session_id if session else None
if not agent_json or not isinstance(agent_json, dict):
return ErrorResponse(
message="Please provide a valid agent JSON object.",
error="Missing or invalid agent_json parameter",
session_id=session_id,
)
nodes = agent_json.get("nodes", [])
if not nodes:
return ErrorResponse(
message="The agent JSON has no nodes. An agent needs at least one block.",
error="empty_agent",
session_id=session_id,
)
try:
blocks = get_blocks_as_dicts()
fixer = AgentFixer()
fixed_agent = fixer.apply_all_fixes(agent_json, blocks)
fixes_applied = fixer.get_fixes_applied()
except Exception as e:
logger.error(f"Fixer error: {e}", exc_info=True)
return ErrorResponse(
message=f"Auto-fix encountered an error: {str(e)}",
error="fix_exception",
session_id=session_id,
)
# Re-validate after fixing
try:
validator = AgentValidator()
is_valid, _ = validator.validate(fixed_agent, blocks)
remaining_errors = validator.errors if not is_valid else []
except Exception as e:
logger.warning(f"Post-fix validation error: {e}", exc_info=True)
remaining_errors = [f"Post-fix validation failed: {str(e)}"]
is_valid = False
if is_valid:
return FixResultResponse(
message=(
f"Applied {len(fixes_applied)} fix(es). "
"Agent graph is now valid!"
),
fixed_agent_json=fixed_agent,
fixes_applied=fixes_applied,
fix_count=len(fixes_applied),
valid_after_fix=True,
remaining_errors=[],
session_id=session_id,
)
return FixResultResponse(
message=(
f"Applied {len(fixes_applied)} fix(es), but "
f"{len(remaining_errors)} issue(s) remain. "
"Review the remaining errors and fix manually."
),
fixed_agent_json=fixed_agent,
fixes_applied=fixes_applied,
fix_count=len(fixes_applied),
valid_after_fix=False,
remaining_errors=remaining_errors,
session_id=session_id,
)