import json import re import logging from llm.openrouter import OpenRouterClient logger = logging.getLogger(__name__) FALLBACK_DECISION = {"action": "HOLD", "size": 0.0, "confidence": 0.0, "reason": "Parse error — defaulting to HOLD"} class Agent: def __init__(self, role: str, system_prompt: str, llm_client: OpenRouterClient): self.role = role self.system_prompt = system_prompt self.llm = llm_client def run(self, context: dict) -> dict: prompt = self.build_prompt(context) raw = self.llm.call(self.system_prompt, prompt) return self.parse(raw) def build_prompt(self, context: dict) -> str: raise NotImplementedError def parse(self, raw: str) -> dict: """Robust JSON parsing: strip markdown fences, regex fallback.""" if not raw: return FALLBACK_DECISION.copy() # Strip markdown code fences cleaned = re.sub(r"```(?:json)?\s*", "", raw).strip() cleaned = re.sub(r"```\s*$", "", cleaned).strip() # Try direct parse try: return json.loads(cleaned) except json.JSONDecodeError: pass # Try to extract first JSON object match = re.search(r"\{[^{}]*\}", cleaned, re.DOTALL) if match: try: return json.loads(match.group()) except json.JSONDecodeError: pass logger.warning(f"[{self.role}] Failed to parse response: {raw[:200]}") return FALLBACK_DECISION.copy()