Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,649 @@
|
||||
# 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/)
|
||||
Reference in New Issue
Block a user