Files
virtual_board_member/app/api/v1/endpoints/week5_features.py
Jonathan Pressnell 5b5714e4c2 feat: Complete Week 5 implementation - Agentic RAG & Multi-Agent Orchestration
- Implement Autonomous Workflow Engine with dynamic task decomposition
- Add Multi-Agent Communication Protocol with message routing
- Create Enhanced Reasoning Chains (CoT, ToT, Multi-Step, Parallel, Hybrid)
- Add comprehensive REST API endpoints for all Week 5 features
- Include 26/26 passing tests with full coverage
- Add complete documentation and API guides
- Update development plan to mark Week 5 as completed

Features:
- Dynamic task decomposition and parallel execution
- Agent registration, messaging, and coordination
- 5 reasoning methods with validation and learning
- Robust error handling and monitoring
- Multi-tenant support and security
- Production-ready architecture

Files added/modified:
- app/services/autonomous_workflow_engine.py
- app/services/agent_communication.py
- app/services/enhanced_reasoning.py
- app/api/v1/endpoints/week5_features.py
- tests/test_week5_features.py
- docs/week5_api_documentation.md
- docs/week5_readme.md
- WEEK5_COMPLETION_SUMMARY.md
- DEVELOPMENT_PLAN.md (updated)

All tests passing: 26/26
2025-08-10 09:25:46 -04:00

475 lines
16 KiB
Python

