Tool Use & Function Calling
Ein Agent wird erst dann handlungsfähig, wenn er mehr kann als Text erzeugen.
Inhaltsverzeichnis
- Warum ein Modell allein oft nicht reicht
- Ein einfaches Beispiel
- Was Function Calling eigentlich bedeutet
- Tools definieren mit
@tool - Warum gute Docstrings über die Tool-Auswahl entscheiden
- Negative Boundaries verhindern Tool-Verwechslungen
- Type Hints sind Pflicht
- Pydantic als Vertrags-Schicht
- Fehlerbehandlung gehört in jedes Tool
- Tool-Ausgaben vor dem Weitergeben filtern
- Werkzeuge zunächst isoliert testen
- Tools an das Modell binden
- Praktische Tool-Beispiele
- Hochriskante Aktionen brauchen zusätzliche Schranken
- Was für Entwickler zuerst wichtig ist
- Abgrenzung zu verwandten Dokumenten
Warum ein Modell allein oft nicht reicht
Sprachmodelle sind stark im Formulieren, Zusammenfassen und Interpretieren von Text. Sie haben aber klare Grenzen. Sie kennen nicht automatisch aktuelle Informationen, können nicht zuverlässig rechnen, greifen nicht selbst auf Dateien zu und führen keine Aktionen in externen Systemen aus. Genau an dieser Stelle beginnt Tool Use.
Werkzeuge erweitern die Fähigkeiten eines Modells über reines Sprachwissen hinaus. Das Modell entscheidet, ob ein Werkzeug gebraucht wird und welche Parameter dafür sinnvoll sind. Der eigentliche Aufruf wird danach von der Anwendung oder dem Agentensystem ausgeführt.
| Grenze des Modells | Typisches Beispiel | Werkzeug löst das Problem |
|---|---|---|
| kein aktuelles Wissen | Wetter, Aktienkurse, heutige Termine | API oder Websuche |
| keine verlässliche Berechnung | 17 * 243 | Rechen-Tool |
| kein Dateizugriff | PDF oder Textdatei lesen | Datei-Tool |
| keine echte Außenwirkung | Termin buchen, E-Mail senden | Kalender- oder Mail-Tool |
[!NOTE] Das Modell führt das Tool nicht selbst aus
Es erzeugt nur die strukturierte Absicht, ein Tool zu verwenden. Die Anwendung validiert und führt den eigentlichen Code aus.
Ein einfaches Beispiel
Ein Assistent soll die Anfrage Multipliziere 7 mit 8 beantworten. Ohne Tool könnte das Modell korrekt antworten, aber die Antwort wäre nicht verlässlich aus einer kontrollierten Operation abgeleitet. Mit Tool Use erkennt das Modell, dass eine Berechnung nötig ist, erzeugt einen Aufruf an multiply(a=7, b=8) und nutzt das Ergebnis danach in der Antwort.
Genau dieses Muster skaliert später auf realere Fälle: Datenbankabfragen, Websuche, Dateilesen oder Freigabeprozesse.
Was Function Calling eigentlich bedeutet
Function Calling ist der Mechanismus, mit dem ein Modell strukturiert angibt, welches Tool mit welchen Parametern ausgeführt werden soll. Das Modell formuliert also nicht nur freien Text, sondern einen maschinenlesbaren Aufruf.
sequenceDiagram
autonumber
participant User
participant LLM
participant Tool
User->>LLM: "Multipliziere 7 mit 8"
LLM->>LLM: Anfrage analysieren
LLM-->>Tool: multiply(a=7, b=8)
Tool-->>LLM: 56
LLM->>User: "Das Ergebnis ist 56."
Für das Modell ist ein Tool letztlich ein Schema mit Name, Beschreibung und Parametern. Anhand dieser Informationen entscheidet es, ob ein Tool passt.
{
"name": "multiply",
"description": "Multipliziert zwei Zahlen.",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "Erste Zahl"},
"b": {"type": "integer", "description": "Zweite Zahl"}
},
"required": ["a", "b"]
}
}
Typischer Fehler: Zu denken, dass das Modell damit bereits sicher und korrekt gehandelt hat. In Wahrheit beginnt Sicherheit erst bei Validierung, Begrenzung und kontrollierter Ausführung.
Tools definieren mit @tool
In LangChain werden Tools typischerweise mit dem @tool-Decorator definiert. Docstring und Type Hints erzeugen dabei das Schema, das das Modell später sieht.
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""Multipliziert zwei ganze Zahlen.
Args:
a: Erste Zahl
b: Zweite Zahl
Returns:
Das Produkt von a und b
"""
return a * b
Dieses Minimalbeispiel zeigt bereits zwei Grundregeln: Type Hints sind notwendig und der Docstring ist nicht bloß Dokumentation für Menschen, sondern Steuerungsinformation für das Modell.
Warum gute Docstrings über die Tool-Auswahl entscheiden
Das Modell wählt ein Tool nicht aufgrund des Python-Codes, sondern auf Basis von Name, Beschreibung und Parametern. Ein schlechter Docstring führt deshalb oft zu falscher oder ausbleibender Tool-Nutzung, selbst wenn die Implementierung technisch korrekt ist.
@tool
def search(q: str) -> str:
"""Sucht etwas."""
return do_search(q)
Dieses Beispiel ist zu vage. Es bleibt offen, wonach gesucht wird, in welchem Bereich gesucht wird und wann das Tool überhaupt verwendet werden soll.
Ein deutlich besserer Docstring grenzt den Zweck, die typischen Anwendungsfälle und die Ausschlüsse explizit ein.
@tool
def search_company_documents(query: str) -> str:
"""🔍 FIRMEN-DOKUMENTENSUCHE – Durchsucht interne Dokumente.
Verwende dieses Tool für Fragen zu:
- Unternehmensrichtlinien und Prozessen
- Produktinformationen und Handbüchern
- internen Regelwerken und Compliance
NICHT geeignet für: Allgemeinwissen, aktuelle Nachrichten, Berechnungen.
Args:
query: Suchbegriff oder Frage in natürlicher Sprache
Returns:
Relevante Textpassagen aus den Firmendokumenten
"""
return document_retriever.search(query)
In der Praxis relevant, wenn: Mehrere ähnliche Tools verfügbar sind und das Modell klar unterscheiden soll, welches Werkzeug wofür gedacht ist.
Negative Boundaries verhindern Tool-Verwechslungen
Sobald ein Agent mehrere ähnliche Werkzeuge verwaltet, entsteht leicht Tool-Overlap. Zwei Tools „suchen“ beide etwas, aber in unterschiedlichen Datenräumen. Ohne klare Ausschlüsse kann das Modell schwer entscheiden, welches Werkzeug gemeint ist.
@tool
def search_products(query: str) -> str:
"""🛒 PRODUKTSUCHE – Durchsucht den Produktkatalog.
NICHT geeignet für: Kundendaten, Bestellungen, Rechnungen.
Verändert KEINE Daten.
"""
...
@tool
def search_customers(query: str) -> str:
"""👤 KUNDENSUCHE – Durchsucht die Kundendatenbank.
NICHT geeignet für: Produktinfos, Lagerbestand, Preise.
Sendet KEINE E-Mails.
"""
...
Gerade bei Agenten mit vielen Tools ist diese Negativabgrenzung kein Zusatz, sondern ein wichtiges Architekturmittel. Sie reduziert Fehlgriffe des Modells deutlich.
Type Hints sind Pflicht
Ohne Type Hints entsteht kein sauberes Schema. Das Modell weiß dann nicht zuverlässig, welche Parameter es liefern soll oder welche Datentypen erwartet werden.
# Falsch
@tool
def add(a, b):
"""Addiert zwei Zahlen."""
return a + b
# Richtig
@tool
def add(a: int, b: int) -> int:
"""Addiert zwei ganze Zahlen."""
return a + b
[!WARNING] Fehlende Type Hints erzeugen schwache Tool-Schemata
Wenn das Schema unvollständig ist, kann das Modell Parameter falsch oder gar nicht befüllen.
Pydantic als Vertrags-Schicht
Bei produktionsnäheren Tools reicht ein Docstring oft nicht aus. Dann wird ein Pydantic-Modell zur eigentlichen Contract-Schicht. Es definiert zugleich das Schema, validiert die Eingaben und macht Übergaben zwischen Komponenten konsistent.
from pydantic import BaseModel, Field
from langchain_core.tools import tool
class RefundRequest(BaseModel):
customer_id: str = Field(description="Eindeutige Kunden-ID")
amount: float = Field(ge=0, description="Erstattungsbetrag in EUR")
reason: str = Field(description="Begründung der Erstattung")
@tool(args_schema=RefundRequest)
def process_refund(customer_id: str, amount: float, reason: str) -> dict:
"""💰 ERSTATTUNG – Verarbeitet eine geprüfte Rückerstattung.
NICHT geeignet für: Kontosperrungen, Kundendaten-Änderungen.
Prüft KEINE Berechtigung – dafür zuerst Policy prüfen.
"""
return {"status": "processed", "amount": amount}
Der Vorteil liegt darin, dass Schema und Validierung nicht auseinanderdriften. Genau deshalb ist Pydantic für höherwertige Tool-Schnittstellen oft sinnvoll.
Fehlerbehandlung gehört in jedes Tool
Ein Tool kann fehlschlagen: Datei nicht gefunden, Datenbank nicht erreichbar, Eingabe ungültig. Gute Tools liefern deshalb nicht nur einen Absturz, sondern eine verständliche Rückmeldung, mit der der Agent weiterarbeiten kann.
@tool
def safe_divide(a: float, b: float) -> str:
"""Dividiert a durch b mit Fehlerbehandlung."""
try:
if b == 0:
return "Fehler: Division durch Null ist nicht erlaubt."
return f"Ergebnis: {a / b:.4f}"
except Exception as e:
return f"Fehler bei der Berechnung: {str(e)}"
Das gilt auch für externe Systeme:
@tool
def query_database(sql: str) -> str:
"""🗄️ DATENBANK – Führt eine SQL-SELECT-Abfrage aus."""
try:
if not sql.strip().upper().startswith("SELECT"):
return "Fehler: Nur SELECT-Anweisungen sind erlaubt."
result = database.execute(sql)
return f"Ergebnis: {result}"
except ConnectionError:
return "Fehler: Keine Verbindung zur Datenbank."
except TimeoutError:
return "Fehler: Abfrage hat zu lange gedauert."
except Exception as e:
return f"Unerwarteter Fehler: {str(e)}"
Typischer Fehler: Nur Error zurückzugeben. Ein Agent braucht eine informative Fehlermeldung, sonst kann er weder sinnvoll erklären noch sinnvoll weiterfragen.
Tool-Ausgaben vor dem Weitergeben filtern
Was ein Werkzeug zurückgibt, ist nicht automatisch das, was in den Agenten-Kontext fließen sollte. Rohe API-Antworten enthalten oft Statusfelder, verschachtelte Metadaten oder große Mengen irrelevanter Daten. Fließt all das ungefiltert in das Kontextfenster, verbraucht es Token, kann das Modell ablenken und erhöht das Risiko, dass nachfolgende Entscheidungen auf Nebeninformationen statt auf dem Kern basieren.
Typischer Fehler: Die Rückgabe eines API-Aufrufs wird direkt als Tool-Ergebnis übergeben, ohne dass geprüft wird, welche Felder für den nächsten Schritt tatsächlich gebraucht werden. Ein Agent, der zehn Suchresultate mit je fünfzig Feldern erhält, wird keinen dieser Einträge besser auswerten als einen, der fünf Treffer mit drei relevanten Feldern bekommt — er wird es aber langsamer und unzuverlässiger tun.
@tool
def search_products(query: str) -> str:
"""Sucht nach Produkten und gibt eine kompakte Liste zurück."""
raw = product_api.search(query)
results = [
{"id": p["id"], "name": p["name"], "price": p["price_eur"]}
for p in raw.get("items", [])[:5]
]
return json.dumps(results, ensure_ascii=False)
Das Filterverhalten gehört zur Tool-Implementierung, nicht zur Prompt-Gestaltung. Eine saubere Abgrenzung zwischen dem, was die externe API liefert, und dem, was der Agent für seine Entscheidung braucht, ist Teil des Harness-Designs.
Werkzeuge zunächst isoliert testen
Bevor ein Tool an einen Agenten gebunden wird, sollte es einzeln geprüft werden. Dazu gehört die direkte Ausführung ebenso wie die Kontrolle von Name, Beschreibung und Schema.
print(f"Name: {multiply.name}")
print(f"Beschreibung: {multiply.description}")
print(f"Schema: {multiply.args_schema.schema()}")
result = multiply.invoke({"a": 7, "b": 8})
print(f"Ergebnis: {result}")
Gerade in Entwicklerprojekten spart dieser Zwischenschritt viel Zeit, weil unklare Schemas oder fehlerhafte Parameter nicht erst im Agentenverbund auffallen.
Tools an das Modell binden
Sobald Werkzeuge definiert sind, können sie an ein Modell gebunden werden. Das Modell entscheidet dann selbst, ob ein Tool sinnvoll ist. Mit bind_tools() wird zunächst nur die Tool-Absicht erzeugt.
from langchain.chat_models import init_chat_model
llm = init_chat_model("openai:gpt-4o-mini", temperature=0.0)
llm_with_tools = llm.bind_tools([multiply, safe_divide])
response = llm_with_tools.invoke("Was ist 15 mal 23?")
print(response.tool_calls)
Erst ein Agent oder eine umgebende Laufzeit führt diesen Aufruf wirklich aus.
from langchain.agents import create_agent
agent = create_agent(
model=llm,
tools=[multiply, safe_divide],
system_prompt="Nutze die verfügbaren Tools für Berechnungen."
)
response = agent.invoke({
"messages": [{"role": "user", "content": "Berechne 15 mal 23"}]
})
Praktische Tool-Beispiele
Ein Datums-Tool ist nützlich, wenn aktuelle Zeitinformation gebraucht wird:
from datetime import datetime
@tool
def get_current_date() -> str:
"""📅 DATUM – Gibt das aktuelle Datum zurück."""
now = datetime.now()
weekdays = ["Montag", "Dienstag", "Mittwoch", "Donnerstag",
"Freitag", "Samstag", "Sonntag"]
weekday = weekdays[now.weekday()]
return f"{weekday}, {now.strftime('%d.%m.%Y')}"
Ein Datei-Tool zeigt gut, wie externe Operationen kontrolliert werden:
from pathlib import Path
@tool
def read_file(filepath: str) -> str:
"""📄 DATEI LESEN – Liest den Inhalt einer Textdatei."""
try:
path = Path(filepath)
if not path.exists():
return f"Fehler: Datei '{filepath}' nicht gefunden."
if not path.is_file():
return f"Fehler: '{filepath}' ist keine Datei."
content = path.read_text(encoding="utf-8")
if len(content) > 5000:
return content[:5000] + "\n\n[... Datei gekürzt ...]"
return content
except Exception as e:
return f"Fehler beim Lesen: {str(e)}"
Ein Websuch-Tool ist besonders für aktuelle Informationen nützlich, die nicht im Modellwissen liegen:
@tool
def web_search(query: str, num_results: int = 3) -> str:
"""🌐 WEBSUCHE – Durchsucht das Internet nach aktuellen Informationen."""
return f"Suchergebnisse für '{query}': [Platzhalter für echte Ergebnisse]"
Hochriskante Aktionen brauchen zusätzliche Schranken
Bei Operationen mit realen Folgen, etwa Rückerstattung, Löschung oder Zahlung, reicht ein einzelnes Tool oft nicht aus. Ein sinnvolles Muster ist Two-Step Veto: Zuerst wird geprüft, danach erst ausgeführt.
@tool
def propose_refund(customer_id: str, amount: float) -> dict:
"""💡 ERSTATTUNGSVORSCHLAG – Prüft, führt aber NICHT aus."""
return PolicyEngine().check_policy(customer_id, amount)
@tool
def commit_refund(customer_id: str, amount: float) -> dict:
"""✅ ERSTATTUNG DURCHFÜHREN – Führt eine bereits geprüfte Erstattung aus."""
return financial_system.process(customer_id, amount)
flowchart LR
A[Agent] --> B[propose_refund]
B --> C{approved?}
C -->|Ja| D[commit_refund]
C -->|Nein| E[escalate_to_human]
Wenn nach einer Policy-Prüfung deterministisch feststeht, welches Tool als Nächstes aufgerufen werden muss, kann ein System den nächsten Tool-Schritt auch erzwingen.
if tool_result.get("action_required") == "escalate_to_human":
client.messages.create(
model="claude-opus-4-6",
messages=messages,
tools=tools,
tool_choice={"type": "tool", "name": "escalate_to_human"}
)
Nicht geeignet, wenn: Tool-Auswahl generell durch Zwang gesteuert wird. Forced tool_choice ist für klare Sonderfälle gedacht, nicht als Dauerersatz für gutes Routing.
Was für Entwickler zuerst wichtig ist
Für einen ersten Agenten reichen meist wenige, klar benannte Werkzeuge mit guten Docstrings und sauberer Fehlerbehandlung. Zu viele Tools auf einmal überfordern nicht nur das Modell, sondern auch das eigene Debugging. Ein kleines, klar abgegrenztes Toolset ist fast immer der bessere Start.
Entwickler unterschätzen oft, dass Tool Use nicht nur neue Fähigkeiten bringt, sondern auch neue Verantwortung. Sobald ein Agent lesen, suchen, schreiben oder externe Systeme verändern kann, wird Tool-Design zur Sicherheitsfrage.
In der Praxis relevant, wenn: Ein Agent auf viele Werkzeuge zugreifen soll, diese aber nicht alle gleichzeitig braucht. Statt alle Tools auf einmal bereitzustellen, kann man dem Agenten zunächst nur wenige, klar beschriebene Einstiegs-Tools geben. Die vollständigen Parameter-Beschreibungen oder spezialisierte Werkzeuge werden erst dann in den Kontext injiziert, wenn der Agent durch einen ersten Tool-Aufruf signalisiert, in welche Richtung er arbeitet. Dieses Prinzip — als Progressive Disclosure bezeichnet — reduziert den Token-Verbrauch, verringert mehrdeutige Tool-Auswahlen und macht das Debugging einfacher, weil in jedem Schritt weniger gleichzeitig entschieden wird.
Abgrenzung zu verwandten Dokumenten
| Dokument | Frage |
|---|---|
| Agenten-Architekturen | Wie werden Werkzeuge in ReAct, Workflows oder Multi-Agent-Systeme eingebettet? |
| Agenten-Sicherheit | Wie werden Tool-Aufrufe abgesichert und Missbrauch begrenzt? |
| RAG-Konzepte | Wann ist Retrieval die bessere Alternative zu direkten Tool-Aufrufen? |
Version: 1.4
Stand: Mai 2026
Kurs: KI-Agenten. Verstehen. Anwenden. Gestalten.