Exam Room · Advanced Generative AI Developer

Choosing a Guardrail Strategy: Managed, Custom, or Both

· 37 min read

Generative AI Development · part of The Exam Room

The situation

A team is putting a generative-AI assistant into production. It answers questions, summarises documents, and drafts responses, all on Amazon Bedrock. Legal, security, and the product owner each hand over a list of things the assistant must never do. The lists do not look alike.

Some entries are the usual suspects. No hate speech, no sexual content, no leaking a customer’s email address or card number, no following user text that overrides the system instructions. Others are specific to this business. Never quote a price outside the published rate card, never name a competitor, never emit a response that fails the internal disclosure template, never mention the unreleased product code name before launch day. A few are structural: the drafting feature has to return valid JSON with a fixed set of fields, or the downstream system rejects it.

Bedrock Guardrails is right there, managed and quick to switch on. What matters is whether it covers the whole list, and if not, what fills the gap and where the two layers meet.

What actually matters

The first cut is whether a rule is a standard category or a bespoke one. Standard categories are the same for every customer. Hate, insults, sexual content, violence, misconduct, common PII types, prompt-injection shapes, generic profanity: a managed service can be trained and tuned on those once and applied everywhere. Bespoke rules encode something only this business holds. Its rate card, its competitor list, its disclosure template, its unreleased code name. A generic content filter was never trained on any of them, and configuration alone will not add a rule that lives in a spreadsheet.

The split is not quite clean, because one managed policy does reach written business rules. Automated Reasoning checks, generally available in three US Regions and three EU Regions, builds a policy by extracting formal logic rules from a source document you upload. At runtime it validates a response against those rules mathematically and returns findings, including unstated assumptions where the response left a rule unaddressed. It operates in detect mode only, so the decision to serve, rewrite or reject still sits in your code. It supports English (US) only, does not work with streaming APIs, and takes source documents up to 5 MB and 50,000 characters. A disclosure template or an eligibility rule set fits that shape. A price that changes weekly does not.

The second property is where a check runs and what it can reach. A managed guardrail sits between the application and the model and evaluates text: the prompt going in, the completion coming out. A custom check does whatever code does. It can call an authoritative service, look a value up in a database, parse output against a schema, or compare a quoted figure to the live rate card. Where the risky output is a computed number, the strongest control removes the failure class instead of filtering it. Have the model emit a query the database executes, so a text-to-SQL transformation returns a result from the system of record rather than a figure the model generated.

There is a boundary in the managed layer worth knowing before you draw any lines. Guardrails policies evaluate user messages, system prompts and model text responses. In tool-use workloads they do not evaluate tool results, tool definitions, or the arguments the model generates for a tool call. PII the model writes into a tool call argument is neither blocked nor masked. Everything that moves through function calling needs its own checks in code.

Then there is coverage on each side of the model. Input filtering stops a bad request before the model is invoked and before the token spend. Output filtering catches what the model produced, which is the only place a wrong price or a leaked code name appears. Some managed policies run on one side only. The prompt-attack filter is configured for input alone, and the contextual grounding check needs a model response, so it runs on output.

The last two properties are the running costs. Every check adds latency and money. A managed guardrail call, a Comprehend call, a database lookup: each has its own price and its own delay, and they stack on every request. Custom logic is also code somebody owns forever. A competitor list goes stale, a regex rots, a schema drifts from the downstream contract. Managed policies move maintenance to AWS and give up some control. Custom checks keep control and carry the maintenance. A defensible design puts the standard categories in the managed layer and reserves custom code for the rules that need it, rather than rebuilding hate-speech detection by hand.

What we’ll filter on

  1. Standard or bespoke: is the rule a common category any customer would share, or does it encode this business’s own knowledge?
  2. Ground truth: does enforcing it need a live value the model does not carry, so that a deterministic check outside the model is the only reliable enforcer?
  3. Coverage: does the control run on the input, the output, or both, and does the rule need both sides?
  4. Latency and cost: what does each check add to the per-request budget in milliseconds and dollars?
  5. Maintenance burden: who owns the logic over time, and how fast does it go stale if nobody tends it?

The landscape

Amazon Bedrock Guardrails (managed). A configurable safety layer that sits between the application and the model, applied to the input and the output. The guardrail is configured separately from the model, so one configuration is reusable across applications; check the model card to confirm a given Bedrock model supports guardrails at inference. Each guardrail has a working draft plus published versions, so a tested configuration can be promoted. The policies cover the standard categories:

  • Denied topics, defined in natural language, so the guardrail blocks whole subjects without you enumerating every phrasing. The default quota is 30 topics per guardrail.
  • Content filters across hate, insults, sexual content, violence and misconduct, each with a strength set independently for prompts and responses, plus a prompt-attack filter for jailbreak and prompt-injection attempts. The Standard tier adds prompt-leakage detection and extends detection into code comments, identifiers and string literals.
  • Word filters: exact-match block lists, plus a managed profanity list. The default quota is 10,000 words per policy.
  • Sensitive-information filters that block or mask PII, using built-in PII types and custom regex patterns, configured separately for input and output. The default quota is 30 regex patterns of up to 500 characters each, and lookaround is not supported.
  • Contextual grounding checks, which score a response for grounding against a supplied source and for relevance against the user’s query, each on a threshold between 0 and 0.99. The source is capped at 100,000 characters, the query at 1,000, and the response at 5,000.
  • Automated Reasoning checks, which validate a response against formal logic rules extracted from a document you upload, and return findings rather than blocking.

Guardrails is also reachable through the standalone ApplyGuardrail API, which evaluates arbitrary text against a guardrail without invoking a model. You can screen content that never goes near Bedrock, or check output from a model hosted elsewhere, and still get the managed policy layer. The custom-regex hook and the word lists let the managed layer absorb a slice of the bespoke work, as long as the rule fits a pattern or a list.

Custom checks (your own code). Everything the managed policies do not cover. This is where business-specific rules live: comparing a quoted price against the live rate card, checking a draft against the current competitor list, enforcing the disclosure template, blocking the unreleased code name. It is where deterministic validators belong. Parsing tool arguments or a drafting response against a strict JSON schema and rejecting anything non-conforming is a hard, repeatable check, and a language model gives no guarantee on it. Custom code is also how you reach a purpose-built service when detection needs more than a filter. Amazon Comprehend offers entity recognition, dominant-language detection, its own PII detection and redaction, and toxicity detection through DetectToxicContent, though toxicity detection is English-only and takes at most ten strings of 1 KB per call. Custom classifiers you have trained cover a category no generic filter handles. Custom checks run wherever you put them, and they can act on ground truth the model was never given.

Orchestrated moderation workflows (Step Functions and Lambda). Once there are more than a couple of custom checks, how they are wired becomes a design decision of its own. AWS Step Functions and Lambda implement a moderation workflow as a state machine rather than a single handler. A Parallel state fans the independent checks out so they run at once: a Comprehend branch doing PII and toxicity detection, an ApplyGuardrail call, a trained safety classifier. The state machine joins the verdicts and decides what happens next. Branching, per-check retries with backoff, and a human-review branch become configuration in the definition instead of nesting inside somebody’s handler. The cost is a hop. An Express workflow is billed per request plus duration and memory, and adds its own latency to every call, so for two or three checks that always run in the same order a plain Lambda chain is cheaper and quicker. Reach for the orchestration once the path has genuine branching, checks worth retrying independently, or a step that waits. A step that waits changes the shape of the whole call. An Express execution times out at five minutes, so a human-review branch needs a Standard workflow, billed per state transition, behind an asynchronous API that returns a job identifier and delivers the verdict later.

Both, layered as defence in depth. The strong pattern is managed guardrails carrying the common categories, hate, PII, prompt attacks, denied topics, grounding, with custom checks carrying the rules that are specific to the business or that need deterministic enforcement. The two stack, so a gap in one is covered by the other. Managed covers the breadth at low marginal effort; custom covers the depth the managed layer cannot reach. The same defence-in-depth reasoning runs through the prompt-injection design, where no single control is sufficient and the layers work because they are independent.

Evaluation

Side by side

Property Managed Bedrock Guardrails Custom checks Both, layered Orchestrated moderation workflow
Standard categories (hate, PII, prompt attacks)
Bespoke rules on live data (rate card, competitor list)
Formal-logic validation of a written rule set
Deterministic output-schema validation
Grounding and relevance scoring
Covers tool results and tool-call arguments
Managed policies on text with no model call (ApplyGuardrail)
Detection models maintained by AWS ✓ (partly)
Low ongoing maintenance burden ✓ (partly)
Reaches external ground truth (Comprehend, a DB)
Independent checks run in parallel, with per-check retries
Human-review branch for borderline cases
Stays a real-time validation mechanism on one hop

Read the first two rows together: neither column alone covers both. Managed guardrails hold the standard categories and have no access to the bespoke ones; custom checks hold the bespoke rules, but rebuilding hate-speech or prompt-attack detection by hand is wasted effort. The third row is the nuance people miss, because a written rule set can go to the managed layer after all. The “both” column ticks rules from every list the team was handed, and no single-layer column does. The fourth column runs that layered design rather than replacing it. The three rows above the last one are where an orchestrated workflow separates itself from a chain of inline checks, and the last row is what the separation costs.

The solution

Start by sorting each rule into standard or bespoke, because the sort does most of the work. Hate, insults, sexual content, violence, misconduct, common PII, generic profanity and prompt-injection shapes are standard. They go to the managed content filters, the sensitive-information filter and the prompt-attack filter, tuned by strength rather than reimplemented. The rate card, the competitor list and the unreleased code name are bespoke. A few rules sit on the line and the managed layer absorbs them at low effort: a fixed code name is a word-filter block-list entry, and a structured internal identifier is a custom-regex PII pattern. Push a rule into the managed layer whenever it fits a block list or a regex, within the per-guardrail quotas, and keep the truly dynamic ones in code, where a price change or a new competitor does not mean re-tuning a guardrail.

For the bespoke rules, decide what ground truth each one needs. A rule that only needs pattern matching stays a simple validator. A rule that needs the current price or the live competitor list needs a lookup against the system of record, run as an output check after the model has produced its draft, because the violation exists only in the generated text. A rule written down as a policy document, such as the disclosure template, is a candidate for an Automated Reasoning policy, provided you act on the findings yourself. A rule that needs specialised detection the managed filters do not offer, entity extraction or a trained domain classifier, calls out to Comprehend or to your own model. Deterministic structure is separate again: validate the output against a JSON schema in code and reject non-conforming responses outright, the same way you would validate any untrusted input.

Before any of this, there is a tier that never reads a prompt. AWS WAF sits on the API Gateway REST API stage with managed rule groups for the common web categories, and rate-based rules that shed a client hammering the assistant on a trailing five-minute count. API Gateway request validation checks that required parameters are present and that the body matches a JSON Schema model, returning a 400 before the integration runs, so a malformed request never reaches a Lambda function or a model call. Usage plans and API keys throttle per key, though AWS states those limits are best-effort and not a cost control, so WAF and AWS Budgets do that job. On the way back, the integration response mapping strips headers and fields the caller has no business seeing. The limit is worth stating plainly: the edge sees volume and shape, never prompt semantics. A well-formed request arriving at a reasonable rate with a jailbreak in the body passes every one of these controls.

