Socrates_docker / classify_character_inquiry.py
AlessandroAmodioNGI's picture
feat: philosopher-to-philosopher handoff + @mention UI sync
3bf7c1f
Raw
History Blame Contribute Delete
5.23 kB
"""
classify_character_inquiry.py
Detect when the user is asking about other available characters.
LLM-only β€” no keyword fast-path (keywords caused too many false positives).
Returns:
{
"is_character_inquiry": bool,
"inquiry_type": "style" | "pragmatic" | "general" | None
style β€” user wants someone with a different conversational style
("someone more direct", "less patient", "more sarcastic")
pragmatic β€” user wants a character better suited to structured guidance
("someone who explains step by step", "someone more patient")
general β€” generic "who else is there" / "what other characters"
}
"""
import json
import re
from typing import Dict, Any, List
from llm_client import client
from config import OPENAI_CLASSIFIER_MODEL
def _parse_json(raw: str, fallback: Any) -> Any:
m = re.search(r"\{.*\}", raw, re.S)
if m:
try:
return json.loads(m.group(0))
except json.JSONDecodeError:
pass
try:
return json.loads(raw)
except json.JSONDecodeError:
return fallback
def classify_character_inquiry(
user_msg: str,
history: List[Dict[str, str]],
) -> Dict[str, Any]:
"""
Detect whether the user is asking about other available characters.
Pure LLM classification β€” no keyword matching.
"""
_NULL = {"is_character_inquiry": False, "inquiry_type": None, "target_character": None}
# Skip very short messages β€” they are almost never character inquiries
if len(user_msg.split()) <= 3:
return _NULL
system = (
"The user is talking with a philosophical chatbot that has multiple characters "
"(Socrates, Diogenes, Nietzsche, Camus, Schopenhauer). "
"Detect whether the user's message is asking about other available characters "
"or requesting a switch to a different one.\n\n"
"Return ONLY valid JSON:\n"
'{ "is_character_inquiry": true/false, "inquiry_type": "style" | "pragmatic" | "general" | null, '
'"target_character": "socrates" | "diogenes" | "nietzsche" | "camus" | "schopenhauer" | null }\n\n'
"is_character_inquiry = true ONLY when the user:\n"
" - Explicitly asks if there is SOMEONE ELSE or ANOTHER CHARACTER available\n"
" - Asks for a character with a different personality or style\n"
" - Asks who else they could speak to\n"
" - Requests to switch to a different character\n\n"
"is_character_inquiry = false when the user:\n"
" - Is asking the CURRENT character to explain something better or in more detail\n"
" - Is asking a follow-up question on the current topic\n"
" - Is asking for step-by-step guidance from the current character\n"
" - Uses phrases like 'walk me through', 'explain more', 'be more detailed'\n"
" (these are requests TO the current character, not requests FOR a new one)\n\n"
"inquiry_type (only when is_character_inquiry = true):\n"
" style β€” user wants a different conversational style (more direct, gentler, etc.)\n"
" pragmatic β€” user wants someone better at structured, step-by-step guidance\n"
" general β€” user just wants to know who else is available\n"
" null β€” not a character inquiry\n\n"
"target_character = the specific character the user wants to be CONNECTED TO / TALK TO / "
"SWITCHED TO by name (e.g. 'can you call Nietzsche?', 'let me talk to Camus', "
"'bring Diogenes', 'pass me to him' referring to a named character). "
"Set it ONLY when the user wants to actually switch to or speak with that specific person. "
"Set it to null if the user merely asks what another character THINKS about the topic "
"(that is a comparison question, not a switch), or names no one specific. "
"When target_character is set, is_character_inquiry must also be true.\n\n"
"When in doubt, return false β€” do not fire on ambiguous phrasing."
)
recent = history[-4:] if history else []
msgs = [{"role": "system", "content": system}]
for h in recent:
role = h.get("role", "user")
if role not in ("user", "assistant"):
role = "user"
msgs.append({"role": role, "content": h.get("content", "")})
msgs.append({"role": "user", "content": user_msg})
try:
resp = client.chat.completions.create(
model=OPENAI_CLASSIFIER_MODEL,
messages=msgs,
temperature=0.1,
)
raw = (resp.choices[0].message.content or "").strip()
parsed = _parse_json(raw, _NULL)
_KNOWN = {"socrates", "diogenes", "nietzsche", "camus", "schopenhauer"}
_target = (parsed.get("target_character") or "").lower().strip()
if _target not in _KNOWN:
_target = None
return {
"is_character_inquiry": bool(parsed.get("is_character_inquiry", False)) or bool(_target),
"inquiry_type": parsed.get("inquiry_type") or None,
"target_character": _target,
}
except Exception:
return _NULL