From 8d0527f9c432fc06169f0adaf5f2a0c9b02e9def Mon Sep 17 00:00:00 2001 From: Otto Date: Sat, 31 Jan 2026 13:51:14 +0000 Subject: [PATCH] fix: include graph schemas for marketplace agents in Agent Generator When marketplace agents are included in the library_agents payload to the Agent Generator service, they were missing required fields (graph_id, graph_version, input_schema, output_schema), causing Pydantic validation to fail with HTTP 422. Changes: - Add agent_graph_id field to StoreAgent model - Include agentGraphId in hybrid search SQL query - Update search_marketplace_agents_for_generation to fetch full graph schemas for marketplace agents - Marketplace agents now return LibraryAgentSummary (unified type) with complete schemas for sub-agent composition - Update deduplication logic to use graph_id instead of name This fixes agent creation failures on dev where the decompose-description endpoint was returning 422 Unprocessable Entity. Fixes: SECRT-1817 --- .../chat/tools/agent_generator/core.py | 79 +++++++++++++------ .../backend/backend/api/features/store/db.py | 2 + .../api/features/store/hybrid_search.py | 2 + .../backend/api/features/store/model.py | 1 + 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/autogpt_platform/backend/backend/api/features/chat/tools/agent_generator/core.py b/autogpt_platform/backend/backend/api/features/chat/tools/agent_generator/core.py index 466f6438a3..9cb03c0581 100644 --- a/autogpt_platform/backend/backend/api/features/chat/tools/agent_generator/core.py +++ b/autogpt_platform/backend/backend/api/features/chat/tools/agent_generator/core.py @@ -7,6 +7,7 @@ from typing import Any, NotRequired, TypedDict from backend.api.features.library import db as library_db from backend.api.features.store import db as store_db +from backend.api.features.store import model as store_model from backend.data.graph import ( Graph, Link, @@ -266,18 +267,18 @@ async def get_library_agents_for_generation( async def search_marketplace_agents_for_generation( search_query: str, max_results: int = 10, -) -> list[MarketplaceAgentSummary]: +) -> list[LibraryAgentSummary]: """Search marketplace agents formatted for Agent Generator. - Note: This returns basic agent info. Full input/output schemas would require - additional graph fetches and is a potential future enhancement. + Fetches marketplace agents and their full schemas so they can be used + as sub-agents in generated workflows. Args: search_query: Search term to find relevant public agents max_results: Maximum number of agents to return (default 10) Returns: - List of MarketplaceAgentSummary (without detailed schemas for now) + List of LibraryAgentSummary with full input/output schemas """ try: response = await store_db.get_store_agents( @@ -286,18 +287,49 @@ async def search_marketplace_agents_for_generation( page_size=max_results, ) - results: list[MarketplaceAgentSummary] = [] - for agent in response.agents: - results.append( - MarketplaceAgentSummary( - name=agent.agent_name, - description=agent.description, - sub_heading=agent.sub_heading, - creator=agent.creator, - is_marketplace_agent=True, + # Filter to agents that have a graph ID + agents_with_graphs = [ + agent for agent in response.agents if agent.agent_graph_id + ] + + if not agents_with_graphs: + return [] + + # Batch-fetch graphs to get input/output schemas + # Use get_graph with user_id=None for public marketplace graphs + import asyncio + + async def fetch_graph_schema( + agent: store_model.StoreAgent, + ) -> LibraryAgentSummary | None: + try: + graph = await get_graph( + graph_id=agent.agent_graph_id, # type: ignore + version=None, # Get active version + user_id=None, # Public graph ) - ) - return results + if graph: + return LibraryAgentSummary( + graph_id=graph.id, + graph_version=graph.version, + name=agent.agent_name, + description=agent.description, + input_schema=graph.input_schema, + output_schema=graph.output_schema, + ) + except Exception as e: + logger.debug( + f"Failed to fetch schema for marketplace agent {agent.agent_name}: {e}" + ) + return None + + # Fetch all schemas concurrently + results = await asyncio.gather( + *[fetch_graph_schema(agent) for agent in agents_with_graphs] + ) + + # Filter out None results + return [r for r in results if r is not None] except Exception as e: logger.warning(f"Failed to search marketplace agents: {e}") return [] @@ -327,8 +359,7 @@ async def get_all_relevant_agents_for_generation( max_marketplace_results: Max marketplace agents to return (default 10) Returns: - List of AgentSummary, library agents first (with full schemas), - then marketplace agents (basic info only) + List of AgentSummary with full schemas (both library and marketplace agents) """ agents: list[AgentSummary] = [] seen_graph_ids: set[str] = set() @@ -365,16 +396,12 @@ async def get_all_relevant_agents_for_generation( search_query=search_query, max_results=max_marketplace_results, ) - library_names: set[str] = set() - for a in agents: - name = a.get("name") - if name and isinstance(name, str): - library_names.add(name.lower()) + # Deduplicate by graph_id (marketplace agents now have full schemas) for agent in marketplace_agents: - agent_name = agent.get("name") - if agent_name and isinstance(agent_name, str): - if agent_name.lower() not in library_names: - agents.append(agent) + graph_id = agent.get("graph_id") + if graph_id and graph_id not in seen_graph_ids: + agents.append(agent) + seen_graph_ids.add(graph_id) return agents diff --git a/autogpt_platform/backend/backend/api/features/store/db.py b/autogpt_platform/backend/backend/api/features/store/db.py index 956fdfa7da..cc0aea5041 100644 --- a/autogpt_platform/backend/backend/api/features/store/db.py +++ b/autogpt_platform/backend/backend/api/features/store/db.py @@ -112,6 +112,7 @@ async def get_store_agents( description=agent["description"], runs=agent["runs"], rating=agent["rating"], + agent_graph_id=agent.get("agentGraphId"), ) store_agents.append(store_agent) except Exception as e: @@ -170,6 +171,7 @@ async def get_store_agents( description=agent.description, runs=agent.runs, rating=agent.rating, + agent_graph_id=agent.agentGraphId, ) # Add to the list only if creation was successful store_agents.append(store_agent) diff --git a/autogpt_platform/backend/backend/api/features/store/hybrid_search.py b/autogpt_platform/backend/backend/api/features/store/hybrid_search.py index 8b0884bb24..e1b8f402c8 100644 --- a/autogpt_platform/backend/backend/api/features/store/hybrid_search.py +++ b/autogpt_platform/backend/backend/api/features/store/hybrid_search.py @@ -600,6 +600,7 @@ async def hybrid_search( sa.featured, sa.is_available, sa.updated_at, + sa."agentGraphId", -- Searchable text for BM25 reranking COALESCE(sa.agent_name, '') || ' ' || COALESCE(sa.sub_heading, '') || ' ' || COALESCE(sa.description, '') as searchable_text, -- Semantic score @@ -659,6 +660,7 @@ async def hybrid_search( featured, is_available, updated_at, + "agentGraphId", searchable_text, semantic_score, lexical_score, diff --git a/autogpt_platform/backend/backend/api/features/store/model.py b/autogpt_platform/backend/backend/api/features/store/model.py index a3310b96fc..c2b16b8c7d 100644 --- a/autogpt_platform/backend/backend/api/features/store/model.py +++ b/autogpt_platform/backend/backend/api/features/store/model.py @@ -38,6 +38,7 @@ class StoreAgent(pydantic.BaseModel): description: str runs: int rating: float + agent_graph_id: str | None = None # Graph ID for sub-agent composition class StoreAgentsResponse(pydantic.BaseModel):