COMPUTERZUGRIFFSSYSTEM

Tool-Nutzungsmuster: Zuverlässige Agent-Tool-Schnittstellen entwickeln


Dein Agent hat ein Tool aufgerufen und einen 40-zeiligen JSON-Blob zurückbekommen — rohe API-Antwort, verschachtelte Objekte, Fehlercodes tief vergraben in einem status-Feld. Das Modell las es, wählte einen plausibel wirkenden Wert und machte weiter. Der Wert war falsch. Drei Schritte später schrieb der Agent zuversichtlich einen Bericht auf Basis falscher Daten.

Das Tool funktionierte. Die Schnittstelle versagte.

Tool-Nutzung ist der Mechanismus, der ein Sprachmodell in einen Agenten verwandelt. Jede Fähigkeit deines Agenten — Datenbanksuchen, Dateien schreiben, APIs aufrufen, Dienste abfragen — kommt über eine Tool-Schnittstelle an. Ist die Schnittstelle schlecht gestaltet, trifft das Modell schlechtere Entscheidungen, selbst wenn der zugrundeliegende Dienst korrekt funktioniert. Dieser Leitfaden behandelt fünf Muster für präzise, zuverlässige und produktionsreife Tool-Schnittstellen.

Voraussetzungen: Kenntnisse in Python und der Claude API. Für Hintergrundwissen zu MCP als Tool-Transport-Schicht, siehe Deinen ersten MCP-Server bauen.


Warum Schnittstellendesign wichtig ist

Wenn ein Agent ein Tool auswählt und nutzt, trifft er zwei Entscheidungen:

  1. Welches Tool aufzurufen — gesteuert durch name und description des Tools
  2. Welche Argumente zu übergeben — gesteuert durch das input_schema des Tools

Mehrdeutige Beschreibungen führen zu falscher Tool-Auswahl. Lockere Schemas erlauben dem Modell, fehlerhafte Eingaben zu übergeben. Unstrukturierte Ergebnisse zwingen das Modell zu raten. Die meisten Agent-Bugs liegen nicht im Reasoning — sie liegen an der Tool-Grenze.


Muster 1: Schema-First-Design

Schreibe das JSON-Schema, bevor du die Implementierung schreibst. Ein strenges Schema schränkt das Modellverhalten in der Eingabephase ein — bevor irgendetwas ausgeführt wird.

import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "search_products",
"description": (
"Durchsucht den Produktkatalog nach Stichwörtern. "
"Gibt eine Liste passender Produkte mit IDs, Namen und Preisen zurück. "
"Verwende es, wenn der Benutzer Produkte finden oder durchstöbern möchte."
),
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Suchbegriffe"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "home", "all"],
"description": "Produktkategorie zum Filtern. Verwende 'all' wenn nicht angegeben."
},
"max_results": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"description": "Anzahl der zurückzugebenden Ergebnisse. Standard: 5"
}
},
"required": ["query", "category"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Finde Elektronik unter 100€"}]
)

Schema-Regeln, die Fehler reduzieren:

  • Verwende enum für Felder mit festen gültigen Werten
  • Setze minimum/maximum auf numerischen Feldern
  • Markiere Felder nur als required, wenn das Tool ohne sie wirklich nicht laufen kann
  • Schreibe Beschreibungen aus Sicht des Modells: “Verwende es, wenn…”

Wann verwenden: Bei jeder Tool-Definition.


Muster 2: Strukturierte Tool-Ergebnisse

Gib typisierte, maschinenlesbare Ergebnisse zurück. Gib niemals rohe API-Antworten zurück.

import json
from dataclasses import dataclass, asdict
from typing import Any, Optional
@dataclass
class ToolResult:
success: bool
data: Optional[Any] = None
error: Optional[str] = None
def to_content(self) -> str:
return json.dumps(asdict(self))
def search_products(
query: str,
category: str,
max_results: int = 5,
) -> ToolResult:
try:
raw_results = _query_database(query, category, limit=max_results)
products = [
{"id": r["product_id"], "name": r["title"], "price": r["price_usd"]}
for r in raw_results
]
return ToolResult(success=True, data={"products": products, "count": len(products)})
except ConnectionError as e:
return ToolResult(success=False, error=f"Datenbank nicht erreichbar: {e}")
except Exception as e:
return ToolResult(success=False, error=f"Suche fehlgeschlagen: {type(e).__name__}: {e}")
def _query_database(query, category, limit):
return []

Das konsistente {success, data, error}-Envelope bedeutet, dass das Modell immer weiß, wo es suchen muss. Für die elegante Fehlerbehandlung, siehe Agent-Fehlerwiederherstellungsmuster.


Muster 3: Parallele Tool-Aufrufe

Claude kann in einer einzigen Antwort mehrere Tools anfordern. Verarbeite sie parallel statt sequenziell.

