Files
Portfolio/blog-posts/07-tool-use-patterns.md
damjan_savicandClaude Opus 4.5 e1bbe5455d Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit:
- Blog-System mit 100+ Artikeln
- Supabase-Integration
- Responsive Design mit Tailwind CSS
- TypeScript-Konfiguration
- Testing-Setup mit Vitest und Playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:07:20 +01:00

650 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<ToolResult> {
// 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<ToolResult> {
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<ToolResult> {
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<ToolResult[]> {
// ✅ 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<any>
): Promise<any> {
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/)