Python & AI Tutorials Logo
LangChain & LangGraph

12: Tools für Ihren Agent erstellen

In Teil IV werden wir einen Agent(agent) erstellen — eine KI, die herausfindet, was mit der Anfrage eines Benutzers zu tun ist, und es dann tut, statt nur darauf zu antworten wie ein Chatbot.

Hier der Unterschied in der Praxis. Angenommen, ein Benutzer fragt: „Bitte stornieren Sie Bestellung #12345." Ein Chatbot würde etwas sagen wie: „Gehen Sie zu Meine Seite > Bestellverlauf und klicken Sie auf die Schaltfläche ‚Stornieren' für diese Bestellung." Von da an liegt es am Benutzer, diese Schritte selbst auszuführen. Ein Agent erledigt stattdessen die Arbeit — er sucht die Bestellung, prüft, ob sie storniert werden kann, und storniert sie. Er ergreift Maßnahmen.

Was das möglich macht, sind Tools(tools): ein Tool, das eine Bestellung sucht, ein Tool, das eine storniert, ein Tool, das eine E-Mail versendet. Mit Tools ist ein LLM nicht mehr auf die Generierung von Text beschränkt, sondern beginnt, tatsächlich Dinge zu erledigen.

Bitte stornieren Sie Bestellung #12345.

ruft get_order(12345) auf

Bestellung gefunden,
storniertbar

ruft cancel_order(12345) auf

Stornierung abgeschlossen

Ihre Bestellung wurde storniert.

Benutzer

Agent-Schleife

Bestellsuche-Tool

Bestellstornierungs-Tool

Wir werden dies in Teil IV Stück für Stück aufbauen: zunächst die Tools, die der Agent verwenden wird (dieses Kapitel), dann das Verbinden dieser Tools mit einem LLM (Kapitel 13) und schließlich die Agent-Schleife, die den Zyklus entscheiden → handeln → beobachten durchläuft (Kapitel 14).

Dieses Kapitel behandelt den ersten Teil — das Definieren von Tools, das Verbinden mit echten Daten und das sichere Behandeln von Fehlern.

12.1) Tools mit dem @tool-Decorator definieren

12.1.1) Wie funktioniert ein Tool?

Wir haben gerade gesehen, wie ein Agent mithilfe von Tools eine Bestellung gesucht und storniert hat. Bevor wir weitermachen, sollten wir eines klarstellen: Wenn Leute sagen „das LLM verwendet ein Tool", klingt das so, als würde das LLM es direkt aufrufen. Das tut es nicht. Das LLM führt selbst niemals etwas aus — alles, was es tut, ist, darum zu bitten, dass ein Tool mit bestimmten Argumenten aufgerufen wird. Die eigentliche Ausführung findet in unserem Code statt.

Damit das funktioniert, muss das LLM wissen, welche Tools existieren und wann jedes davon zutrifft. Daher kommt jedes Tool mit drei Metadaten:

  • name — eine kurze Kennung wie get_order, die das LLM verwendet, um anzugeben, welches Tool es haben möchte.
  • description — ein Satz, der beschreibt, was das Tool tut und wann es zu verwenden ist. Das liest das LLM, um das richtige Tool für die Aufgabe auszuwählen.
  • Input-Schema — was die Parameter des Tools sind: ihre Namen, Typen und Bedeutung. Das LLM benötigt dies, um die Argumente korrekt auszufüllen.

Nichts davon erfordert zusätzliche Arbeit Ihrerseits. name kommt direkt vom Namen der Funktion, description kommt von ihrem Docstring und das Input-Schema kommt von den Typ-Hints der Parameter. Alles, was Sie tun müssen, ist, LangChains @tool-Decorator anzufügen.

12.1.2) Ihr erstes Tool erstellen

Setzen wir das in die Praxis um. Schreiben Sie eine Funktion mit Typ-Hints und einem Docstring und dekorieren Sie sie dann mit @tool.

python
from langchain.tools import tool
 
@tool
def get_weather(city: str) -> str:
    """Ruft das aktuelle Wetter für eine bestimmte Stadt ab."""
    return f"It's always sunny in {city}!"

Sehen wir uns an, was @tool für uns generiert hat.

python
print(get_weather.name)
# Ausgabe: get_weather
 
