Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
6.2 kB · 160 lines
Python
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161"""Player text -> Facade discourse acts, by way of Jev.
Every question is asked in one request against the same state. Parameterquestions are always asked and only read when the winning act uses them; thatcosts a few input tokens and saves a second round trip."""import refrom dataclasses import asdict, dataclass
from . import da_schema as schemafrom .contexts import describe as describe_contextfrom .jev_client import choice_question
@dataclass(frozen=True)class Utterance: text: str # NLU contexts the game has active: what Trip and Grace are listening for. contexts: tuple = () # Most recent lines spoken by the characters, oldest first. recent_dialogue: tuple = ()
@dataclass(frozen=True)class DiscourseAct: """The five ints of a DAWME, plus names for logs and the console.""" da_id: int char_id: int = schema.NO_CHARACTER param1: int = schema.NO_PARAM param2: int = schema.NO_PARAM param3: int = schema.NO_PARAM name: str = "" param_names: tuple = () # The parameter questions this act read, in slot order (for the monitor). param_families: tuple = () probability: float = 0.0
def as_dict(self): return asdict(self)
# The game's own "I didn't get that" act; Trip and Grace deflect on it.NOT_UNDERSTOOD = DiscourseAct(da_id=schema.SYSTEM_DOESNT_UNDERSTAND, name="systemDoesntUnderstand")
@dataclass(frozen=True)class Classification: acts: tuple understood: bool answers: dict # question name -> ChoiceAnswer; empty when Jev was not asked
SCENE = ( "The player is a dinner guest at the apartment of Trip (the husband) and " "Grace (the wife), old friends whose marriage is falling apart. The player " "types short, casual, often lowercase lines.")_NAME = re.compile(r"\b(trip|grace)\b", re.IGNORECASE)
def normalize_names(text): """Players type 'trip stop'; lowercase, 'trip' reads as a noun, not a man.""" return _NAME.sub(lambda match: match.group(1).capitalize(), text)
def build_state(utterance): state = {"scene": SCENE, "player_said": normalize_names(utterance.text)} if utterance.recent_dialogue: state["trip_and_grace_just_said"] = list(utterance.recent_dialogue) situation = [d for d in (describe_context(c) for c in utterance.contexts) if d] if situation: state["what_is_happening"] = situation return state
def _build_questions(): act_criteria = {name: act.description for name, act in schema.ACTS.items()} act_criteria[schema.OTHER_ACT] = schema.OTHER_ACT_DESCRIPTION questions = { "act": choice_question( "What kind of conversational move is player_said?", act_criteria ), "addressee": choice_question( "Who is the player talking to?", schema.ADDRESSEE_CRITERIA ), } for family in schema.FAMILIES.values(): questions[family.name] = choice_question(family.instructions, family.criteria()) return questions
QUESTIONS = _build_questions()
def _addressee(answers, min_probability): answer = answers["addressee"] if answer.probability(answer.choice) < min_probability: return schema.NO_CHARACTER return schema.ADDRESSEE_SPRITE.get(answer.choice, schema.NO_CHARACTER)
def _param(answers, family_name, min_probability): """(id, option name) for one parameter slot, or unset if Jev is unsure.""" answer = answers[family_name] if answer.choice == "none" or answer.probability(answer.choice) < min_probability: return schema.NO_PARAM, "" return schema.FAMILIES[family_name].id_of(answer.choice), answer.choice
def interpret(answers, settings): """Turn Jev's distributions into the acts the game should see.""" char_id = _addressee(answers, settings.param_min_probability) acts = []
# Gate on what Jev picked, not on how peaked the distribution is: 0.51 # oppose vs 0.33 pacify is two good readings, not a failure to understand. # Deflect only when "other" wins or the winner is a weak plurality. act_answer = answers["act"] act_probability = act_answer.probability(act_answer.choice) if act_answer.choice != schema.OTHER_ACT and act_probability >= settings.act_min_probability: spec = schema.ACTS[act_answer.choice] params = [_param(answers, fam, settings.param_min_probability) for fam in spec.slots] ids = [p[0] for p in params] + [schema.NO_PARAM] * (3 - len(params)) acts.append(DiscourseAct( da_id=spec.da_id, char_id=char_id, param1=ids[0], param2=ids[1], param3=ids[2], name=act_answer.choice, param_names=tuple(p[1] for p in params), param_families=spec.slots, probability=act_probability, ))
reference, question_word = schema.REFERENCE.name, schema.QUESTION_WORD.name ref_id, ref_name = _param(answers, reference, settings.reference_min_probability) if ref_id != schema.NO_PARAM: # As in the game's DAReferTo rules: never addressed to anyone, and the # question word defaults to "why" when the line has none. word_id, word_name = _param(answers, question_word, settings.param_min_probability) if word_id == schema.NO_PARAM: word_id, word_name = schema.REFER_TO_DEFAULT_QUESTION_WORD, "" acts.append(DiscourseAct( da_id=schema.REFER_TO, param1=ref_id, param2=word_id, name="referTo", param_names=(ref_name, word_name), param_families=(reference, question_word), probability=answers[reference].probability(ref_name), ))
understood = bool(acts) # The game's DefaultDA rule emits this next to every real act, so a beat # with no reaction to the real act deflects instead of going silent. acts.append(NOT_UNDERSTOOD) return Classification(acts=tuple(acts), understood=understood, answers=answers)
def classify(utterance, client, settings): if not utterance.text.strip(): # Nothing to ask Jev about; do not pay for a request. return Classification(acts=(NOT_UNDERSTOOD,), understood=False, answers={}) answers = client.decide(build_state(utterance), QUESTIONS) return interpret(answers, settings)