Function Calling ermöglicht LLMs, strukturierte Anfragen zu stellen um externe Tools aufzurufen.

User: "Was ist 2+3?"

Ohne Function Calling:
  LLM: "2+3 ist 5"  (versucht im Kopf zu rechnen, könnte falsch sein)

Mit Function Calling:
  LLM: "Ich rufe die Funktion 'add' mit 2 und 3 auf"
  System: Ruft add(2, 3) auf → 5
  LLM: "2+3 ist 5"  (garantiert korrekt!)

OpenAI Format

OpenAI's Standard für Function Calling.

Function Definition

tools = [
    {
        "type": "function",
        "function": {
            "name": "add",
            "description": "Addiert zwei Zahlen",
            "parameters": {
                "type": "object",
                "properties": {
                    "a": {
                        "type": "number",
                        "description": "Erste Zahl"
                    },
                    "b": {
                        "type": "number",
                        "description": "Zweite Zahl"
                    }
                },
                "required": ["a", "b"]
            }
        }
    }
]

API Call

from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{
        "role": "user",
        "content": "Was ist 2+3?"
    }],
    tools=tools,
    tool_choice="auto"  # Automatisch Function Calling
)

# Response enthält tool_calls
tool_calls = response.choices[0].message.tool_calls

for tool_call in tool_calls:
    if tool_call.function.name == "add":
        args = json.loads(tool_call.function.arguments)
        result = add(args["a"], args["b"])
        print(f"Ergebnis: {result}")

Anthropic Tool Use

Claude's Format ist ähnlich aber strukturiert anders.

Tool Definition

tools = [
    {
        "name": "add",
        "description": "Addiere zwei Zahlen",
        "input_schema": {
            "type": "object",
            "properties": {
                "a": {
                    "type": "number",
                    "description": "Erste Zahl"
                },
                "b": {
                    "type": "number",
                    "description": "Zweite Zahl"
                }
            },
            "required": ["a", "b"]
        }
    }
]

API Call

import anthropic
import json

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=tools,
    messages=[{
        "role": "user",
        "content": "Was ist 2+3?"
    }]
)

# Tool Use blocks
for block in response.content:
    if block.type == "tool_use":
        print(f"Tool: {block.name}")
        print(f"Input: {block.input}")

Open-Source Function Calling

Hermes (NousResearch)

Spezialisiert auf Agentic Verhalten.

from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "NousResearch/Hermes-2-Pro-Mistral-7B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

# Definiere Tools
tools_json = json.dumps([
    {
        "name": "add",
        "description": "Addiere zwei Zahlen",
        "parameters": {
            "type": "object",
            "properties": {
                "a": {"type": "number"},
                "b": {"type": "number"}
            }
        }
    }
])

# Prompt mit Tools
prompt = f"""System: Du kannst folgende Functions aufrufen: {tools_json}

Wenn du eine Function aufrufen willst, antworte mit:
<tool_call>
{{"name": "function_name", "arguments": {{"arg1": value1}}}}
</tool_call>

User: Was ist 2+3?"""

inputs = tokenizer(prompt, return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(outputs[0]))

Gorilla (UC Berkeley)

Großes Modell speziell für API Calling.

gorilla-7b:  Aus 1000+ APIs trainiert
gorilla-13b: Noch besser
gorilla-34b: State-of-the-art Function Calling

Spezialität: Genauigkeit bei komplexen API Calls

JSON Schema für Tools

Struktur

tool_schema = {
    "name": "search_web",
    "description": "Suche im Internet",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Was soll ich suchen?"
            },
            "num_results": {
                "type": "integer",
                "description": "Wie viele Ergebnisse (1-10)?",
                "minimum": 1,
                "maximum": 10
            },
            "language": {
                "type": "string",
                "enum": ["de", "en", "fr", "es"],
                "description": "Sprache der Ergebnisse"
            }
        },
        "required": ["query"]  # num_results, language sind optional
    }
}

Validierung

import jsonschema

def validate_tool_call(tool_name, arguments, schema):
    """Validiere ob Arguments dem Schema entsprechen"""
    try:
        jsonschema.validate(arguments, schema["parameters"])
        return True
    except jsonschema.ValidationError as e:
        print(f"Validierungsfehler: {e.message}")
        return False

