Wie ein Agent Tools nutzt (Grundlagen) — Python (Vollständige Implementierung)

Vollständiges runnable Tool-Calling-Beispiel mit Allowlist, Tool-Ausführung und Agent-Loop.
Auf dieser Seite
  1. Was dieses Beispiel zeigt
  2. Projektstruktur
  3. So startest du
  4. Code
  5. tools.py — Tools, die tatsächlich ausgeführt werden
  6. executor.py — Sicherheitsgrenze und Ausführung
  7. llm.py — Modellaufruf und Beschreibung verfügbarer Tools
  8. main.py — Agent-Loop (model → tool → model)
  9. requirements.txt
  10. Beispielausgabe
  11. Warum das ein Agent ist und kein einzelner model call
  12. Was du in diesem Beispiel ändern kannst
  13. Vollständiger Code auf GitHub

Das ist eine vollständige Implementierung des Beispiels aus dem Artikel Wie ein Agent Tools nutzt (Grundlagen).

Wenn du den Artikel noch nicht gelesen hast, starte dort. Hier liegt der Fokus nur auf dem Code.


Was dieses Beispiel zeigt

  • Wie LLM entscheidet, wann ein Tool aufgerufen werden soll
  • Wie das System prüft, ob das Tool erlaubt ist (Allowlist)
  • Wie man einen Tool-Aufruf ausführt und das Ergebnis an das Modell zurückgibt
  • Wie der Agent den Loop beendet, wenn die Daten ausreichen

Projektstruktur

TEXT
examples/
└── foundations/
    └── tool-calling-basics/
        └── python/
            ├── main.py           # agent loop
            ├── llm.py            # model call + tool definitions
            ├── executor.py       # allowlist check + tool execution
            ├── tools.py          # tools (business logic)
            └── requirements.txt

Diese Aufteilung ist in einem realen Projekt praktisch: Modell, Policy und Tool-Ausführung sind nicht in einer Datei vermischt.


So startest du

1. Repository klonen und in den Ordner wechseln:

BASH
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd examples/foundations/tool-calling-basics/python

2. Abhängigkeiten installieren:

BASH
pip install -r requirements.txt

3. API-Schlüssel setzen:

BASH
export OPENAI_API_KEY="sk-..."

4. Beispiel starten:

BASH
python main.py

⚠️ Wenn du vergisst, den Schlüssel zu setzen, sagt der Agent das sofort mit einem Hinweis, was zu tun ist.


Code

tools.py — Tools, die tatsächlich ausgeführt werden

PYTHON
from typing import Any

USERS = {
    42: {"id": 42, "name": "Anna", "tier": "pro"},
    7: {"id": 7, "name": "Max", "tier": "free"},
}

BALANCES = {
    42: {"currency": "USD", "value": 128.40},
    7: {"currency": "USD", "value": 0.0},
}


def get_user_profile(user_id: int) -> dict[str, Any]:
    user = USERS.get(user_id)
    if not user:
        return {"error": f"user {user_id} not found"}
    return {"user": user}


def get_user_balance(user_id: int) -> dict[str, Any]:
    balance = BALANCES.get(user_id)
    if not balance:
        return {"error": f"balance for user {user_id} not found"}
    return {"balance": balance}

Das Modell hat keinen direkten Zugriff auf die Dictionaries. Es kann nur darum bitten, diese Funktionen aufzurufen.


executor.py — Sicherheitsgrenze und Ausführung

PYTHON
import json
from typing import Any

from tools import get_user_balance, get_user_profile

TOOL_REGISTRY = {
    "get_user_profile": get_user_profile,
    "get_user_balance": get_user_balance,
}

ALLOWED_TOOLS = {"get_user_profile", "get_user_balance"}


def execute_tool_call(tool_name: str, arguments_json: str) -> dict[str, Any]:
    if tool_name not in ALLOWED_TOOLS:
        return {"error": f"tool '{tool_name}' is not allowed"}

    tool = TOOL_REGISTRY.get(tool_name)
    if tool is None:
        return {"error": f"tool '{tool_name}' not found"}

    try:
        args = json.loads(arguments_json or "{}")
    except json.JSONDecodeError:
        return {"error": "invalid JSON arguments"}

    try:
        result = tool(**args)
    except TypeError as exc:
        return {"error": f"invalid arguments: {exc}"}

    return {"tool": tool_name, "result": result}

Die Kernidee hier: Auch wenn das Modell etwas Seltsames anfragt, führt das System nur aus, was explizit erlaubt ist.


llm.py — Modellaufruf und Beschreibung verfügbarer Tools

PYTHON
import os
from openai import OpenAI

api_key = os.environ.get("OPENAI_API_KEY")

if not api_key:
    raise EnvironmentError(
        "OPENAI_API_KEY is not set.\n"
        "Run: export OPENAI_API_KEY='sk-...'"
    )

client = OpenAI(api_key=api_key)

SYSTEM_PROMPT = """
You are an AI support agent. When you need data, call the available tools.
Once you have enough information, give a short answer.
""".strip()

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_user_profile",
            "description": "Returns user profile by user_id",
            "parameters": {
                "type": "object",
                "properties": {"user_id": {"type": "integer"}},
                "required": ["user_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_user_balance",
            "description": "Returns user balance by user_id",
            "parameters": {
                "type": "object",
                "properties": {"user_id": {"type": "integer"}},
                "required": ["user_id"],
            },
        },
    },
]