print(get_weather.description)
# Ausgabe: Ruft das aktuelle Wetter für eine bestimmte Stadt ab.
 
print(get_weather.args)
# Ausgabe: {'city': {'title': 'City', 'type': 'string'}}

Der Funktionsname get_weather wurde zu seinem name, der Docstring wurde zu seiner description und der Typ-Hint city: str wurde zu seinem Input-Schema. Alle drei Metadaten aus dem vorherigen Abschnitt wurden automatisch generiert. Das verwendet das LLM, um ein Tool auszuwählen und seine Argumente auszufüllen.

Sobald @tool angefügt ist, wird die Funktion zu einem LangChain-Tool-Objekt, was bedeutet, dass Sie sie nicht mehr wie eine normale Funktion aufrufen können — get_weather("Paris") funktioniert nicht. Stattdessen rufen Sie sie mit .invoke() auf, derselben Standard-Ausführungsmethode, die wir für Chains zurück in Kapitel 6 verwendet haben. Argumente werden als Dictionary übergeben:

python
result = get_weather.invoke({"city": "Paris"})
print(result)
# Ausgabe: It's always sunny in Paris!

12.1.3) name und description anpassen

Standardmäßig kommt name vom Funktionsnamen und description vom Docstring. Sie können beides überschreiben.

Übergeben Sie einen Namen als erstes Argument an @tool:

python
@tool("web_search")
def search(query: str) -> str:
    """Durchsucht das Web nach Informationen."""
    return f"Results for: {query}"
 
print(search.name)
# Ausgabe: web_search

Sie können auch die Beschreibung überschreiben, indem Sie den Parameter description verwenden. Das ist nützlich, wenn Sie den Docstring als Notiz für andere Entwickler beibehalten möchten, während Sie dem LLM etwas Maßgeschneiderteres geben:

python
@tool("calculator", description="Führt Arithmetik aus. Verwenden Sie dies für jedes mathematische Problem.")
def calc(expression: str) -> str:
    """Wertet eine mathematische Ausdrucks-Zeichenkette aus."""
    return str(eval(expression))  # WARNUNG: eval() ist unsicher. Verwenden Sie es niemals in Produktion.

Bleiben Sie bei snake_case für Tool-Namen — einige LLM-Anbieter lehnen Namen mit Leerzeichen oder Sonderzeichen ab.

12.1.4) Ein Input-Schema mit Pydantic definieren

Wenn ein Tool mehrere Parameter annimmt oder Sie jeden einzeln beschreiben möchten, definieren Sie das Input-Schema stattdessen mit einem Pydantic-Modell. Das sind dieselben BaseModel und Field, die wir für strukturierte Ausgabe zurück in Kapitel 7 verwendet haben.

python
from pydantic import BaseModel, Field
from langchain.tools import tool
 
class WeatherInput(BaseModel):
    """Eingabe für Wetterabfragen."""
    location: str = Field(description="Stadtname (z. B. Seoul, Tokio)")
    units: str = Field(default="celsius", description="Temperatureinheit (celsius oder fahrenheit)")
 
@tool(args_schema=WeatherInput)
def get_weather_detailed(location: str, units: str = "celsius") -> str:
    """Ruft das aktuelle Wetter mit einer gewählten Temperatureinheit ab."""
    temp = 22 if units == "celsius" else 72
    return f"Current weather in {location}: {temp} degrees {units[0].upper()}"

Was auch immer Sie in Field(description=...) schreiben, wird Teil des Input-Schemas, das das LLM liest, sodass es genau weiß, was jeder Parameter bedeutet. Meistens sind Typ-Hints und ein klarer Docstring alles, was Sie brauchen — greifen Sie nur dann zu args_schema, wenn Sie diese zusätzliche Detailebene pro Parameter benötigen.

12.2) Tool-Fehler behandeln

In der realen Welt können Tools fehlschlagen — eine Datenbankverbindung bricht ab oder es taucht eine Eingabe auf, die Sie nicht eingeplant hatten. In diesem Abschnitt behandeln wir diese Fehler innerhalb des Tools selbst, sodass der Agent sinnvoll reagieren kann, statt abrupt zu stoppen. Zunächst richten wir die Funktionen ein, auf die sich unsere Tools stützen werden.

12.2.1) Vorbereitung: Funktionen zur Produktsuche

python
# product_service.py
 
