PydanticAI vs LangChain Agents (Production-Vergleich) + Code

  • WĂ€hle richtig, ohne Demo-getriebene Reue.
  • Sieh, was in Prod bricht (Ops, Kosten, Drift).
  • Migration + Entscheidungs-Checkliste bekommen.
  • Defaults mitnehmen: Budgets, Validation, Stop-Reasons.
Typed Outputs vs flexible Abstraktionen. Wo beides hilft, wo es Failure Modes versteckt, und was du in Prod brauchst: Validation, Budgets und Observability.
Auf dieser Seite
  1. Problem (aus der Praxis)
  2. Schnelle Entscheidung (wer sollte was wÀhlen)
  3. Warum man in Prod die falsche Wahl trifft
  4. Vergleichstabelle
  5. Wo das in Production bricht
  6. Implementierungsbeispiel (echter Code)
  7. Echter Incident (mit Zahlen)
  8. Migrationspfad (A → B)
  9. Entscheidungshilfe
  10. AbwÀgungen
  11. Wann du es NICHT nutzen solltest
  12. Checkliste (Copy/Paste)
  13. Sicheres Default-Config-Snippet (JSON/YAML)
  14. FAQ (3–5)
  15. Verwandte Seiten (3–6 Links)

Problem (aus der Praxis)

Irgendwann landest du bei demselben Prod-Problem: Output-Shape ist wichtiger als Prosa.

Wenn du Tools callst und Side Effects triggerst, brauchst du:

  • Schema Validation
  • Invariants
  • fail-closed Verhalten

Typed-first Frameworks sind deshalb attraktiv. Flexible Frameworks sind nĂŒtzlich — bis Teams die Boundaries vergessen.

Schnelle Entscheidung (wer sollte was wÀhlen)

  • Typed-first (PydanticAI-style): wenn Tools/Actions dominieren und Validation Default sein soll.
  • LangChain agents: wenn du viele Integrationen willst und Boundaries bewusst selbst enforce’st.
  • Ohne Validation: beide Optionen enden in Silent Failures.

Warum man in Prod die falsche Wahl trifft

  • Framework wird als Governance-Ersatz gesehen (ist es nicht).
  • Structured Outputs werden als “nice-to-have” behandelt (in Prod sind sie Safety).
  • “Viele Integrationen” wird ĂŒberbewertet (mehr Blast Radius ohne Gateway).

Vergleichstabelle

| Kriterium | Typed-first | Flexible agents | Prod-Relevanz | |---|---|---|---| | Default Validation | höher | hĂ€ngt von dir ab | Fail closed | | Integration Surface | kleiner | grĂ¶ĂŸer | Blast radius | | Debuggability | besser mit Typen | besser mit Instrumentierung | Traces | | Drift Handling | besser testbar | riskanter | Golden tasks |

Wo das in Production bricht

Typed-first:

  • Schema Maintenance
  • Over-constraints (mehr rejects)
  • Typing als “Security” missverstanden

Flexible:

  • best-effort parsing
  • silent coercion
  • Tool Outputs als Instruktionen
  • Output shapes driften ohne Tests

Implementierungsbeispiel (echter Code)

Framework egal: strict boundary zwischen Model Output und Actions.

PYTHON
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class Decision:
  kind: str  # "final" | "tool"
  tool: str | None
  args: dict[str, Any] | None
  answer: str | None


class InvalidDecision(RuntimeError):
  pass


def validate_decision(obj: Any) -> Decision:
  if not isinstance(obj, dict):
      raise InvalidDecision("expected object")
  kind = obj.get("kind")
  if kind not in {"final", "tool"}:
      raise InvalidDecision("invalid kind")
  if kind == "final":
      ans = obj.get("answer")
      if not isinstance(ans, str) or not ans.strip():
          raise InvalidDecision("missing answer")
      return Decision(kind="final", tool=None, args=None, answer=ans)
  tool = obj.get("tool")
  args = obj.get("args")
  if not isinstance(tool, str):
      raise InvalidDecision("missing tool")
  if not isinstance(args, dict):
      raise InvalidDecision("missing args")
  return Decision(kind="tool", tool=tool, args=args, answer=None)
JAVASCRIPT
export class InvalidDecision extends Error {}