"""
Week 5 Features API Endpoints
Autonomous workflow engine, agent communication, and enhanced reasoning capabilities.
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from pydantic import BaseModel
from app.core.auth import get_current_tenant
from app.services.autonomous_workflow_engine import (
autonomous_workflow_engine,
WorkflowDefinition,
WorkflowExecution,
WorkflowStatus,
TaskStatus
)
from app.services.agent_communication import (
agent_communication_manager,
AgentMessage,
MessageType,
MessagePriority
)
from app.services.enhanced_reasoning import (
enhanced_reasoning_engine,
ReasoningMethod,
ReasoningResult
)
from app.services.agentic_rag_service import AgentType, AgentTask
from app.core.cache import cache_service
import uuid
from datetime import datetime
router = APIRouter()
# Request/Response Models
class WorkflowCreateRequest(BaseModel):
name: str
description: str
tasks: List[Dict[str, Any]]
dependencies: Optional[Dict[str, List[str]]] = None
max_parallel_tasks: int = 5
timeout_seconds: int = 300
class WorkflowExecuteRequest(BaseModel):
workflow_id: str
context: Optional[Dict[str, Any]] = None
class AgentMessageRequest(BaseModel):
recipient: str
message_type: str
payload: Dict[str, Any]
priority: str = "normal"
correlation_id: Optional[str] = None
class ReasoningRequest(BaseModel):
query: str
method: str = "chain_of_thought"
max_steps: int = 10
context: Optional[Dict[str, Any]] = None
class WorkflowStatusResponse(BaseModel):
execution_id: str
status: str
task_results: Dict[str, Any]
task_status: Dict[str, str]
start_time: Optional[str] = None
end_time: Optional[str] = None
error: Optional[str] = None
# Workflow Engine Endpoints
@router.post("/workflows", summary="Create a new workflow")
async def create_workflow(
request: WorkflowCreateRequest,
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Create a new workflow definition."""
try:
# Convert task dictionaries to AgentTask objects
tasks = []
for task_data in request.tasks:
task = AgentTask(
id=task_data.get("id", str(uuid.uuid4())),
agent_type=AgentType(task_data["agent_type"]),
description=task_data["description"],
input_data=task_data.get("input_data", {}),
dependencies=task_data.get("dependencies", []),
priority=task_data.get("priority", 1),
created_at=datetime.utcnow()
)
tasks.append(task)
workflow = await autonomous_workflow_engine.create_workflow(
name=request.name,
description=request.description,
tasks=tasks,
dependencies=request.dependencies,
max_parallel_tasks=request.max_parallel_tasks,
timeout_seconds=request.timeout_seconds
)
return {
"workflow_id": workflow.id,
"name": workflow.name,
"description": workflow.description,
"task_count": len(workflow.tasks),
"status": "created"
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to create workflow: {str(e)}")
@router.post("/workflows/{workflow_id}/execute", summary="Execute a workflow")
async def execute_workflow(
workflow_id: str,
request: WorkflowExecuteRequest,
background_tasks: BackgroundTasks,
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Execute a workflow."""
try:
# Get agents from agentic RAG service
from app.services.agentic_rag_service import agentic_rag_service
agents = agentic_rag_service.agents
# Execute workflow
execution = await autonomous_workflow_engine.execute_workflow(
workflow_id=workflow_id,
tenant_id=str(tenant_id),
agents=agents,
context=request.context
)
return {
"execution_id": execution.id,
"workflow_id": workflow_id,
"status": execution.status.value,
"start_time": execution.start_time.isoformat() if execution.start_time else None,
"message": "Workflow execution started"
}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to execute workflow: {str(e)}")
@router.get("/workflows/{execution_id}/status", summary="Get workflow execution status")
async def get_workflow_status(
execution_id: str,
tenant_id: str = Depends(get_current_tenant),
) -> WorkflowStatusResponse:
"""Get the status of a workflow execution."""
try:
execution = await autonomous_workflow_engine.get_workflow_status(execution_id)
if not execution:
raise HTTPException(status_code=404, detail="Workflow execution not found")
return WorkflowStatusResponse(
execution_id=execution.id,
status=execution.status.value,
task_results=execution.task_results,
task_status={k: v.value for k, v in execution.task_status.items()},
start_time=execution.start_time.isoformat() if execution.start_time else None,
end_time=execution.end_time.isoformat() if execution.end_time else None,
error=execution.error
)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to get workflow status: {str(e)}")
@router.post("/workflows/{execution_id}/cancel", summary="Cancel a workflow execution")
async def cancel_workflow(
execution_id: str,
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Cancel a running workflow execution."""
try:
success = await autonomous_workflow_engine.cancel_workflow(execution_id)
if not success:
raise HTTPException(status_code=404, detail="Workflow execution not found or already completed")
return {
"execution_id": execution_id,
"status": "cancelled",
"message": "Workflow execution cancelled successfully"
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to cancel workflow: {str(e)}")
@router.get("/workflows/metrics", summary="Get workflow engine metrics")
async def get_workflow_metrics(
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Get workflow engine performance metrics."""
try:
metrics = await autonomous_workflow_engine.get_metrics()
return metrics
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to get metrics: {str(e)}")
# Agent Communication Endpoints
@router.post("/agents/register", summary="Register an agent")
async def register_agent(
agent_id: str = Query(..., description="Agent ID"),
agent_type: str = Query(..., description="Agent type"),
capabilities: List[str] = Query(..., description="Agent capabilities"),
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Register an agent with the communication system."""
try:
await agent_communication_manager.register_agent(
agent_id=agent_id,
agent_type=AgentType(agent_type),
capabilities=capabilities
)
return {
"agent_id": agent_id,
"status": "registered",
"message": "Agent registered successfully"
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to register agent: {str(e)}")
@router.post("/agents/unregister", summary="Unregister an agent")
async def unregister_agent(
agent_id: str = Query(..., description="Agent ID"),
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Unregister an agent from the communication system."""
try:
await agent_communication_manager.unregister_agent(agent_id)
return {
"agent_id": agent_id,
"status": "unregistered",
"message": "Agent unregistered successfully"
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to unregister agent: {str(e)}")
@router.post("/agents/message", summary="Send a message to an agent")
async def send_agent_message(
request: AgentMessageRequest,
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Send a message to a specific agent."""
try:
message = AgentMessage(
id=str(uuid.uuid4()),
sender="api",
recipient=request.recipient,
message_type=MessageType(request.message_type),
payload=request.payload,
priority=MessagePriority(request.priority),
correlation_id=request.correlation_id
)
success = await agent_communication_manager.send_message(message)
if not success:
raise HTTPException(status_code=400, detail="Failed to send message")
return {
"message_id": message.id,
"recipient": message.recipient,
"status": "sent",
"timestamp": message.timestamp.isoformat()
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to send message: {str(e)}")
@router.get("/agents/{agent_id}/messages", summary="Receive messages for an agent")
async def receive_agent_messages(
agent_id: str,
timeout: float = Query(1.0, description="Timeout in seconds"),
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Receive messages for a specific agent."""
try:
message = await agent_communication_manager.receive_message(agent_id, timeout)
if not message:
return {
"agent_id": agent_id,
"message": None,
"status": "no_messages"
}
return {
"agent_id": agent_id,
"message": {
"id": message.id,
"sender": message.sender,
"message_type": message.message_type.value,
"payload": message.payload,
"priority": message.priority.value,
"timestamp": message.timestamp.isoformat(),
"correlation_id": message.correlation_id
},
"status": "received"
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to receive message: {str(e)}")
@router.get("/agents/status", summary="Get agent communication status")
async def get_agent_communication_status(
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Get the status of the agent communication system."""
try:
status = await agent_communication_manager.get_status()
return status
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to get status: {str(e)}")
# Enhanced Reasoning Endpoints
@router.post("/reasoning", summary="Perform enhanced reasoning")
async def perform_reasoning(
request: ReasoningRequest,
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Perform enhanced reasoning using various methods."""
try:
# Convert method string to enum
method_map = {
"chain_of_thought": ReasoningMethod.CHAIN_OF_THOUGHT,
"tree_of_thoughts": ReasoningMethod.TREE_OF_THOUGHTS,
"multi_step": ReasoningMethod.MULTI_STEP,
"parallel": ReasoningMethod.PARALLEL,
"hybrid": ReasoningMethod.HYBRID
}
method = method_map.get(request.method, ReasoningMethod.CHAIN_OF_THOUGHT)
# Prepare context
context = request.context or {}
context["tenant_id"] = str(tenant_id)
context["query"] = request.query
# Perform reasoning
result = await enhanced_reasoning_engine.reason(
query=request.query,
context=context,
method=method,
max_steps=request.max_steps
)
return {
"chain_id": result.chain_id,
"method": result.method.value,
"final_answer": result.final_answer,
"confidence": result.confidence,
"reasoning_steps": result.reasoning_steps,
"validation_metrics": result.validation_metrics,
"execution_time": result.execution_time,
"metadata": result.metadata
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Reasoning failed: {str(e)}")
@router.get("/reasoning/stats", summary="Get reasoning statistics")
async def get_reasoning_stats(
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Get statistics about reasoning performance."""
try:
stats = await enhanced_reasoning_engine.get_reasoning_stats()
return stats
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to get reasoning stats: {str(e)}")
# Combined Week 5 Features Endpoint
@router.get("/week5/status", summary="Get Week 5 features status")
async def get_week5_status(
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Get comprehensive status of all Week 5 features."""
try:
# Get status from all Week 5 components
workflow_metrics = await autonomous_workflow_engine.get_metrics()
agent_status = await agent_communication_manager.get_status()
reasoning_stats = await enhanced_reasoning_engine.get_reasoning_stats()
return {
"workflow_engine": {
"status": "active",
"metrics": workflow_metrics
},
"agent_communication": {
"status": "active" if agent_status["running"] else "inactive",
"metrics": agent_status
},
"enhanced_reasoning": {
"status": "active",
"stats": reasoning_stats
},
"overall_status": "operational"
}
except Exception as e:
return {
"workflow_engine": {"status": "error", "error": str(e)},
"agent_communication": {"status": "error", "error": str(e)},
"enhanced_reasoning": {"status": "error", "error": str(e)},
"overall_status": "error"
}
# Health check endpoint for Week 5 features
@router.get("/week5/health", summary="Health check for Week 5 features")
async def week5_health_check(
tenant_id: str = Depends(get_current_tenant),
) -> Dict[str, Any]:
"""Health check for Week 5 features."""
try:
# Basic health checks
workflow_metrics = await autonomous_workflow_engine.get_metrics()
agent_status = await agent_communication_manager.get_status()
# Check if components are responding
workflow_healthy = workflow_metrics.get("total_executions", 0) >= 0
agent_healthy = agent_status.get("running", False)
overall_healthy = workflow_healthy and agent_healthy
return {
"status": "healthy" if overall_healthy else "unhealthy",
"components": {
"workflow_engine": "healthy" if workflow_healthy else "unhealthy",
"agent_communication": "healthy" if agent_healthy else "unhealthy",
"enhanced_reasoning": "healthy" # Always healthy if we can reach this point
},
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
return {
"status": "unhealthy",
"error": str(e),
"timestamp": datetime.utcnow().isoformat()
}