def ask_model(messages: list[dict]):
    completion = client.chat.completions.create(
        model="gpt-4.1-mini",
        messages=[{"role": "system", "content": SYSTEM_PROMPT}, *messages],
        tools=TOOLS,
        tool_choice="auto",
    )
    return completion.choices[0].message

Die Tools sind hier als Schema beschrieben. Das Modell sieht diese Liste und wählt daraus.


main.py — Agent-Loop (model → tool → model)

PYTHON
import json

from executor import execute_tool_call
from llm import ask_model

MAX_STEPS = 6

TASK = "Prepare a short account summary for user_id=42: name, tier, and balance."


def to_assistant_message(message) -> dict:
    tool_calls = []
    for tc in message.tool_calls or []:
        tool_calls.append(
            {
                "id": tc.id,
                "type": "function",
                "function": {
                    "name": tc.function.name,
                    "arguments": tc.function.arguments,
                },
            }
        )
    return {
        "role": "assistant",
        "content": message.content or "",
        "tool_calls": tool_calls,
    }


def run():
    messages: list[dict] = [{"role": "user", "content": TASK}]

    for step in range(1, MAX_STEPS + 1):
        print(f"\n=== STEP {step} ===")
        assistant = ask_model(messages)
        messages.append(to_assistant_message(assistant))

        text = assistant.content or ""
        if text.strip():
            print(f"Assistant: {text.strip()}")

        tool_calls = assistant.tool_calls or []
        if not tool_calls:
            print("\nDone: model finished without a new tool call.")
            return

        for tc in tool_calls:
            print(f"Tool call: {tc.function.name}({tc.function.arguments})")
            execution = execute_tool_call(
                tool_name=tc.function.name,
                arguments_json=tc.function.arguments,
            )
            print(f"Tool result: {execution}")

            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": json.dumps(execution, ensure_ascii=False),
                }
            )

    print("\nStop: MAX_STEPS reached.")


if __name__ == "__main__":
    run()

Das ist der klassische Loop: Modell fordert ein Tool an, das System führt aus, das Ergebnis geht ans Modell zurück, dann folgt die nächste Entscheidung.


requirements.txt

TEXT
openai>=1.0.0

Beispielausgabe

TEXT
=== STEP 1 ===
Tool call: get_user_profile({"user_id": 42})
Tool result: {'tool': 'get_user_profile', 'result': {'user': {'id': 42, 'name': 'Anna', 'tier': 'pro'}}}

=== STEP 2 ===
Tool call: get_user_balance({"user_id": 42})
Tool result: {'tool': 'get_user_balance', 'result': {'balance': {'currency': 'USD', 'value': 128.4}}}

=== STEP 3 ===
Assistant: User Anna is a pro tier member with a current balance of 128.4 USD.

Done: model finished without a new tool call.

Warum das ein Agent ist und kein einzelner model call

Ein model callAgent mit tools
Arbeitet nur mit bereits vorhandenem Text
Kann neue Daten über ein Tool anfragen
Hat eine explizite Ausführungsgrenze (Allowlist)
Macht mehrere Schritte bis zur fertigen Antwort

Was du in diesem Beispiel ändern kannst

  • Blockiere get_user_balance in ALLOWED_TOOLS — was gibt der Agent dem Nutzer zurück?
  • Ersetze user_id=42 durch user_id=999 — wie verarbeitet der Agent den Fehler?
  • Füge ein Tool get_user_orders hinzu, aber füge es nicht zu ALLOWED_TOOLS hinzu — versucht das Modell, es aufzurufen?
  • Füge ein Limit für die Anzahl von tool calls getrennt von MAX_STEPS hinzu

Vollständiger Code auf GitHub

Im Repository liegt die vollständige Version dieser Demo: mit ALLOWED_TOOLS, Schritt-Loop und Fehlerbehandlung. Wenn du es schnell starten oder den Code Zeile für Zeile durchgehen willst, öffne das vollständige Beispiel.

Vollständigen Code auf GitHub ansehen ↗
⏱️ 6 Min. LesezeitAktualisiert Mär, 2026Schwierigkeit: ★☆☆
Integriert: Production ControlOnceOnly
Guardrails für Tool-Calling-Agents
Shippe dieses Pattern mit Governance:
  • Budgets (Steps / Spend Caps)
  • Tool-Permissions (Allowlist / Blocklist)
  • Kill switch & Incident Stop
  • Idempotenz & Dedupe
  • Audit logs & Nachvollziehbarkeit
Integrierter Hinweis: OnceOnly ist eine Control-Layer für Production-Agent-Systeme.
Autor

Diese Dokumentation wird von Engineers kuratiert und gepflegt, die AI-Agenten in der Produktion betreiben.

Die Inhalte sind KI-gestützt, mit menschlicher redaktioneller Verantwortung für Genauigkeit, Klarheit und Produktionsrelevanz.

Patterns und Empfehlungen basieren auf Post-Mortems, Failure-Modes und operativen Incidents in produktiven Systemen, auch bei der Entwicklung und dem Betrieb von Governance-Infrastruktur für Agenten bei OnceOnly.