Claude Platform Docs
MessagesTools

Tool-Runner (SDK)

Verwende den Tool-Runner des SDK, um die agentische Schleife, das Error-Wrapping und die Typsicherheit automatisch zu handhaben.

Der „tool runner“ (Tool-Runner) übernimmt die agentische Schleife, das Error-Wrapping und die Typsicherheit, damit du dich nicht darum kümmern musst. Wenn du eine Human-in-the-Loop-Freigabe, benutzerdefiniertes Logging oder bedingte Ausführung benötigst, verwende stattdessen die manuelle Schleife.

Anstatt Tool-Aufrufe, Tool-Ergebnisse und die Konversationsverwaltung manuell zu handhaben, erledigt der Tool-Runner automatisch Folgendes:

  • Führt Tools aus, wenn Claude sie aufruft
  • Handhabt den Request/Response-Zyklus
  • Verwaltet den Konversationszustand
  • Bietet Typsicherheit und Validierung

Grundlegende Verwendung

Definiere Tools mit den SDK-Helfern und verwende dann den Tool-Runner, um sie auszuführen.

Je nach Tool-Signatur des SDK gibt ein Tool sein Ergebnis als String oder als Content-Blöcke (Text-, Bild- oder Dokument-Blöcke) zurück, sodass ein Tool multimodale Ergebnisse zurückgeben kann. Ein zurückgegebener String wird zu einem einzelnen Text-Content-Block. Um strukturierte Daten wie ein JSON-Objekt oder eine Zahl zurückzugeben, kodiere sie zuerst als String.

Verwende den @beta_tool-Decorator, um Tools mit Type Hints und Docstrings zu definieren.

import json
from anthropic import Anthropic, beta_tool

client = Anthropic()


@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
    """Get the current weather in a given location.

    Args:
        location: The city and state, e.g. San Francisco, CA
        unit: Temperature unit, either 'celsius' or 'fahrenheit'
    """
    return json.dumps({"temperature": "20°C", "condition": "Sunny"})


@beta_tool
def calculate_sum(a: int, b: int) -> str:
    """Add two numbers together.

    Args:
        a: First number
        b: Second number
    """
    return str(a + b)


runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[get_weather, calculate_sum],
    messages=[
        {
            "role": "user",
            "content": "What's the weather like in Paris? Also, what's 15 + 27?",
        }
    ],
)
for message in runner:
    print(message)

Der @beta_tool-Decorator untersucht die Funktionsargumente und den Docstring, um das JSON-Schema für dich abzuleiten.

Über den Tool-Runner iterieren

Der Tool-Runner ist ein Iterable, das Nachrichten von Claude liefert. Bei jeder Iteration prüft der Runner, ob Claude eine Tool-Nutzung angefordert hat. Falls ja, führt er das Tool aus, sendet das Ergebnis automatisch an Claude zurück und liefert dann die nächste Nachricht von Claude, um deine Schleife fortzusetzen.

Du kannst die Schleife in jeder Iteration mit einer break-Anweisung beenden. Der Runner läuft, bis Claude eine Nachricht ohne Tool-Nutzung zurückgibt oder bis er max_iterations erreicht, falls du dies gesetzt hast.

Wenn du keine Zwischennachrichten benötigst, kannst du die finale Nachricht direkt abrufen:

Verwende runner.until_done(), um die finale Nachricht zu erhalten.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[get_weather, calculate_sum],
    messages=[
        {
            "role": "user",
            "content": "What's the weather like in Paris? Also, what's 15 + 27?",
        }
    ],
)
final_message = runner.until_done()
for block in final_message.content:
    if block.type == "text":
        print(block.text)

Erweiterte Verwendung

Innerhalb der Schleife kannst du jede Antwortnachricht lesen und den Zustand des Runners vor dem nächsten API-Aufruf ändern. Jede Iteration folgt diesem Lebenszyklus:

  1. Der Runner sendet eine Anfrage mit seinem aktuellen Zustand an die Messages API.
  2. Der Runner liefert die Antwortnachricht an deinen Schleifenkörper.
  3. Dein Schleifenkörper wird ausgeführt. Du kannst die Nachricht lesen und optional den Zustand des Runners ändern.
  4. Wenn dein Schleifenkörper zurückkehrt, prüft der Runner, ob du seinen Nachrichtenverlauf geändert hast.
    • Wenn du den Nachrichtenverlauf nicht geändert hast: Enthält die Nachricht Tool-Aufrufe, hängt der Runner die Assistant-Nachricht und die Tool-Ergebnisse an und fährt fort. Gibt es keine Tool-Aufrufe, wird die Schleife beendet.
    • Wenn du den Nachrichtenverlauf geändert hast: Der Runner überspringt sein automatisches Anhängen und verwendet deinen Zustand unverändert. Siehe Den Nachrichtenverlauf übernehmen.

