Normal path: execute → tool → observe.
Problem (aus der Praxis)
Dein Tool liefert JSON.
Bis es das nicht tut.
Proxy injectet HTML. Response ist truncat’t. Tool Version renamed Felder.
Das Modell sieht “irgendwas” und macht weiter. So bekommst du:
- falsche Decisions
- falsche Writes
- und den schlimmsten Bug: “es ist nicht gecrasht, es war nur falsch”
Tool Output ist die Realität des Agents. Wenn die Realität korrupt ist, sind Decisions korrupt.
Warum das in Production bricht
1) Output wird als trusted angenommen
Viele Teams validieren Inputs und ignorieren Outputs. Bei Agents ist das falsch:
- Output steuert Entscheidungen
- Output ist die Hauptquelle für silent corruption
2) Partial Responses passieren
Timeouts failen nicht immer clean. Du bekommst:
- halbes JSON
- 200 mit Error Payload
- leeren Body mit Success Status
3) Schema Drift ist konstant
Internal APIs ändern sich. Vendors ändern sich. Deine Tools ändern sich. Ohne Output Validation merkst du’s erst über User-Failures.
4) Das Modell “smooth’t” Corruption
Menschen stoppen bei invalid JSON. Modelle füllen Lücken. Für Text: Feature. Für Tool Actions: Bug.
Implementierungsbeispiel (echter Code)
Safe Pattern:
- size limits
- content-type
- schema + invariants
- fail closed/degrade
import json
from typing import Any
class ToolOutputInvalid(RuntimeError):
pass
def parse_json_strict(raw: str, *, max_chars: int = 200_000) -> Any:
if len(raw) > max_chars:
raise ToolOutputInvalid("tool output too large")
try:
return json.loads(raw)
except Exception as e:
raise ToolOutputInvalid(f"invalid JSON: {type(e).__name__}")
def validate_user_profile(obj: Any) -> dict[str, Any]:
if not isinstance(obj, dict):
raise ToolOutputInvalid("expected object")
if "user_id" not in obj or not isinstance(obj["user_id"], str):
raise ToolOutputInvalid("missing user_id")
if "plan" in obj and obj["plan"] not in {"free", "pro", "enterprise"}:
raise ToolOutputInvalid("invalid plan enum")
return obj
def get_user_profile(user_id: str) -> dict[str, Any]:
raw = http_get(f"https://api.internal/users/{user_id}") # (pseudo)
obj = parse_json_strict(raw)
return validate_user_profile(obj)export class ToolOutputInvalid extends Error {}
export function parseJsonStrict(raw, { maxChars = 200_000 } = {}) {
if (raw.length > maxChars) throw new ToolOutputInvalid("tool output too large");
try {
return JSON.parse(raw);
} catch (e) {
throw new ToolOutputInvalid("invalid JSON: " + (e && e.name ? e.name : "Error"));
}
}
export function validateUserProfile(obj) {
if (!obj || typeof obj !== "object") throw new ToolOutputInvalid("expected object");
if (typeof obj.user_id !== "string") throw new ToolOutputInvalid("missing user_id");
if ("plan" in obj && !["free", "pro", "enterprise"].includes(obj.plan)) {
throw new ToolOutputInvalid("invalid plan enum");
}
return obj;
}Absichtlich strikt. Wenn Output kaputt ist, ist die richtige Reaktion oft:
- stoppen
- partial returnen
- Failure class loggen
Nicht: “raten”.
Echter Incident (mit Zahlen)
Agent updatete CRM Notes basierend auf user profile.
user.profile lieferte manchmal HTML Error Pages mit 200 (Proxy misconfig).
Das Modell extrahierte “plan=enterprise” aus irgendeinem Banner.
Impact:
- 23 CRM Records falsch getaggt
- Sales verbrannte ~3 Stunden
- Cleanup Job + Restore aus Logs (unvollständig)
Fix:
- content-type checks + strict JSON parse
- schema validation + enums
- fail closed + safe-mode (skip writes)
- metrics:
tool_output_invalidrate
Tools lügen auf langweilige Weise. Dein Agent muss widersprechen.
Abwägungen
- Strikte Validation erhöht hard failures bei Drift (gut: du siehst es).
- fail closed senkt success rate, verhindert silent corruption.
- Schema Maintenance ist Arbeit. Silent data corruption ist schlimmere Arbeit.
Wann du es NICHT nutzen solltest
- Wenn das Tool strong typed end-to-end ist und du es voll kontrollierst: weniger Validation (size limits trotzdem).
- Wenn Tool free-form Text liefert: wrap mit extractor → structured output.
- Wenn du nicht stoppen darfst: du brauchst fallback tools, nicht looser validation.
Checkliste (Copy/Paste)
- [ ] Max response size
- [ ] content-type check
- [ ] strict parse
- [ ] schema + enums
- [ ] invariants (ids, ranges)
- [ ] fail closed/degrade (keine writes auf invalid output)
- [ ] metrics/alerts auf invalid output rate
- [ ] log args hash + tool version + error class
Sicheres Default-Config-Snippet (JSON/YAML)
validation:
tool_output:
fail_closed: true
max_chars: 200000
require_content_type: "application/json"
enforce_enums: true
safe_mode:
on_invalid_output: "skip_writes"
metrics:
track: ["tool_output_invalid_rate"]
FAQ (3–5)
Von Patterns genutzt
Verwandte Failures
Q: Ist Output Validation bei internen Tools nicht redundant?
A: Nein. Interne Tools driften auch. “Intern” heißt nur: der Bug gehört dir.
Q: Was ist der Worst Case ohne Validation?
A: Silent corruption: falsche Writes, die “erfolgreich” aussehen. Du merkst es Tage später.
Q: Brauche ich eine JSON Schema Library?
A: Nicht sofort. Starte mit strict parse + Invariants. Schemas dort, wo Blast Radius hoch ist.
Q: Wie hängt das mit Prompt Injection zusammen?
A: Corrupted Outputs enthalten oft untrusted Text. Validieren und als Data behandeln, nicht als Instructions.
Verwandte Seiten (3–6 Links)
- Foundations: Wie Agents Tools nutzen · Production-ready Agent
- Failure: Prompt Injection · Halluzinierte Quellen
- Governance: Tool Permissions
- Production stack: Production Stack