The config.params section stores key-value pairs that you can reference elsewhere in the configuration using $(conf.params.<KEY>). Values in this section support AI Vault references, making them the correct place to store API keys, tokens, and other secrets.
The Azure custom example stores the API key and base URL as params:
params:
base_url: !env AZURE_CONTENT_SAFETY_URL
api_key: !env AZURE_CONTENT_SAFETY_KEY
The Mistral example stores both the API key and the model name:
params:
api_key: !env MISTRAL_MODERATION_TOKEN
model: mistral-moderation-2411
Put any value you need to reference in headers, body, or functions in params.
The config.request section defines the URL, headers, and body of the HTTP call to the guardrail service. Each field supports template variable substitution.
Azure custom guardrail request:
request:
url: "$(conf.params.base_url)/contentsafety/text:analyze?api-version=2023-10-01"
headers:
Ocp-Apim-Subscription-Key: "$(conf.params.api_key)"
Content-Type: application/json
body:
text: "$(content)"
categories: "[\"Hate\",\"SelfHarm\",\"Sexual\",\"Violence\"]"
outputType: "\"FourSeverityLevels\""
Mistral custom guardrail request:
request:
url: https://api.mistral.ai/v1/moderations
headers:
Authorization: "Bearer $(conf.params.api_key)"
Content-Type: application/json
body:
model: "$(conf.params.model)"
input: "$(content)"
The url field is the full URL of the guardrail API endpoint. For Mistral, it’s the standard moderation endpoint, written as a literal string. For Azure, the base URL comes from params.base_url and the template appends the fixed API path and query string. This split exists because kongctl’s !env tag resolves to the entire value of a field at apply time, so you can’t concatenate it with a literal suffix in the same YAML scalar. The $(conf.params.base_url) substitution runs separately, inside the AI Custom Guardrail Policy itself, at request time.
The headers section defines HTTP headers sent with the request. Use $(conf.params.<KEY>) to reference stored credentials. Notice how the Azure and Mistral APIs use different header names (Ocp-Apim-Subscription-Key vs Authorization) and formats (raw key vs Bearer token). The AI Custom Guardrail Policy handles both through template substitution.
The body section defines the JSON request body. All values are strings. The AI Policy replaces the $(content) variable with the text extracted from the chat messages (based on the text_source setting). For values that need to be JSON arrays or strings within the JSON body, you embed the JSON syntax in the string value. In the Azure example, categories is set to "[\"Hate\",\"SelfHarm\",\"Sexual\",\"Violence\"]" because Azure expects a JSON array, and outputType is "\"FourSeverityLevels\"" because Azure expects a JSON string.
The config.functions section contains named Lua functions that parse the guardrail service’s response. Each function receives the deserialized JSON response body as its argument and must return a table of named values that the response section can reference.
Azure custom guardrail function:
functions:
parse_response: |
return function(resp)
local blocked = {}
for _, cat in ipairs(resp.categoriesAnalysis or {}) do
if cat.severity >= 2 then
table.insert(blocked, cat.category .. " (severity: " .. cat.severity .. ")")
end
end
local should_block = #blocked > 0
local message
if should_block then
message = "Content blocked by Azure Content Safety: " .. table.concat(blocked, "; ")
else
message = "Content passed Azure Content Safety checks"
end
return {
should_block = should_block,
message = message
}
end
This function walks through Azure’s categoriesAnalysis array. Each entry has a category string and a numeric severity score. The function checks whether any category’s severity meets or exceeds the threshold of 2. If so, it collects the category name and severity into a blocked list. The function returns a table with should_block (a boolean) and message (a descriptive string).
Mistral custom guardrail function:
functions:
check_response: |
return function(resp)
local blocked_categories = {}
for _, result in ipairs(resp.results) do
for category, is_flagged in pairs(result.categories) do
if is_flagged then
table.insert(blocked_categories, category)
end
end
end
local block = #blocked_categories > 0
local reason
if block then
reason = "Content moderation failed: " .. table.concat(blocked_categories, ", ")
else
reason = "Content moderation passed"
end
return {
block = block,
block_message = reason
}
end
Mistral’s response format is different from Azure’s. Instead of numeric severity scores, Mistral returns a results array where each entry has a categories object. Each key in categories is a category name (like sexual or violence_and_threats), and the value is a boolean indicating whether that category was flagged. The function iterates over all results and categories, collecting any flagged category names. It returns block (boolean) and block_message (string).
The key insight is that both functions follow the same pattern: iterate over the service-specific response structure, decide whether to block, and return a table with the decision and a message. The Lua function contains all the response format differences. The rest of the AI Policy configuration doesn’t need to know how the guardrail service structures its output.
The config.response section maps function return values to the AI Policy’s block decision.
Azure custom guardrail response:
response:
block: "$(parse_response.should_block)"
block_message: "$(parse_response.message)"
Mistral custom guardrail response:
response:
block: "$(check_response.block)"
block_message: "$(check_response.block_message)"
The block field references a boolean value from a function’s return table. When this value is true, the AI Policy blocks the request. The block_message field references a string value returned to the client in the error response.
The template syntax is $(<function_name>.<field_name>), where the function name matches a key in config.functions and the field name matches a key in the table returned by that function.
|
Variable
|
Description
|
$(content)
|
Text being inspected, extracted based on text_source
|
$(source)
|
Current inspection phase: INPUT or OUTPUT
|
$(conf.params.<KEY>)
|
Value from config.params
|
$(resp)
|
Raw guardrail service response (used in functions)
|
$(<function>.<field>)
|
Field from a function’s return table
|