Guardrails AIguardrails-ai 0.11.0 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson

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.

python
"""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, outcome

Three 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

Example
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.

Example
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

Example
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 24The extractor, lesson 25
What it validatesA sentence going to a customerFields of an object
Where the rules liveWhole output, and the questionA JSON path per field
When it failsRepair, withhold, or refuse to sendRepair a field, or ask the model again
What it costsOne model callOne, 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.

Try it yourself
  • Give order_id an on_fail="reask" and script a reply with a three digit order number.
  • Add a summary field to Ticket with a ValidLength on 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.