Twenty lessons. Today's exercise composes five of them. No new concepts.
Build a 3-turn conversation where the system prompt constrains the model to reply only in JSON {"answer": "<string>"}. Each user turn is a short question. For each turn, parse the JSON and assert the shape. Print the parsed answer.
import json
from pydantic_ai import Agent
agent = Agent(model, system_prompt='Reply only with valid JSON: {"answer": "<your answer as a string>"}. No prose, no markdown.')
questions = ["What is the capital of France?", "What about Germany?", "What about Spain?"]
history = []
results = []
for q in questions:
result = agent.run_sync(q, message_history=history)
parsed = json.loads(result.output.strip())
results.append(parsed)
print(parsed.get("answer"))
history = result.all_messages()Five primitives composed:
| Primitive | From | Used for |
|---|---|---|
| System prompt | Day 17 | Persistent format constraint across all turns |
| Multi-turn / message_history | Day 16 | "What about Germany?" needs prior question's context |
| Structured output (JSON) | Day 11 | Asks the model to reply in {"answer": ...} |
json.loads parsing | Day 11 | Convert response string to a Python dict |
| Schema assertion | Day 13 | Verify each parsed dict has the expected key |
No new concepts. The hard part is composing them in the right order.
The shape Agent(model, system_prompt=...) + message_history + json.loads(result.output) is essentially every chatbot you'll build for the rest of your career. AI Intermediate adds tool calling, evaluation, and agent loops — but they all sit on top of this 5-primitive foundation.
Deliberately not in scope for an AI-Beg synthesis:
For today: compose what you have, end of track.
Twenty lessons. Today's exercise composes five of them. No new concepts.
Build a 3-turn conversation where the system prompt constrains the model to reply only in JSON {"answer": "<string>"}. Each user turn is a short question. For each turn, parse the JSON and assert the shape. Print the parsed answer.
import json
from pydantic_ai import Agent
agent = Agent(model, system_prompt='Reply only with valid JSON: {"answer": "<your answer as a string>"}. No prose, no markdown.')
questions = ["What is the capital of France?", "What about Germany?", "What about Spain?"]
history = []
results = []
for q in questions:
result = agent.run_sync(q, message_history=history)
parsed = json.loads(result.output.strip())
results.append(parsed)
print(parsed.get("answer"))
history = result.all_messages()Five primitives composed:
| Primitive | From | Used for |
|---|---|---|
| System prompt | Day 17 | Persistent format constraint across all turns |
| Multi-turn / message_history | Day 16 | "What about Germany?" needs prior question's context |
| Structured output (JSON) | Day 11 | Asks the model to reply in {"answer": ...} |
json.loads parsing | Day 11 | Convert response string to a Python dict |
| Schema assertion | Day 13 | Verify each parsed dict has the expected key |
No new concepts. The hard part is composing them in the right order.
The shape Agent(model, system_prompt=...) + message_history + json.loads(result.output) is essentially every chatbot you'll build for the rest of your career. AI Intermediate adds tool calling, evaluation, and agent loops — but they all sit on top of this 5-primitive foundation.
Deliberately not in scope for an AI-Beg synthesis:
For today: compose what you have, end of track.
Create a free account to get started. Paid plans unlock all tracks.