Tool Use ist das "Gehirn" von AI Agents. Das LLM entscheidet WANN und WIE Tools zu nutzen sind.

Grundkonzept

User: "Was ist das Wetter in Berlin?"
   ↓
LLM: "Ich brauche das get_weather Tool mit city=Berlin"
   ↓
System: Ruft get_weather("Berlin") auf
   ↓
Result: "20°C, sonnig"
   ↓
LLM: "In Berlin sind es 20°C und die Sonne scheint"
   ↓
User: "Danke für die Info!"

Das ist Tool Use: Das LLM denkt, der Code handelt.

Teil 1: Tool Schema Definition

Tools sind JSON Schema + Beschreibung.

Basis-Tool

{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Ruft das aktuelle Wetter ab",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "Stadtname (z.B. Berlin, New York)"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperatur-Einheit"
                }
            },
            "required": ["city"]
        }
    }
}

Schema Feld-Erklärung:

  • name: Eindeutige Tool-ID (snake_case)
  • description: Was das Tool tut (2-3 Sätze)
  • parameters: JSON Schema für Eingaben
  • required: Pflicht-Parameter (kann leer sein)

Multi-Parameter Tool

{
    "type": "function",
    "function": {
        "name": "search_database",
        "description": "Sucht in der Datenbank nach Dokumenten",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Suchterm (z.B. 'AI Trends 2026')"
                },
                "date_from": {
                    "type": "string",
                    "format": "date",
                    "description": "Start-Datum (YYYY-MM-DD)"
                },
                "date_to": {
                    "type": "string",
                    "format": "date",
                    "description": "End-Datum (YYYY-MM-DD)"
                },
                "limit": {
                    "type": "integer",
                    "description": "Max. Ergebnisse (default: 10)",
                    "minimum": 1,
                    "maximum": 100
                }
            },
            "required": ["query"]
        }
    }
}

Teil 2: Claude Tool Use

Einfaches Beispiel

# claude_tools.py
from anthropic import Anthropic

client = Anthropic()

# Tools definieren
tools = [
    {
        "type": "function",
        "function": {
            "name": "calculator",
            "description": "Führt mathematische Berechnungen durch",
            "parameters": {
                "type": "object",
                "properties": {
                    "operation": {
                        "type": "string",
                        "enum": ["add", "subtract", "multiply", "divide"],
                        "description": "Operation"
                    },
                    "a": {"type": "number", "description": "Erste Zahl"},
                    "b": {"type": "number", "description": "Zweite Zahl"}
                },
                "required": ["operation", "a", "b"]
            }
        }
    }
]

# Tool implementieren
def execute_calculator(operation: str, a: float, b: float) -> str:
    if operation == "add":
        return str(a + b)
    elif operation == "subtract":
        return str(a - b)
    elif operation == "multiply":
        return str(a * b)
    elif operation == "divide":
        if b == 0:
            return "Error: Division by zero"
        return str(a / b)

# Claude mit Tools
def solve_math_problem(problem: str) -> str:
    messages = [
        {"role": "user", "content": problem}
    ]

    # Erste Response
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )

    # Loop bis fertig
    while response.stop_reason == "tool_use":
        # Tool-Call extrahieren
        tool_use = None
        for content in response.content:
            if content.type == "tool_use":
                tool_use = content
                break

        if not tool_use:
            break

        # Tool ausführen
        tool_name = tool_use.name
        tool_input = tool_use.input
        tool_use_id = tool_use.id

        print(f"Claude: Rufe {tool_name} auf mit {tool_input}")

        result = execute_calculator(**tool_input)
        print(f"Result: {result}")

        # Nächster Turn mit Result
        messages.append({"role": "assistant", "content": response.content})
        messages.append({
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": result
                }
            ]
        })

        # Erneut Claude aufrufen
        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )

    # Finale Antwort
    for content in response.content:
        if hasattr(content, "text"):
            return content.text

# Test
answer = solve_math_problem("Was ist 123 mal 456?")
print(f"\nFinal Answer: {answer}")

Output:

Claude: Rufe calculator auf mit {'operation': 'multiply', 'a': 123, 'b': 456}
Result: 56088

Final Answer: 123 mal 456 ergibt 56.088

Teil 3: Parallel Tool Calling

Moderne LLMs können mehrere Tools gleichzeitig aufrufen.

# parallel_tools.py
from anthropic import Anthropic

