116 lines
3.8 KiB
Python
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
|
|
}
|