Skip to main content

Guardrails

The SDK scans outbound prompts and inbound responses for things that usually shouldn't be in them — email addresses, national ID numbers, card numbers, phone numbers, API keys — and reacts according to the mode you set.

This runs in your process, before the request leaves it. A prompt blocked here never reaches the gateway, the provider, or your telemetry.

Modes

observra.configure({
serviceName: "my-app",
guardrailMode: "block",
});

"warn" — default

Logs what matched and sends the request unchanged. Use it to find out what your prompts actually contain before you start enforcing anything.

"redact"

Masks the matched text before sending. The model sees a redacted prompt; your application code is untouched.

Redaction changes what the model receives, which can change its answer. Roll it out somewhere you can compare results.

"block"

Throws GuardrailViolation and never sends the request.

import * as observra from "observra-sdk-node";

try {
await client.chat.completions.create({
model: "llama-3.3-70b-versatile",
messages: [{ role: "user", content: "My SSN is 123-45-6789." }],
});
} catch (err) {
if (err instanceof observra.GuardrailViolation) {
console.error("blocked:", err.violations.map((v) => v.label).join(", "));
// -> blocked: ssn
}
}

The error carries every match, so you can tell the user precisely what to remove.

Built-in patterns

LabelMatches
emailEmail addresses
ssnUS Social Security numbers, 123-45-6789
credit_card13–16 consecutive digits
credit_card_groupedCard numbers written in groups, 4111 1111 1111 1111
phonePhone numbers, 555-123-4567
phone_parensPhone numbers with an area code in parentheses
api_key_tokenKeys with common prefixes — sk_, pk_, gsk_, fw_, obs_

These are regular expressions, which means two honest limitations:

  • False positives happen. credit_card matches any run of 13–16 digits, and plenty of order numbers look like that. Start on "warn" and see what fires.
  • They are not a compliance control. They catch the common, well-formed shapes. A determined user, or an unusual format, will get through. Treat this as a safety net, not a boundary.

The streaming asymmetry

This is the one behaviour that surprises people.

Input (your prompt)Output (model response)
Non-streamed callEnforced — block blocksEnforced — block blocks
Streamed callEnforced — block blocksAdvisory only

With streaming, chunks reach your code as they arrive. By the time the full response exists to be scanned, you already have every byte of it. There is no point at which the SDK could withhold it without buffering the entire response first — which would defeat streaming.

So streamed output is scanned to annotate the trace, never to alter or withhold what your code received. A violation is visible in the dashboard afterwards; it does not stop anything.

If you need enforced output guardrails, don't stream.

Performance

Scanning is bounded: a payload is scanned up to a fixed length and no further, so a pathological prompt can't stall your request path. Beyond that limit, content is unscanned rather than the request being delayed.

What guardrails do not do

  • They don't scan tool results you inject yourself. Content you add to the message array is scanned as part of the outbound prompt on the next call, like any other message — but there's no separate hook around tool execution.
  • They don't replace the gateway's own checks. Observra can also run guardrail rules server-side, configured per Environment in the dashboard. SDK guardrails catch things before they leave your process; gateway guardrails apply to every client regardless of SDK. They complement each other.
  • They don't inspect binary content. Images, audio, and files are not scanned.

A sensible rollout

  1. Ship with "warn" and leave it for a week.
  2. Read what fired. Expect false positives on credit_card and phone.
  3. Move to "redact" or "block" once you know the shape of your own traffic.

Going straight to "block" on a busy application tends to break legitimate requests on day one.

Next