mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-01 02:15:27 -05:00
Compare commits
1 Commits
master
...
fix/readme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7607b93947 |
@@ -54,7 +54,7 @@ Before proceeding with the installation, ensure your system meets the following
|
|||||||
### Updated Setup Instructions:
|
### Updated Setup Instructions:
|
||||||
We've moved to a fully maintained and regularly updated documentation site.
|
We've moved to a fully maintained and regularly updated documentation site.
|
||||||
|
|
||||||
👉 [Follow the official self-hosting guide here](https://docs.agpt.co/platform/getting-started/)
|
👉 [Follow the official self-hosting guide here](https://agpt.co/docs/platform/getting-started/getting-started)
|
||||||
|
|
||||||
|
|
||||||
This tutorial assumes you have Docker, VSCode, git and npm installed.
|
This tutorial assumes you have Docker, VSCode, git and npm installed.
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
"""Shared agent search functionality for find_agent and find_library_agent tools."""
|
"""Shared agent search functionality for find_agent and find_library_agent tools."""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from backend.api.features.library import db as library_db
|
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 db as store_db
|
||||||
from backend.data import graph as graph_db
|
|
||||||
from backend.data.graph import GraphModel
|
|
||||||
from backend.util.exceptions import DatabaseError, NotFoundError
|
from backend.util.exceptions import DatabaseError, NotFoundError
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -17,7 +14,6 @@ from .models import (
|
|||||||
NoResultsResponse,
|
NoResultsResponse,
|
||||||
ToolResponseBase,
|
ToolResponseBase,
|
||||||
)
|
)
|
||||||
from .utils import fetch_graph_from_store_slug
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -58,28 +54,7 @@ async def search_agents(
|
|||||||
if source == "marketplace":
|
if source == "marketplace":
|
||||||
logger.info(f"Searching marketplace for: {query}")
|
logger.info(f"Searching marketplace for: {query}")
|
||||||
results = await store_db.get_store_agents(search_query=query, page_size=5)
|
results = await store_db.get_store_agents(search_query=query, page_size=5)
|
||||||
|
for agent in results.agents:
|
||||||
# Fetch all graphs in parallel for better performance
|
|
||||||
async def fetch_marketplace_graph(
|
|
||||||
creator: str, slug: str
|
|
||||||
) -> GraphModel | None:
|
|
||||||
try:
|
|
||||||
graph, _ = await fetch_graph_from_store_slug(creator, slug)
|
|
||||||
return graph
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(
|
|
||||||
f"Failed to fetch input schema for {creator}/{slug}: {e}"
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
graphs = await asyncio.gather(
|
|
||||||
*(
|
|
||||||
fetch_marketplace_graph(agent.creator, agent.slug)
|
|
||||||
for agent in results.agents
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for agent, graph in zip(results.agents, graphs):
|
|
||||||
agents.append(
|
agents.append(
|
||||||
AgentInfo(
|
AgentInfo(
|
||||||
id=f"{agent.creator}/{agent.slug}",
|
id=f"{agent.creator}/{agent.slug}",
|
||||||
@@ -92,7 +67,6 @@ async def search_agents(
|
|||||||
rating=agent.rating,
|
rating=agent.rating,
|
||||||
runs=agent.runs,
|
runs=agent.runs,
|
||||||
is_featured=False,
|
is_featured=False,
|
||||||
inputs=graph.input_schema if graph else None,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else: # library
|
else: # library
|
||||||
@@ -102,32 +76,7 @@ async def search_agents(
|
|||||||
search_term=query,
|
search_term=query,
|
||||||
page_size=10,
|
page_size=10,
|
||||||
)
|
)
|
||||||
|
for agent in results.agents:
|
||||||
# Fetch all graphs in parallel for better performance
|
|
||||||
# (list_library_agents doesn't include nodes for performance)
|
|
||||||
async def fetch_library_graph(
|
|
||||||
graph_id: str, graph_version: int
|
|
||||||
) -> GraphModel | None:
|
|
||||||
try:
|
|
||||||
return await graph_db.get_graph(
|
|
||||||
graph_id=graph_id,
|
|
||||||
version=graph_version,
|
|
||||||
user_id=user_id,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(
|
|
||||||
f"Failed to fetch input schema for graph {graph_id}: {e}"
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
graphs = await asyncio.gather(
|
|
||||||
*(
|
|
||||||
fetch_library_graph(agent.graph_id, agent.graph_version)
|
|
||||||
for agent in results.agents
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for agent, graph in zip(results.agents, graphs):
|
|
||||||
agents.append(
|
agents.append(
|
||||||
AgentInfo(
|
AgentInfo(
|
||||||
id=agent.id,
|
id=agent.id,
|
||||||
@@ -141,7 +90,6 @@ async def search_agents(
|
|||||||
has_external_trigger=agent.has_external_trigger,
|
has_external_trigger=agent.has_external_trigger,
|
||||||
new_output=agent.new_output,
|
new_output=agent.new_output,
|
||||||
graph_id=agent.graph_id,
|
graph_id=agent.graph_id,
|
||||||
inputs=graph.input_schema if graph else None,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.info(f"Found {len(agents)} agents in {source}")
|
logger.info(f"Found {len(agents)} agents in {source}")
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ class ResponseType(str, Enum):
|
|||||||
OPERATION_STARTED = "operation_started"
|
OPERATION_STARTED = "operation_started"
|
||||||
OPERATION_PENDING = "operation_pending"
|
OPERATION_PENDING = "operation_pending"
|
||||||
OPERATION_IN_PROGRESS = "operation_in_progress"
|
OPERATION_IN_PROGRESS = "operation_in_progress"
|
||||||
# Input validation
|
|
||||||
INPUT_VALIDATION_ERROR = "input_validation_error"
|
|
||||||
|
|
||||||
|
|
||||||
# Base response model
|
# Base response model
|
||||||
@@ -64,10 +62,6 @@ class AgentInfo(BaseModel):
|
|||||||
has_external_trigger: bool | None = None
|
has_external_trigger: bool | None = None
|
||||||
new_output: bool | None = None
|
new_output: bool | None = None
|
||||||
graph_id: str | 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):
|
class AgentsFoundResponse(ToolResponseBase):
|
||||||
@@ -194,20 +188,6 @@ class ErrorResponse(ToolResponseBase):
|
|||||||
details: dict[str, Any] | None = None
|
details: dict[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
class InputValidationErrorResponse(ToolResponseBase):
|
|
||||||
"""Response when run_agent receives unknown input fields."""
|
|
||||||
|
|
||||||
type: ResponseType = ResponseType.INPUT_VALIDATION_ERROR
|
|
||||||
unrecognized_fields: list[str] = Field(
|
|
||||||
description="List of input field names that were not recognized"
|
|
||||||
)
|
|
||||||
inputs: dict[str, Any] = Field(
|
|
||||||
description="The agent's valid input schema for reference"
|
|
||||||
)
|
|
||||||
graph_id: str | None = None
|
|
||||||
graph_version: int | None = None
|
|
||||||
|
|
||||||
|
|
||||||
# Agent output models
|
# Agent output models
|
||||||
class ExecutionOutputInfo(BaseModel):
|
class ExecutionOutputInfo(BaseModel):
|
||||||
"""Summary of a single execution's outputs."""
|
"""Summary of a single execution's outputs."""
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from .models import (
|
|||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
ExecutionOptions,
|
ExecutionOptions,
|
||||||
ExecutionStartedResponse,
|
ExecutionStartedResponse,
|
||||||
InputValidationErrorResponse,
|
|
||||||
SetupInfo,
|
SetupInfo,
|
||||||
SetupRequirementsResponse,
|
SetupRequirementsResponse,
|
||||||
ToolResponseBase,
|
ToolResponseBase,
|
||||||
@@ -274,22 +273,6 @@ class RunAgentTool(BaseTool):
|
|||||||
input_properties = graph.input_schema.get("properties", {})
|
input_properties = graph.input_schema.get("properties", {})
|
||||||
required_fields = set(graph.input_schema.get("required", []))
|
required_fields = set(graph.input_schema.get("required", []))
|
||||||
provided_inputs = set(params.inputs.keys())
|
provided_inputs = set(params.inputs.keys())
|
||||||
valid_fields = set(input_properties.keys())
|
|
||||||
|
|
||||||
# Check for unknown input fields
|
|
||||||
unrecognized_fields = provided_inputs - valid_fields
|
|
||||||
if unrecognized_fields:
|
|
||||||
return InputValidationErrorResponse(
|
|
||||||
message=(
|
|
||||||
f"Unknown input field(s) provided: {', '.join(sorted(unrecognized_fields))}. "
|
|
||||||
f"Agent was not executed. Please use the correct field names from the schema."
|
|
||||||
),
|
|
||||||
session_id=session_id,
|
|
||||||
unrecognized_fields=sorted(unrecognized_fields),
|
|
||||||
inputs=graph.input_schema,
|
|
||||||
graph_id=graph.id,
|
|
||||||
graph_version=graph.version,
|
|
||||||
)
|
|
||||||
|
|
||||||
# If agent has inputs but none were provided AND use_defaults is not set,
|
# If agent has inputs but none were provided AND use_defaults is not set,
|
||||||
# always show what's available first so user can decide
|
# always show what's available first so user can decide
|
||||||
|
|||||||
@@ -402,42 +402,3 @@ async def test_run_agent_schedule_without_name(setup_test_data):
|
|||||||
# Should return error about missing schedule_name
|
# Should return error about missing schedule_name
|
||||||
assert result_data.get("type") == "error"
|
assert result_data.get("type") == "error"
|
||||||
assert "schedule_name" in result_data["message"].lower()
|
assert "schedule_name" in result_data["message"].lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio(loop_scope="session")
|
|
||||||
async def test_run_agent_rejects_unknown_input_fields(setup_test_data):
|
|
||||||
"""Test that run_agent returns input_validation_error for unknown input fields."""
|
|
||||||
user = setup_test_data["user"]
|
|
||||||
store_submission = setup_test_data["store_submission"]
|
|
||||||
|
|
||||||
tool = RunAgentTool()
|
|
||||||
agent_marketplace_id = f"{user.email.split('@')[0]}/{store_submission.slug}"
|
|
||||||
session = make_session(user_id=user.id)
|
|
||||||
|
|
||||||
# Execute with unknown input field names
|
|
||||||
response = await tool.execute(
|
|
||||||
user_id=user.id,
|
|
||||||
session_id=str(uuid.uuid4()),
|
|
||||||
tool_call_id=str(uuid.uuid4()),
|
|
||||||
username_agent_slug=agent_marketplace_id,
|
|
||||||
inputs={
|
|
||||||
"unknown_field": "some value",
|
|
||||||
"another_unknown": "another value",
|
|
||||||
},
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response is not None
|
|
||||||
assert hasattr(response, "output")
|
|
||||||
assert isinstance(response.output, str)
|
|
||||||
result_data = orjson.loads(response.output)
|
|
||||||
|
|
||||||
# Should return input_validation_error type with unrecognized fields
|
|
||||||
assert result_data.get("type") == "input_validation_error"
|
|
||||||
assert "unrecognized_fields" in result_data
|
|
||||||
assert set(result_data["unrecognized_fields"]) == {
|
|
||||||
"another_unknown",
|
|
||||||
"unknown_field",
|
|
||||||
}
|
|
||||||
assert "inputs" in result_data # Contains the valid schema
|
|
||||||
assert "Agent was not executed" in result_data["message"]
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import logging
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic_core import PydanticUndefined
|
|
||||||
|
|
||||||
from backend.api.features.chat.model import ChatSession
|
from backend.api.features.chat.model import ChatSession
|
||||||
from backend.data.block import get_block
|
from backend.data.block import get_block
|
||||||
from backend.data.execution import ExecutionContext
|
from backend.data.execution import ExecutionContext
|
||||||
@@ -75,22 +73,15 @@ class RunBlockTool(BaseTool):
|
|||||||
self,
|
self,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
block: Any,
|
block: Any,
|
||||||
input_data: dict[str, Any] | None = None,
|
|
||||||
) -> tuple[dict[str, CredentialsMetaInput], list[CredentialsMetaInput]]:
|
) -> tuple[dict[str, CredentialsMetaInput], list[CredentialsMetaInput]]:
|
||||||
"""
|
"""
|
||||||
Check if user has required credentials for a block.
|
Check if user has required credentials for a block.
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id: User ID
|
|
||||||
block: Block to check credentials for
|
|
||||||
input_data: Input data for the block (used to determine provider via discriminator)
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[matched_credentials, missing_credentials]
|
tuple[matched_credentials, missing_credentials]
|
||||||
"""
|
"""
|
||||||
matched_credentials: dict[str, CredentialsMetaInput] = {}
|
matched_credentials: dict[str, CredentialsMetaInput] = {}
|
||||||
missing_credentials: list[CredentialsMetaInput] = []
|
missing_credentials: list[CredentialsMetaInput] = []
|
||||||
input_data = input_data or {}
|
|
||||||
|
|
||||||
# Get credential field info from block's input schema
|
# Get credential field info from block's input schema
|
||||||
credentials_fields_info = block.input_schema.get_credentials_fields_info()
|
credentials_fields_info = block.input_schema.get_credentials_fields_info()
|
||||||
@@ -103,33 +94,14 @@ class RunBlockTool(BaseTool):
|
|||||||
available_creds = await creds_manager.store.get_all_creds(user_id)
|
available_creds = await creds_manager.store.get_all_creds(user_id)
|
||||||
|
|
||||||
for field_name, field_info in credentials_fields_info.items():
|
for field_name, field_info in credentials_fields_info.items():
|
||||||
effective_field_info = field_info
|
# field_info.provider is a frozenset of acceptable providers
|
||||||
if field_info.discriminator and field_info.discriminator_mapping:
|
# field_info.supported_types is a frozenset of acceptable types
|
||||||
# Get discriminator from input, falling back to schema default
|
|
||||||
discriminator_value = input_data.get(field_info.discriminator)
|
|
||||||
if discriminator_value is None:
|
|
||||||
field = block.input_schema.model_fields.get(
|
|
||||||
field_info.discriminator
|
|
||||||
)
|
|
||||||
if field and field.default is not PydanticUndefined:
|
|
||||||
discriminator_value = field.default
|
|
||||||
|
|
||||||
if (
|
|
||||||
discriminator_value
|
|
||||||
and discriminator_value in field_info.discriminator_mapping
|
|
||||||
):
|
|
||||||
effective_field_info = field_info.discriminate(discriminator_value)
|
|
||||||
logger.debug(
|
|
||||||
f"Discriminated provider for {field_name}: "
|
|
||||||
f"{discriminator_value} -> {effective_field_info.provider}"
|
|
||||||
)
|
|
||||||
|
|
||||||
matching_cred = next(
|
matching_cred = next(
|
||||||
(
|
(
|
||||||
cred
|
cred
|
||||||
for cred in available_creds
|
for cred in available_creds
|
||||||
if cred.provider in effective_field_info.provider
|
if cred.provider in field_info.provider
|
||||||
and cred.type in effective_field_info.supported_types
|
and cred.type in field_info.supported_types
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -143,8 +115,8 @@ class RunBlockTool(BaseTool):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Create a placeholder for the missing credential
|
# Create a placeholder for the missing credential
|
||||||
provider = next(iter(effective_field_info.provider), "unknown")
|
provider = next(iter(field_info.provider), "unknown")
|
||||||
cred_type = next(iter(effective_field_info.supported_types), "api_key")
|
cred_type = next(iter(field_info.supported_types), "api_key")
|
||||||
missing_credentials.append(
|
missing_credentials.append(
|
||||||
CredentialsMetaInput(
|
CredentialsMetaInput(
|
||||||
id=field_name,
|
id=field_name,
|
||||||
@@ -212,9 +184,10 @@ class RunBlockTool(BaseTool):
|
|||||||
|
|
||||||
logger.info(f"Executing block {block.name} ({block_id}) for user {user_id}")
|
logger.info(f"Executing block {block.name} ({block_id}) for user {user_id}")
|
||||||
|
|
||||||
|
# Check credentials
|
||||||
creds_manager = IntegrationCredentialsManager()
|
creds_manager = IntegrationCredentialsManager()
|
||||||
matched_credentials, missing_credentials = await self._check_block_credentials(
|
matched_credentials, missing_credentials = await self._check_block_credentials(
|
||||||
user_id, block, input_data
|
user_id, block
|
||||||
)
|
)
|
||||||
|
|
||||||
if missing_credentials:
|
if missing_credentials:
|
||||||
|
|||||||
Reference in New Issue
Block a user