feat: comprehensive test infrastructure improvements
- Fix unit test imports: add env setup in conftest.py before module imports - Add 24 new auth router tests (RBAC, preferences, password validation) - Add 16 new tests for litellm and chat_summary routers - Apply black formatting and ruff linting across codebase - Add pre-commit hooks configuration (black, ruff, file checks) - Increase CI coverage threshold from 40% to 50% Test Results: - 206 tests passing (91 unit + 115 integration) - Coverage: 58.79% on core modules - auth.py: 57% → 85%, litellm.py: 23% → 87%, chat_summary.py: 41% → 100% - auth_service: 96.51%, config: 100%, rbac: 93.48%
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
"""
|
||||
Agent Executor Service - Executes agent tasks and manages their lifecycle
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from db import opc_db
|
||||
from services.agents.base_agent import BaseAgent
|
||||
from services import email_service
|
||||
import httpx
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from db import opc_db
|
||||
from services import email_service
|
||||
from services.agents.base_agent import BaseAgent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -22,7 +25,7 @@ class AgentExecutor:
|
||||
"""Manages agent execution lifecycle"""
|
||||
|
||||
def __init__(self):
|
||||
self.agents: Dict[str, BaseAgent] = {}
|
||||
self.agents: dict[str, BaseAgent] = {}
|
||||
self.running = False
|
||||
|
||||
async def initialize(self):
|
||||
@@ -35,7 +38,7 @@ class AgentExecutor:
|
||||
name=agent_data["name"],
|
||||
role=agent_data["role"],
|
||||
system_prompt=agent_data["system_prompt"],
|
||||
config=agent_data["config"]
|
||||
config=agent_data["config"],
|
||||
)
|
||||
self.agents[agent_data["id"]] = agent
|
||||
logger.info(f"Loaded agent: {agent.name} ({agent.agent_id})")
|
||||
@@ -54,6 +57,7 @@ class AgentExecutor:
|
||||
print(f"[Executor] Error in executor loop: {e}")
|
||||
logger.error(f"Error in executor loop: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
await asyncio.sleep(5) # Check every 5 seconds
|
||||
@@ -74,9 +78,11 @@ class AgentExecutor:
|
||||
|
||||
for execution in executions:
|
||||
try:
|
||||
print(f"[Executor] Processing execution {execution['id']} for agent {execution['agent_id']} (status: {execution['status']})")
|
||||
print(
|
||||
f"[Executor] Processing execution {execution['id']} for agent {execution['agent_id']} (status: {execution['status']})"
|
||||
)
|
||||
|
||||
if execution['status'] == 'approved':
|
||||
if execution["status"] == "approved":
|
||||
# Resume approved execution
|
||||
await self._resume_approved_execution(execution)
|
||||
else:
|
||||
@@ -87,7 +93,7 @@ class AgentExecutor:
|
||||
logger.error(f"Failed to execute agent {execution['agent_id']}: {e}")
|
||||
await self._mark_execution_failed(execution["id"], str(e))
|
||||
|
||||
async def _execute_agent(self, execution: Dict[str, Any]):
|
||||
async def _execute_agent(self, execution: dict[str, Any]):
|
||||
"""Execute a single agent task"""
|
||||
agent_id = execution["agent_id"]
|
||||
task_id = execution["task_id"]
|
||||
@@ -127,7 +133,7 @@ class AgentExecutor:
|
||||
# Send completion notification
|
||||
await self._send_completion_notification(agent, task, result)
|
||||
|
||||
async def _resume_approved_execution(self, execution: Dict[str, Any]):
|
||||
async def _resume_approved_execution(self, execution: dict[str, Any]):
|
||||
"""Resume an approved execution and execute its actions"""
|
||||
agent_id = execution["agent_id"]
|
||||
task_id = execution["task_id"]
|
||||
@@ -160,12 +166,9 @@ class AgentExecutor:
|
||||
# Send completion notification
|
||||
await self._send_completion_notification(agent, task, result)
|
||||
|
||||
async def _build_context(self, task: Dict[str, Any]) -> Dict[str, Any]:
|
||||
async def _build_context(self, task: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Build context for agent execution"""
|
||||
context = {
|
||||
"task": task,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
context = {"task": task, "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
# Get related tasks from same project
|
||||
if task.get("project_id"):
|
||||
@@ -178,7 +181,7 @@ class AgentExecutor:
|
||||
|
||||
return context
|
||||
|
||||
async def _execute_actions(self, task: Dict[str, Any], actions: list):
|
||||
async def _execute_actions(self, task: dict[str, Any], actions: list):
|
||||
"""Execute proposed actions"""
|
||||
for action in actions:
|
||||
action_type = action.get("type")
|
||||
@@ -203,7 +206,7 @@ class AgentExecutor:
|
||||
logger.error(f"Failed to execute action {action_type}: {e}")
|
||||
raise
|
||||
|
||||
async def _action_create_subtask(self, parent_task: Dict[str, Any], action: Dict[str, Any]):
|
||||
async def _action_create_subtask(self, parent_task: dict[str, Any], action: dict[str, Any]):
|
||||
"""Create a subtask"""
|
||||
await opc_db.create_task(
|
||||
title=action.get("title", "Subtask"),
|
||||
@@ -212,48 +215,52 @@ class AgentExecutor:
|
||||
priority=parent_task.get("priority", "medium"),
|
||||
project_id=parent_task.get("project_id"),
|
||||
tags=parent_task.get("tags", []),
|
||||
actor="agent"
|
||||
actor="agent",
|
||||
)
|
||||
logger.info(f"Created subtask: {action.get('title')}")
|
||||
|
||||
async def _action_update_status(self, task: Dict[str, Any], action: Dict[str, Any]):
|
||||
async def _action_update_status(self, task: dict[str, Any], action: dict[str, Any]):
|
||||
"""Update task status"""
|
||||
new_status = action.get("status")
|
||||
if new_status:
|
||||
await opc_db.update_task(
|
||||
task_id=task["id"],
|
||||
updates={"status": new_status},
|
||||
actor="agent"
|
||||
)
|
||||
await opc_db.update_task(task_id=task["id"], updates={"status": new_status}, actor="agent")
|
||||
logger.info(f"Updated task {task['id']} status to {new_status}")
|
||||
|
||||
async def _action_send_notification(self, action: Dict[str, Any]):
|
||||
async def _action_send_notification(self, action: dict[str, Any]):
|
||||
"""Send notification"""
|
||||
message = action.get("message", "")
|
||||
if message and TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
|
||||
await self._send_telegram(message)
|
||||
|
||||
async def _action_add_comment(self, task: Dict[str, Any], action: Dict[str, Any]):
|
||||
async def _action_add_comment(self, task: dict[str, Any], action: dict[str, Any]):
|
||||
"""Add comment to task (stored in history)"""
|
||||
comment = action.get("comment", "")
|
||||
if comment:
|
||||
# Store as task history
|
||||
pool = await opc_db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("""
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO task_history (task_id, action, actor, actor_type, new_value)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
""", task["id"], "comment", "agent", "agent", comment)
|
||||
""",
|
||||
task["id"],
|
||||
"comment",
|
||||
"agent",
|
||||
"agent",
|
||||
comment,
|
||||
)
|
||||
|
||||
async def _update_execution_status(self, execution_id: int, status: str):
|
||||
"""Update execution status"""
|
||||
await opc_db.update_execution_status(execution_id, status)
|
||||
|
||||
async def _mark_execution_completed(self, execution_id: int, result: Dict[str, Any]):
|
||||
async def _mark_execution_completed(self, execution_id: int, result: dict[str, Any]):
|
||||
"""Mark execution as completed"""
|
||||
pool = await opc_db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("""
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agent_executions
|
||||
SET status = $1,
|
||||
output_result = $2,
|
||||
@@ -261,36 +268,46 @@ class AgentExecutor:
|
||||
actions_executed = $3,
|
||||
completed_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $4
|
||||
""", "completed",
|
||||
""",
|
||||
"completed",
|
||||
opc_db.json.dumps(result),
|
||||
opc_db.json.dumps(result.get("actions", [])),
|
||||
execution_id)
|
||||
execution_id,
|
||||
)
|
||||
|
||||
async def _mark_execution_failed(self, execution_id: int, error: str):
|
||||
"""Mark execution as failed"""
|
||||
pool = await opc_db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("""
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agent_executions
|
||||
SET status = $1, error_message = $2, completed_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3
|
||||
""", "failed", error, execution_id)
|
||||
""",
|
||||
"failed",
|
||||
error,
|
||||
execution_id,
|
||||
)
|
||||
|
||||
async def _mark_pending_approval(self, execution_id: int, result: Dict[str, Any]):
|
||||
async def _mark_pending_approval(self, execution_id: int, result: dict[str, Any]):
|
||||
"""Mark execution as pending approval"""
|
||||
pool = await opc_db.get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("""
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agent_executions
|
||||
SET status = $1,
|
||||
output_result = $2,
|
||||
actions_proposed = $3,
|
||||
requires_approval = TRUE
|
||||
WHERE id = $4
|
||||
""", "pending_approval",
|
||||
""",
|
||||
"pending_approval",
|
||||
opc_db.json.dumps(result),
|
||||
opc_db.json.dumps(result.get("actions", [])),
|
||||
execution_id)
|
||||
execution_id,
|
||||
)
|
||||
|
||||
async def _send_telegram(self, message: str):
|
||||
"""Send Telegram notification"""
|
||||
@@ -299,16 +316,12 @@ class AgentExecutor:
|
||||
async with httpx.AsyncClient(proxy=proxy_url, timeout=10.0) as client:
|
||||
await client.post(
|
||||
f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage",
|
||||
data={
|
||||
"chat_id": TELEGRAM_CHAT_ID,
|
||||
"text": message,
|
||||
"parse_mode": "Markdown"
|
||||
}
|
||||
data={"chat_id": TELEGRAM_CHAT_ID, "text": message, "parse_mode": "Markdown"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram notification: {e}")
|
||||
|
||||
async def _send_approval_notification(self, agent: BaseAgent, task: Dict[str, Any], result: Dict[str, Any]):
|
||||
async def _send_approval_notification(self, agent: BaseAgent, task: dict[str, Any], result: dict[str, Any]):
|
||||
"""Send notification for approval request"""
|
||||
message = f"""🤖 *Agent Approval Required*
|
||||
|
||||
@@ -331,11 +344,11 @@ Proposed Actions:
|
||||
await email_service.send_agent_approval_email(
|
||||
agent_name=agent.name,
|
||||
task=task,
|
||||
reasoning=result.get('reasoning', 'No reasoning provided'),
|
||||
actions=result.get('actions', [])
|
||||
reasoning=result.get("reasoning", "No reasoning provided"),
|
||||
actions=result.get("actions", []),
|
||||
)
|
||||
|
||||
async def _send_completion_notification(self, agent: BaseAgent, task: Dict[str, Any], result: Dict[str, Any]):
|
||||
async def _send_completion_notification(self, agent: BaseAgent, task: dict[str, Any], result: dict[str, Any]):
|
||||
"""Send notification for completed execution"""
|
||||
message = f"""✅ *Agent Task Completed*
|
||||
|
||||
@@ -349,14 +362,12 @@ Actions Executed: {len(result.get('actions', []))}
|
||||
|
||||
# Send Email
|
||||
await email_service.send_agent_completion_email(
|
||||
agent_name=agent.name,
|
||||
task=task,
|
||||
actions_count=len(result.get('actions', []))
|
||||
agent_name=agent.name, task=task, actions_count=len(result.get("actions", []))
|
||||
)
|
||||
|
||||
|
||||
# Global executor instance
|
||||
_executor: Optional[AgentExecutor] = None
|
||||
_executor: AgentExecutor | None = None
|
||||
|
||||
|
||||
async def get_executor() -> AgentExecutor:
|
||||
@@ -382,5 +393,6 @@ async def start_executor():
|
||||
except Exception as e:
|
||||
print(f"ERROR in start_executor: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user