The sentiment classifier used a system prompt plus .strip().lower() to keep outputs clean. What can still go wrong even with both?
The model could still return "High urgency" or "critical" instead of one of the three words, and my code would fall through all the label checks?
Exactly. The system prompt is a request, not a contract. For closed-set outputs PydanticAI has a stronger tool: result_type=Literal[...]. You declare the valid strings up front, and PydanticAI enforces them — validating the model's response and retrying invisibly if it misses:
from typing import Literal
result = Agent(model, result_type=Literal["high", "medium", "low"]).run_sync(text)
print(result.output) # guaranteed "high", "medium", or "low"No system prompt and no .strip().lower()? The constraint does all the work?
The Literal type does both jobs. When you pass result_type=Literal[...], PydanticAI tells the model the exact allowed values, validates the response against them, and retries if the model misses. The string you receive is always one of the three — guaranteed:
def classify_urgency(text: str) -> str:
agent = Agent(model, result_type=Literal["high", "medium", "low"])
return agent.run_sync(text).outputSo when would I still use the system-prompt approach from Day 6?
When the output is not a closed set. Summaries, translations, free-form answers — those cannot be expressed as a Literal. Any time your output has a finite list of valid string values, Literal is the right tool. It gives you enum-level safety for three extra characters of code.
So my downstream routing code can compare directly against "high", "medium", "low" without any defensive stripping?
Exactly. The type system enforces the constraint and the rest of your code gets simpler. Write classify_urgency(text) now — import Literal, set result_type=Literal["high", "medium", "low"], return result.output as-is.
TL;DR: result_type=Literal[...] guarantees output is one of a fixed set — no normalization needed.
from typing import Literal — the one import you needresult_type=Literal["a", "b", "c"] — declares the allowed values| Approach | Guarantees | Code |
|---|---|---|
system_prompt + .strip().lower() | Best-effort | ~3 lines |
result_type=Literal[...] | Validated | ~1 line |
Use Literal whenever the output is a finite closed set of strings.
The sentiment classifier used a system prompt plus .strip().lower() to keep outputs clean. What can still go wrong even with both?
The model could still return "High urgency" or "critical" instead of one of the three words, and my code would fall through all the label checks?
Exactly. The system prompt is a request, not a contract. For closed-set outputs PydanticAI has a stronger tool: result_type=Literal[...]. You declare the valid strings up front, and PydanticAI enforces them — validating the model's response and retrying invisibly if it misses:
from typing import Literal
result = Agent(model, result_type=Literal["high", "medium", "low"]).run_sync(text)
print(result.output) # guaranteed "high", "medium", or "low"No system prompt and no .strip().lower()? The constraint does all the work?
The Literal type does both jobs. When you pass result_type=Literal[...], PydanticAI tells the model the exact allowed values, validates the response against them, and retries if the model misses. The string you receive is always one of the three — guaranteed:
def classify_urgency(text: str) -> str:
agent = Agent(model, result_type=Literal["high", "medium", "low"])
return agent.run_sync(text).outputSo when would I still use the system-prompt approach from Day 6?
When the output is not a closed set. Summaries, translations, free-form answers — those cannot be expressed as a Literal. Any time your output has a finite list of valid string values, Literal is the right tool. It gives you enum-level safety for three extra characters of code.
So my downstream routing code can compare directly against "high", "medium", "low" without any defensive stripping?
Exactly. The type system enforces the constraint and the rest of your code gets simpler. Write classify_urgency(text) now — import Literal, set result_type=Literal["high", "medium", "low"], return result.output as-is.
TL;DR: result_type=Literal[...] guarantees output is one of a fixed set — no normalization needed.
from typing import Literal — the one import you needresult_type=Literal["a", "b", "c"] — declares the allowed values| Approach | Guarantees | Code |
|---|---|---|
system_prompt + .strip().lower() | Best-effort | ~3 lines |
result_type=Literal[...] | Validated | ~1 line |
Use Literal whenever the output is a finite closed set of strings.
Create a free account to get started. Paid plans unlock all tracks.