+
+
+
+
+ {dropdownOpen && available.length > 0 && (
+ <>
+
setDropdownOpen(false)}
+ />
+
+ {available.map((v) => (
+
+ ))}
+
+ >
+ )}
+
+
+ {guardrails.length === 0 ? (
+
No validators added
+ ) : (
+
+ {guardrails.map((g) => (
+
+
+ {getValidatorName(g.validator_config_id)}
+
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/app/lib/data/guardrails/validators.ts b/app/lib/data/guardrails/validators.ts
new file mode 100644
index 00000000..d250defa
--- /dev/null
+++ b/app/lib/data/guardrails/validators.ts
@@ -0,0 +1,86 @@
+import type { ValidatorMeta } from "@/app/lib/types/guardrails";
+
+export const VALIDATOR_META: readonly ValidatorMeta[] = [
+ {
+ validator_type: "ban_list",
+ validator_name: "Ban List",
+ description:
+ "Validates that the output does not contain banned words using fuzzy search.",
+ },
+ {
+ validator_type: "gender_assumption_bias",
+ validator_name: "Gender Assumption Bias",
+ description:
+ "Detects gender assumption biases across different domains like healthcare and education.",
+ },
+ {
+ validator_type: "uli_slur_match",
+ validator_name: "Lexical Slur Match",
+ description:
+ "Detects and filters offensive slurs in multiple languages using lexical matching.",
+ },
+ {
+ validator_type: "llm_critic",
+ validator_name: "LLM Critic",
+ description:
+ "Uses an LLM to critique and evaluate the quality and safety of generated text.",
+ },
+ {
+ validator_type: "pii_remover",
+ validator_name: "PII Remover",
+ description:
+ "Detects and removes personally identifiable information (PII) from text.",
+ },
+ {
+ validator_type: "llamaguard_7b",
+ validator_name: "Llamaguard 7B",
+ description:
+ "Uses Meta's Llamaguard 7B model to detect unsafe or harmful content in text.",
+ },
+ {
+ validator_type: "profanity_free",
+ validator_name: "Profanity Free",
+ description:
+ "Detects and filters profane or offensive language from generated text.",
+ },
+ {
+ validator_type: "topic_relevance",
+ validator_name: "Topic Relevance",
+ description:
+ "Validates that the generated text stays on topic and is relevant to the given context.",
+ },
+] as const;
+
+export const KNOWN_ARRAY_OPTIONS: Record
= {
+ entity_types: [
+ "CREDIT_CARD",
+ "EMAIL_ADDRESS",
+ "IBAN_CODE",
+ "IP_ADDRESS",
+ "LOCATION",
+ "MEDICAL_LICENSE",
+ "NRP",
+ "PERSON",
+ "PHONE_NUMBER",
+ "URL",
+ "IN_AADHAAR",
+ "IN_PAN",
+ "IN_PASSPORT",
+ "IN_VEHICLE_REGISTRATION",
+ "IN_VOTER",
+ ],
+ languages: ["en", "hi"],
+};
+
+export const KNOWN_SINGLE_OPTIONS: Record = {
+ categories: ["generic", "healthcare", "education", "all"],
+};
+
+export const GUARDRAILS_FIELD_TOOLTIPS: Record = {
+ validator_type:
+ "Choose the validator you want to configure. Each validator enforces a specific rule on the input or output.",
+ stage:
+ 'Where this validator runs — "input" checks the user\'s message before it reaches the LLM, "output" checks the LLM\'s response before it is returned.',
+ on_fail_action:
+ '"fix" attempts to auto-remediate the violation, "exception" raises an error and blocks the response, "rephrase" asks the model to rewrite the output.',
+};
diff --git a/app/lib/guardrailsClient.ts b/app/lib/guardrailsClient.ts
new file mode 100644
index 00000000..97f617dd
--- /dev/null
+++ b/app/lib/guardrailsClient.ts
@@ -0,0 +1,87 @@
+import { NextRequest } from "next/server";
+
+const GUARDRAILS_URL = process.env.GUARDRAILS_URL || "http://localhost:8001";
+
+/**
+ * Server-side passthrough proxy to the Guardrails backend.
+ * Used by Next.js route handlers (/api/guardrails/*).
+ * Auth priority: GUARDRAILS_TOKEN env var → X-API-KEY + Cookie from request.
+ */
+export async function guardrailsClient(
+ request: NextRequest | Request,
+ endpoint: string,
+ options: RequestInit & { skipEnvToken?: boolean } = {},
+) {
+ const { skipEnvToken, ...fetchOptions } = options;
+ const headers = new Headers(fetchOptions.headers);
+ if (!(fetchOptions.body instanceof FormData)) {
+ headers.set("Content-Type", "application/json");
+ }
+
+ const token = !skipEnvToken && process.env.GUARDRAILS_TOKEN;
+ if (token) {
+ headers.set("Authorization", `Bearer ${token}`);
+ } else {
+ const apiKey = request.headers.get("X-API-KEY") || "";
+ const cookie = request.headers.get("Cookie") || "";
+ if (apiKey) headers.set("X-API-KEY", apiKey);
+ if (cookie) headers.set("Cookie", cookie);
+ }
+
+ const response = await fetch(`${GUARDRAILS_URL}${endpoint}`, {
+ ...fetchOptions,
+ headers,
+ });
+
+ const text = response.status === 204 ? "" : await response.text();
+ const data = text ? JSON.parse(text) : null;
+
+ return { status: response.status, data };
+}
+
+/**
+ * Variant of guardrailsClient that always forwards the caller's API key/cookie
+ * and never falls back to GUARDRAILS_TOKEN. Use for routes that must act on
+ * behalf of the end user (e.g. ban_lists).
+ */
+export function guardrailsUserClient(
+ request: NextRequest | Request,
+ endpoint: string,
+ options: RequestInit = {},
+) {
+ return guardrailsClient(request, endpoint, {
+ ...options,
+ skipEnvToken: true,
+ });
+}
+
+/**
+ * Client-side fetch helper for guardrails Next.js route handlers (/api/guardrails/*).
+ * Parses JSON and throws on non-OK responses.
+ */
+export async function guardrailsFetch(
+ path: string,
+ options: RequestInit = {},
+): Promise {
+ const headers = new Headers(options.headers);
+ if (!(options.body instanceof FormData) && !headers.has("Content-Type")) {
+ if (options.body) headers.set("Content-Type", "application/json");
+ }
+
+ const res = await fetch(path, {
+ ...options,
+ headers,
+ credentials: "include",
+ });
+
+ if (!res.ok) {
+ const body = (await res.json().catch(() => ({}))) as Record<
+ string,
+ unknown
+ >;
+ throw new Error((body?.error as string) ?? `Request failed: ${res.status}`);
+ }
+
+ if (res.status === 204) return undefined as unknown as T;
+ return res.json() as Promise;
+}
diff --git a/app/lib/navConfig.ts b/app/lib/navConfig.ts
index 57e999a4..76a535f7 100644
--- a/app/lib/navConfig.ts
+++ b/app/lib/navConfig.ts
@@ -43,4 +43,10 @@ export const NAV_ITEMS: NavItemConfig[] = [
],
gateDescription: "Log in to manage prompts and model configurations.",
},
+ {
+ name: "Guardrails",
+ route: "/guardrails",
+ icon: "shield",
+ gateDescription: "Log in to manage guardrails and validators.",
+ },
];
diff --git a/app/lib/types/configs.ts b/app/lib/types/configs.ts
index 971f6afa..659bf0e5 100644
--- a/app/lib/types/configs.ts
+++ b/app/lib/types/configs.ts
@@ -19,6 +19,8 @@ export interface SavedConfig {
vectorStoreIds: string;
tools?: Tool[];
commit_message?: string | null;
+ input_guardrails?: GuardrailRef[];
+ output_guardrails?: GuardrailRef[];
}
export interface ConfigGroup {
@@ -80,8 +82,17 @@ export interface CompletionConfig {
params: CompletionParams;
}
+export interface GuardrailRef {
+ validator_config_id: string;
+}
+
export interface ConfigBlob {
completion: CompletionConfig;
+ prompt_template?: {
+ template: string;
+ };
+ input_guardrails?: GuardrailRef[];
+ output_guardrails?: GuardrailRef[];
}
// Request Types
diff --git a/app/lib/types/guardrails.ts b/app/lib/types/guardrails.ts
new file mode 100644
index 00000000..b493db92
--- /dev/null
+++ b/app/lib/types/guardrails.ts
@@ -0,0 +1,64 @@
+export interface ValidatorMeta {
+ validator_type: string;
+ validator_name: string;
+ description: string;
+}
+
+export interface ValidatorConfigSchema {
+ title: string;
+ type: string;
+ properties: Record<
+ string,
+ {
+ type?: string;
+ title?: string;
+ default?: unknown;
+ enum?: string[];
+ anyOf?: unknown[];
+ items?: unknown;
+ $ref?: string;
+ const?: string;
+ }
+ >;
+ $defs?: Record<
+ string,
+ {
+ enum?: string[];
+ type?: string;
+ }
+ >;
+ required?: string[];
+ additionalProperties?: boolean;
+}
+
+export interface Validator {
+ type: string;
+ config: ValidatorConfigSchema;
+}
+
+export interface SavedValidatorConfig {
+ id: string;
+ name: string;
+ type: string;
+ config?: Record;
+ stage?: string;
+ on_fail_action?: string;
+ is_enabled?: boolean;
+ created_at?: string;
+ updated_at?: string;
+ organization_id?: number;
+ project_id?: number;
+ [key: string]: unknown;
+}
+
+export function formatValidatorName(type: string): string {
+ return type
+ .split("_")
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
+ .join(" ");
+}
+
+export interface OrgContext {
+ organization_id: number;
+ project_id: number;
+}
diff --git a/app/lib/utils.ts b/app/lib/utils.ts
index f3435ff6..27c28c55 100644
--- a/app/lib/utils.ts
+++ b/app/lib/utils.ts
@@ -129,6 +129,8 @@ export const flattenConfigVersion = (
vectorStoreIds: tools[0]?.knowledge_base_ids?.[0] || "",
tools,
commit_message: version.commit_message,
+ input_guardrails: blob.input_guardrails,
+ output_guardrails: blob.output_guardrails,
};
};
diff --git a/app/lib/utils/guardrails.ts b/app/lib/utils/guardrails.ts
new file mode 100644
index 00000000..d3743071
--- /dev/null
+++ b/app/lib/utils/guardrails.ts
@@ -0,0 +1,59 @@
+import { NextRequest } from "next/server";
+import type {
+ ValidatorConfigSchema,
+ ValidatorMeta,
+} from "@/app/lib/types/guardrails";
+import { VALIDATOR_META } from "@/app/lib/data/guardrails/validators";
+
+export const VALIDATOR_META_BY_TYPE: Record =
+ Object.fromEntries(VALIDATOR_META.map((v) => [v.validator_type, v]));
+
+/**
+ * Builds the backend endpoint for validator configs, forwarding
+ * organization_id and project_id query params from the incoming request.
+ */
+export function buildValidatorConfigEndpoint(
+ request: NextRequest,
+ config_id?: string,
+): string {
+ const { searchParams } = new URL(request.url);
+ const params = new URLSearchParams();
+ const organizationId = searchParams.get("organization_id");
+ const projectId = searchParams.get("project_id");
+ if (organizationId) params.append("organization_id", organizationId);
+ if (projectId) params.append("project_id", projectId);
+ const qs = params.toString();
+ const base = config_id
+ ? `/api/v1/guardrails/validators/configs/${config_id}`
+ : `/api/v1/guardrails/validators/configs`;
+ return `${base}${qs ? `?${qs}` : ""}`;
+}
+
+/**
+ * Resolves a `$ref` pointer like "#/$defs/GuardrailOnFail" against the
+ * provided `$defs` map and returns its `enum` values (or an empty array).
+ */
+export function resolveRef(
+ ref: string,
+ defs: ValidatorConfigSchema["$defs"],
+): string[] {
+ const key = ref.replace("#/$defs/", "");
+ return defs?.[key]?.enum ?? [];
+}
+
+/**
+ * Builds the initial form values from a validator's config schema by
+ * pulling each property's `default` (skipping `type`).
+ */
+export function buildDefaultValues(
+ schema: ValidatorConfigSchema,
+): Record {
+ const values: Record = {};
+ for (const [key, prop] of Object.entries(schema.properties)) {
+ if (key === "type") continue;
+ if ("default" in prop) {
+ values[key] = prop.default;
+ }
+ }
+ return values;
+}
diff --git a/package-lock.json b/package-lock.json
index 3aa4d57b..8b01b53f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -79,6 +79,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -1596,6 +1597,7 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -1655,6 +1657,7 @@
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
@@ -2154,6 +2157,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2495,6 +2499,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -2810,6 +2815,7 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
+ "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
@@ -3179,6 +3185,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3364,6 +3371,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -5836,6 +5844,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5845,6 +5854,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -6738,6 +6748,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -6900,6 +6911,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -7200,6 +7212,7 @@
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}