LLMs geben oft Text aus. Wir brauchen strukturierte Daten: JSON, nicht "hier ist etwas".
Problem
Falsch:
LLM Output: "Die Person heißt Anna, lebt in Berlin, ist 28 Jahre alt und arbeitet als Developer"
Jetzt brauchst du Regex um das zu parsen. Fragile!
Richtig:
{
"name": "Anna",
"city": "Berlin",
"age": 28,
"job": "Developer"
}
Das ist strukturiert, validierbar, typsicher.
Teil 1: Claude JSON Mode
Claude kann direkt JSON ausgeben.
# json_mode.py
from anthropic import Anthropic
client = Anthropic()
# JSON Parsing prompt
prompt = """Extrahiere diese Informationen als JSON:
Name, Stadt, Alter, Beruf
Text: "Mein Name ist Anna. Ich lebe in Berlin. Ich bin 28 Jahre alt und arbeite als Developer."
Antworte NUR mit JSON, keine zusätzlichen Worte."""
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
import json
data = json.loads(response.content[0].text)
print(f"Name: {data['name']}")
print(f"City: {data['city']}")
Wichtig: Der Prompt MUSS sagen "Antworte nur mit JSON".
Teil 2: Pydantic + Anthropic
Typensicher mit Pydantic:
# pydantic_claude.py
from pydantic import BaseModel
from anthropic import Anthropic
import json
# Schema definieren
class Person(BaseModel):
name: str
city: str
age: int
job: str
client = Anthropic()
# Schema als JSON String
schema = json.dumps({
"type": "object",
"properties": {
"name": {"type": "string"},
"city": {"type": "string"},
"age": {"type": "integer"},
"job": {"type": "string"}
},
"required": ["name", "city", "age", "job"]
})
prompt = f"""Extrahiere diese Informationen als JSON.
Schema: {schema}
Text: "Mein Name ist Anna. Ich lebe in Berlin. Ich bin 28 Jahre alt und arbeite als Developer."
Antworte nur mit JSON."""
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
# Validiere mit Pydantic
data = Person(**json.loads(response.content[0].text))
print(f"Validierte Person: {data}")
print(f"Name: {data.name}, Age: {data.age}")
Pydantic validiert automatisch:
agemuss Integer sein- Alle required Felder müssen existieren
Teil 3: Instructor (Einfachste Lösung)
Instructor macht Structured Output super einfach:
pip install instructor
# instructor_demo.py
import instructor
from anthropic import Anthropic
from pydantic import BaseModel
# Anthropic Client mit Instructor
client = instructor.from_anthropic(Anthropic())
# Schema
class Person(BaseModel):
name: str
city: str
age: int
job: str
# Das ist es! Keine JSON Strings, keine Validation
person = client.messages.create(
model="claude-3-5-sonnet-20241022",
response_model=Person,
messages=[
{
"role": "user",
"content": """Extrahiere diese Informationen:
Text: "Mein Name ist Anna. Ich lebe in Berlin. Ich bin 28 Jahre alt und arbeite als Developer."
Gebe die Informationen strukturiert aus."""
}
]
)
print(f"Name: {person.name}")
print(f"Age: {person.age}")
# Type-Checking funktioniert auch!
age_plus_5 = person.age + 5 # IDE kennt das age int ist!
Warum Instructor?
- ✓ Keine JSON Parsing nötig
- ✓ Automatische Validierung
- ✓ Type-Hints funktionieren
- ✓ Retry bei ungültigen Responses
- ✓ Streaming Support
Teil 4: Komplexere Schemas
# complex_schema.py
from pydantic import BaseModel, Field
from typing import Optional, List
class Address(BaseModel):
street: str
city: str
postal_code: str
class Contact(BaseModel):
email: str
phone: Optional[str] = None # Optional
class Company(BaseModel):
name: str
employees: List[str] # List von Strings
address: Address # Nested Object
contact: Contact
class ExtractedData(BaseModel):
companies: List[Company] # Array von Companies
total: int = Field(..., ge=0) # Must be >= 0
# Nutzen mit Instructor
import instructor
from anthropic import Anthropic
client = instructor.from_anthropic(Anthropic())
result = client.messages.create(
model="claude-3-5-sonnet-20241022",
response_model=ExtractedData,
messages=[
{
"role": "user",
"content": """Extrahiere diese Firmen-Info:
"AI Engineering ist eine Firma in Wien mit Mitarbeitern: Anna, Bob, Charlie.
Contact: [email protected].
Flux GmbH ist in Berlin mit Mitarbeitern: Diana, Eve."
Antworte strukturiert."""
}
]
)
print(f"Total: {result.total}")
for company in result.companies:
print(f"Company: {company.name}")
print(f" Location: {company.address.city}")
print(f" Employees: {company.employees}")
Teil 5: Validation & Retries
Was wenn das LLM ungültige Daten gibt?
# retry_logic.py
from pydantic import BaseModel, validator
import instructor
from anthropic import Anthropic
class Person(BaseModel):
name: str
age: int
@validator("age")
def age_valid(cls, v):
if v < 0 or v > 150:
raise ValueError("Age must be 0-150")
return v
client = instructor.from_anthropic(
Anthropic(),
mode=instructor.Mode.MD_JSON # Markdown JSON Mode
)
# Instructor retried automatisch!
person = client.messages.create(
model="claude-3-5-sonnet-20241022",
response_model=Person,
messages=[
{
"role": "user",
"content": "Name: Anna, Age: 28"
}
]
)
print(f"{person.name} is {person.age} years old")
Wenn Claude "Age: 999" antwortet:
- Pydantic wirft Validator-Fehler
- Instructor sendet Error zurück an Claude
- Claude antwortet mit korrektem Wert
- Fertig!
Das ist automatisches Retrying.
Teil 6: Conditional Fields
Manche Felder sind optional je nach Typ:
# conditional.py
from pydantic import BaseModel, Field, discriminator
from typing import Union, Literal
class Article(BaseModel):
type: Literal["article"]
title: str
content: str
class Video(BaseModel):
type: Literal["video"]
title: str
duration_seconds: int
url: str
class Podcast(BaseModel):
type: Literal["podcast"]
title: str
duration_seconds: int
host: str
# Union: Entweder Article ODER Video ODER Podcast
class Content(BaseModel):
items: list[Union[Article, Video, Podcast]]
# Nutzen
result = client.messages.create(
model="claude-3-5-sonnet-20241022",
response_model=Content,
messages=[
{
"role": "user",
"content": """Extrahiere Content-Info:
"Der Artikel 'AI Trends' diskutiert neue Models.
Das Video 'How to Fine-tune' dauert 15 Minuten.
Der Podcast 'Tech Talk' mit Alice."""
}
]
)
for item in result.items:
print(f"Type: {item.type}")
if isinstance(item, Article):
print(f" Article: {item.title}")
elif isinstance(item, Video):
print(f" Video: {item.title} ({item.duration_seconds}s)")
elif isinstance(item, Podcast):
print(f" Podcast: {item.host} - {item.title}")
Teil 7: OpenAI vs Anthropic
| Feature | Claude | GPT-4 |
|---|---|---|
| JSON Mode | Prompt-basiert | Native response_format |
| Instructor | ✓ | ✓ |
| Validation | Via Pydantic | Via Pydantic |
OpenAI JSON Mode
# openai_json.py
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[
{
"role": "user",
"content": "Extract: Name, Age"
}
],
response_format={"type": "json_object"} # Native JSON!
)
import json
data = json.loads(response.choices[0].message.content)
Teil 8: Streaming mit Structured Output
Streaming + Structured = Schwierig. Aber mit Instructor:
# streaming_structured.py
import instructor
from anthropic import Anthropic
from pydantic import BaseModel
client = instructor.from_anthropic(Anthropic())
class Person(BaseModel):
name: str
age: int
# Streaming funktioniert auch
for person in client.messages.create_streaming(
model="claude-3-5-sonnet-20241022",
response_model=Person,
messages=[
{"role": "user", "content": "Name: Anna, Age: 28"}
]
):
print(f"Partial: {person}")
Top-5 Fehlerbehebung
1. "JSON decode error"
# Claude gab nicht-JSON aus
# Grund: Prompt zu vage
# Vorher
"Extrahiere die Info"
# Nachher
"Antworte AUSSCHLIESSLICH mit JSON. Keine anderen Worte."
2. "Validation Error: age is not int"
# Claude gab age="28" statt age=28
# Reason: Schema nicht klar
# Mit Pydantic Validator:
@validator("age")
def age_int(cls, v):
if isinstance(v, str):
return int(v)
return v
3. Instructor gibt None zurück
# Response war ungültig und konnte nicht gefixed werden
# Debugging:
for attempt in range(3):
try:
result = client.messages.create(...)
if result:
return result
except Exception as e:
print(f"Attempt {attempt}: {e}")
return None
4. Große Schemas sind langsam
# Schema mit 100 Feldern ist ineffizient
# Better: Teile in multiple Calls auf
# Statt:
class MegaSchema(BaseModel):
field_1: str
# ... 99 weitere Felder
# Besser:
class SectionA(BaseModel):
name: str
email: str
class SectionB(BaseModel):
age: int
address: str
# Rufe zwei Mal auf
5. Optional Felder werden nicht returned
# Pydantic skipped None Values
# Lösung:
class Model(BaseModel):
name: str
middle_name: Optional[str] = None
class Config:
# Behalte None Werte
exclude_none = False
Best Practices
- Schema First → Definiere Pydantic Model
- Instructor nutzen → Einfachste Lösung
- Validation → Nutze Validators
- Prompt klar → "Antworte nur mit JSON"
- Fallback → Was wenn Parsing fehlschlägt?
Zusammenfassung
Structured Output Workflow:
1. Pydantic Schema definieren
2. Instructor initialisieren
3. response_model=YourSchema
4. Automatic Parsing + Validation
5. Type-safe Daten ausgeben
Nächste Schritte:
- Bulk Data Extraction
- Streaming mit Structured Output
- Custom Validators für komplexe Logik
