4. Type hints, dataclasses & Pydantic¶
Intermediate · 9 min read
Type hints document what a function expects and returns; editors and tools like mypy use them
to catch mistakes. Dataclasses and Pydantic turn those hints into real data objects.
4.1 Type hints¶
def score_chunks(query: str, chunks: list[str], top_k: int = 3) -> list[tuple[str, float]]:
"""Return (chunk, score) pairs for the best `top_k` chunks."""
words = set(query.lower().split())
scored = [(c, len(words & set(c.lower().split())) / len(words)) for c in chunks]
return sorted(scored, key=lambda pair: pair[1], reverse=True)[:top_k]
print(score_chunks("refund policy", ["refund within 30 days", "shipping policy", "refund policy"], top_k=1))
# → [('refund policy', 1.0)]
Hints are not enforced at run time — they're documentation that tools can check.
4.2 Optional values and unions¶
def get_model(name: str | None = None) -> str: # `str | None` = "a string or None" (3.10+)
return name or "gpt-4o-mini" # fall back to a default
print(get_model()) # → gpt-4o-mini
On older Python versions you'll see the same thing written Optional[str] (from typing).
4.3 Dataclasses¶
from dataclasses import dataclass, field
@dataclass
class Chunk:
text: str
source: str
score: float = 0.0 # default value
tags: list[str] = field(default_factory=list) # safe mutable default
c = Chunk("Refunds within 30 days.", "policy.md", 0.91)
print(c) # → Chunk(text='Refunds within 30 days.', source='policy.md', score=0.91, tags=[])
print(c.source) # → policy.md
@dataclass writes __init__, __repr__ and __eq__ for you. Add frozen=True to make objects read-only.
4.4 Pydantic: validated data¶
Pydantic (pip install pydantic) checks and converts data at run
time — ideal for API payloads and LLM output.
from pydantic import BaseModel, Field, ValidationError
class Ticket(BaseModel):
category: str
priority: int = Field(ge=1, le=5) # must be between 1 and 5
summary: str
# Pretend this JSON came back from an LLM asked to classify a support email.
raw = '{"category": "billing", "priority": "2", "summary": "Charged twice"}'
ticket = Ticket.model_validate_json(raw) # parse + validate in one step
print(ticket.priority + 1) # → 3 "2" was converted to the int 2
try:
Ticket.model_validate_json('{"category": "bug", "priority": 9, "summary": "x"}')
except ValidationError as err:
print("invalid:", err.error_count(), "error") # → invalid: 1 error
Why it matters for GenAI
"Return JSON matching this schema" is one of the most common LLM tasks. Define the schema as a Pydantic model, validate the reply, and retry with the error message if it fails — many SDKs accept the Pydantic model directly for structured outputs.
Practice¶
- Create a dataclass
Messagewithroleandcontent, then a functionto_api(messages: list[Message]) -> list[dict].