system-prompts-and-models-o.../dealix/core/agents/base.py
2026-05-01 14:03:52 +03:00

77 lines
2.3 KiB
Python

"""
Base Agent — shared foundation for all agents.
الفئة الأساسية لكل الوكلاء.
"""
from __future__ import annotations
import json
import re
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any
from core.errors import AgentError
from core.llm import get_router
from core.logging import get_logger
from core.utils import generate_id, utcnow
logger = get_logger(__name__)
class BaseAgent(ABC):
"""
Base class for all agents.
Provides: unique id, logging, LLM access, JSON-safe LLM parsing.
"""
name: str = "base_agent"
def __init__(self, agent_id: str | None = None) -> None:
self.agent_id = agent_id or generate_id(self.name)
self.created_at: datetime = utcnow()
self.router = get_router()
self.log = logger.bind(agent=self.name, agent_id=self.agent_id)
@abstractmethod
async def run(self, *args: Any, **kwargs: Any) -> Any:
"""Execute the agent's core work."""
...
# ── Utilities ───────────────────────────────────────────────
@staticmethod
def parse_json_response(text: str) -> dict[str, Any]:
"""
Safely parse JSON from an LLM response that may have prose around it.
يستخرج JSON بأمان حتى لو كان فيه نص حوله.
"""
if not text:
return {}
# 1. Try raw parse
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# 2. Try extracting fenced block ```json ... ```
fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
if fenced:
try:
return json.loads(fenced.group(1))
except json.JSONDecodeError:
pass
# 3. Try largest {...} block
largest = re.search(r"\{[\s\S]*\}", text)
if largest:
try:
return json.loads(largest.group(0))
except json.JSONDecodeError:
pass
raise AgentError(f"Could not parse JSON from LLM response: {text[:300]}")
def __repr__(self) -> str:
return f"<{self.__class__.__name__} id={self.agent_id}>"