Socrates_docker / classify_chat.py
AlessandroAmodioNGI's picture
Claude Sonnet 5
Split the classifier into a core agent + conditional follow-up agents
78243e3
Raw History Blame Contribute Delete
5.42 kB
from typing import Optional
import concurrent.futures
from classify_chat_helper import (
call_core_classification_llm,
call_opinion_trigger_llm,
call_news_formulation_llm,
call_thread_match_llm,
)
from classify_parameters import TRACKED_FIELDS, ALLOWED_STORY_TOPICS, TOPIC_DESCRIPTIONS
from db_user import load_user_info, save_user_info, update_country
from db_5_process_session import _load_history
_VALID_TOPICS = set(TOPIC_DESCRIPTIONS.keys())
RECENT_HISTORY_LIMIT = 5
def analyze_message(
user_id: str,
query: str,
character_id: str = "socrates",
core_model: Optional[str] = None,
opinion_model: Optional[str] = None,
secondary_model: Optional[str] = None,
):
"""
Agent 1 (core: topic/response_mode/user_info/topic_for_tale/needs_news_fetch/needs_coverage_check)
and Agent 2a (opinion-trigger, scoped to the active character) run in parallel every turn.
Agent 2b (news question formulation) and Agent 2c (philosophy-thread matching) are conditional
follow-ups, fired only when Agent 1's own output says they're relevant.
"""
# --- Load current user_info for this user ---
user_info = load_user_info(user_id)
profile = user_info.get("user_profile", {})
missing_fields = [f for f in ["name", "living_country", "origin_country"] if not profile.get(f)]
# --- Trim history to the most recent turns ---
history = _load_history("chat_history_short", user_id)
all_messages = []
for session in history.get("sessions", []):
all_messages.extend(session.get("messages", []))
recent_history = all_messages[-RECENT_HISTORY_LIMIT:] if all_messages else []
# --- Agent 1 + Agent 2a in parallel ---
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as _ex:
_f_core = _ex.submit(
call_core_classification_llm, query, recent_history, user_info, missing_fields,
character_id=character_id, model=core_model,
)
_f_opinion = _ex.submit(
call_opinion_trigger_llm, query, recent_history,
character_id=character_id, model=opinion_model,
)
core = _f_core.result()
opinion = _f_opinion.result()
# Strip empty/blank values — classifier returns all fields, most empty
raw_extracted = core.get("extracted_user_info", {})
extracted_info = {
k: v for k, v in raw_extracted.items()
if v not in (None, "", [], {})
}
topic = core.get("topic", "personal")
# Guard: LLM sometimes bleeds response_mode values (e.g. "dialogic") into topic.
if topic not in _VALID_TOPICS:
topic = "personal"
response_mode = core.get("response_mode", "dialogic")
topic_for_tale = core.get("topic_for_tale", "none")
if topic_for_tale not in ALLOWED_STORY_TOPICS:
topic_for_tale = "none"
needs_news_fetch = bool(core.get("needs_news_fetch", False))
# --- Agent 2b — conditional: news question formulation ---
news_topic, news_question, news_temporal_context = [], "", ""
if needs_news_fetch:
news = call_news_formulation_llm(query, recent_history, model=secondary_model)
news_topic = news.get("news_topic", [])
news_question = news.get("news_question", "")
news_temporal_context = news.get("news_temporal_context", "")
# --- Agent 2c — conditional: philosophy-thread matching ---
philosophy_thread_index, philosophy_depth_requested = -1, ""
if response_mode == "philosophy_thread":
thread = call_thread_match_llm(query, recent_history, character_id=character_id, model=secondary_model)
philosophy_thread_index = thread.get("philosophy_thread_index", -1)
philosophy_depth_requested = thread.get("philosophy_depth_requested", "") or ""
# --- Update user_info in Supabase only when genuinely new info was extracted ---
if extracted_info:
_prev_living = profile.get("living_country", "")
_prev_origin = profile.get("origin_country", "")
profile.update(extracted_info)
user_info["user_profile"] = profile
save_user_info(user_info, user_id)
# Only update countries when the LLM extracted a value that differs from what was stored
if "living_country" in extracted_info and extracted_info["living_country"] != _prev_living:
update_country("living", extracted_info["living_country"], user_id)
if "origin_country" in extracted_info and extracted_info["origin_country"] != _prev_origin:
update_country("origin", extracted_info["origin_country"], user_id)
return {
"topic": topic,
"response_mode": response_mode,
"user_info": user_info,
"relevant_missing": missing_fields,
"topic_for_tale": topic_for_tale,
"chunks": [],
"needs_news_fetch": needs_news_fetch,
"news_topic": news_topic,
"news_question": news_question,
"news_temporal_context": news_temporal_context,
"socratic_trigger": opinion.get("socratic_trigger", "none") or "none",
"trigger_subtype": opinion.get("trigger_subtype", "") or "",
"socratic_alignment": opinion.get("socratic_alignment", "none") or "none",
"needs_coverage_check": bool(core.get("needs_coverage_check", False)),
"philosophy_thread_index": philosophy_thread_index,
"philosophy_depth_requested": philosophy_depth_requested,
}