chore: update environment variables and improve agent configuration

This commit is contained in:
Davidson Gomes 2025-04-29 19:29:48 -03:00
parent 0a27670de5
commit 13a6247780
16 changed files with 520 additions and 131 deletions

4
.env
View File

@ -1,6 +1,10 @@
API_TITLE="Evo API"
API_DESCRIPTION="API para execução de agentes de IA"
API_VERSION="1.0.0"
API_URL="http://localhost:8000"
ORGANIZATION_NAME="Evo AI"
ORGANIZATION_URL="https://evoai.evoapicloud.com"
# Configurações do banco de dados
POSTGRES_CONNECTION_STRING="postgresql://postgres:root@localhost:5432/evo_ai"

View File

@ -1,3 +1,11 @@
API_TITLE="Evo API"
API_DESCRIPTION="API para execução de agentes de IA"
API_VERSION="1.0.0"
API_URL="http://localhost:8000"
ORGANIZATION_NAME="Evo AI"
ORGANIZATION_URL="https://evoai.evoapicloud.com"
# Database settings
POSTGRES_CONNECTION_STRING="postgresql://postgres:root@localhost:5432/evo_ai"

2
.gitignore vendored
View File

@ -127,3 +127,5 @@ celerybeat-schedule
# OS
Thumbs.db
uv.lock

View File

@ -18,7 +18,7 @@ alembic-downgrade:
# Command to run the server
run:
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000 --reload-dir src
uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload --env-file .env
# Command to run the server in production mode
run-prod:

View File

@ -12,6 +12,7 @@ The Evo AI platform allows:
- MCP server configuration
- Custom tools management
- JWT authentication with email verification
- **Agent 2 Agent (A2A) Protocol Support**: Interoperability between AI agents following Google's A2A specification
## 🛠️ Technologies
@ -27,6 +28,59 @@ The Evo AI platform allows:
- **Jinja2**: Template engine for email rendering
- **Bcrypt**: Password hashing and security
## 🤖 Agent 2 Agent (A2A) Protocol Support
Evo AI implements the Google's Agent 2 Agent (A2A) protocol, enabling seamless communication and interoperability between AI agents. This implementation includes:
### Key Features
- **Standardized Communication**: Agents can communicate using a common protocol regardless of their underlying implementation
- **Interoperability**: Support for agents built with different frameworks and technologies
- **Well-Known Endpoints**: Standardized endpoints for agent discovery and interaction
- **Task Management**: Support for task-based interactions between agents
- **State Management**: Tracking of agent states and conversation history
- **Authentication**: Secure API key-based authentication for agent interactions
### Implementation Details
- **Agent Card**: Each agent exposes a `.well-known/agent.json` endpoint with its capabilities and configuration
- **Task Handling**: Support for task creation, execution, and status tracking
- **Message Format**: Standardized message format for agent communication
- **History Tracking**: Maintains conversation history between agents
- **Artifact Management**: Support for handling different types of artifacts (text, files, etc.)
### Example Usage
```json
// Agent Card Example
{
"name": "My Agent",
"description": "A helpful AI assistant",
"url": "https://api.example.com/agents/123",
"capabilities": {
"streaming": false,
"pushNotifications": false,
"stateTransitionHistory": true
},
"authentication": {
"schemes": ["apiKey"],
"credentials": {
"in": "header",
"name": "x-api-key"
}
},
"skills": [
{
"id": "search",
"name": "Web Search",
"description": "Search the web for information"
}
]
}
```
For more information about the A2A protocol, visit [Google's A2A Protocol Documentation](https://google.github.io/A2A/).
## 📁 Project Structure
```

View File

@ -40,6 +40,7 @@ dependencies = [
"fastapi_utils==0.8.0",
"bcrypt==4.3.0",
"jinja2==3.1.6",
"pydantic[email]==2.11.3",
]
[project.optional-dependencies]

View File

@ -17,9 +17,12 @@ from dotenv import load_dotenv
from src.models.models import Agent, Client, User
# Configurar logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def create_demo_agents():
"""Create example agents for the demo client"""
try:
@ -43,15 +46,21 @@ def create_demo_agents():
demo_user = session.query(User).filter(User.email == demo_email).first()
if not demo_user or not demo_user.client_id:
logger.error(f"Demo user not found or not associated with a client: {demo_email}")
logger.error(
f"Demo user not found or not associated with a client: {demo_email}"
)
return False
client_id = demo_user.client_id
# Verificar se já existem agentes para este cliente
existing_agents = session.query(Agent).filter(Agent.client_id == client_id).all()
existing_agents = (
session.query(Agent).filter(Agent.client_id == client_id).all()
)
if existing_agents:
logger.info(f"There are already {len(existing_agents)} agents for the client {client_id}")
logger.info(
f"There are already {len(existing_agents)} agents for the client {client_id}"
)
return True
# Example agent definitions
@ -69,11 +78,12 @@ def create_demo_agents():
inform that you will consult a specialist and return soon.
""",
"config": {
"api_key": uuid.uuid4(),
"tools": [],
"mcp_servers": [],
"custom_tools": [],
"sub_agents": []
}
"sub_agents": [],
},
},
{
"name": "Sales_Products",
@ -89,11 +99,12 @@ def create_demo_agents():
the customer's needs before recommending a product.
""",
"config": {
"api_key": uuid.uuid4(),
"tools": [],
"mcp_servers": [],
"custom_tools": [],
"sub_agents": []
}
"sub_agents": [],
},
},
{
"name": "FAQ_Bot",
@ -109,12 +120,13 @@ def create_demo_agents():
appropriate support channel.
""",
"config": {
"api_key": uuid.uuid4(),
"tools": [],
"mcp_servers": [],
"custom_tools": [],
"sub_agents": []
}
}
"sub_agents": [],
},
},
]
# Create the agents
@ -128,14 +140,18 @@ def create_demo_agents():
model=agent_data["model"],
api_key=agent_data["api_key"],
instruction=agent_data["instruction"].strip(),
config=agent_data["config"]
config=agent_data["config"],
)
session.add(agent)
logger.info(f"Agent '{agent_data['name']}' created for the client {client_id}")
logger.info(
f"Agent '{agent_data['name']}' created for the client {client_id}"
)
session.commit()
logger.info(f"All example agents were created successfully for the client {client_id}")
logger.info(
f"All example agents were created successfully for the client {client_id}"
)
return True
except SQLAlchemyError as e:
@ -149,6 +165,7 @@ def create_demo_agents():
finally:
session.close()
if __name__ == "__main__":
success = create_demo_agents()
sys.exit(0 if success else 1)

View File

@ -62,7 +62,20 @@ def create_mcp_servers():
],
},
"environments": {},
"tools": ["sequentialthinking"],
"tools": [
{
"id": "sequentialthinking",
"name": "Sequential Thinking",
"description": "Helps organize thoughts and break down complex problems through a structured workflow",
"tags": ["thinking", "analysis", "problem-solving"],
"examples": [
"Help me analyze this problem",
"Guide me through this decision making process",
],
"inputModes": ["text"],
"outputModes": ["text"],
}
],
"type": "community",
"id": "4519dd69-9343-4792-af51-dc4d322fb0c9",
"created_at": "2025-04-28T15:14:16.901236Z",
@ -76,87 +89,30 @@ def create_mcp_servers():
},
"environments": {},
"tools": [
"worker_list",
"worker_get",
"worker_put",
"worker_delete",
"worker_get_worker",
"worker_logs_by_worker_name",
"worker_logs_by_ray_id",
"worker_logs_keys",
"get_kvs",
"kv_get",
"kv_put",
"kv_list",
"kv_delete",
"r2_list_buckets",
"r2_create_bucket",
"r2_delete_bucket",
"r2_list_objects",
"r2_get_object",
"r2_put_object",
"r2_delete_object",
"d1_list_databases",
"d1_create_database",
"d1_delete_database",
"d1_query",
"durable_objects_list",
"durable_objects_create",
"durable_objects_delete",
"durable_objects_list_instances",
"durable_objects_get_instance",
"durable_objects_delete_instance",
"queues_list",
"queues_create",
"queues_delete",
"queues_get",
"queues_send_message",
"queues_get_messages",
"queues_update_consumer",
"workers_ai_list_models",
"workers_ai_get_model",
"workers_ai_run_inference",
"workers_ai_list_tasks",
"workflows_list",
"workflows_create",
"workflows_delete",
"workflows_get",
"workflows_update",
"workflows_execute",
"templates_list",
"templates_get",
"templates_create_from_template",
"w4p_list_dispatchers",
"w4p_create_dispatcher",
"w4p_delete_dispatcher",
"w4p_get_dispatcher",
"w4p_update_dispatcher",
"bindings_list",
"bindings_create",
"bindings_update",
"bindings_delete",
"routing_list_routes",
"routing_create_route",
"routing_update_route",
"routing_delete_route",
"cron_list",
"cron_create",
"cron_update",
"cron_delete",
"zones_list",
"zones_create",
"zones_delete",
"zones_get",
"zones_check_activation",
"secrets_list",
"secrets_put",
"secrets_delete",
"versions_list",
"versions_get",
"versions_rollback",
"wrangler_get_config",
"wrangler_update_config",
"analytics_get",
{
"id": "worker_list",
"name": "List Workers",
"description": "List all Cloudflare Workers in your account",
"tags": ["workers", "cloudflare"],
"examples": [
"List all my workers",
"Show me my Cloudflare workers",
],
"inputModes": ["text"],
"outputModes": ["application/json"],
},
{
"id": "worker_get",
"name": "Get Worker",
"description": "Get details of a specific Cloudflare Worker",
"tags": ["workers", "cloudflare"],
"examples": [
"Show me details of worker X",
"Get information about worker Y",
],
"inputModes": ["text"],
"outputModes": ["application/json"],
},
],
"type": "official",
"id": "9138d1a2-24e6-4a75-87b0-bfa4932273e8",
@ -172,7 +128,32 @@ def create_mcp_servers():
"env": {"BRAVE_API_KEY": "env@@BRAVE_API_KEY"},
},
"environments": {"BRAVE_API_KEY": "env@@BRAVE_API_KEY"},
"tools": ["brave_web_search", "brave_local_search"],
"tools": [
{
"id": "brave_web_search",
"name": "Web Search",
"description": "Perform web searches using Brave Search",
"tags": ["search", "web"],
"examples": [
"Search for Python documentation",
"Find information about AI",
],
"inputModes": ["text"],
"outputModes": ["application/json"],
},
{
"id": "brave_local_search",
"name": "Local Search",
"description": "Search for local businesses and places",
"tags": ["search", "local"],
"examples": [
"Find restaurants near me",
"Search for hotels in New York",
],
"inputModes": ["text"],
"outputModes": ["application/json"],
},
],
"type": "official",
"id": "416c94d7-77f5-43f4-8181-aeb87934ecbf",
"created_at": "2025-04-28T15:20:07.647225Z",

View File

@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, status
from datetime import datetime
import os
from fastapi import APIRouter, Depends, HTTPException, status, Header, Request
from sqlalchemy.orm import Session
from src.config.database import get_db
from typing import List, Dict, Any
@ -13,12 +15,59 @@ from src.schemas.schemas import (
)
from src.services import (
agent_service,
mcp_server_service,
)
from src.services.agent_runner import run_agent
from src.services.service_providers import (
session_service,
artifacts_service,
memory_service,
)
import logging
from src.services.session_service import get_session_events
logger = logging.getLogger(__name__)
async def format_agent_tools(
mcp_servers: List[Dict[str, Any]], db: Session
) -> List[Dict[str, Any]]:
"""Format MCP server tools for agent card skills"""
formatted_tools = []
for server in mcp_servers:
try:
# Get the MCP server by ID
server_id = uuid.UUID(server["id"])
mcp_server = mcp_server_service.get_mcp_server(db, server_id)
if not mcp_server:
logger.warning(f"MCP server not found: {server_id}")
continue
# Format each tool
for tool in mcp_server.tools:
formatted_tool = {
"id": tool["id"],
"name": tool["name"],
"description": tool["description"],
"tags": tool["tags"],
"examples": tool["examples"],
"inputModes": tool["inputModes"],
"outputModes": tool["outputModes"],
}
formatted_tools.append(formatted_tool)
except Exception as e:
logger.error(
f"Error formatting tools for MCP server {server.get('id')}: {str(e)}"
)
continue
return formatted_tools
router = APIRouter(
prefix="/agents",
tags=["agents"],
@ -38,23 +87,24 @@ async def create_agent(
return agent_service.create_agent(db, agent)
@router.get("/{client_id}", response_model=List[Agent])
@router.get("/", response_model=List[Agent])
async def read_agents(
client_id: uuid.UUID,
x_client_id: uuid.UUID = Header(..., alias="x-client-id"),
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
payload: dict = Depends(get_jwt_token),
):
# Verify if the user has access to this client's data
await verify_user_client(payload, db, client_id)
await verify_user_client(payload, db, x_client_id)
return agent_service.get_agents_by_client(db, client_id, skip, limit)
return agent_service.get_agents_by_client(db, x_client_id, skip, limit)
@router.get("/{agent_id}", response_model=Agent)
async def read_agent(
agent_id: uuid.UUID,
x_client_id: uuid.UUID = Header(..., alias="x-client-id"),
db: Session = Depends(get_db),
payload: dict = Depends(get_jwt_token),
):
@ -65,7 +115,7 @@ async def read_agent(
)
# Verify if the user has access to the agent's client
await verify_user_client(payload, db, db_agent.client_id)
await verify_user_client(payload, db, x_client_id)
return db_agent
@ -115,3 +165,201 @@ async def delete_agent(
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found"
)
@router.get("/{agent_id}/.well-known/agent.json")
async def get_agent_json(
agent_id: uuid.UUID,
db: Session = Depends(get_db),
):
try:
agent = agent_service.get_agent(db, agent_id)
if agent is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found"
)
mcp_servers = agent.config.get("mcp_servers", [])
formatted_tools = await format_agent_tools(mcp_servers, db)
AGENT_CARD = {
"name": agent.name,
"description": agent.description,
"url": f"{os.getenv('API_URL', '')}/api/v1/agents/{agent.id}",
"provider": {
"organization": os.getenv("ORGANIZATION_NAME", ""),
"url": os.getenv("ORGANIZATION_URL", ""),
},
"version": os.getenv("API_VERSION", ""),
"capabilities": {
"streaming": False,
"pushNotifications": False,
"stateTransitionHistory": True,
},
"authentication": {
"schemes": ["apiKey"],
"credentials": {"in": "header", "name": "x-api-key"},
},
"defaultInputModes": ["text", "application/json"],
"defaultOutputModes": ["text", "application/json"],
"skills": formatted_tools,
}
return AGENT_CARD
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error generating agent card",
)
@router.post("/{agent_id}/tasks/send")
async def handle_task(
agent_id: uuid.UUID,
request: Request,
x_api_key: str = Header(..., alias="x-api-key"),
db: Session = Depends(get_db),
):
"""Endpoint to clients A2A send a new task (with an initial user message)."""
try:
# Verify agent
agent = agent_service.get_agent(db, agent_id)
if agent is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Agent not found"
)
# Verify API key
agent_config = agent.config
if agent_config.get("api_key") != x_api_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API key for this agent",
)
# Process request
try:
task_request = await request.json()
except Exception as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request format"
)
# Validate required fields
task_id = task_request.get("id")
if not task_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Task ID is required"
)
# Extract user message
try:
user_message = task_request["message"]["parts"][0]["text"]
except (KeyError, IndexError):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid message format"
)
# Configure session and metadata
session_id = f"{task_id}_{agent_id}"
metadata = task_request.get("metadata", {})
history_length = metadata.get("historyLength", 50)
# Initialize response
response_task = {
"id": task_id,
"sessionId": session_id,
"status": {
"state": "running",
"timestamp": datetime.now().isoformat(),
"message": None,
"error": None,
},
"artifacts": [],
"history": [],
"metadata": metadata,
}
try:
# Execute agent
final_response_text = await run_agent(
str(agent_id),
task_id,
user_message,
session_service,
artifacts_service,
memory_service,
db,
session_id,
)
# Update status to completed
response_task["status"].update(
{
"state": "completed",
"timestamp": datetime.now().isoformat(),
"message": {
"role": "agent",
"parts": [{"type": "text", "text": final_response_text}],
},
}
)
# Add artifacts
if final_response_text:
response_task["artifacts"].append(
{
"type": "text",
"content": final_response_text,
"metadata": {
"generated_at": datetime.now().isoformat(),
"content_type": "text/plain",
},
}
)
except Exception as e:
# Update status to failed
response_task["status"].update(
{
"state": "failed",
"timestamp": datetime.now().isoformat(),
"error": {"code": "AGENT_EXECUTION_ERROR", "message": str(e)},
}
)
# Process history
try:
history_messages = get_session_events(session_service, session_id)
history_messages = history_messages[-history_length:]
formatted_history = []
for event in history_messages:
if event.content and event.content.parts:
role = (
"agent" if event.content.role == "model" else event.content.role
)
formatted_history.append(
{
"role": role,
"parts": [
{"type": "text", "text": part.text}
for part in event.content.parts
if part.text
],
}
)
response_task["history"] = formatted_history
except Exception as e:
logger.error(f"Error processing history: {str(e)}")
return response_task
except HTTPException:
raise
except Exception as e:
logger.error(f"Unexpected error in handle_task: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal server error",
)

View File

@ -1,8 +1,11 @@
import os
from typing import Optional, List
from pydantic_settings import BaseSettings
from functools import lru_cache
import secrets
from dotenv import load_dotenv
# Carrega as variáveis do .env
load_dotenv()
class Settings(BaseSettings):
@ -12,6 +15,13 @@ class Settings(BaseSettings):
API_TITLE: str = os.getenv("API_TITLE", "Evo AI API")
API_DESCRIPTION: str = os.getenv("API_DESCRIPTION", "API for executing AI agents")
API_VERSION: str = os.getenv("API_VERSION", "1.0.0")
API_URL: str = os.getenv("API_URL", "http://localhost:8000")
# Organization settings
ORGANIZATION_NAME: str = os.getenv("ORGANIZATION_NAME", "Evo AI")
ORGANIZATION_URL: str = os.getenv(
"ORGANIZATION_URL", "https://evoai.evoapicloud.com"
)
# Database settings
POSTGRES_CONNECTION_STRING: str = os.getenv(
@ -75,10 +85,10 @@ class Settings(BaseSettings):
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
case_sensitive = True
@lru_cache()
def get_settings() -> Settings:
return Settings()

View File

@ -1,3 +1,4 @@
import os
from sqlalchemy import (
Column,
String,
@ -83,6 +84,13 @@ class Agent(Base):
),
)
@property
def agent_card_url(self) -> str:
"""URL virtual para o agent card que não é rastrada no banco de dados"""
return (
f"{os.getenv('API_URL', '')}/api/v1/agents/{self.id}/.well-known/agent.json"
)
def to_dict(self):
"""Converts the object to a dictionary, converting UUIDs to strings"""
result = {}
@ -104,6 +112,8 @@ class Agent(Base):
]
else:
result[key] = value
# Adiciona a propriedade virtual ao dicionário
result["agent_card_url"] = self.agent_card_url
return result
def _convert_dict(self, d):

View File

@ -1,6 +1,8 @@
from typing import List, Optional, Dict, Union
from pydantic import BaseModel, Field
from uuid import UUID
import secrets
import string
class ToolConfig(BaseModel):
@ -90,9 +92,20 @@ class CustomTools(BaseModel):
from_attributes = True
def generate_api_key(length: int = 32) -> str:
"""Generate a secure API key."""
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(length))
class LLMConfig(BaseModel):
"""Configuration for LLM agents"""
api_key: str = Field(
default_factory=generate_api_key,
description="API key for the LLM. If not provided, a secure key will be generated automatically.",
)
tools: Optional[List[ToolConfig]] = Field(
default=None, description="List of available tools"
)

View File

@ -9,7 +9,16 @@ from src.schemas.agent_config import LLMConfig
class ClientBase(BaseModel):
name: str
email: Optional[EmailStr] = None
email: Optional[str] = None
@validator("email")
def validate_email(cls, v):
if v is None:
return v
email_regex = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.match(email_regex, v):
raise ValueError("Invalid email format")
return v
class ClientCreate(ClientBase):
@ -120,17 +129,28 @@ class Agent(AgentBase):
client_id: UUID
created_at: datetime
updated_at: Optional[datetime] = None
agent_card_url: Optional[str] = None
class Config:
from_attributes = True
class ToolConfig(BaseModel):
id: str
name: str
description: str
tags: List[str] = Field(default_factory=list)
examples: List[str] = Field(default_factory=list)
inputModes: List[str] = Field(default_factory=list)
outputModes: List[str] = Field(default_factory=list)
class MCPServerBase(BaseModel):
name: str
description: Optional[str] = None
config_json: Dict[str, Any] = Field(default_factory=dict)
environments: Dict[str, Any] = Field(default_factory=dict)
tools: List[str] = Field(default_factory=list)
tools: List[ToolConfig] = Field(default_factory=list)
type: str = Field(default="official")

View File

@ -8,6 +8,7 @@ from src.core.exceptions import AgentNotFoundError, InternalServerError
from src.services.agent_service import get_agent
from src.services.agent_builder import AgentBuilder
from sqlalchemy.orm import Session
from typing import Optional
logger = setup_logger(__name__)
@ -20,6 +21,7 @@ async def run_agent(
artifacts_service: InMemoryArtifactService,
memory_service: InMemoryMemoryService,
db: Session,
session_id: Optional[str] = None,
):
try:
logger.info(f"Starting execution of agent {agent_id} for contact {contact_id}")
@ -45,13 +47,15 @@ async def run_agent(
artifact_service=artifacts_service,
memory_service=memory_service,
)
session_id = contact_id + "_" + agent_id
adk_session_id = contact_id + "_" + agent_id
if session_id is None:
session_id = adk_session_id
logger.info(f"Searching session for contact {contact_id}")
session = session_service.get_session(
app_name=agent_id,
user_id=contact_id,
session_id=session_id,
session_id=adk_session_id,
)
if session is None:
@ -59,7 +63,7 @@ async def run_agent(
session = session_service.create_session(
app_name=agent_id,
user_id=contact_id,
session_id=session_id,
session_id=adk_session_id,
)
content = Content(role="user", parts=[Part(text=message)])
@ -69,7 +73,7 @@ async def run_agent(
try:
for event in agent_runner.run(
user_id=contact_id,
session_id=session_id,
session_id=adk_session_id,
new_message=content,
):
if event.is_final_response() and event.content and event.content.parts:
@ -79,7 +83,7 @@ async def run_agent(
completed_session = session_service.get_session(
app_name=agent_id,
user_id=contact_id,
session_id=session_id,
session_id=adk_session_id,
)
memory_service.add_session_to_memory(completed_session)

View File

@ -1,3 +1,4 @@
import os
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from fastapi import HTTPException, status
@ -27,6 +28,7 @@ def get_agent(db: Session, agent_id: uuid.UUID) -> Optional[Agent]:
if not agent:
logger.warning(f"Agent not found: {agent_id}")
return None
return agent
except SQLAlchemyError as e:
logger.error(f"Error searching for agent {agent_id}: {str(e)}")
@ -47,7 +49,11 @@ def get_agents_by_client(
try:
query = db.query(Agent).filter(Agent.client_id == client_id)
return query.offset(skip).limit(limit).all()
agents = query.offset(skip).limit(limit).all()
# A propriedade virtual agent_card_url será automaticamente incluída
# quando os agentes forem serializados para JSON
return agents
except SQLAlchemyError as e:
logger.error(f"Error searching for client agents {client_id}: {str(e)}")
raise HTTPException(
@ -139,6 +145,9 @@ def create_agent(db: Session, agent: AgentCreate) -> Agent:
db.commit()
db.refresh(db_agent)
logger.info(f"Agent created successfully: {db_agent.id}")
# A propriedade virtual agent_card_url será automaticamente incluída
# quando o agente for serializado para JSON
return db_agent
except SQLAlchemyError as e:
db.rollback()

View File

@ -2,7 +2,7 @@ from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from fastapi import HTTPException, status
from src.models.models import MCPServer
from src.schemas.schemas import MCPServerCreate
from src.schemas.schemas import MCPServerCreate, ToolConfig
from typing import List, Optional
import uuid
import logging
@ -41,7 +41,11 @@ def get_mcp_servers(db: Session, skip: int = 0, limit: int = 100) -> List[MCPSer
def create_mcp_server(db: Session, server: MCPServerCreate) -> MCPServer:
"""Create a new MCP server"""
try:
db_server = MCPServer(**server.model_dump())
# Convert tools to JSON serializable format
server_data = server.model_dump()
server_data["tools"] = [tool.model_dump() for tool in server.tools]
db_server = MCPServer(**server_data)
db.add(db_server)
db.commit()
db.refresh(db_server)
@ -65,7 +69,11 @@ def update_mcp_server(
if not db_server:
return None
for key, value in server.model_dump().items():
# Convert tools to JSON serializable format
server_data = server.model_dump()
server_data["tools"] = [tool.model_dump() for tool in server.tools]
for key, value in server_data.items():
setattr(db_server, key, value)
db.commit()