# Tool Use Patterns: Wie AI-Agenten mit externen APIs interagieren **Meta-Description:** Implementieren Sie robustes Function Calling für KI-Agenten. Best Practices für Tool-Definition, Error Handling, Security und skalierbare Architekturen. **Keywords:** Function Calling, Tool Use, AI Agents, API Integration, LLM Tools, Claude Function Calling, OpenAI Functions --- ## Einführung Function Calling (oder Tool Use) ist das Fundament für produktive KI-Anwendungen. Es ermöglicht LLMs, externe APIs aufzurufen, Datenbanken abzufragen und reale Aktionen auszuführen – statt nur Text zu generieren. Aber die Implementierung birgt Fallstricke: Security-Risiken, unvorhersehbare Aufrufe, Kostenexplosionen. In diesem Artikel zeige ich bewährte Patterns aus meinen Produktionsprojekten. --- ## Das Grundprinzip ``` ┌─────────────────────────────────────────────────────────────┐ │ TOOL USE FLOW │ │ │ │ User LLM Tools User │ │ │ │ │ │ │ │ │ Request │ │ │ │ │ │──────────→ │ │ │ │ │ │ │ │ │ │ │ │ │ Tool Selection │ │ │ │ │ │─────────────────│ │ │ │ │ │ │ │ │ │ │ │ Tool Call │ │ │ │ │ │────────────────→│ │ │ │ │ │ │ │ │ │ │ │ Tool Result │ │ │ │ │ │←────────────────│ │ │ │ │ │ │ │ │ │ │ Response │ │ │ │ │ │←───────────│ │ │ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Tool-Definition: Best Practices ### Die Anatomie eines guten Tools ```typescript interface ToolDefinition { name: string; // Eindeutig, beschreibend description: string; // Kritisch für LLM-Entscheidung parameters: JSONSchema; // Strikt typisiert returns: JSONSchema; // Optional, für Dokumentation } // ✅ Gutes Tool const searchProductsTool = { name: "search_products", description: `Durchsucht die Produktdatenbank nach Artikeln. Nutze dieses Tool wenn der User nach Produkten sucht, Preise wissen will, oder Verfügbarkeit prüfen möchte. Gibt maximal 10 Ergebnisse zurück.`, parameters: { type: "object", properties: { query: { type: "string", description: "Suchbegriff für Produktname oder Kategorie" }, category: { type: "string", enum: ["electronics", "clothing", "furniture", "sports"], description: "Optionale Kategorie-Filterung" }, max_price: { type: "number", description: "Maximaler Preis in EUR" }, limit: { type: "integer", default: 5, minimum: 1, maximum: 10, description: "Anzahl der Ergebnisse" } }, required: ["query"] } }; // ❌ Schlechtes Tool const badTool = { name: "search", // Zu vage description: "Sucht Sachen", // Nicht aussagekräftig parameters: { type: "object", properties: { q: { type: "string" } // Kryptischer Parametername } } }; ``` ### Naming Conventions ```typescript // ✅ Gute Namen "search_products" // Verb + Nomen "get_user_profile" "send_email" "calculate_shipping" "book_flight_ticket" // ❌ Schlechte Namen "search" // Zu vage "doThing" // Nicht beschreibend "handler" // Was handelt es? "process" // Was wird prozessiert? ``` ### Enum statt Freitext ```typescript // ✅ Gut: Enum für begrenzte Werte parameters: { status: { type: "string", enum: ["pending", "approved", "rejected"], description: "Filtert nach Status" } } // ❌ Schlecht: Freitext für begrenzte Werte parameters: { status: { type: "string", description: "Status: pending, approved, oder rejected" } } ``` --- ## Implementierung mit Claude ```typescript import Anthropic from "@anthropic-ai/sdk"; const anthropic = new Anthropic(); // Tool-Definitionen const tools: Anthropic.Tool[] = [ { name: "get_weather", description: "Ruft aktuelle Wetterdaten für einen Ort ab", input_schema: { type: "object", properties: { location: { type: "string", description: "Stadt und Land, z.B. 'Berlin, Deutschland'" }, unit: { type: "string", enum: ["celsius", "fahrenheit"], default: "celsius" } }, required: ["location"] } }, { name: "search_database", description: "Durchsucht die interne Datenbank", input_schema: { type: "object", properties: { query: { type: "string" }, table: { type: "string", enum: ["users", "products", "orders"] } }, required: ["query", "table"] } } ]; // Tool-Implementierungen const toolImplementations = { get_weather: async (input: { location: string; unit?: string }) => { const response = await fetch( `https://api.weather.com/v1/current?location=${input.location}` ); return response.json(); }, search_database: async (input: { query: string; table: string }) => { const results = await db[input.table].search(input.query); return results; } }; // Agent-Loop async function runAgent(userMessage: string) { const messages: Anthropic.MessageParam[] = [ { role: "user", content: userMessage } ]; while (true) { const response = await anthropic.messages.create({ model: "claude-3-5-sonnet-latest", max_tokens: 1000, tools, messages }); // Prüfe ob Tool-Calls vorhanden if (response.stop_reason === "tool_use") { const toolUseBlocks = response.content.filter( block => block.type === "tool_use" ); // Führe alle Tool-Calls aus const toolResults = await Promise.all( toolUseBlocks.map(async (toolUse) => { const impl = toolImplementations[toolUse.name]; const result = await impl(toolUse.input); return { type: "tool_result" as const, tool_use_id: toolUse.id, content: JSON.stringify(result) }; }) ); // Füge Results zur Conversation hinzu messages.push({ role: "assistant", content: response.content }); messages.push({ role: "user", content: toolResults }); } else { // Keine weiteren Tool-Calls, fertig return response.content; } } } ``` --- ## Security Best Practices ### 1. Input-Validierung ```typescript import { z } from "zod"; // Schema für Tool-Inputs const searchInputSchema = z.object({ query: z.string().min(1).max(500), table: z.enum(["users", "products", "orders"]), limit: z.number().int().min(1).max(100).default(10) }); async function executeToolSafely( toolName: string, input: unknown ): Promise { // 1. Validiere Input const schema = toolSchemas[toolName]; const validatedInput = schema.parse(input); // 2. Prüfe Berechtigungen if (!userHasPermission(currentUser, toolName)) { throw new UnauthorizedError(`Kein Zugriff auf ${toolName}`); } // 3. Ausführen return await toolImplementations[toolName](validatedInput); } ``` ### 2. Principle of Least Privilege ```typescript // ✅ Gut: Spezifische, eingeschränkte Tools const tools = [ { name: "read_user_profile", // Nur lesen description: "Liest das Profil eines Users (nur öffentliche Daten)" }, { name: "update_own_profile", // Nur eigenes Profil description: "Aktualisiert das eigene Profil des eingeloggten Users" } ]; // ❌ Schlecht: Zu mächtige Tools const badTools = [ { name: "execute_sql", // Voller DB-Zugriff description: "Führt beliebige SQL-Queries aus" } ]; ``` ### 3. User Confirmation für kritische Aktionen ```typescript interface ToolWithConfirmation { name: string; requiresConfirmation: boolean; confirmationMessage: (input: any) => string; } const sendEmailTool: ToolWithConfirmation = { name: "send_email", requiresConfirmation: true, confirmationMessage: (input) => `E-Mail an ${input.to} senden mit Betreff "${input.subject}"?` }; async function executeWithConfirmation( tool: ToolWithConfirmation, input: any ): Promise { if (tool.requiresConfirmation) { const confirmed = await requestUserConfirmation( tool.confirmationMessage(input) ); if (!confirmed) { return { status: "cancelled", reason: "User declined" }; } } return await toolImplementations[tool.name](input); } ``` ### 4. Rate Limiting pro Tool ```typescript import { RateLimiter } from "limiter"; const toolRateLimiters = { send_email: new RateLimiter({ tokensPerInterval: 10, interval: "hour" }), search_database: new RateLimiter({ tokensPerInterval: 100, interval: "minute" }), external_api: new RateLimiter({ tokensPerInterval: 1000, interval: "day" }) }; async function rateLimitedExecute(toolName: string, input: any) { const limiter = toolRateLimiters[toolName]; if (!limiter.tryRemoveTokens(1)) { throw new RateLimitError(`Rate limit für ${toolName} erreicht`); } return await toolImplementations[toolName](input); } ``` --- ## Error Handling ### Zwei Arten von Errors ```typescript interface ToolError { type: "user_facing" | "model_facing"; message: string; retryable: boolean; } function handleToolError(error: Error, toolName: string): ToolError { // User-facing: Zeige dem Enduser if (error instanceof ValidationError) { return { type: "user_facing", message: "Bitte geben Sie gültige Eingaben an.", retryable: true }; } // Model-facing: Nur für das LLM, nicht dem User zeigen if (error instanceof DatabaseError) { return { type: "model_facing", message: "Datenbankfehler. Versuche alternative Methode.", retryable: true }; } // Sensitive Errors niemals leaken if (error instanceof InternalError) { return { type: "model_facing", message: "Interner Fehler aufgetreten.", retryable: false }; } } ``` ### Retry-Logik ```typescript async function executeWithRetry( toolName: string, input: any, maxRetries = 3 ): Promise { let lastError: Error; for (let i = 0; i < maxRetries; i++) { try { return await toolImplementations[toolName](input); } catch (error) { lastError = error; // Nur bei retryable Errors wiederholen if (!isRetryable(error)) { throw error; } // Exponential Backoff await sleep(Math.pow(2, i) * 1000); } } throw lastError; } ``` --- ## Performance-Optimierung ### Parallele Tool-Ausführung ```typescript // Das LLM kann mehrere Tools gleichzeitig aufrufen // → Führen Sie sie parallel aus! async function executeToolCalls(toolCalls: ToolCall[]): Promise { // ✅ Parallel (schnell) return await Promise.all( toolCalls.map(call => executeToolSafely(call.name, call.input)) ); // ❌ Sequentiell (langsam) // const results = []; // for (const call of toolCalls) { // results.push(await executeToolSafely(call.name, call.input)); // } // return results; } ``` ### Tool Count Optimieren ```typescript // OpenAI/Anthropic Empfehlung: Max 20 Tools // ❌ Schlecht: 50 spezifische Tools const tooManyTools = [ "search_users_by_name", "search_users_by_email", "search_users_by_id", // ... 47 weitere ]; // ✅ Besser: Wenige flexible Tools const consolidatedTools = [ { name: "search_users", description: "Sucht User nach verschiedenen Kriterien", parameters: { filter_type: { enum: ["name", "email", "id"] }, filter_value: { type: "string" } } } ]; ``` ### Lazy Loading von Tools ```typescript // Nicht alle Tools immer laden function getToolsForContext(context: Context): Tool[] { const tools: Tool[] = []; // Basis-Tools immer verfügbar tools.push(searchTool, helpTool); // Kontext-spezifische Tools if (context.user.isAdmin) { tools.push(adminTool); } if (context.conversation.topic === "orders") { tools.push(orderTool, shippingTool, refundTool); } return tools; } ``` --- ## Prompt Engineering für Tool Use ### System Prompt Guidance ```typescript const systemPrompt = `Du bist ein hilfreicher Assistent mit Zugriff auf Tools. ## Tool-Nutzung - Nutze search_products wenn der User nach Produkten sucht - Nutze get_order_status wenn der User nach einer Bestellung fragt - Nutze contact_support nur wenn du nicht helfen kannst ## Wichtige Regeln - Frage nach wenn Informationen fehlen (z.B. Bestellnummer) - Führe keine Tool-Calls aus wenn du die Antwort schon weißt - Erkläre dem User was du tust bevor du ein Tool aufrufst ## Verboten - Rufe niemals delete_* Tools ohne explizite User-Bestätigung auf - Greife nicht auf andere User-Daten zu `; ``` ### Clarification Requests ```typescript // Instruiere das Modell, bei Unklarheiten nachzufragen const clarificationPrompt = ` Wenn der User nach Hotelsuche fragt aber kein Datum nennt: NICHT: tool_call: search_hotels({location: "Berlin"}) SONDERN: "Für welches Datum möchten Sie ein Hotel in Berlin suchen?" `; ``` --- ## Monitoring & Observability ```typescript interface ToolCallMetrics { toolName: string; duration: number; success: boolean; inputTokens: number; outputTokens: number; error?: string; } class ToolMonitor { async trackCall( toolName: string, input: any, execute: () => Promise ): Promise { const startTime = Date.now(); try { const result = await execute(); await this.record({ toolName, duration: Date.now() - startTime, success: true, inputTokens: estimateTokens(input), outputTokens: estimateTokens(result) }); return result; } catch (error) { await this.record({ toolName, duration: Date.now() - startTime, success: false, error: error.message }); throw error; } } async alertOnAnomaly(metrics: ToolCallMetrics) { // Alert bei ungewöhnlich vielen Tool-Calls const recentCalls = await this.getRecentCalls(metrics.toolName, "1h"); if (recentCalls.length > 1000) { await this.alert(`Ungewöhnlich viele Calls für ${metrics.toolName}`); } } } ``` --- ## Fazit Tool Use ist mächtig, aber mit Verantwortung: 1. **Klare Definitionen:** Gute Namen, detaillierte Descriptions, strenge Schemas 2. **Security First:** Validierung, Least Privilege, Confirmations 3. **Robustes Error Handling:** Unterscheide User- und Model-Errors 4. **Performance:** Parallele Ausführung, Tool-Count begrenzen 5. **Observability:** Monitoring für alle Tool-Calls Tool Calling transformiert LLMs von Text-Generatoren zu handlungsfähigen Agenten. Die Investition in solide Patterns zahlt sich mehrfach aus. --- ## Bildprompts für diesen Artikel **Bild 1 – Hero Image:** "Robot arm reaching into a toolbox filled with API icons and code symbols, industrial yet modern style" **Bild 2 – Integration Diagram:** "Interconnected puzzle pieces showing different API logos (database, email, calendar), smooth 3D render" **Bild 3 – Multi-Tool Architecture:** "AI brain with multiple tentacles connecting to various service icons, octopus-inspired futuristic design" --- ## Quellen - [OpenAI: Function Calling Guide](https://platform.openai.com/docs/guides/function-calling) - [Anthropic: Tool Use Documentation](https://docs.anthropic.com/claude/docs/tool-use) - [Prompting Guide: Function Calling with LLMs](https://www.promptingguide.ai/applications/function_calling) - [Composio: Tool Calling Explained](https://composio.dev/blog/ai-agent-tool-calling-guide) - [Martinuke Blog: Anatomy of Tool Calling](https://martinuke0.github.io/posts/2026-01-07-the-anatomy-of-tool-calling-in-llms-a-deep-dive/)