abargum's picture
Inter-2 Audio playground
f821a4a
Raw History Blame Contribute Delete
4.02 kB
"""The playground's task presets: prompts, the JSON schema each can be constrained to, and
whether the schema is on by default.
Emotion the seven-label emotion prompt, no system prompt of our own; free output by default
Combined the Inter-2 Audio system prompt + one instruction covering engagement, social
signals, emotion and delivery; constrained by default, since free output can skip
sections
When constrained, every label is limited to the values listed in the prompt and every
rationale is capped at 300 characters.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
PROMPTS = Path(__file__).parent / "prompts"
EMOTIONS = ["angry", "disgusted", "fearful", "happy", "neutral", "sad", "surprised"]
CATALOG_EMOTIONS = ["Anger", "Disgust", "Fear", "Happiness", "Neutral", "Sadness", "Surprise"]
SIGNALS = ["Hesitation", "Agreement", "Uncertainty", "Frustration", "Interest", "Confidence"]
ENGAGEMENT = ["engaged", "neutral", "disengaged"]
DELIVERY = ["Normal", "Crying", "Laughing", "Mumbling", "Shouting", "Singing", "Whispering"]
LEVELS = ["high", "medium", "low"]
RATIONALE_MAX_CHARS = 300 # keeps the longest constrained answer well inside 768 new tokens
def _prompt(name: str) -> str:
return (PROMPTS / name).read_text(encoding="utf-8").strip()
def emotion_schema(confidence_steps: int = 4, evidence_max_chars: int = 0) -> dict:
"""{primary_emotion, confidence, evidence}: the label limited to the 7 emotions.
confidence is limited to i/confidence_steps (a power of 2, so xgrammar keeps
the short float form) and evidence is capped at evidence_max_chars (0 forces
it empty): the label is what matters for this task, and an unbounded number
can loop on digits. Properties are generated in this order,
so the label comes first and neither cap can change it under greedy decoding.
"""
if confidence_steps < 1 or confidence_steps & (confidence_steps - 1):
raise ValueError(f"confidence_steps must be a power of 2, got {confidence_steps}")
return {
"type": "object",
"properties": {
"primary_emotion": {"type": "string", "enum": EMOTIONS},
"confidence": {"enum": [i / confidence_steps for i in range(confidence_steps + 1)]},
"evidence": {"type": "string", "maxLength": evidence_max_chars},
},
"required": ["primary_emotion", "confidence", "evidence"],
}
def _labelled_list(names: list[str], min_items: int, max_items: int, rationale: bool = True) -> dict:
fields = {
"name": {"type": "string", "enum": names},
"probability": {"type": "string", "enum": LEVELS},
}
if rationale:
fields["rationale"] = {"type": "string", "maxLength": RATIONALE_MAX_CHARS}
return {
"type": "array",
"minItems": min_items,
"maxItems": max_items,
"items": {
"type": "object",
"properties": fields,
"required": list(fields),
},
}
def combined_schema() -> dict:
"""{engagement_status, signals, emotions, delivery}, in that order (the instruction's order)."""
return {
"type": "object",
"properties": {
"engagement_status": {"type": "string", "enum": ENGAGEMENT},
"signals": _labelled_list(SIGNALS, 0, len(SIGNALS)),
"emotions": _labelled_list(CATALOG_EMOTIONS, 1, 1),
"delivery": {"type": "string", "enum": DELIVERY},
},
"required": ["engagement_status", "signals", "emotions", "delivery"],
}
@dataclass(frozen=True)
class Task:
system_prompt: str
user_prompt: str
schema: dict
constrain: bool # the schema checkbox's default for this task
TASKS: dict[str, Task] = {
"Emotion": Task("", _prompt("emotion_user.txt"), emotion_schema(), constrain=False),
"Combined": Task(_prompt("shared_system.txt"), _prompt("combined_user.txt"), combined_schema(), constrain=True),
}
DEFAULT_TASK = "Emotion"