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
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:
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd examples/foundations/tool-calling-basics/python
2. Abhängigkeiten installieren:
pip install -r requirements.txt
3. API-Schlüssel setzen:
export OPENAI_API_KEY="sk-..."
4. Beispiel starten:
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
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
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
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)
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
openai>=1.0.0
Beispielausgabe
=== 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 call | Agent 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_balanceinALLOWED_TOOLS— was gibt der Agent dem Nutzer zurück? - Ersetze
user_id=42durchuser_id=999— wie verarbeitet der Agent den Fehler? - Füge ein Tool
get_user_ordershinzu, aber füge es nicht zuALLOWED_TOOLShinzu — versucht das Modell, es aufzurufen? - Füge ein Limit für die Anzahl von tool calls getrennt von
MAX_STEPShinzu
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.