export function validateDecision(obj) {
if (!obj || typeof obj !== "object") throw new InvalidDecision("expected object");
const kind = obj.kind;
if (kind !== "final" && kind !== "tool") throw new InvalidDecision("invalid kind");
if (kind === "final") {
  if (typeof obj.answer !== "string" || !obj.answer.trim()) throw new InvalidDecision("missing answer");
  return { kind: "final", answer: obj.answer };
}
if (typeof obj.tool !== "string") throw new InvalidDecision("missing tool");
if (!obj.args || typeof obj.args !== "object") throw new InvalidDecision("missing args");
return { kind: "tool", tool: obj.tool, args: obj.args };
}

Echter Incident (mit Zahlen)

Flexible Agent mit best-effort Tool Call Parsing. Bei Partial Outage kam HTML im Tool Output, Modell kopierte Teile in args. Parser coercte es, Write passierte.

Impact:

  • 17 Runs schrieben Garbage in eine Queue
  • downstream Workers crashten ~25 Minuten
  • On-call ~2 Stunden Root Cause, weil Logs nur “final answer” hatten

Fix: strict parsing + schema validation + fail-closed vor Writes + metric invalid_decision_rate.

Migrationspfad (A → B)

  • Flexible → typed-first: Boundary validieren, kleines Decision Schema definieren, Writes zuerst typisieren.
  • Typed-first → flexible: Typen fĂŒr Actions behalten, free-form Text nur ohne Side Effects.

Entscheidungshilfe

  • Writes/Money → typed/validated boundaries zuerst.
  • Experimente → Flex ok, aber Budgets + Logs.
  • Multi-Tenant → strict validation non-negotiable.

AbwÀgungen

  • Validation senkt completion rate (meistens gut).
  • Typing kostet Maintenance.
  • FlexibilitĂ€t shippt schneller, aber mit mehr Prod-Surprises.

Wann du es NICHT nutzen solltest

  • Typing ist keine Security.
  • Kein best-effort parsing fĂŒr Write Actions.
  • Validation Failures sind ein Metric, kein Makel.

Checkliste (Copy/Paste)

  • [ ] Model decisions schema-validate
  • [ ] Tool outputs schema+invariants validate
  • [ ] Fail-closed fĂŒr Writes
  • [ ] Budgets + stop reasons
  • [ ] Tool call logs + audit
  • [ ] Canary changes; drift messen

Sicheres Default-Config-Snippet (JSON/YAML)

YAML
validation:
  model_decision:
    fail_closed: true
    schema: "Decision(kind, tool?, args?, answer?)"
  tool_output:
    fail_closed: true
    max_chars: 200000
budgets:
  max_steps: 25
  max_tool_calls: 12
monitoring:
  track: ["invalid_decision_rate", "tool_output_invalid_rate", "stop_reason"]

FAQ (3–5)

Garantiert Typing correctness?
Nein. Es garantiert Shape. Invariants, Permissions und safe-mode bleiben nötig.
Ist LangChain unsicher?
Nicht per se. Es ist flexibel. Safety kommt von Budgets, Validation und Tool Gateway.
Was zuerst typisieren?
Alles mit Writes/Money: Tool Calls, Approvals, Budget Outputs.
Kann strict validation completion senken?
Ja. Das ist oft der Punkt: nicht raten, Failure Paths explizit behandeln.

Nicht sicher, ob das dein Fall ist?

Agent gestalten ->
⏱ 5 Min. Lesezeit ‱ Aktualisiert MĂ€r, 2026Schwierigkeit: ★★☆
Integriert: Production ControlOnceOnly
Guardrails fĂŒr Tool-Calling-Agents
Shippe dieses Pattern mit Governance:
  • Budgets (Steps / Spend Caps)
  • Tool-Permissions (Allowlist / Blocklist)
  • Kill switch & Incident Stop
  • Idempotenz & Dedupe
  • Audit logs & Nachvollziehbarkeit
Integrierter Hinweis: OnceOnly ist eine Control-Layer fĂŒr Production-Agent-Systeme.
Autor

Diese Dokumentation wird von Engineers kuratiert und gepflegt, die AI-Agenten in der Produktion betreiben.

Die Inhalte sind KI-gestĂŒtzt, mit menschlicher redaktioneller Verantwortung fĂŒr Genauigkeit, Klarheit und Produktionsrelevanz.

Patterns und Empfehlungen basieren auf Post-Mortems, Failure-Modes und operativen Incidents in produktiven Systemen, auch bei der Entwicklung und dem Betrieb von Governance-Infrastruktur fĂŒr Agenten bei OnceOnly.