client = Anthropic()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "Ruft Aktienkurs ab",
            "parameters": {
                "type": "object",
                "properties": {
                    "symbol": {
                        "type": "string",
                        "description": "Aktien-Symbol (z.B. AAPL, GOOGL)"
                    }
                },
                "required": ["symbol"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_news",
            "description": "Ruft aktuelle News ab",
            "parameters": {
                "type": "object",
                "properties": {
                    "ticker": {
                        "type": "string",
                        "description": "Aktien-Symbol"
                    }
                },
                "required": ["ticker"]
            }
        }
    }
]

def get_stock_price(symbol: str) -> str:
    prices = {"AAPL": "195.50", "GOOGL": "140.25", "MSFT": "425.00"}
    return prices.get(symbol, "Symbol nicht gefunden")

def get_news(ticker: str) -> str:
    news_db = {
        "AAPL": "Apple präsentiert neues iPhone",
        "GOOGL": "Google investiert in KI"
    }
    return news_db.get(ticker, "Keine News")

# Parallel Tool Calling
def get_market_insights(symbols: list) -> str:
    prompt = f"Gib mir Aktienkurse und News für: {', '.join(symbols)}"

    messages = [
        {"role": "user", "content": prompt}
    ]

    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )

    # Alle Tool-Calls (parallel!) sammeln
    tool_results = []

    for content in response.content:
        if content.type == "tool_use":
            tool_name = content.name
            tool_input = content.input
            tool_use_id = content.id

            # Parallel ausführen
            if tool_name == "get_stock_price":
                result = get_stock_price(**tool_input)
            elif tool_name == "get_news":
                result = get_news(**tool_input)

            tool_results.append({
                "type": "tool_result",
                "tool_use_id": tool_use_id,
                "content": result
            })

    # Alle Results auf einmal zurück
    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": tool_results
    })

    # Finale Response
    final = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )

    for content in final.content:
        if hasattr(content, "text"):
            return content.text

# Test
insights = get_market_insights(["AAPL", "GOOGL"])
print(insights)

Wichtig: Claude ruft get_stock_price und get_news gleichzeitig auf!

Teil 4: Claude vs OpenAI vs Gemini

Aspekt Claude OpenAI Gemini
Tool Definition Anthropic Format OpenAI Format Google Format
Parallel Tools ✓ ✓ ✓
Auto Retry ✓ Manuell Manuell
Error Handling Gut OK OK
Streaming ✓ ✓ ✓

OpenAI Function Calling

# openai_tools.py
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Gets weather",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# OpenAI API call
response = client.chat.completions.create(
    model="gpt-4-turbo",
    messages=[{"role": "user", "content": "Was ist das Wetter in Berlin?"}],
    tools=tools,
    tool_choice="auto"
)

# Tool Call extrahieren
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    print(f"Tool: {tool_call.function.name}")
    print(f"Args: {tool_call.function.arguments}")

Google Gemini Function Calling

# gemini_tools.py
import google.generativeai as genai

model = genai.GenerativeModel("gemini-2.0-flash")

tools = [
    genai.types.Tool(
        function_declarations=[
            genai.types.FunctionDeclaration(
                name="get_weather",
                description="Gets weather",
                parameters=genai.types.Schema(
                    type=genai.types.Type.OBJECT,
                    properties={
                        "location": genai.types.Schema(
                            type=genai.types.Type.STRING,
                            description="City name"
                        )
                    },
                    required=["location"]
                )
            )
        ]
    )
]

response = model.generate_content(
    "Was ist das Wetter in Berlin?",
    tools=tools
)

# Function calls sind in response.function_calls
for call in response.function_calls:
    print(f"Function: {call.name}")
    print(f"Args: {call.args}")

Teil 5: Best Practices

1. Tool-Beschreibung ist KRITISCH

Schlecht:

"description": "Tool"

Gut:

"description": "Sucht Produkte in unserem Katalog nach Preis, Kategorie und Verfügbarkeit"

Das LLM nutzt die Beschreibung um zu entscheiden OB es das Tool nutzen soll!

2. Parameter beschreiben

Schlecht:

"properties": {
    "q": {"type": "string"}
}

Gut:

"properties": {
    "q": {
        "type": "string",
        "description": "Suchterm (z.B. 'rote Schuhe Größe 42')"
    }
}

3. Enum für begrenzte Optionen

