Integrations
Reflex Layer sits between your language model and your text-to-speech. Every integration does the same job: it holds the model's text one sentence at a time, checks it, and passes on either the sentence or the fallback line.
Available now: the HTTP API and the Vapi proxy. On request: Pipecat, LiveKit and the Node client, until those packages are published. There is no public download or package registry entry yet; email support if you need one of those clients.
How streaming checks work
- The first sentence is checked with the caller signals (intent, handoff, mood, end of turn).
- Each later sentence is checked together with everything already approved in the reply, so rules that depend on the whole reply still work. These later checks skip the signals to save time.
- If a sentence is blocked, the fallback line is spoken instead, the rest of the reply is dropped, and the model's stream is closed.
- The first check returns a
continuationToken. The clients send it, withalreadySpoken(characters already spoken), on each later check of the same reply, so all of them show up as one turn in the call log and are billed as one check (each follow-up after the 12th in one reply is billed as one more). The token is signed, tied to the reply's first sentence, and expires 2 minutes after the first check. - The first sentence waits for its check, which typically adds one check's latency before the agent starts speaking. Later sentences are checked while the earlier ones are being spoken.
- A check can carry up to 4,000 characters of draft. If one reply runs longer, the clients check each later sentence on its own (each billed as one check) rather than skipping it.
- Set
mode="first_sentence"to check only the first sentence. This is cheaper and faster, but it gives less protection.
base_url (or the REFLEX_BASE_URL environment variable) is required. If Reflex Layer can't be reached within the client timeout (600 ms by default), or answers with a server error, a quota error (402) or a rate limit (429), the client logs a warning and follows its on_unavailable policy: "pass" speaks the draft, and "fallback" speaks your fallback line.
Pipecat
pip install "reflex-layer[pipecat]" # pre-release: pip install ./integrations/python[pipecat]
from reflex_layer import ReflexClient
from reflex_layer.pipecat import ReflexLayer
reflex = ReflexLayer(ReflexClient(api_key=os.environ["REFLEX_API_KEY"], agent_id="agt_...", base_url="https://YOUR-REFLEX-HOST"))
pipeline = Pipeline([
transport.input(),
stt,
reflex.caller(), # remembers what the caller said
user_aggregator,
llm,
reflex.reply(), # holds each sentence until it's approved
tts,
transport.output(),
assistant_aggregator, # records what was actually said
])
Create one ReflexLayer per call. Pass on_outcome= to receive each reply's result, for example to start a transfer when outcome.handoff_recommended is true. Interruptions cancel any pending check.
Tested with pipecat-ai 1.15.0.
LiveKit Agents
pip install "reflex-layer[livekit]" # pre-release: pip install ./integrations/python[livekit]
from livekit.agents import Agent
from reflex_layer import ReflexClient
from reflex_layer.livekit import ReflexGuardMixin
class FrontDesk(ReflexGuardMixin, Agent):
def __init__(self):
super().__init__(instructions="You are the front desk for Bright Smile Dental.")
self.reflex_setup(ReflexClient(agent_id="agt_...", base_url="https://YOUR-REFLEX-HOST"))
The mixin overrides llm_node, so the approved text is what reaches TTS, the transcript and the chat history. Tool calls pass through, unless the reply they belong to was blocked. If you already have a custom llm_node, move its logic into reflex_upstream(). After each reply, self.reflex_last holds the outcome.
Tested with livekit-agents 1.8.11.
Vapi
Reflex Layer can act as Vapi's Custom LLM. Vapi sends its OpenAI-style request to Reflex Layer. We forward it to your own model, check each sentence, and stream approved text back to Vapi.
In the dashboard, open Connect and scroll to Vapi proxy: upstream model. Choose the agent and enter your model's OpenAI-compatible base URL (for example
https://api.openai.com/v1), the model name, and its API key. The key is encrypted and never shown again.In Vapi, set the assistant's model provider to Custom LLM with this URL:
https://YOUR-REFLEX-HOST/v1/vapi/agt_...Then add your Reflex Layer API key as the bearer token.
Place a test call. Blocked turns appear in the call log within a few seconds.
Tool calls are passed through unchanged. Non-streaming requests are also supported, and the whole reply is checked at once.
Generic HTTP
Use POST /v1/check from any backend. Two rules matter:
- Set a client timeout (we suggest 600 ms) and decide what happens on timeout. Speaking the draft is the usual choice.
- Speak
say, not your draft, wheneverdecisionisblock.
import { ReflexClient } from "reflex-layer"; // pre-release: integrations/node
const reflex = new ReflexClient({ agentId: "agt_...", baseUrl: "https://YOUR-REFLEX-HOST" }); // REFLEX_API_KEY from the environment
const r = await reflex.checkSafe({ callId, turnIndex, callerText, draftReply });
speak(r.say);
if (r.signals?.handoff?.recommended) offerTransfer();
To check streamed text in Node, use reflex.guardStream(chunks, { callId, callerText, turnIndex }). It yields the text to speak.
Webhooks
Set a webhook under Connect to receive turn.blocked, turn.review and call.handoff events, for example to text the office manager when a caller needs a callback. Always verify the signature:
from reflex_layer import verify_webhook
event = verify_webhook(request.body, request.headers["x-reflex-signature"], WEBHOOK_SECRET)
import { verifyWebhook } from "reflex-layer";
const event = verifyWebhook(rawBody, req.headers["x-reflex-signature"], process.env.REFLEX_WEBHOOK_SECRET);
Both reject a bad signature, or a timestamp more than five minutes old.