feat(voice): complete Spike 2 voice pipeline with quality-tested prompt engine
Spike 2 delivers the full voice pipeline: queue → worker pool → sr-voice
child process (stdio JSONL) → cache → disk. Three rounds of quality testing
with Paula, Mellanie, and Gestalt produced iterative prompt improvements.
Prompt engine (prompt_builder.rs):
- Example-based epistemic marker integration (not keyword lists)
- Length-aware Angry tell variant (preserves facts on long content)
- Double-prompt technique: REMEMBER block repeats constraints near OUTPUT:
- Imperative injection framing (composition engine controls frequency)
- Anti-invention constraint ("do not add information not in the input")
- Universal RULES cleaned: worldbuilding moved to culture personas
Worker pool (worker.rs):
- Output post-processor strips after first newline (prevents prompt leakage)
- Watchdog poll loop (1s ticks) replaces blocking sleep for cancel
- Child health check before writing (try_wait)
Test infrastructure:
- voice_pipeline.rs: end-to-end test, auto-detects real sr-voice or mock
- voice_quality_batch.rs: 39 edge-case prompts for quality review
- mock-stdio.sh: Python JSONL mock for CI (no model needed)
- Makefile targets: test-voice-mock, test-voice-real
Quality results (Gemma 2B Q4_K_M, CPU ~13 t/s):
- Epistemic markers: naturally integrated (round 1 comma-lists fixed)
- Tell differentiation: 3/5 working (Nervous, Guarded, Angry)
- Information preservation: ~90% (up from ~70%)
- Prompt leakage: eliminated
- Open: Friendly/RoutineDeviation tells inert (#651), Factual bypass (#650)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Mock sr-voice stdio mode for pipeline testing (D-138).
|
||||
|
||||
Reads JSONL from stdin, writes JSONL to stdout. Simulates inference
|
||||
by uppercasing the base text portion of the prompt as the "voiced" output.
|
||||
|
||||
Usage: echo '{"prompt":"Re-voice this.","seed":42}' | ./mock-stdio.sh serve --stdio
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
print("mock-sr-voice: stdio mode (no model loaded)", file=sys.stderr, flush=True)
|
||||
|
||||
# Use readline() loop — Python's `for line in sys.stdin` has an internal
|
||||
# read-ahead buffer that blocks on piped stdin until 8KB is available.
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
try:
|
||||
req = json.loads(line)
|
||||
except json.JSONDecodeError as e:
|
||||
print(json.dumps({"error": f"invalid JSON: {e}"}), flush=True)
|
||||
continue
|
||||
|
||||
prompt = req.get("prompt", "")
|
||||
if not prompt:
|
||||
print(json.dumps({"error": "missing prompt field"}), flush=True)
|
||||
continue
|
||||
|
||||
# Extract the INPUT line from the prompt (last INPUT: before OUTPUT:)
|
||||
input_text = ""
|
||||
for pline in prompt.split("\n"):
|
||||
if pline.startswith("INPUT: "):
|
||||
input_text = pline[7:]
|
||||
if not input_text:
|
||||
input_text = prompt[:80]
|
||||
|
||||
voiced = f"[VOICED] {input_text.upper()}"
|
||||
|
||||
result = {
|
||||
"text": voiced,
|
||||
"tokens_generated": len(voiced.split()),
|
||||
"generation_time_ms": 50,
|
||||
"tokens_per_sec": 240.0,
|
||||
"prefill_time_ms": 10,
|
||||
}
|
||||
print(json.dumps(result), flush=True)
|
||||
|
||||
print("mock-sr-voice: stdin closed, exiting", file=sys.stderr, flush=True)
|
||||
Reference in New Issue
Block a user