Skip to content

UAT UT1/UT2/UT6: pi voice tools never invoked — qwen3.5:4b answers directly under the production prompt #165

Description

@BrettKinny

Symptom. The pi voice tools do not fire on the live voice path. The outer model answers
directly (and hallucinates memory acks) instead of calling tools.

Verification (2026-07-11, code + live-container probes). Root cause isolated with a live
reproduction; two corrections to the original finding.

  • Extension loads and all 7 tools ARE registered — ruled out a wiring failure. A live
    probe with the exact production flags (pi --mode rpc --provider ollama --model qwen3.5:4b --no-session --no-context-files --offline --no-skills ...) lists all 7 voice
    tools plus 4 pi coding built-ins (read, bash, edit, write) that are never disabled.
    With an explicit "use the remember tool now" instruction, the tool fires and executes
    end-to-end — the dispatch path is fully functional.
  • Live repro of the failure: replaying the exact UAT purple-turn (JSON-wrapped user
    text + kid-mode HARD-CONSTRAINTS suffix) produced 43 frames, zero toolCall frames,
    and the same hallucinated "😊 I've saved your favourite colour as purple!" reply.
  • Correction 1 — the "no brain.db write" evidence was a WAL artifact. grep purple brain.db = 0 but grep purple brain.db-wal = 6: SQLite WAL held the un-checkpointed
    rows. The extension's per-turn agent_end conversation logger wrote ~46
    voice_conversation_* rows during the session (proving the extension ran on every turn).
    What's genuinely absent is any voice_core_* row — the remember tool was never
    called; the verbal ack was hallucinated. UT1/UT2/UT6 conclusions stand.
  • Correction 2 — UT7/UT8 "working tools" never went through pi. Vision and sing/song
    requests are intent-detection shortcuts inside xiaozhi-server
    (receiveAudioHandle.py:1093-1098 and :589-726) that bypass pi's tool loop entirely
    (the photo turn reached pi as an injected [You just used your camera...] message).
    Zero pi tool calls occurred in the entire UAT session.

Why the 4b doesn't call tools (contributing causes, in rough order):

  1. The per-turn HARD-CONSTRAINTS suffix ("Begin your reply now", "EXACTLY ONE emoji ... as
    the first character", 1–2 short sentences) actively steers toward an immediate text
    reply; a tool call is in tension with "first character is an emoji".
  2. Tool descriptions hedge ("Use sparingly", "Use only when the quick chat path can't
    handle it") and --thinking off removes deliberation.
  3. No persona/system prompt reaches pi (--no-context-files; only the last user message is
    forwarded) — pi runs on its default coding-agent system prompt.
  4. models.json gives qwen3.5:4b a 4096-token context; with 11 tool defs + system
    prompt + ~450-token suffix, compaction fired twice on a single fresh turn.
  5. User text arrives JSON-wrapped ({"content": ...}) — pi_voice.py:88-95 forwards the
    raw JSON instead of unwrapping like receiveAudioHandle.py:1043 does.

Also fix (logging-visibility gap, confirmed mechanism). docker logs dotty-pi is empty
by construction: PID 1 is sleep infinity and each RPC turn is a docker exec child whose
stdio goes to PiClient. --no-session disables pi's own transcripts, and pi_client.py:265
logs unknown frames only at DEBUG. Suggested: log toolCall frames at INFO in
PiClient, and/or drop --no-session/tee transcripts.

Side finds. XIAOZHI_HOST is unset in the dotty-pi container, so the pi play_song
tool would fail if it ever were called (play_song.ts:104); a PiClientError: new_session timed out at 19:25:05 (pre-UAT) shows the RPC channel has reliability wobbles.

Expected. Keepable facts write to brain.db (category=core); hard questions swap to
qwen3.6:27b-think with a visible pause.

Original log excerpt.

19:57:39 大模型收到用户消息: "...my favourite colour is purple, please remember that."
19:57:40 发送第一段语音: I'll remember that purple is your favorite color   # 0.6s, no tool
20:00:25 大模型收到用户消息: "Dotty, what is 47 times 89?"
20:00:25 发送第一段语音: 47 times 89 equals 4183                            # 0.7s, no 27b-think swap

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); verification included read-only live-container probes. 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