Then place the checks on the right side of the model and mind the budget. On the input, strip the control characters and inline markup that exist only to steer the model, then run the guardrail’s content filters on the prompt to block prompt attacks and disallowed topics before invocation, which avoids the token spend on a request that would have been blocked anyway. One gotcha decides whether that works at all. With InvokeModel and InvokeModelWithResponseStream, wrap the user’s text in guardrail input tags. Without them the prompt-attack filter does not evaluate the user input, because a developer’s system instruction and a user’s attempt to override it look alike. On the output, run the guardrail again for PII, content violations and grounding, then run the custom checks that need the generated text. Order them to fail fast, so an expensive Comprehend call or database lookup only runs on requests that passed the cheap gates.

Use ApplyGuardrail where the text does not flow through a Bedrock InvokeModel call but still needs screening: content from another source, or output you want to check independently of the generation call. It extends the managed policy layer beyond the model invocation itself, which matters when the architecture is not a single call-and-response.

A word on maintenance, because it decides the long-run cost. Every custom check is code the team owns. The competitor list drifts, the disclosure template changes, the schema evolves with the downstream contract. Keep that surface as small as the rules allow. Move anything the managed layer can express, a block list, a regex, a denied topic, into the guardrail, where AWS maintains the detection models and you maintain only the configuration. Reserve custom code for the rules that need live ground truth or deterministic enforcement, and give each one an owner and a review cadence, so a stale competitor list does not become the weakest control without anyone noticing.

Worked example

The assistant’s document-drafting feature has to satisfy three of the handed-over rules at once: never quote a price off the rate card, never name a competitor, and always return valid JSON with a fixed set of fields. It also inherits the standard safety rules every feature carries.

The standard rules go to a managed guardrail associated with the drafting call. The prompt-attack filter and denied topics run on the input, with the user’s text wrapped in guardrail input tags. Content filters, the PII sensitive-information filter and the grounding check run on the output. The unreleased code name, a fixed string, goes into the guardrail’s word-filter block list. The internal reference-number format goes in as a custom-regex PII pattern set to mask on output. That is the slice of the list the managed layer carries, switched on by configuration and maintained by AWS.

The three feature-specific rules need code the guardrail cannot supply. After the model returns a draft, an output validator parses it against the JSON schema, and a response missing a field or malformed is rejected before it reaches the downstream system. A rate-card check pulls every figure out of the draft and compares it against the live rate-card service, failing the response if a quoted price is not on the current card. The guardrail has no access to that price list, so nothing in the managed layer can make that comparison. A competitor scan checks the draft against the current competitor list, held in a table an owner updates, and blocks a draft that names one. The checks are ordered cheap-first, so schema validation runs before the rate-card lookup and a malformed draft never triggers a call to the rate-card service.

The result is that no rule is enforced in the wrong place. Hate speech and PII run on AWS’s trained models. The rate card is a live lookup against the system of record. The JSON contract is a deterministic parser. Each rule sits where its shape and its ground truth put it, and the managed and custom layers together cover a list that neither covers alone.

What’s worth remembering

  1. Sort every rule into standard or bespoke first: standard categories go to managed Guardrails, business-specific rules go to custom code, and that sort decides most of the design.
  2. A rule that needs live ground truth, a current price or an up-to-date competitor list, needs a deterministic check outside the model; a rule written down as a document can instead go to Automated Reasoning checks, which return findings and leave the blocking to you.
  3. Managed policies stop at the text boundary. They do not evaluate tool results, tool definitions or model-generated tool arguments, and without guardrail input tags the prompt-attack filter skips the user input entirely.
  4. Layer the two: managed guardrails for breadth across the standard categories, custom checks for the depth they cannot reach, each running on the side of the model where the violation appears.
  5. Push a rule into the managed layer whenever it fits a block list, a regex or a denied topic, within the per-guardrail quotas, and give every remaining custom check an owner and a review cadence.

These posts are LLM-aided. Backbone, original writing, and structure by Craig. Research and editing by Craig + LLM. Proof-reading by Craig.