PRODUCTS = {
    1: {"name": "Wireless Mouse", "price": 29.99, "stock": 120},
    2: {"name": "Mechanical Keyboard", "price": 89.99, "stock": 0},
    3: {"name": "USB-C Hub", "price": 45.50, "stock": 35},
    4: {"name": "Laptop Stand", "price": 39.00, "stock": 8},
}
 
def fetch_product(product_id: int) -> dict:
    """Sucht Produktinformationen anhand der ID."""
    product = PRODUCTS.get(product_id)
    if product is None:
        raise ValueError(f"Product with ID {product_id} not found.")
    return product
 
def fetch_stock(product_id: int) -> int:
    """Gibt die Lagerbestandsmenge für ein Produkt zurück."""
    product = PRODUCTS.get(product_id)
    if product is None:
        raise ValueError(f"Product with ID {product_id} not found.")
    return product["stock"]

Beide Funktionen lösen einen ValueError aus, wenn ihnen eine Produkt-ID übergeben wird, die nicht existiert.

12.2.2) Fehler in einem Tool behandeln

Verpacken wir fetch_product in ein get_product-Tool.

python
from langchain.tools import tool
from product_service import fetch_product
 
@tool
def get_product(product_id: int) -> str:
    """Sucht ein Produkt anhand seiner ID. Gibt seinen Namen, Preis und Lagerbestand zurück."""
    product = fetch_product(product_id)
    return f"Product {product_id}: {product['name']} — ${product['price']:.2f}, {product['stock']} in stock."

Mit einer gültigen ID funktioniert es wie erwartet.

python
print(get_product.invoke({"product_id": 1}))
# Ausgabe: Product 1: Wireless Mouse — $29.99, 120 in stock.

Übergeben Sie aber eine ID, die nicht existiert, löst fetch_product einen ValueError aus, den nichts abfängt — die Ausführung des Agents stoppt genau dort.

python
print(get_product.invoke({"product_id": 99}))
# ValueError: Product with ID 99 not found.

Die Lösung ist unkompliziert: Fangen Sie die Ausnahme innerhalb des Tools ab und geben Sie eine Zeichenkette zurück, die das LLM verstehen kann, statt sie sich ausbreiten zu lassen. Ob Erfolg oder Fehler, das Tool gibt immer eine Zeichenkette zurück, und das LLM verwendet diese Zeichenkette, um zu entscheiden, was als Nächstes zu tun ist.

python
from langchain.tools import tool
from product_service import fetch_product
 
@tool
def get_product(product_id: int) -> str:
    """Sucht ein Produkt anhand seiner ID. Gibt seinen Namen, Preis und Lagerbestand zurück."""
    try:
        product = fetch_product(product_id)
        return f"Product {product_id}: {product['name']} — ${product['price']:.2f}, {product['stock']} in stock."
    except ValueError as e:
        return f"Error: {e}"
    except Exception as e:
        return f"Unexpected error looking up product {product_id}: {e}"
python
print(get_product.invoke({"product_id": 1}))
# Ausgabe: Product 1: Wireless Mouse — $29.99, 120 in stock.
 
print(get_product.invoke({"product_id": 99}))
# Ausgabe: Error: Product with ID 99 not found.

Eine nicht existierende ID löst keine Ausnahme mehr aus — stattdessen gibt sie eine Fehlermeldung zurück, die das LLM verstehen kann.

Wenden wir dasselbe Muster auf check_stock an:

python
from product_service import fetch_stock
 
@tool
def check_stock(product_id: int) -> str:
    """Prüft, ob ein Produkt aktuell auf Lager ist."""
    try:
        stock = fetch_stock(product_id)
        if stock > 0:
            return f"{stock} units available."
        return "Out of stock."
    except ValueError as e:
        return f"Error: {e}"
    except Exception as e:
        return f"Unexpected error checking stock for product {product_id}: {e}"

Auf diese Weise erstellte Tools können direkt mit .invoke() getestet werden. Stellen Sie sicher, dass ein Tool für sich allein korrekt funktioniert, bevor Sie es mit einem LLM verbinden. Andernfalls können Sie später, wenn innerhalb des Agents etwas schiefgeht, nicht erkennen, ob das Tool kaputt ist oder das Modell einfach eine schlechte Entscheidung getroffen hat.