"properties": {
    "status": {
        "type": "string",
        "enum": ["pending", "processing", "completed"],
        "description": "Auftrags-Status"
    }
}

Das LLM kann nur diese 3 Werte wählen.

4. Max/Min für Zahlen

"properties": {
    "max_results": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100,
        "description": "Max Ergebnisse"
    }
}

Verhindert unrealistische Eingaben (z.B. 999.999 Ergebnisse).

5. Fehlerbehandlung

def execute_tool(tool_name: str, tool_input: dict) -> str:
    """Sichere Tool-Ausführung"""

    try:
        if tool_name == "get_weather":
            location = tool_input.get("location", "")
            if not location:
                return "Error: location is required"
            return f"Weather in {location}: 20°C, sunny"

        elif tool_name == "search_database":
            # ... Implementation ...
            pass

        else:
            return f"Unknown tool: {tool_name}"

    except Exception as e:
        return f"Error: {str(e)}"

Teil 6: Tool Use Anti-Patterns

❌ Tool zu allgemein

# Falsch: "do_anything" Tool
{
    "name": "do_anything",
    "description": "Does anything you want"
}

Das LLM weiß nicht wann es sinnvoll ist!

❌ Zu viele Tools

Mehr als 20 Tools: Das LLM wird überfordert.

Besser: Tools in Kategorien teilen.

❌ Tools ohne Error Handling

# Falsch: Kein Error Handling
def get_user(user_id):
    return users[user_id]  # KeyError wenn nicht existiert!

# Richtig:
def get_user(user_id):
    if user_id not in users:
        return f"Error: User {user_id} not found"
    return users[user_id]

❌ Infinite Loops

# Falsch: LLM ruft Tool auf, das wieder Tool braucht
Tool A → Tool B → Tool C → Tool A → ...

# Besser: Limit für Tool-Aufrufe
max_tool_calls = 10

Teil 7: Production Tool Use

# production_tools.py
import logging
from typing import Optional
from anthropic import Anthropic

logger = logging.getLogger(__name__)

class ProductionToolUse:
    def __init__(self, max_tool_calls: int = 10, timeout: int = 30):
        self.client = Anthropic()
        self.max_tool_calls = max_tool_calls
        self.timeout = timeout
        self.tools = []  # Definieren

    def execute_with_tools(self, prompt: str) -> Optional[str]:
        messages = [
            {"role": "user", "content": prompt}
        ]

        tool_call_count = 0

        while tool_call_count < self.max_tool_calls:
            try:
                response = self.client.messages.create(
                    model="claude-3-5-sonnet-20241022",
                    max_tokens=1024,
                    tools=self.tools,
                    messages=messages,
                    timeout=self.timeout
                )

                if response.stop_reason == "end_turn":
                    # Fertig
                    for content in response.content:
                        if hasattr(content, "text"):
                            return content.text
                    return None

                elif response.stop_reason == "tool_use":
                    # Tools ausführen
                    tool_results = []

                    for content in response.content:
                        if content.type == "tool_use":
                            result = self._execute_tool(content.name, content.input)
                            tool_results.append({
                                "type": "tool_result",
                                "tool_use_id": content.id,
                                "content": result
                            })

                    messages.append({"role": "assistant", "content": response.content})
                    messages.append({
                        "role": "user",
                        "content": tool_results
                    })

                    tool_call_count += 1

                else:
                    logger.warning(f"Unexpected stop_reason: {response.stop_reason}")
                    return None

            except Exception as e:
                logger.error(f"Tool execution error: {e}")
                return None

        logger.warning(f"Max tool calls ({self.max_tool_calls}) reached")
        return None

    def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
        # Implementiere Tools hier
        return "Tool result"

Zusammenfassung

Tool Use Pattern:

  1. Definieren → JSON Schema + Beschreibung
  2. Übergeben → An LLM API
  3. LLM entscheidet → Wann welches Tool
  4. Ausführen → Dein Code läuft das Tool
  5. Zurück → Result an LLM
  6. Loop → Bis LLM fertig

Best Practices:

  • ✓ Klare, spezifische Beschreibungen
  • ✓ Enum für Optionen, min/max für Zahlen
  • ✓ Error Handling in jedem Tool
  • ✓ Max 15-20 Tools
  • ✓ Max tool_calls Limit (Sicherheit)

Nächste Schritte:

  • RAG Tool integration
  • Database Query Tool
  • API Call Tool (für externe Services)