Files
microdao-daarion/services/agents-service/agent_executor.py
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

223 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Agent Executor — Виконання запитів до LLM та обробка відповідей
"""
import asyncio
import time
from typing import Dict, Any, Optional
from datetime import datetime
import httpx
class AgentExecutionError(Exception):
"""Помилка виконання агента"""
pass
class AgentExecutor:
"""
Виконує запити до LLM для агентів
Features:
- Виклик LLM через HTTP/gRPC
- Timeout handling
- Token counting
- Retry logic
- Error handling
"""
def __init__(
self,
llm_endpoint: str = "http://localhost:11434", # Ollama by default
default_model: str = "llama3.1:8b",
timeout_seconds: int = 30,
max_retries: int = 2
):
self.llm_endpoint = llm_endpoint
self.default_model = default_model
self.timeout_seconds = timeout_seconds
self.max_retries = max_retries
async def execute(
self,
agent_id: str,
prompt: str,
system_prompt: Optional[str] = None,
model: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 500
) -> Dict[str, Any]:
"""
Виконати запит до LLM
Args:
agent_id: ID агента
prompt: User prompt
system_prompt: System prompt (опційно)
model: Модель LLM (опційно, default: self.default_model)
temperature: Temperature (0.0 - 1.0)
max_tokens: Максимальна кількість токенів у відповіді
Returns:
Dict з результатом:
{
"success": bool,
"response_text": str,
"tokens_used": int,
"latency_ms": int,
"model": str
}
Raises:
AgentExecutionError: Якщо виконання не вдалося
"""
model = model or self.default_model
start_time = time.time()
try:
# Виклик LLM
result = await self._call_llm(
prompt=prompt,
system_prompt=system_prompt,
model=model,
temperature=temperature,
max_tokens=max_tokens
)
latency_ms = int((time.time() - start_time) * 1000)
return {
"success": True,
"response_text": result["response"],
"tokens_used": result.get("tokens_used", 0),
"latency_ms": latency_ms,
"model": model
}
except asyncio.TimeoutError:
raise AgentExecutionError(f"LLM timeout after {self.timeout_seconds}s")
except Exception as e:
raise AgentExecutionError(f"LLM execution failed: {str(e)}")
async def _call_llm(
self,
prompt: str,
system_prompt: Optional[str],
model: str,
temperature: float,
max_tokens: int
) -> Dict[str, Any]:
"""
Виклик LLM через HTTP (Ollama API)
Returns:
Dict з відповіддю LLM
"""
# Формуємо повний prompt
full_prompt = prompt
if system_prompt:
full_prompt = f"{system_prompt}\n\n{prompt}"
# Ollama API endpoint
url = f"{self.llm_endpoint}/api/generate"
payload = {
"model": model,
"prompt": full_prompt,
"stream": False,
"options": {
"temperature": temperature,
"num_predict": max_tokens
}
}
async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
try:
response = await client.post(url, json=payload)
response.raise_for_status()
data = response.json()
return {
"response": data.get("response", ""),
"tokens_used": data.get("eval_count", 0) + data.get("prompt_eval_count", 0)
}
except httpx.TimeoutException:
raise asyncio.TimeoutError()
except httpx.HTTPError as e:
# Fallback до mock відповіді при помилці LLM
print(f"⚠️ LLM error (falling back to mock): {e}")
return {
"response": f"[Agent] I received your message: {prompt[:100]}... (LLM unavailable, this is a fallback response)",
"tokens_used": 50
}
async def execute_with_retry(
self,
agent_id: str,
prompt: str,
**kwargs
) -> Dict[str, Any]:
"""
Виконати запит з retry logic
Args:
agent_id: ID агента
prompt: User prompt
**kwargs: Додаткові параметри для execute()
Returns:
Dict з результатом виконання
"""
last_error = None
for attempt in range(1, self.max_retries + 1):
try:
return await self.execute(agent_id, prompt, **kwargs)
except AgentExecutionError as e:
last_error = e
print(f"⚠️ Attempt {attempt}/{self.max_retries} failed: {e}")
if attempt < self.max_retries:
# Exponential backoff
await asyncio.sleep(2 ** attempt)
# Всі спроби невдалі
raise last_error or AgentExecutionError("Unknown error")
async def execute_batch(
self,
tasks: list[Dict[str, Any]]
) -> list[Dict[str, Any]]:
"""
Виконати кілька запитів паралельно
Args:
tasks: Список задач, кожна з яких містить:
{"agent_id": str, "prompt": str, ...}
Returns:
Список результатів виконання
"""
results = await asyncio.gather(*[
self.execute(**task)
for task in tasks
], return_exceptions=True)
# Обробити помилки
processed_results = []
for i, result in enumerate(results):
if isinstance(result, Exception):
processed_results.append({
"success": False,
"error": str(result),
"agent_id": tasks[i].get("agent_id")
})
else:
processed_results.append(result)
return processed_results