Files
gh-jeremylongshore-claude-c…/agent/tools.py
2025-11-29 18:51:24 +08:00

513 lines
16 KiB
Python

# Copyright 2025 Jeremy Longshore
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
"""Tools for ADK Orchestrator Agent - A2A protocol and coordination functions"""
import asyncio
import json
from typing import Dict, List, Optional, Any, Union
from datetime import datetime
import httpx
from pydantic import BaseModel, Field
# Pydantic models for structured data
class AgentCard(BaseModel):
"""Agent Card for A2A protocol discovery"""
name: str
description: str
url: str
version: str = "1.0.0"
capabilities: Dict[str, Any] = Field(default_factory=dict)
skills: List[Dict[str, Any]] = Field(default_factory=list)
input_modes: List[str] = Field(default_factory=lambda: ["text/plain"])
output_modes: List[str] = Field(default_factory=lambda: ["text/plain"])
class AgentInvocation(BaseModel):
"""Request structure for agent invocation"""
agent_name: str
input_data: Dict[str, Any]
timeout_seconds: int = 30
session_id: Optional[str] = None
auth_token: Optional[str] = None
class WorkflowConfig(BaseModel):
"""Configuration for multi-agent workflows"""
pattern: str # "sequential", "parallel", "loop"
agents: List[str]
max_iterations: int = 10
timeout_seconds: int = 300
error_strategy: str = "fail_fast" # or "continue_on_error"
# Tool Functions
async def discover_agents(
registry_url: Optional[str] = None,
filter_capabilities: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Discovers available agents via A2A protocol.
Args:
registry_url: Optional registry endpoint (defaults to Vertex AI Engine registry)
filter_capabilities: Optional list of required capabilities
Returns:
Dictionary containing discovered agents and their cards
"""
try:
# Default to Vertex AI Engine agent registry
if not registry_url:
registry_url = "https://agent-engine.googleapis.com/v1/agents"
discovered_agents = []
# In production, this would make actual HTTP requests
# For now, return example structure
async with httpx.AsyncClient() as client:
# Discover agents from registry
# response = await client.get(registry_url)
# agents = response.json()
# Example agents (would come from actual discovery)
example_agents = [
{
"name": "data-analyst",
"description": "Analyzes data and generates insights",
"url": "https://agent-engine.googleapis.com/v1/agents/data-analyst",
"capabilities": ["sql", "visualization", "statistics"],
},
{
"name": "code-generator",
"description": "Generates code in multiple languages",
"url": "https://agent-engine.googleapis.com/v1/agents/code-generator",
"capabilities": ["python", "javascript", "sql"],
}
]
# Filter by capabilities if specified
for agent in example_agents:
if filter_capabilities:
if any(cap in agent.get("capabilities", []) for cap in filter_capabilities):
discovered_agents.append(agent)
else:
discovered_agents.append(agent)
return {
"status": "success",
"discovered_count": len(discovered_agents),
"agents": discovered_agents,
"registry": registry_url,
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
return {
"status": "error",
"error": str(e),
"discovered_count": 0,
"agents": []
}
async def invoke_agent(
invocation: AgentInvocation,
retry_count: int = 3
) -> Dict[str, Any]:
"""Invokes a specific agent via A2A protocol.
Args:
invocation: Agent invocation configuration
retry_count: Number of retry attempts
Returns:
Agent response including results and metadata
"""
try:
# Construct A2A request
a2a_request = {
"jsonrpc": "2.0",
"method": "agent.invoke",
"params": {
"input": invocation.input_data,
"session_id": invocation.session_id
},
"id": f"req-{datetime.utcnow().timestamp()}"
}
# In production, make actual A2A protocol request
async with httpx.AsyncClient() as client:
# response = await client.post(
# f"{agent_url}/a2a",
# json=a2a_request,
# timeout=invocation.timeout_seconds,
# headers={"Authorization": f"Bearer {invocation.auth_token}"}
# )
# Example response
response_data = {
"jsonrpc": "2.0",
"result": {
"output": f"Processed request for {invocation.agent_name}",
"metadata": {
"processing_time_ms": 1234,
"tokens_used": 567
}
},
"id": a2a_request["id"]
}
return {
"status": "success",
"agent": invocation.agent_name,
"result": response_data["result"],
"session_id": invocation.session_id,
"timestamp": datetime.utcnow().isoformat()
}
except asyncio.TimeoutError:
return {
"status": "timeout",
"agent": invocation.agent_name,
"error": f"Agent invocation timed out after {invocation.timeout_seconds}s",
"session_id": invocation.session_id
}
except Exception as e:
return {
"status": "error",
"agent": invocation.agent_name,
"error": str(e),
"session_id": invocation.session_id
}
async def manage_agent_session(
action: str, # "create", "get", "update", "delete"
session_id: Optional[str] = None,
session_data: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Manages agent sessions for stateful interactions.
Args:
action: Session action to perform
session_id: Session identifier
session_data: Session data to store/update
Returns:
Session information and status
"""
try:
if action == "create":
# Create new session
new_session_id = f"session-{datetime.utcnow().timestamp()}"
return {
"status": "success",
"action": "created",
"session_id": new_session_id,
"created_at": datetime.utcnow().isoformat()
}
elif action == "get":
# Retrieve session
return {
"status": "success",
"action": "retrieved",
"session_id": session_id,
"data": session_data or {},
"retrieved_at": datetime.utcnow().isoformat()
}
elif action == "update":
# Update session
return {
"status": "success",
"action": "updated",
"session_id": session_id,
"updated_at": datetime.utcnow().isoformat()
}
elif action == "delete":
# Delete session
return {
"status": "success",
"action": "deleted",
"session_id": session_id,
"deleted_at": datetime.utcnow().isoformat()
}
else:
return {
"status": "error",
"error": f"Unknown action: {action}"
}
except Exception as e:
return {
"status": "error",
"action": action,
"error": str(e)
}
async def validate_agent_card(
agent_url: str,
strict_mode: bool = True
) -> Dict[str, Any]:
"""Validates an agent's card against A2A specification.
Args:
agent_url: URL to fetch agent card
strict_mode: Whether to enforce strict validation
Returns:
Validation results and agent card if valid
"""
try:
async with httpx.AsyncClient() as client:
# Fetch agent card
# response = await client.get(f"{agent_url}/agent-card")
# card_data = response.json()
# Example validation
card_data = {
"name": "example-agent",
"description": "An example agent",
"url": agent_url,
"version": "1.0.0",
"capabilities": {"nlp": True, "code": True}
}
# Validate using Pydantic
card = AgentCard(**card_data)
return {
"status": "valid",
"agent_card": card.model_dump(),
"validation_mode": "strict" if strict_mode else "lenient",
"validated_at": datetime.utcnow().isoformat()
}
except Exception as e:
return {
"status": "invalid",
"error": str(e),
"agent_url": agent_url
}
async def deploy_to_vertex_engine(
agent_name: str,
project_id: str,
location: str = "us-central1",
config: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Deploys an agent to Vertex AI Engine.
Args:
agent_name: Name of agent to deploy
project_id: GCP project ID
location: Deployment location
config: Deployment configuration
Returns:
Deployment status and endpoint information
"""
try:
deployment_config = config or {
"machine_type": "n1-standard-4",
"replica_count": 2,
"auto_scaling": True
}
# In production, use Vertex AI SDK
# from google.cloud import aiplatform
# aiplatform.init(project=project_id, location=location)
# endpoint = aiplatform.Endpoint.create(...)
return {
"status": "deployed",
"agent": agent_name,
"project": project_id,
"location": location,
"endpoint": f"https://{location}-aiplatform.googleapis.com/v1/projects/{project_id}/locations/{location}/endpoints/{agent_name}",
"config": deployment_config,
"deployed_at": datetime.utcnow().isoformat()
}
except Exception as e:
return {
"status": "deployment_failed",
"agent": agent_name,
"error": str(e)
}
async def monitor_agent_health(
agent_names: List[str],
include_metrics: bool = True
) -> Dict[str, Any]:
"""Monitors health and metrics for deployed agents.
Args:
agent_names: List of agents to monitor
include_metrics: Whether to include detailed metrics
Returns:
Health status and metrics for each agent
"""
try:
health_results = {}
for agent_name in agent_names:
# In production, query actual health endpoints
health_results[agent_name] = {
"status": "healthy",
"availability": 99.95,
"response_time_ms": 234,
"error_rate": 0.001
}
if include_metrics:
health_results[agent_name]["metrics"] = {
"requests_per_minute": 120,
"tokens_per_request": 450,
"cache_hit_rate": 0.85,
"memory_usage_mb": 512
}
return {
"status": "success",
"timestamp": datetime.utcnow().isoformat(),
"agents": health_results,
"summary": {
"total_agents": len(agent_names),
"healthy": len([h for h in health_results.values() if h["status"] == "healthy"]),
"unhealthy": len([h for h in health_results.values() if h["status"] != "healthy"])
}
}
except Exception as e:
return {
"status": "error",
"error": str(e)
}
async def create_agent_team(
team_name: str,
agent_roles: Dict[str, str],
coordination_rules: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Creates a team of agents for collaborative tasks.
Args:
team_name: Name for the agent team
agent_roles: Mapping of agents to their roles
coordination_rules: Rules for agent coordination
Returns:
Team configuration and status
"""
try:
team_config = {
"name": team_name,
"agents": agent_roles,
"coordination": coordination_rules or {
"decision_maker": list(agent_roles.keys())[0] if agent_roles else None,
"voting_enabled": False,
"consensus_required": False
},
"created_at": datetime.utcnow().isoformat()
}
return {
"status": "created",
"team": team_config,
"agent_count": len(agent_roles)
}
except Exception as e:
return {
"status": "error",
"error": str(e)
}
async def coordinate_workflow(
workflow: WorkflowConfig,
input_data: Dict[str, Any]
) -> Dict[str, Any]:
"""Coordinates multi-agent workflow execution.
Args:
workflow: Workflow configuration
input_data: Input data for the workflow
Returns:
Workflow execution results
"""
try:
results = []
if workflow.pattern == "sequential":
# Execute agents in sequence
current_input = input_data
for agent in workflow.agents:
invocation = AgentInvocation(
agent_name=agent,
input_data=current_input
)
result = await invoke_agent(invocation)
results.append(result)
# Pass output to next agent
if result["status"] == "success":
current_input = result.get("result", {}).get("output", current_input)
elif workflow.error_strategy == "fail_fast":
break
elif workflow.pattern == "parallel":
# Execute agents in parallel
tasks = [
invoke_agent(AgentInvocation(agent_name=agent, input_data=input_data))
for agent in workflow.agents
]
results = await asyncio.gather(*tasks, return_exceptions=True)
elif workflow.pattern == "loop":
# Execute agents in a loop
iteration = 0
current_input = input_data
while iteration < workflow.max_iterations:
for agent in workflow.agents:
invocation = AgentInvocation(
agent_name=agent,
input_data=current_input
)
result = await invoke_agent(invocation)
results.append(result)
# Check loop condition (simplified)
if result.get("result", {}).get("complete", False):
iteration = workflow.max_iterations
break
iteration += 1
return {
"status": "completed",
"pattern": workflow.pattern,
"results": results,
"agents_invoked": workflow.agents,
"execution_time_ms": 5678, # Would be actual timing
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
return {
"status": "error",
"pattern": workflow.pattern,
"error": str(e)
}