from concurrent.futures import ThreadPoolExecutor, as_completed
TOOL_REGISTRY = {
"search_products": search_products,
}
def dispatch_tool(name: str, inputs: dict) -> ToolResult:
handler = TOOL_REGISTRY.get(name)
if not handler:
return ToolResult(success=False, error=f"Unbekanntes Tool: {name}")
return handler(**inputs)
def process_tool_calls(response: anthropic.types.Message) -> list[dict]:
tool_uses = [
block for block in response.content
if block.type == "tool_use"
]
if not tool_uses:
return []
def execute(tool_use):
result = dispatch_tool(tool_use.name, tool_use.input)
return {
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result.to_content(),
}
with ThreadPoolExecutor(max_workers=len(tool_uses)) as executor:
futures = {executor.submit(execute, tu): tu for tu in tool_uses}
results = []
for future in as_completed(futures):
results.append(future.result())
return results
def run_agent(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
for block in response.content:
if hasattr(block, "text"):
return block.text
return ""
if response.stop_reason == "tool_use":
tool_results = process_tool_calls(response)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
break
return ""

Für die Orchestrierung mehrerer Agenten, die jeweils Tools aufrufen, siehe Multi-Agent-Muster.


Muster 4: Sicherer Tool-Aufruf-Wrapper

Lass Tool-Ausnahmen niemals unbehandelt den Agent-Loop erreichen.

import signal
def timeout_handler(signum, frame):
raise TimeoutError("Tool-Ausführung hat das Zeitlimit überschritten")
def safe_tool_call(
name: str,
inputs: dict,
timeout_seconds: int = 30,
) -> ToolResult:
"""
Führt ein Tool mit Zeitlimit aus und fängt alle Ausnahmen ab.
Gibt immer ein ToolResult zurück — wirft niemals eine Ausnahme.
"""
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_seconds)
try:
return dispatch_tool(name, inputs)
except TimeoutError:
return ToolResult(
success=False,
error=f"Tool '{name}' hat nach {timeout_seconds}s das Zeitlimit überschritten"
)
except Exception as e:
return ToolResult(
success=False,
error=f"Tool '{name}' hat {type(e).__name__} ausgelöst: {e}"
)
finally:
signal.alarm(0)

Wenn ein Tool success: false zurückgibt, kann das Modell entscheiden, ob es erneut versuchen, eine Alternative ausprobieren oder den Fehler melden soll. Für umfassendere Retry-Strategien, siehe Fehlerwiederherstellungsmuster.


Muster 5: Ergebnisvalidierung und Kürzung

Validiere Tool-Ergebnisse, bevor du sie an das Modell zurückgibst.

MAX_TOOL_RESULT_CHARS = 8000
def validate_result(result: ToolResult, expected_keys: list[str]) -> ToolResult:
if not result.success or not isinstance(result.data, dict):
return result
missing = [k for k in expected_keys if k not in result.data]
if missing:
return ToolResult(
success=False,
error=f"Tool-Antwort fehlt erwartete Felder: {missing}"
)
return result
def truncate_result(result: ToolResult) -> ToolResult:
content = result.to_content()
if len(content) <= MAX_TOOL_RESULT_CHARS:
return result
truncated_data = {
"truncated": True,
"chars_omitted": len(content) - MAX_TOOL_RESULT_CHARS,
"content": content[:MAX_TOOL_RESULT_CHARS],
}
return ToolResult(
success=result.success,
data=truncated_data,
error="Ergebnis gekürzt — zu groß für das Kontextfenster",
)
def safe_tool_call_validated(
name: str,
inputs: dict,
expected_keys: list[str] | None = None,
) -> ToolResult:
result = safe_tool_call(name, inputs)
if expected_keys:
result = validate_result(result, expected_keys)
result = truncate_result(result)
return result

Für die Beobachtung und das Debuggen dieser Fehler in der Produktion, siehe Debugging und Observability.


Häufige Fehler

Fehler 1: Rohe API-Antworten zurückgeben

Das Modell erhält ein verschachteltes Objekt mit 30 Feldern, die meisten irrelevant. Es wählt das falsche.

Lösung: Forme die Antwort vor der Rückgabe. Gib nur zurück, was das Modell für seine nächste Entscheidung braucht.

Fehler 2: Tools mit Nebenwirkungen ohne Bestätigung

Ein Tool, das eine E-Mail sendet oder einen Eintrag löscht, sollte nicht stillschweigend ausgeführt werden.

Lösung: Für irreversible Aktionen verwende ein Zwei-Tool-Muster: plan_email gibt eine Vorschau zurück, send_email sendet tatsächlich.

Fehler 3: Überlappende Tool-Verantwortlichkeiten

Zwei Tools, die ähnliche Dinge tun, zwingen das Modell zum Raten.

Lösung: Jedes Tool sollte einen eindeutigen, nicht überlappenden Zweck haben.

Fehler 4: Kein Timeout bei externen Tools

Ein langsamer Drittanbieter-API-Aufruf blockiert den gesamten Agent-Loop auf unbestimmte Zeit.

Lösung: Setze immer ein Timeout (Muster 4).


Produktions-Checkliste

  • Jedes Tool hat eine Beschreibung aus Modellsicht (“Verwende es, wenn…”)
  • Enum-Felder für alle Eingaben mit festen Werten
  • Jedes Tool gibt {success, data, error} zurück — niemals rohe Antworten
  • Tool-Aufrufe in safe_tool_call eingewickelt
  • Parallele Ausführung für Multi-Tool-Antworten
  • Timeout für jedes externe Tool gesetzt
  • Ergebniskürzung für variable Nutzlastgrößen
  • Validierung für Tools, die externe APIs aufrufen

Nächste Schritte

  1. Beginne mit dem Schema — schreibe dein input_schema vor dem Funktionskörper
  2. Füge den ToolResult-Wrapper zu jedem bestehenden Tool hinzu
  3. Integriere safe_tool_call um deinen Agent-Loop abzusichern
  4. Richte Ergebnisvalidierung ein für Tools, die externe APIs aufrufen

Verwandte Leitfäden: