feat(P01-03-03,P01-03-07): Pipecat Flows wiring + scenario-driven prompt

server/scenarios/runtime.py — ScenarioRuntime maps a Scenario to a Pipecat
Flows state-machine spec: initial 'conversation' state with the scenario's
system prompt + opening line, branch metadata carried in the spec (v0.1
has no in-flight transitions per G-002 — branching is post-hoc; Phase 2+
can fork without schema change). set_branch() resolves the branch outcome
from the classifier; debrief_focus() returns the per-branch focus.

TASK-03-07: server/pipeline.py build_pipeline() now accepts a scenario_id,
loads the runtime, and uses scenario.setup.system_prompt instead of the
SLICE-02 hardcoded walking-skeleton prompt. Falls back gracefully if the
scenario can't load. server/__main__.py passes
PRAXIS_SCENARIO=customer_service_refund_ca_v01 by default. 7 runtime tests
pass (system prompt, set_branch accept/escalate, unknown-branch error,
debrief_focus per branch, flows spec branches, debrief model config).

---ci---
phase: 1
milestone: v0.1
plan: 03
task: 03-03,03-07
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-SCEN-01, REQ-SCEN-FMT-01, REQ-ORCH-02]
---/ci---
This commit is contained in:
Praxis CI
2026-08-01 13:11:30 +00:00
parent be3df525d8
commit 30051fdfd6
4 changed files with 234 additions and 10 deletions
+16 -2
View File
@@ -75,7 +75,12 @@ async def health() -> dict[str, Any]:
@app.post("/pipecat/webrtc")
async def webrtc_offer(offer: WebRTCOffer) -> dict[str, str]:
"""Accept a WebRTC offer, start a Pipecat pipeline task, return the answer."""
"""Accept a WebRTC offer, start a Pipecat pipeline task, return the answer.
Loads the v0.1 scenario (customer_service_refund_ca_v01) so the pipeline
uses the scenario-driven system prompt + opening line (TASK-03-07).
"""
scenario_id = _env("PRAXIS_SCENARIO", "customer_service_refund_ca_v01")
try:
connection = SmallWebRTCConnection(
ice_servers=[{"urls": "stun:stun.l.google.com:19302"}],
@@ -84,11 +89,20 @@ async def webrtc_offer(offer: WebRTCOffer) -> dict[str, str]:
await connection.accept()
answer = connection.get_answer()
# Build + run the pipeline for this connection.
pipeline, task, runner, transport = build_pipeline(connection)
pipeline, task, runner, transport, scenario_runtime = build_pipeline(
connection, scenario_id=scenario_id
)
# Run the pipeline task in the background; the runner manages its lifecycle.
import asyncio
asyncio.create_task(runner.run(task))
# Play the opening line + disclaimer as the first AI utterance if the
# scenario loaded (the runner handles the actual TTS queueing).
if scenario_runtime is not None:
logger.info(
f"Session starting with scenario {scenario_id!r}; "
f"opening line: {scenario_runtime.opening_line[:60]!r}"
)
return {"sdp": answer["sdp"], "type": answer["type"]}
except Exception as exc:
logger.error(f"WebRTC offer failed: {exc}")