Files
opus-orchestrator-ai/opus_orchestrator/agents/base.py
T

144 lines
3.7 KiB
Python
Raw Normal View History

"""Base agent class for Opus Orchestrator."""
from abc import ABC, abstractmethod
from typing import Any, Generic, Optional, TypeVar
from pydantic import BaseModel
from opus_orchestrator.config import AgentConfig, get_config
from opus_orchestrator.utils.llm import LLMClient, get_llm_client
T = TypeVar("T", bound=BaseModel)
class AgentResponse(BaseModel):
"""Standard response from an agent."""
success: bool
output: Any
error: Optional[str] = None
metadata: dict[str, Any] = {}
class Config:
arbitrary_types_allowed = True
class BaseAgent(ABC, Generic[T]):
"""Base class for all Opus agents.
Each agent has:
- A specific role (from Fortress documentation)
- System prompts derived from Fortress methodologies
- Input/output schemas
- Execution logic
"""
def __init__(
self,
role: str,
description: str,
system_prompt: str,
output_schema: type[T] | None = None,
config: Optional[AgentConfig] = None,
):
self.role = role
self.description = description
self.system_prompt = system_prompt
self.output_schema = output_schema
self.config = config or get_config().agent
self._llm_client: Optional[LLMClient] = None
@property
def llm_client(self) -> LLMClient:
"""Get or create LLM client."""
if self._llm_client is None:
self._llm_client = get_llm_client()
return self._llm_client
@abstractmethod
async def execute(self, input_data: Any, context: dict[str, Any]) -> AgentResponse:
"""Execute the agent's task.
Args:
input_data: The input data for this agent
context: Additional context from the orchestrator
Returns:
AgentResponse with output and metadata
"""
pass
async def call_llm(
self,
system_prompt: str,
user_prompt: str,
temperature: Optional[float] = None,
) -> str:
"""Call the LLM with prompts.
Args:
system_prompt: System prompt
user_prompt: User prompt
temperature: Optional temperature override
Returns:
Generated text
"""
temp = temperature if temperature is not None else self.config.temperature
return await self.llm_client.complete(
system_prompt=system_prompt,
user_prompt=user_prompt,
temperature=temp,
max_tokens=self.config.max_tokens,
)
def build_system_prompt(self, context: dict[str, Any]) -> str:
"""Build the full system prompt with context.
Args:
context: Additional context to inject
Returns:
Complete system prompt
"""
base = self.system_prompt
if context:
context_str = "\n\n## Context\n"
for key, value in context.items():
context_str += f"- **{key}**: {value}\n"
return base + context_str
return base
def build_user_prompt(self, task: str, input_data: Any) -> str:
"""Build the user prompt for a specific task.
Args:
task: Description of the task
input_data: Input data formatted for the task
Returns:
Complete user prompt
"""
return f"""## Task
{task}
## Input
{input_data}
## Instructions
Please complete this task following the methodology specified in your system prompt.
"""
async def cleanup(self):
"""Clean up resources."""
if self._llm_client:
await self._llm_client.close()
self._llm_client = None