Den Nachrichtenverlauf übernehmen

Standardmäßig verwaltet der Runner den Konversationszustand für dich: Nach jedem Zug mit Tool-Aufruf hängt er die Assistant-Nachricht und etwaige Tool-Ergebnisse an seinen eigenen Nachrichtenverlauf an. Du übernimmst den Nachrichtenverlauf, wenn du einen Zug wiederholen (die Antwort verwerfen und erneut senden), eine Folgenachricht einfügen oder das Tool-Ergebnis selbst erstellen möchtest.

Du übernimmst, indem du die Nachrichten des Runners innerhalb des Schleifenkörpers änderst. Die genaue Methode hängt vom SDK ab. Siehe die folgenden sprachspezifischen Tabs.

Wenn du für eine Iteration übernimmst, hängt der Runner die Assistant-Nachricht oder die Tool-Ergebnisse dieses Zugs nicht an. Du bist dann dafür verantwortlich, die Konversation gültig zu halten: Hänge die Assistant-Nachricht und ein Tool-Ergebnis selbst an (wenn der Zug zählen soll), ändere den Zustand bedingt, damit die Schleife weiterhin enden kann, wenn es keine Tool-Aufrufe gibt, und übergib max_iterations, um die Schleife zu begrenzen. Alle sieben SDKs unterstützen max_iterations.

Verwende generate_tool_call_response(), um das Tool-Ergebnis zu untersuchen oder zu berechnen. Der Aufruf von append_messages() innerhalb der Schleife signalisiert dem Runner, dass du den Verlauf selbst verwaltest. Schließe daher die Assistant-Nachricht und das Tool-Ergebnis in das ein, was du anhängst.

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    max_iterations=10,
    tools=[get_weather],
    messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()
    if tool_response is not None:
        # append_messages() markiert den Zustand als geändert, daher überspringt der Runner
        # sein automatisches Anhängen für diese Iteration. Hänge die Assistant-Nachricht und
        # das Tool-Ergebnis selbst an, plus etwaige Folgenachrichten.
        runner.append_messages(
            message,
            tool_response,
            {"role": "user", "content": "Please be concise."},
        )
    # Wenn kein Tool-Aufruf vorliegt, lass den Zustand unverändert, damit die Schleife endet.

Um Anfrageparameter wie max_tokens zu ändern, ohne den Nachrichtenverlauf zu übernehmen, verwende set_messages_params(). Der Runner hängt die Assistant-Nachricht und das Tool-Ergebnis weiterhin automatisch an.

for message in runner:
    runner.set_messages_params(lambda params: {**params, "max_tokens": 2048})

Automatische Kontextverwaltung

Für lang laufende agentische Aufgaben unterstützen die Tool-Runner von TypeScript und Ruby automatische Komprimierung (compaction), die Zusammenfassungen generiert, wenn die Token-Nutzung einen Schwellenwert überschreitet, sodass die Konversation über die Grenzen des „context window“ (Kontextfenster) hinaus fortgesetzt werden kann. Beide SDKs haben diese clientseitige Option zugunsten der serverseitigen Komprimierung abgekündigt, die über den Anfrageparameter context_management mit dem Tool-Runner jedes SDK funktioniert. Das Python SDK (v1.0 und später) sowie die Tool-Runner von Go, Java, C# und PHP enthalten keine clientseitige Komprimierung.

Tool-Ausführung debuggen

Wenn ein Tool eine Exception wirft, fängt der Tool-Runner sie ab und gibt den Fehler als Tool-Ergebnis mit is_error: true an Claude zurück. Das Tool-Ergebnis enthält die Nachricht der Exception (in Python ihren Typ und ihre Nachricht), nicht den vollständigen Stack-Trace.

Was das SDK protokolliert, ist sprachspezifisch. Das Python SDK protokolliert die vollständige Exception einschließlich Stack-Trace über das Standardmodul logging, wann immer ein Tool eine unbehandelte Exception auslöst. Die SDKs für Python, TypeScript und Java lesen die Umgebungsvariable ANTHROPIC_LOG, um das Logging des SDK zu aktivieren, das Details zu Anfragen und Antworten umfasst:

# Auf Info-Level protokollieren
export ANTHROPIC_LOG=info

# Auf Debug-Level protokollieren für ausführlichere Ausgabe
export ANTHROPIC_LOG=debug

