Skip to content

UAT UC3: leading-emoji face contract unenforced on PiVoiceLLM path โ€” surprise/thinking faces never renderย #168

Description

@BrettKinny

Symptom. On clean, completed single turns, the leading-emoji face animation only fires
for sad (and smile, per UC2). Surprise and thinking never change the face.

Expected. Each reply's leading emoji drives a distinct face (๐Ÿ˜Š๐Ÿ˜†๐Ÿ˜ข๐Ÿ˜ฎ๐Ÿค”๐Ÿ˜ ๐Ÿ˜๐Ÿ˜๐Ÿ˜ด).

Code verification (2026-07-11) โ€” kid-mode-filter hypothesis REFUTED. The kid_mode
full-turn filter (custom-providers/textUtils.py:121-147, post-4233bb2) is a pure regex
gate, not a rewriter: on a clean turn it re-yields the buffered chunks byte-for-byte
(leading emoji intact โ€” asserted by tests/test_voice_content_filter.py), and on a hit it
replaces the whole turn with CONTENT_FILTER_REPLACEMENT, which itself starts with ๐Ÿ˜.
The dashboard's "Content filter โ€” recent hits" card showed no filter activity all
session, and the video frames (19:52:40, 19:53:02) show a neutral face during a normally
spoken reply โ€” no replacement text was played.

Actual root cause the code supports. On the live PiVoiceLLM path the emoji contract
is enforced only by prompt steering (textUtils.py:34-35, _BASE_SUFFIX rule 2).
There is no fallback/re-prefix code anywhere in pi_voice.py/pi_client.py โ€” unlike
the OpenAICompat provider, which buffers the reply head and prepends ๐Ÿ˜ when the model
doesn't lead with an allowed emoji (openai_compat.py:229-246). So whenever qwen3.5:4b
omits the prefix or only ever picks ๐Ÿ˜Š/๐Ÿ˜ข, the firmware never sees ๐Ÿ˜ฎ/๐Ÿค”. (Server-side
get_emotion()/EMOJI_MAP do map ๐Ÿ˜ฎโ†’surprised and ๐Ÿค”โ†’thinking correctly, so it's not a
mapping gap.)

Suggested fix. Port the OpenAICompat leading-emoji enforcement into the pi_voice
stream path (buffer to first non-whitespace, prepend fallback emoji if absent), and/or
strengthen persona-prompt steering. Optionally log the raw first chunk per turn to measure
how often the 4b model actually emits each emoji.

Repro. kid_mode ON or OFF (irrelevant per above); ask a surprising / thinking-provoking
question; face stays neutral while a completed spoken reply plays. First UAT attempt was
also confounded by ASR fragmentation (see the ASR mishear issue) + a device crash
(~19:50:55); the clean retry still failed.


From the filmed UAT session 2026-07-11 (uat-sessions/2026-07-11/: results.csv, container logs, two video recordings). Investigated and drafted with AI assistance (Claude Fable 5 via Claude Code); human-reviewed before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:xiaozhiUnraid xiaozhi-server container + custom providersbugSomething isn't workingready-for-humanRequires human implementation or live-device verificationstatus:activeReady to start, not blocked

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions