AI features in Dart and Flutter apps usually start with a provider call. That is also where a few uncomfortable questions begin:
- Does a user prompt contain an email address, card number, or API key?
- Is retrieved RAG content trying to override the system prompt?
- Is streamed output about to leak a secret or produce unsafe tool-call arguments?
- Can we add these protections without sending user data to another service?
I built ai_guardrails to make those checks a small, local layer around any LLM integration.
It is a pure-Dart, provider-agnostic package for Dart and Flutter. It has no runtime dependencies and does not make network calls by itself. Use it with OpenAI, Gemini, Anthropic, a local model, or your own HTTP gateway.
The basic idea
Create a scanner chain for input and output, then wrap the call you already have:
import 'package:ai_guardrails/ai_guardrails.dart';
final guard = AiGuard(
inputScanners: [
PiiScanner(action: GuardAction.redact),
SecretScanner(),
PromptInjectionScanner(threshold: 0.5),
InvisibleTextScanner(),
],
outputScanners: [
RepetitionScanner(),
SchemaValidator({
'type': 'object',
'required': ['answer'],
'properties': {
'answer': {'type': 'string'},
},
}),
],
);
final outcome = await guard.run(
input: userMessage,
llmCall: (sanitizedInput) => myLlm.complete(sanitizedInput),
);
if (outcome.blocked) {
print('Blocked at ${outcome.blockedStage}: ${outcome.blockReason}');
return;
}
print(outcome.output);
Redacting scanners run in order, so the model receives the final sanitized input. If an input scanner blocks, the provider call never happens.
What it covers
ai_guardrails includes local heuristic scanners for common LLM-risk surfaces:
- PII detection and redaction across US, EU, India, Brazil, Mexico, Japan, South Korea, Canada, and Australia
- Secret detection for API keys, tokens, JWTs, and private-key blocks
- Prompt-injection, Unicode-smuggling, suspicious URL, and padding-attack detection
- Generated-code, SQL, HTML, JSON, URL, numeric-range, and choice validation
- Tool-call validation and tool-output scanning for agentic workflows
- RAG retrieval scanning, streaming response scanning, multi-turn escalation, and declarative conversation flows
- Policy profiles, JSON configuration, audit logs, metrics, OpenTelemetry-compatible tracing, benchmarking, and red-team probing
The package also supports optional async scanners. Semantic checks such as fact checking, topic safety, hallucination checking, and embedding grounding use callbacks you provide, so the core package stays provider-neutral.
PII without sacrificing a natural response
For many chat experiences, redacting PII before it leaves the device is the safe choice—but users still expect a natural response. ai_guardrails supports a round trip for that case:
final guard = AiGuard(
inputScanners: [PiiScanner(action: GuardAction.redact)],
);
final outcome = await guard.run(
input: 'Email alice@example.com about the release.',
llmCall: myLlm.complete,
);
// The model sees: "Email [EMAIL_1] about the release."
// outcome.output can restore the placeholder for the app.
// outcome.rawOutput preserves the model's pre-rehydration response.
That behavior is deliberate and should be used only where returning the original value to the user is appropriate. The redaction map and raw output remain available when an application needs tighter control.
Designed for the pipeline, not just a single prompt
LLM safety does not end at a chat textbox. The package has first-class stages for the inputs an application assembles around a model call:
// Drop poisoned or unsafe RAG chunks before prompt assembly.
final retrieval = await guard.runRetrievalStage(retrievedChunks);
// Scan untrusted tool results before returning them to the model.
final toolResults = await guard.runToolOutputStage([
ToolOutput(toolName: 'search', content: untrustedSearchResult),
]);
For streaming applications, StreamingAiGuard scans complete response segments as they arrive and ends the stream when a scanner blocks. For whole-document rules such as JSON schema validation, run a final full-output pass after the stream completes.
What this package is—and is not
The built-in local scanners are deterministic heuristics. They are intentionally fast, offline, and inexpensive, but they do not understand every meaning, evasion, or cultural context. Treat them as defense in depth, not as a compliance certification or a substitute for application authorization.
For semantic checks, the package exposes callback boundaries rather than embedding a provider SDK. Your app decides whether to use an on-device model, a private endpoint, or a cloud model—and owns the associated privacy and cost choices.
Try it
dart pub add ai_guardrails
The package is open source under Apache-2.0. The README includes provider examples, configuration-driven policies, and a full scanner reference.
I would especially love feedback from teams building Flutter copilots, RAG search, local-model experiences, and agentic tools. What safety checks are you currently reimplementing in every Dart project?
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.