Die SDKs für Go, Ruby, C# und PHP lesen ANTHROPIC_LOG nicht. Außerhalb von Python protokolliert kein SDK ein fehlgeschlagenes Tool: Um zu sehen, warum ein Tool fehlgeschlagen ist, fange die Exception innerhalb der Tool-Funktion ab und protokolliere sie, bevor du zurückkehrst oder sie erneut wirfst.

Tool-Fehler abfangen

Standardmäßig werden Tool-Fehler an Claude zurückgegeben, das dann angemessen reagieren kann. Möglicherweise möchtest du jedoch Fehler erkennen und anders behandeln, zum Beispiel um die Ausführung frühzeitig zu stoppen oder eine benutzerdefinierte Fehlerbehandlung zu implementieren.

Verwende in den SDKs für Python und TypeScript die Tool-Response-Methode (generate_tool_call_response() in Python, generateToolResponse() in TypeScript), um Tool-Ergebnisse abzufangen und auf Fehler zu prüfen, bevor sie an Claude gesendet werden. Die anderen SDKs stellen diesen Hook nicht bereit. Ihre Tabs beschreiben die nächstliegende Alternative:

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[my_tool],
    messages=[{"role": "user", "content": "Run my_tool with the query 'hello'."}],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()

    if tool_response is not None:
        # tool_response ist ein dict: {"role": "user", "content": [...]}
        # Prüfe, ob ein Tool-Ergebnis einen Fehler enthält
        for block in tool_response["content"]:
            if block.get("is_error"):
                # Option 1: Löse eine Exception aus, um die Schleife zu stoppen
                raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                # Option 2: Protokolliere und fahre fort (überlasse es Claude)
                # logger.error(f"Tool error: {json.dumps(block['content'])}")

    # Verarbeite die Nachricht normal
    print(message.content)

Tool-Ergebnisse ändern

Du kannst Tool-Ergebnisse ändern, bevor sie an Claude zurückgesendet werden. Dies ist nützlich, um Metadaten wie cache_control hinzuzufügen, um Prompt-Caching für Tool-Ergebnisse zu aktivieren, oder um die Tool-Ausgabe zu transformieren.

Verwende in den SDKs für Python und TypeScript die Tool-Response-Methode, um das Tool-Ergebnis zu erhalten, und ändere es dann, bevor der Runner fortfährt. Ob du das geänderte Ergebnis explizit anhängst oder es direkt veränderst, hängt vom SDK ab. Siehe die Code-Kommentare in jedem Tab.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[search_documents],
    messages=[
        {
            "role": "user",
            "content": "Search for information about the climate of San Francisco",
        }
    ],
)

for message in runner:
    tool_response = runner.generate_tool_call_response()

    if tool_response is not None:
        # tool_response ist ein dict: {"role": "user", "content": [...]}
        # Ändere das Tool-Ergebnis, um Cache-Control hinzuzufügen
        for block in tool_response["content"]:
            if block["type"] == "tool_result":
                # Füge cache_control hinzu, um dieses Tool-Ergebnis zu cachen
                block["cache_control"] = {"type": "ephemeral"}

        # Hänge die geänderte Antwort an (dies verhindert das automatische Anhängen des Originals)
        runner.append_messages(message, tool_response)

    print(message.content)

Streaming

Aktiviere „streaming“ (Streaming), um die Antwort jedes Zugs inkrementell zu verarbeiten. Jede Iteration liefert ein Stream-Objekt, über dessen Events du iterieren kannst.

Setze stream=True und verwende get_final_message(), um die akkumulierte Nachricht zu erhalten.

client = anthropic.Anthropic()

runner = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[calculate_sum],
    messages=[{"role": "user", "content": "What is 15 + 27?"}],
    stream=True,
)

# Beim Streaming gibt der Runner BetaMessageStream zurück
for message_stream in runner:
    for event in message_stream:
        print("event:", event)
    print("message:", message_stream.get_final_message())

print(runner.until_done())

Nächste Schritte

Erzwinge die Einhaltung des JSON-Schemas bei Claudes Tool-Eingaben mit grammatikbeschränktem Sampling.

Parse tool_use-Blöcke, formatiere tool_result-Antworten und behandle Fehler mit is_error.

Aktiviere, formatiere und deaktiviere parallele Tool-Aufrufe, mit Hinweisen zum Nachrichtenverlauf und zur Fehlerbehebung.

Lege Tool-Schemas fest, schreibe wirksame Beschreibungen und steuere, wann Claude deine Tools aufruft.

Was this page helpful?