chore: update environment variables and improve agent configuration
This commit is contained in:
parent
0a27670de5
commit
13a6247780
4
.env
4
.env
@ -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"
|
||||
|
@ -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"
|
||||
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -126,4 +126,6 @@ celerybeat-schedule
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
Thumbs.db
|
||||
Thumbs.db
|
||||
|
||||
uv.lock
|
||||
|
2
Makefile
2
Makefile
@ -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:
|
||||
|
54
README.md
54
README.md
@ -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
|
||||
|
||||
```
|
||||
|
@ -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]
|
||||
|
@ -17,43 +17,52 @@ 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:
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# Get database settings
|
||||
db_url = os.getenv("POSTGRES_CONNECTION_STRING")
|
||||
if not db_url:
|
||||
logger.error("Environment variable POSTGRES_CONNECTION_STRING not defined")
|
||||
return False
|
||||
|
||||
|
||||
# Connect to the database
|
||||
engine = create_engine(db_url)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
|
||||
try:
|
||||
# Obter o cliente demo pelo email do usuário
|
||||
demo_email = os.getenv("DEMO_EMAIL", "demo@exemplo.com")
|
||||
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
|
||||
agents = [
|
||||
{
|
||||
@ -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,14 +120,15 @@ def create_demo_agents():
|
||||
appropriate support channel.
|
||||
""",
|
||||
"config": {
|
||||
"api_key": uuid.uuid4(),
|
||||
"tools": [],
|
||||
"mcp_servers": [],
|
||||
"custom_tools": [],
|
||||
"sub_agents": []
|
||||
}
|
||||
}
|
||||
"sub_agents": [],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Create the agents
|
||||
for agent_data in agents:
|
||||
# Create the agent
|
||||
@ -128,27 +140,32 @@ 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:
|
||||
session.rollback()
|
||||
logger.error(f"Database error when creating example agents: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error when creating example agents: {str(e)}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = create_demo_agents()
|
||||
sys.exit(0 if success else 1)
|
||||
sys.exit(0 if success else 1)
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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")
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user