Exact text constraints
Use an OPA policy when generated text must meet a checkable requirement beyond being a string. This example asks for a peaceful-garden poem with roses three times, calm once, and the letter e twelve times, ignoring case.
The model writes the poem. The policy counts its words and letters. No state or permission context is needed.
Define the text and its requirements
Save the schema and policy below. allow is the acceptance rule. It counts whole Unicode word tokens, so roses counts but rosebuds does not. Digits and underscores are treated as part of words. The letter count applies to the entire poem.
The optional feedback rule explains a failed count to help Struct find a compliant answer. Its scope: ["poem"] identifies the output field to which the message applies.
{
"type": "object",
"properties": {"poem": {"type": "string", "description": "The requested poem."}},
"required": ["poem"],
"additionalProperties": false
}
package poem
default allow := false
allow if {
text := lower(input.output.poem)
words := regex.find_n(`[\p{L}\p{M}\p{N}_]+`, text, -1)
count([word | some word in words; word == "roses"]) == 3
count([word | some word in words; word == "calm"]) == 1
count(split(text, "e")) - 1 == 12
}
# Explicitly exported guidance; never an alternative authorization decision.
feedback := [{
"code": "letter_count",
"scope": ["poem"],
"message": sprintf("The rejected poem contained %d occurrences of the letter e; exactly 12 are required. The retained string prefix contains %d, so its continuation needs %d more. The completed poem must also contain roses exactly three times and calm exactly once. Do not close the string before satisfying all counts.", [failed, retained, 12-retained]),
}] if {
failed := count(split(lower(input.output.poem), "e")) - 1
retained := count(split(lower(object.get(input.repair, "string_prefix", "")), "e")) - 1
}
Generate with repair feedback
The request includes the requirements in the input as well as the policy. It enables feedback with policy_feedback: "chat" and allows eight retries after the initial attempt.
Before you run this example
Call https://api.blatent.ai from your server. Set your API key in the same terminal where you run the example:
export BLATENT_API_KEY='YOUR_SERVICE_KEY'
Python requires Python 3.10+ and HTTPX:
python -m pip install "httpx>=0.28,<1"
TypeScript requires Node.js 24+ and uses its built-in fetch. The cURL examples require curl and jq.
Save the displayed files and the selected client in one directory. Run the client there with python example.py, node example.ts or bash example.sh. Downloads use the example's name in place of example.
- Python
- TypeScript
- cURL
import json
import os
from pathlib import Path
import httpx
request = {
"input": "Write a short poem about a peaceful garden. Use the whole word roses exactly three times, calm exactly once, and the letter e exactly twelve times (case-insensitive).",
"policy": {
"engine": "opa",
"entrypoint": "data.poem.allow",
"modules": {
"poem.rego": Path("poem.rego").read_text(encoding="utf-8"),
},
"feedback_entrypoint": "data.poem.feedback",
},
"policy_max_retries": 8,
"policy_feedback": "chat",
"schema": json.loads(Path("poem.schema.json").read_text(encoding="utf-8")),
}
with httpx.Client(
base_url="https://api.blatent.ai",
headers={"Authorization": f"Bearer {os.environ['BLATENT_API_KEY']}"},
timeout=195,
) as client:
response = client.post("/v1/resolve", json=request)
response.raise_for_status()
print(json.dumps(response.json()["output"]))
import { readFileSync } from "node:fs";
const request = {
"input": "Write a short poem about a peaceful garden. Use the whole word roses exactly three times, calm exactly once, and the letter e exactly twelve times (case-insensitive).",
"policy": {
"engine": "opa",
"entrypoint": "data.poem.allow",
"modules": {
"poem.rego": readFileSync("poem.rego", "utf8"),
},
"feedback_entrypoint": "data.poem.feedback",
},
"policy_max_retries": 8,
"policy_feedback": "chat",
"schema": JSON.parse(readFileSync("poem.schema.json", "utf8")),
};
const response = await fetch(
"https://api.blatent.ai" + "/v1/resolve",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLATENT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(request),
signal: AbortSignal.timeout(195_000),
},
);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(JSON.stringify((await response.json()).output));
set -eu
body=$(jq -n --argjson body '{"input":"Write a short poem about a peaceful garden. Use the whole word roses exactly three times, calm exactly once, and the letter e exactly twelve times (case-insensitive).","policy":{"engine":"opa","entrypoint":"data.poem.allow","modules":{},"feedback_entrypoint":"data.poem.feedback"},"policy_max_retries":8,"policy_feedback":"chat"}' \
--slurpfile f0 'poem.schema.json' \
--rawfile f1 'poem.rego' \
'$body | setpath(["schema"]; $f0[0]) | setpath(["policy","modules","poem.rego"]; $f1)')
curl --fail-with-body --silent --show-error \
"https://api.blatent.ai/v1/resolve" \
-H "Authorization: Bearer $BLATENT_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary "$body"
feedback_entrypoint identifies the Rego feedback rule. Feedback helps Struct search for alternatives; only allow decides whether an answer is compliant. This happens within the same API request.
An observed result
A live verification of the inline request above returned:
{"poem":"roses are red, roses are deep, roses keep calm and sleep"}
The counts are roses = 3, calm = 1, e = 12. In the original live verification, Struct found this compliant poem after one retry. With policy_max_retries: 0, the same request returned 422 POLICY_SEARCH_EXHAUSTED without a poem. This demonstrates a successful search for an alternative answer, not a promised success rate.
Adapt the pattern
Keep the field description, requested text and enforced rules consistent. State exact counting conventions: whole words or substrings, case sensitivity, and which characters count. Make feedback explain the failed requirement and the relevant field.
If no compliant text is found within budget, the response is 422 POLICY_SEARCH_EXHAUSTED, without a poem. More retries are not a guarantee: the model needs enough useful guidance to find a solution. Search budgets and feedback explains how to choose and handle that behavior.
The policy can count words; it cannot establish whether the poem is beautiful. Evaluate subjective quality separately.