What Guardrails puts in the prompt
Lessons 17 and 18 handed parse text that was already JSON. A model has to be told to produce JSON, and the documentation makes it sound as if Guardrails does that for you.
The quickstart lists two ways structured output happens: function calling for models that support it, and prompt optimisation for the rest, where, in its words, the schema of the expected output is added to the prompt. Print what the model actually receives.
from pretend_guardrails import PretendModel
model = PretendModel(replies=['{"order_id": "8821", "issue": "late", "priority": 2}'])
tickets = Guard.for_pydantic(Ticket)
tickets(model, messages=[{"role": "user", "content": "File a ticket for order 8821."}])
print(model.prompts[0][-1]["content"])Byte for byte what you wrote. No schema, no instruction to return JSON, nothing. The stand-in returned valid JSON only because it was told to; a real model handed that prompt would have written a sentence.
Asking for it
prompt = "File a ticket for order 8821.\n${gr.complete_json_suffix_v2}"
model = PretendModel(replies=['{"order_id": "8821", "issue": "late", "priority": 2}'])
tickets(model, messages=[{"role": "user", "content": prompt}])
print(model.prompts[0][-1]["content"])${gr.complete_json_suffix_v2} is a placeholder Guardrails replaces with the schema, the instruction and four worked examples. It lives in guardrails/constants.xml alongside complete_json_suffix and complete_json_suffix_v3, which are shorter variants.
So the schema does reach the prompt, and only because you asked. Leave the placeholder out and Guardrails validates whatever arrives without ever having asked for the right shape. That is the difference between a course that runs and a course that works.
Filling in the question
The same substitution machinery takes values of your own through prompt_params.
model = PretendModel(replies=['{"order_id": "8821", "issue": "late", "priority": 2}'])
messages = [{"role": "user", "content": "File a ticket for order ${order}."}]
tickets(model, messages=messages, prompt_params={"order": "8821"})
print(model.prompts[0][-1]["content"])Worth using rather than an f-string, because the substitution happens inside Guardrails and the original template is what goes into the history. When you are looking at a week of calls, the template is the thing you want to group by.
- Swap
complete_json_suffix_v2forcomplete_json_suffix_v3and compare the two prompts. - Add a
descriptionto each field ofTicketand print the prompt again. Descriptions travel to the model. - Leave a
${order}in the prompt and pass noprompt_params. Decide whether the result is what you want a customer to see.
This is what real progress feels like.