Can We Safely Use the Answer Botie Returned?
A response can be valid JSON and still name the wrong pump or contain unwanted markup. Check its structure, its relationship to the request, and the context that will consume it.
15 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
A Correct String Can Become an Instruction to Another Program
BOTIE
Typed validation libraries help applications reject malformed records, while guardrail libraries organise additional checks and their failure actions. Our teaching example keeps the questions visible: are the fields usable, do their identifiers match this request, and will the display treat the content as text? These checks leave the correctness of the inspection advice for a separate review.
A Valid Answer for the Wrong Pump
Botie returns a neat record with an asset, a citation and an inspection note. Every field is a string. The interface can read it. Then the technician notices that the answer concerns P8, while the request was about P7.
There is another boundary after generating an answer. We have to decide whether the record means what this request requires and how the next program will use its contents. A string that belongs in a text panel should not quietly become executable markup or a command.
The candidate drafts are hand-authored. Passing checks does not verify maintenance advice or run a model.
Step 1 of 2
Usable fields can still contain the wrong relationship
The requested asset is P7. The supplied citation map contains M7 for P7 and M8 for P8.
| Candidate | Shape | Relationship to request |
|---|---|---|
| asset P7; citation M7; nonblank text | Usable | IDs match |
| asset P8; citation M8; nonblank text | Usable | Wrong requested asset |
| asset P7; citation M8; nonblank text | Usable | Citation belongs to P8 |
asset P7; citation M7; nonblank text
- Shape
- Usable
- Relationship to request
- IDs match
asset P8; citation M8; nonblank text
- Shape
- Usable
- Relationship to request
- Wrong requested asset
asset P7; citation M8; nonblank text
- Shape
- Usable
- Relationship to request
- Citation belongs to P8
Step 2 of 2
Keep each acceptance claim narrow
A later check depends on what the earlier check actually established.
- 1
Field validation
Required nonblank strings; no extra fields - 2
Request comparison
Asset and citation map match this request - 3
Text encoding
Markup characters remain display text - 4
Review
Does the supplied evidence support the advice?
The next program decides what a string can do
Field shape, request relationships and display encoding are distinct operations whose failures require different repairs.
Which boundary rejects this draft?
Check the fields, then compare the requested asset and citation map. Encode accepted draft text for an HTML text node.
BOTIE
Draft route and encoded text
{
"route": "draft for review",
"htmlText": "Check <coupling> & mounting."
}Follow the operations
1. Proposed maintenance draft
{ "asset": "P7", "citation": "M7", "text": "Check <coupling> & mounting." }2. Check the draft fields
{ "valid": true, "draft": { "asset": "P7", "citation": "M7", "text": "Check <coupling> & mounting." } }3. Asset requested by the technician
P7
4. Supplied citation-to-asset map
{ "M7": "P7", "M8": "P8" }5. Match the requested asset and citation
{ "route": "draft for review", "text": "Check <coupling> & mounting." }6. Encode text for an HTML text node
{ "route": "draft for review", "htmlText": "Check <coupling> & mounting." }7. Result
{ "route": "draft for review", "htmlText": "Check <coupling> & mounting." }
The example does not establish factual support, maintenance safety or permission to perform an inspection. Its encoding is specific to an HTML text node.
1. Begin with the contract the next component needs
The draft panel expects exactly asset, citation and text. Require those fields to contain nonblank strings and reject unexpected fields. This prevents a hidden execute flag from being accepted merely because the visible note looks ordinary.
Schema validation deals with representation. It can describe types, required properties and some value constraints. Provider-supported structured generation can help produce that representation, but the application should still validate what it receives. A truncated response, changed configuration or different producer can reach the same boundary.
A valid structure makes the next check possible; it does not make the content correct.
2. Compare identifiers with this request’s evidence
The asset field must equal the requested asset. The citation must appear in the evidence supplied to this run and refer to that asset. These are semantic constraints because they concern relationships between the record and its surrounding data.
Our workshop stores a citation-to-asset map to make the comparison visible. It is deliberately incomplete as an evidence check. A citation can refer to the correct pump while the sentence contradicts its manual. Content support needs examination of the actual excerpt, possibly with domain rules, a reviewer or a separately evaluated model judge.
Avoid a success label that claims more than was checked. We call the surviving record a draft for review.
3. Use strict types deliberately
Validation libraries may convert values into the declared type. Coercion is useful in some interfaces, but a boundary may need to reject a value supplied in the wrong representation. Pydantic provides strict validation controls for that choice.
The local recipe declares strict string fields, rejects extra keys and checks for whitespace-only values. It validates a Python dictionary with model_validate. Other input modes and types have their own rules, so test the actual entry point used by the application. A type annotation alone does not run a check over arbitrary incoming data.
Keep this structural recipe separate from the request-specific evidence checks.
Run in your own environment
Declare the draft shape with Pydantic
Run this structural check in your own Python environment. The later asset and citation comparisons still belong in application code.
Install a compatible Pydantic 2.x release in a local virtual environment and record the resolved version in your lockfile.
This example validates invented local data. It needs no model provider or credential.
from pydantic import BaseModel, ConfigDict, ValidationError, field_validator
class Draft(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")
asset: str
citation: str
text: str
@field_validator("asset", "citation", "text")
@classmethod
def nonblank(cls, value: str) -> str:
if not value.strip():
raise ValueError("The field must contain text")
return value
candidate = {"asset":"P7", "citation":"M7", "text":"Inspect the mounting."}
try:
draft = Draft.model_validate(candidate)
except ValidationError:
print("repair the draft fields")
else:
print(draft.model_dump())Strict fields and forbidden extra keys establish the record contract. This program has not checked permissions, citation support or how the text will be rendered.
Pydantic field validators. Documentation checked .
4. Render text according to where it will appear
An HTML parser gives angle brackets a meaning beyond ordinary text. If the draft contains <strong>Inspect</strong>, a text panel should show those characters as text unless the product intentionally supports that markup. Use a text-content API or the framework’s normal escaped interpolation.
The offline example uses Python’s html.escape to demonstrate encoding before insertion into an HTML text node. Encoding is specific to the destination context. It does not make a string safe as JavaScript, a shell command, SQL or an arbitrary URL. Do not pass this encoded string to another interpreter and assume the same protection applies.
If the frontend already escapes text, pass it raw text and let that boundary encode once. Double encoding would show the escape sequences themselves.
Keep markup characters visible as text
This standard-library program encodes a short draft for an HTML text context. It does not execute or render HTML.
from html import escape
note = "Inspect <coupling> & mounting."
encoded = escape(note, quote=True)
print(encoded)
print(note)Inspect <coupling> & mounting.
Inspect <coupling> & mounting.The source string stays unchanged. The encoded version is suitable for the stated HTML text context only.
Check yourself
Questions on Parts 1 to 4
Question 1 of 2
Which check rejects the candidate?
Use the information below.
Requested asset P7. Candidate {asset:P8,citation:M8,text:"Inspect the mounting."}. All fields are nonblank strings and M8 belongs to P8.5. Treat links, rich text and executable requests separately
If the interface allows Markdown, decide which features it needs. A remote image can cause a network request when rendered, and a link can point to an unwanted scheme or destination. Disable raw HTML when it is unnecessary, restrict URL schemes and destinations as appropriate, and use a maintained sanitizer for the rich content the product intentionally supports.
If the result is meant to query a database, use parameterized queries and application-owned query structure. If it proposes a tool call, return to the authorisation and argument checks before execution. Never use an LLM’s output as an unrestricted command just because it passed a text-quality filter.
Every consumer introduces its own interpretation rules. Name the actual consumer when choosing the protection.
6. Choose what a failed validator does
Guardrails AI validators define criteria and report a pass or failure. Configured failure actions determine what happens next. A retry may request a different answer; a failure can also stop processing or trigger another application path. Inspect the outcome and use only a result that passed the required checks.
Automatic repair is appropriate only when changing the value preserves the intended task and policy. Silently replacing an invalid asset with the requested asset could attach a note about P8 to P7. For this lesson, inconsistent fields return repair draft with empty display text. A bounded retry can ask for a new candidate, which must pass every check again.
A repaired string is a new candidate, not an exemption from validation.
7. Hold streamed content until its required checks finish
Streaming makes an interface feel responsive, but a final output check cannot take back text already shown or a tool already executed. Decide which parts can be displayed before full validation. A typing indicator or progress state can be useful while a protected result is withheld.
If validation operates on chunks, test content that spans a chunk boundary and check the final assembled message as needed. Structured output may be incomplete until the stream finishes. Keep cancellation, truncated output and validator errors distinct from successful completion.
The workshop uses complete records so its validation order is visible. A streaming application needs additional tests for that order.
8. Test misleading success cases
Keep a correctly shaped draft for the wrong asset in the suite. Add a citation from the right evidence collection but for another asset. Then supply markup characters in an otherwise matching draft and verify that the output remains text.
A correct implementation should preserve ordinary drafts while repairing invalid relationships. Separately test whether the note is supported by the excerpt and whether a technician finds it useful. This separation gives a failure a location: representation, request relationship, display handling or advice quality.
A single overall guardrail score would hide which of those responsibilities failed.
Check yourself
Questions on Parts 1 to 8
Question 1 of 2
What additional question remains?
Use the information below.
Candidate asset P7 cites M7. The supplied map says M7 belongs to P7. The candidate sentence contradicts M7’s actual text.Try it with Botie
Build with Botie
Write and run Python