The ticket extractor
The desk in lesson 24 checks sentences. It never touches the structured half of the library, so this is a second, smaller build that does: the same conversation turned into a ticket a queue can sort.
It reaches what the first one cannot. The field rules are lesson 18, the schema in the prompt is lesson 19, and the automatic reask when the model answers in prose is lesson 20.
"""A second, smaller build: the desk files a ticket instead of replying."""
from guardrails import Guard
from guardrails_ai.regex_match import RegexMatch
from guardrails_ai.valid_choices import ValidChoices
from guardrails_ai.valid_range import ValidRange
from pydantic import BaseModel, Field
PROMPT = ("File a support ticket for this message.\n"
"${message}\n"
"${gr.complete_json_suffix_v2}")
class Ticket(BaseModel):
order_id: str = Field(description="the four digit order number")
issue: str = Field(description="late, damaged or wrong item")
priority: int = Field(description="1 is urgent, 3 can wait")
def build():
return (Guard.for_pydantic(Ticket)
.use(RegexMatch(regex=r"^\d{4}$", on_fail="noop"), on="$.order_id")
.use(ValidChoices(choices=["late", "damaged", "wrong item"],
on_fail="noop"), on="$.issue")
.use(ValidRange(min=1, max=3, on_fail="fix"), on="$.priority"))
def file_ticket(model, message):
guard = build()
outcome = guard(model, messages=[{"role": "user", "content": PROMPT}],
prompt_params={"message": message}, num_reasks=1)
return guard, outcomeThree fields, three rules, and one prompt with two placeholders. ${message} is filled from prompt_params and ${gr.complete_json_suffix_v2} is filled by Guardrails with the schema. The field descriptions travel with that schema, which is how the model learns that priority counts down rather than up.
A ticket, filed
from pretend_guardrails import PretendModel
from ticket_desk import file_ticket
model = PretendModel(replies=['{"order_id": "8821", "issue": "late", "priority": 9}'])
guard, outcome = file_ticket(model, "order 8821 has not turned up and I need it today")
print(outcome.validation_passed)
print(outcome.validated_output)The model asked for priority nine. ValidRange is set to fix, so the ticket was filed at three and the queue never saw an impossible number. The verdict is True because, as lesson 10 put it, the verdict describes the value you are being handed.
from pretend_guardrails import PretendModel
from ticket_desk import file_ticket
model = PretendModel(replies=['{"order_id": "8821", "issue": "late", "priority": 9}'])
guard, outcome = file_ticket(model, "order 8821 has not turned up")
for summary in sorted(outcome.validation_summaries, key=lambda s: s.property_path):
print(summary.property_path, summary.validator_name, "|", summary.failure_reason)When the model writes prose
from pretend_guardrails import PretendModel
from ticket_desk import file_ticket
model = PretendModel(replies=[
"I think it is late?",
'{"order_id": "8821", "issue": "late", "priority": 2}',
])
guard, outcome = file_ticket(model, "order 8821 has not turned up")
print(outcome.validated_output)
print("model calls:", len(model.prompts))
print("iterations:", len(guard.history.last.iterations))The first answer was not JSON, so Guardrails asked again with the schema attached and the second answer parsed. Nobody set on_fail="reask" anywhere; a structural failure reasks on its own while num_reasks allows it.
What the two builds show together
| The desk, lesson 24 | The extractor, lesson 25 | |
|---|---|---|
| What it validates | A sentence going to a customer | Fields of an object |
| Where the rules live | Whole output, and the question | A JSON path per field |
| When it fails | Repair, withhold, or refuse to send | Repair a field, or ask the model again |
| What it costs | One model call | One, or two when the JSON is wrong |
Most real applications are both. The input guard belongs on every request, the sentence rules belong on anything a customer reads, and the structured Guard belongs wherever the output feeds another program.
- Give
order_idanon_fail="reask"and script a reply with a three digit order number. - Add a
summaryfield toTicketwith aValidLengthon it, so the queue gets a one line title. - Feed the extractor the reply the desk produced in lesson 24 and file a ticket from your own guarded output.
Every expert started right here.