Це повна реалізація прикладу зі статті Як агент використовує інструменти (Основи).
Якщо ти ще не читав статтю, почни з неї. Тут фокус лише на коді.
Що цей приклад демонструє
- Як LLM вирішує, коли треба викликати інструмент
- Як система перевіряє, чи інструмент дозволений (allowlist)
- Як виконати виклик інструменту і повернути результат моделі
- Як агент завершує цикл, коли даних достатньо
Структура проєкту
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
Такий поділ зручний у реальному проєкті: модель, policy і виконання інструментів не змішані в одному файлі.
Як запустити
1. Клонуй репозиторій і перейди в папку:
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd examples/foundations/tool-calling-basics/python
2. Встанови залежності:
pip install -r requirements.txt
3. Вкажи API-ключ:
export OPENAI_API_KEY="sk-..."
4. Запусти приклад:
python main.py
⚠️ Якщо забудеш вказати ключ — агент одразу скаже про це з підказкою що робити.
Що ми будуємо в коді
Ми робимо робота-помічника, який вміє просити дані в інструментів.
- AI каже, який інструмент їй потрібен
- система перевіряє: цей інструмент дозволений чи ні
- якщо дозволений, інструмент повертає дані, і робот дає відповідь
Просто: попросив інструмент → отримав факт → відповів людині.
Код
tools.py — інструменти, які реально виконуються
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}
Модель не має доступу до словників напряму. Вона може тільки попросити викликати ці функції.
executor.py — межа безпеки і виконання
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}
Тут головна ідея: навіть якщо модель просить щось дивне, система виконує тільки те, що явно дозволено.
llm.py — виклик моделі і опис доступних інструментів
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
Інструменти тут описані як schema. Модель бачить цей список і обирає серед нього.
main.py — агентний цикл (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()
Це класичний цикл: модель просить інструмент, система виконує, результат повертається моделі, потім наступне рішення.
requirements.txt
openai>=1.0.0
Приклад виводу
=== 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.
Чому це агент, а не один model call
| Один model call | Агент з tools | |
|---|---|---|
| Працює тільки з наявним текстом | ✅ | ❌ |
| Може запитати нові дані через tool | ❌ | ✅ |
| Має явну межу виконання (allowlist) | ❌ | ✅ |
| Робить кілька кроків до готової відповіді | ❌ | ✅ |
Що змінити в цьому прикладі
- Заблокуй
get_user_balanceвALLOWED_TOOLS— що поверне агент користувачу? - Заміни
user_id=42наuser_id=999— як агент обробить помилку? - Додай інструмент
get_user_ordersале не додавай його вALLOWED_TOOLS— чи спробує модель його викликати? - Додай ліміт на кількість tool calls окремо від
MAX_STEPS
Повний код на GitHub
У репозиторії лежить повна версія цього демо: з ALLOWED_TOOLS, циклом кроків і обробкою помилок.
Якщо хочеш швидко запустити або розібрати код построково, відкрий приклад цілком.