Files
project-lyra/cortex/autonomy/monologue/monologue.py

116 lines
3.8 KiB
Python

import os
import json
import logging
from typing import Dict
from llm.llm_router import call_llm
# Configuration
MONOLOGUE_LLM = os.getenv("MONOLOGUE_LLM", "PRIMARY").upper()
VERBOSE_DEBUG = os.getenv("VERBOSE_DEBUG", "false").lower() == "true"
# Logger
logger = logging.getLogger(__name__)
if VERBOSE_DEBUG:
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(
'%(asctime)s [MONOLOGUE] %(levelname)s: %(message)s',
datefmt='%H:%M:%S'
))
logger.addHandler(console_handler)
MONOLOGUE_SYSTEM_PROMPT = """
You are Lyra's inner monologue.
You think privately.
You do NOT speak to the user.
You do NOT solve the task.
You only reflect on intent, tone, and depth.
Return ONLY valid JSON with:
- intent (string)
- tone (neutral | warm | focused | playful | direct)
- depth (short | medium | deep)
- consult_executive (true | false)
"""
class InnerMonologue:
async def process(self, context: Dict) -> Dict:
# Build full prompt with system instructions merged in
full_prompt = f"""{MONOLOGUE_SYSTEM_PROMPT}
User message:
{context['user_message']}
Self state:
{context['self_state']}
Context summary:
{context['context_summary']}
Output JSON only:
"""
# Call LLM using configured backend
if VERBOSE_DEBUG:
logger.debug(f"[InnerMonologue] Calling LLM with backend: {MONOLOGUE_LLM}")
logger.debug(f"[InnerMonologue] Prompt length: {len(full_prompt)} chars")
result = await call_llm(
full_prompt,
backend=MONOLOGUE_LLM,
temperature=0.7,
max_tokens=200
)
if VERBOSE_DEBUG:
logger.debug(f"[InnerMonologue] Raw LLM response:")
logger.debug(f"{'='*80}")
logger.debug(result)
logger.debug(f"{'='*80}")
logger.debug(f"[InnerMonologue] Response length: {len(result) if result else 0} chars")
# Parse JSON response - extract just the JSON part if there's extra text
try:
# Try direct parsing first
parsed = json.loads(result)
if VERBOSE_DEBUG:
logger.debug(f"[InnerMonologue] Successfully parsed JSON directly: {parsed}")
return parsed
except json.JSONDecodeError:
# If direct parsing fails, try to extract JSON from the response
if VERBOSE_DEBUG:
logger.debug(f"[InnerMonologue] Direct JSON parse failed, attempting extraction...")
# Look for JSON object (starts with { and ends with })
import re
json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', result, re.DOTALL)
if json_match:
json_str = json_match.group(0)
try:
parsed = json.loads(json_str)
if VERBOSE_DEBUG:
logger.debug(f"[InnerMonologue] Successfully extracted and parsed JSON: {parsed}")
return parsed
except json.JSONDecodeError as e:
if VERBOSE_DEBUG:
logger.warning(f"[InnerMonologue] Extracted JSON still invalid: {e}")
else:
if VERBOSE_DEBUG:
logger.warning(f"[InnerMonologue] No JSON object found in response")
# Final fallback
if VERBOSE_DEBUG:
logger.warning(f"[InnerMonologue] All parsing attempts failed, using fallback")
else:
print(f"[InnerMonologue] JSON extraction failed")
print(f"[InnerMonologue] Raw response was: {result[:500]}")
return {
"intent": "unknown",
"tone": "neutral",
"depth": "medium",
"consult_executive": False
}