fix(autopilot): Include input schema in agent discovery + validate unknown fields

Fixes OPEN-2980

Part 1: Include input schema in find_agent and find_library_agent responses
- Added 'inputs' field to AgentInfo model
- Fetch graph input_schema for marketplace agents via store details
- Fetch graph input_schema for library agents via graph_id
- Graceful fallback to None if schema fetch fails

Part 2: Reject unknown input fields in run_agent
- Check provided inputs against valid schema fields
- Return AgentDetailsResponse with error if unknown fields detected
- Prevents silent failures where agents run with defaults instead of user inputs
This commit is contained in:
Otto
2026-01-30 23:26:50 +00:00
parent cc4839bedb
commit 8572b12d0a
3 changed files with 74 additions and 1 deletions

View File

@@ -1,10 +1,11 @@
"""Shared agent search functionality for find_agent and find_library_agent tools."""
import logging
from typing import Literal
from typing import Any, Literal
from backend.api.features.library import db as library_db
from backend.api.features.store import db as store_db
from backend.data import graph as graph_db
from backend.util.exceptions import DatabaseError, NotFoundError
from .models import (
@@ -20,6 +21,44 @@ logger = logging.getLogger(__name__)
SearchSource = Literal["marketplace", "library"]
async def _fetch_input_schema_for_store_agent(
creator: str, slug: str
) -> dict[str, Any] | None:
"""Fetch input schema for a marketplace agent."""
try:
store_agent = await store_db.get_store_agent_details(creator, slug)
graph_meta = await store_db.get_available_graph(
store_agent.store_listing_version_id
)
graph = await graph_db.get_graph(
graph_id=graph_meta.id,
version=graph_meta.version,
user_id=None,
include_subgraphs=False,
)
return graph.input_schema if graph else None
except Exception as e:
logger.warning(f"Failed to fetch input schema for {creator}/{slug}: {e}")
return None
async def _fetch_input_schema_for_library_agent(
graph_id: str, user_id: str
) -> dict[str, Any] | None:
"""Fetch input schema for a library agent."""
try:
graph = await graph_db.get_graph(
graph_id=graph_id,
version=None, # Get latest version
user_id=user_id,
include_subgraphs=False,
)
return graph.input_schema if graph else None
except Exception as e:
logger.warning(f"Failed to fetch input schema for graph {graph_id}: {e}")
return None
async def search_agents(
query: str,
source: SearchSource,
@@ -55,6 +94,10 @@ async def search_agents(
logger.info(f"Searching marketplace for: {query}")
results = await store_db.get_store_agents(search_query=query, page_size=5)
for agent in results.agents:
# Fetch input schema for the agent
input_schema = await _fetch_input_schema_for_store_agent(
agent.creator, agent.slug
)
agents.append(
AgentInfo(
id=f"{agent.creator}/{agent.slug}",
@@ -67,6 +110,7 @@ async def search_agents(
rating=agent.rating,
runs=agent.runs,
is_featured=False,
inputs=input_schema,
)
)
else: # library
@@ -77,6 +121,10 @@ async def search_agents(
page_size=10,
)
for agent in results.agents:
# Fetch input schema for the agent
input_schema = await _fetch_input_schema_for_library_agent(
agent.graph_id, user_id # type: ignore[arg-type]
)
agents.append(
AgentInfo(
id=agent.id,
@@ -90,6 +138,7 @@ async def search_agents(
has_external_trigger=agent.has_external_trigger,
new_output=agent.new_output,
graph_id=agent.graph_id,
inputs=input_schema,
)
)
logger.info(f"Found {len(agents)} agents in {source}")

View File

@@ -62,6 +62,10 @@ class AgentInfo(BaseModel):
has_external_trigger: bool | None = None
new_output: bool | None = None
graph_id: str | None = None
inputs: dict[str, Any] | None = Field(
default=None,
description="Input schema for the agent, including field names, types, and defaults",
)
class AgentsFoundResponse(ToolResponseBase):

View File

@@ -310,6 +310,26 @@ class RunAgentTool(BaseTool):
graph_version=graph.version,
)
# Check for unknown input fields - reject to prevent silent failures
valid_fields = set(input_properties.keys())
unknown_fields = provided_inputs - valid_fields
if unknown_fields:
credentials = extract_credentials_from_schema(
graph.credentials_input_schema
)
return AgentDetailsResponse(
message=(
f"Unknown input field(s) provided: {', '.join(sorted(unknown_fields))}. "
f"Agent was not executed. "
f"Valid input fields are: {', '.join(sorted(valid_fields)) or 'none'}."
),
session_id=session_id,
agent=self._build_agent_details(graph, credentials),
user_authenticated=True,
graph_id=graph.id,
graph_version=graph.version,
)
# Step 4: Execute or Schedule
if is_schedule:
return await self._schedule_agent(