Provider Injection Examples#
Supafone keeps three choices independent:
| Choice | SDK field | What it controls |
|---|---|---|
| Speaking runtime | provider | The agent receiving caller audio and producing speech |
| Supervisor model | llm and oracle_model | The independent model that evaluates call state and writes a bounded directive |
| Injector adapter | inject_via or the speaking provider | The native control or developer-owned context that receives the directive |
The supervisor model is sometimes informally called the injector model. It does not write directly to a provider socket. It produces the instruction; Supafone's injector adapter compiles that instruction into the correct provider action. This separation lets Claude supervise an OpenAI Realtime agent, OpenAI supervise Grok, or a Supafone-managed Oracle supervise Ultravox without changing the caller-facing model.
Choose the supervisor model#
Supafone-managed, with no additional model key:
from supafone_labs import SupafoneLabs
supervisor = SupafoneLabs(
provider="gpt_realtime",
llm="hosted",
oracle_model="supafone-labs-oracle",
mode="return",
)Bring Anthropic, OpenAI, or xAI:
claude_supervisor = SupafoneLabs(
provider="gpt_realtime",
llm="anthropic",
oracle_model="claude-haiku-4-5-20251001",
mode="return",
)
openai_supervisor = SupafoneLabs(
provider="grok",
llm="openai",
oracle_model="gpt-4.1-mini",
mode="return",
)
grok_supervisor = SupafoneLabs(
provider="ultravox",
llm="xai",
oracle_model="grok-4-fast",
mode="return",
)Credentials stay in SUPAFONE_LABS_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, or XAI_API_KEY. They never belong in browser code, prompts, or provider events.
One delivery loop#
Every integration follows the same sequence:
result = await supervisor.observe(raw_provider_event)
for action in result.actions:
await deliver_to_active_call(action.payload)action.payload is already the provider-native wire object. Do not turn it back into a generic prompt patch.
OpenAI Realtime#
OpenAI Realtime accepts a system-role conversation.item.create. Send the compiled payload unchanged over the active Realtime WebSocket:
supervisor = SupafoneLabs(
provider="gpt_realtime",
llm="anthropic",
oracle_model="claude-haiku-4-5-20251001",
mode="return",
)
async for raw in openai_socket:
result = await supervisor.observe(json.loads(raw))
for action in result.actions:
if action.kind == "conversation_item_create":
await openai_socket.send(json.dumps(action.payload))The accepted action adds silent system context and does not issue a separate response.create. See the complete @@LABSTOKEN1@@.
Grok Voice Agent#
Grok accepts per-response instructions on response.create. The instruction applies to the next response and then expires:
supervisor = SupafoneLabs(
provider="grok",
llm="openai",
oracle_model="gpt-4.1-mini",
mode="return",
)
async for raw in grok_socket:
result = await supervisor.observe(json.loads(raw))
for action in result.actions:
if action.kind == "response_create":
await grok_socket.send(json.dumps(action.payload))The wire payload is response.create.instructions, not an OpenAI-style standing session mutation. See @@LABSTOKEN1@@.
Gemini Developer Live#
Gemini Live's initial systemInstruction is session configuration. Its mid-session clientContent and realtime text messages are ordinary conversation content, not a documented hidden instruction channel. The Gemini native socket does not receive a hidden action from the adapter:
observer = SupafoneLabs(
provider="gemini_live",
llm="hosted",
oracle_model="supafone-labs-oracle",
mode="return",
)
result = await observer.observe(raw_gemini_event)
if result.directive:
record_for_live_qa(result.directive)
assert result.actions == []For live Gemini steering, run Gemini inside a developer-owned framework such as LiveKit or Pipecat and select that host as the injector adapter:
supervisor = SupafoneLabs(
provider="gemini_live",
inject_via="livekit",
llm="hosted",
oracle_model="supafone-labs-oracle",
mode="return",
)
result = await supervisor.observe(raw_gemini_event)
for action in result.actions:
if action.kind == "chat_context_append":
await append_to_livekit_context(action.payload)The host-owned context is the injector; Supafone does not disguise a visible Gemini user turn as a system directive. See @@LABSTOKEN0@@ and @@LABSTOKEN1@@.
Ultravox#
Ultravox receives a deferred user_text_message with urgency="later":
supervisor = SupafoneLabs(
provider="ultravox",
llm="hosted",
oracle_model="supafone-labs-oracle",
mode="return",
)
result = await supervisor.observe(raw_ultravox_event)
for action in result.actions:
if action.kind == "inject_message":
await ultravox_socket.send(json.dumps(action.payload))See @@LABSTOKEN0@@.
Vapi#
Enable monitorPlan.controlEnabled so Vapi includes the live call's controlUrl. Post Supafone's add-message payload to that URL:
supervisor = SupafoneLabs(provider="vapi", llm="hosted", mode="return")
result = await supervisor.observe(vapi_webhook)
for action in result.actions:
if action.kind == "control_add_message":
response = await http.post(control_url, json=action.payload)
response.raise_for_status()See @@LABSTOKEN0@@.
LiveKit and Pipecat#
These frameworks run in your process, so your application owns the model context. LiveKit actions append a system message to ChatContext; Pipecat actions push an LLMMessagesAppendFrame with run_llm=false:
livekit_supervisor = SupafoneLabs(provider="livekit", llm="hosted", mode="return")
pipecat_supervisor = SupafoneLabs(provider="pipecat", llm="hosted", mode="return")Use the complete @@LABSTOKEN0@@ and @@LABSTOKEN1@@ for the host SDK calls that apply each action.
Failure behavior#
The supervisor remains off the audio path. A timeout, low-confidence directive, unsupported injector, or provider error produces no action; the speaking agent continues normally. Check the Framework Coverage matrix before treating event normalization as proof of live injection support.