# Usage
args = {"query": "Python tutorial", "num_results": 5}
is_valid = validate_tool_call("search_web", args, tool_schema)

Parallel Function Calling

Mehrere Functions gleichzeitig aufrufen.

OpenAI Parallel Calls

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{
        "role": "user",
        "content": "Was ist 2+3, 5*6 und 10/2?"
    }],
    tools=tools
)

# Multiple tool_calls in einer Response
tool_calls = response.choices[0].message.tool_calls

# Parallel ausführen
results = []
for tool_call in tool_calls:
    result = execute_tool(tool_call.function.name, tool_call.function.arguments)
    results.append({
        "tool_call_id": tool_call.id,
        "result": result
    })

# Feedback an LLM
response2 = client.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": "..."},
        {"role": "assistant", "content": "", "tool_calls": tool_calls},
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_call_id": r["tool_call_id"],
                    "content": str(r["result"])
                }
                for r in results
            ]
        }
    ],
    tools=tools
)

Fehlerbehandlung

Ungültige Arguments

def safe_tool_call(tool_name, arguments_str):
    try:
        # Parse Arguments
        arguments = json.loads(arguments_str)

        # Validiere gegen Schema
        schema = get_tool_schema(tool_name)
        jsonschema.validate(arguments, schema["parameters"])

        # Führe aus
        result = execute_tool(tool_name, arguments)
        return {"success": True, "result": result}

    except json.JSONDecodeError:
        return {
            "success": False,
            "error": "Invalid JSON in arguments"
        }
    except jsonschema.ValidationError as e:
        return {
            "success": False,
            "error": f"Validation error: {e.message}"
        }
    except Exception as e:
        return {
            "success": False,
            "error": f"Execution error: {str(e)}"
        }

# Gib Fehler zurück an LLM
result = safe_tool_call("add", '{"a": 2, "b": "3"}')
# → {"success": False, "error": "Validation error: '3' is not of type 'number'"}

# LLM kann dann korrigieren

Agent Loop (Komplett Beispiel)

def run_agent(user_input):
    messages = [
        {"role": "user", "content": user_input}
    ]

    while True:
        # Schritt 1: LLM antwortet (evtl mit Tool Calls)
        response = client.chat.completions.create(
            model="gpt-4",
            messages=messages,
            tools=tools
        )

        # Schritt 2: Wenn Tool Calls, führe aus
        if response.choices[0].message.tool_calls:
            # Tool Calls sammeln
            tool_calls = response.choices[0].message.tool_calls
            tool_results = []

            for tool_call in tool_calls:
                result = execute_tool(
                    tool_call.function.name,
                    json.loads(tool_call.function.arguments)
                )
                tool_results.append({
                    "tool_call_id": tool_call.id,
                    "result": str(result)
                })

            # Feedback an LLM
            messages.append({"role": "assistant", "content": response.choices[0].message.content})
            messages.append({
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_call_id": r["tool_call_id"],
                        "content": r["result"]
                    }
                    for r in tool_results
                ]
            })

        else:
            # Schritt 3: Keine Tool Calls, LLM gibt finale Antwort
            final_response = response.choices[0].message.content
            print(f"Agent: {final_response}")
            break

# Usage
run_agent("Was ist 2+3 und 5*6?")

Best Practices

1. Gute Beschreibungen

# ❌ Schlecht
{"name": "func", "description": "Do something"}

# ✅ Gut
{
    "name": "search_documents",
    "description": "Suche in Dokumentendatenbank nach relevanten Dokumenten. Nutze diese Funktion um Informationen zu finden.",
    "parameters": {
        "properties": {
            "query": {
                "description": "Suchbegriff oder Frage (z.B. 'Wie viele Mitarbeiter hat die Firma?')"
            }
        }
    }
}

2. Minimale Tools

❌ 50 Functions: LLM verwirrt, wählt falsche
✅ 3-5 Functions: LLM wählt richtig

3. Timeout und Retry

import signal
import time

def execute_with_timeout(func, args, timeout=5):
    def timeout_handler(signum, frame):
        raise TimeoutError(f"Tool execution exceeded {timeout}s")

    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(timeout)

    try:
        result = func(**args)
        signal.alarm(0)  # Cancel alarm
        return result
    except TimeoutError:
        return {"error": "Tool execution timeout"}