Як агент використовує інструменти (Основи) — Python (повна реалізація)

Повний runnable приклад tool calling з allowlist, виконанням інструментів і циклом агента.
На цій сторінці
  1. Що цей приклад демонструє
  2. Структура проєкту
  3. Як запустити
  4. Що ми будуємо в коді
  5. Код
  6. tools.py — інструменти, які реально виконуються
  7. executor.py — межа безпеки і виконання
  8. llm.py — виклик моделі і опис доступних інструментів
  9. main.py — агентний цикл (model → tool → model)
  10. requirements.txt
  11. Приклад виводу
  12. Чому це агент, а не один model call
  13. Що змінити в цьому прикладі
  14. Повний код на GitHub

Це повна реалізація прикладу зі статті Як агент використовує інструменти (Основи).

Якщо ти ще не читав статтю, почни з неї. Тут фокус лише на коді.


Що цей приклад демонструє

  • Як LLM вирішує, коли треба викликати інструмент
  • Як система перевіряє, чи інструмент дозволений (allowlist)
  • Як виконати виклик інструменту і повернути результат моделі
  • Як агент завершує цикл, коли даних достатньо

Структура проєкту

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

Такий поділ зручний у реальному проєкті: модель, policy і виконання інструментів не змішані в одному файлі.


Як запустити

1. Клонуй репозиторій і перейди в папку:

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

2. Встанови залежності:

BASH
pip install -r requirements.txt

3. Вкажи API-ключ:

BASH
export OPENAI_API_KEY="sk-..."

4. Запусти приклад:

BASH
python main.py

⚠️ Якщо забудеш вказати ключ — агент одразу скаже про це з підказкою що робити.


Що ми будуємо в коді

Ми робимо робота-помічника, який вміє просити дані в інструментів.

  • AI каже, який інструмент їй потрібен
  • система перевіряє: цей інструмент дозволений чи ні
  • якщо дозволений, інструмент повертає дані, і робот дає відповідь

Просто: попросив інструмент → отримав факт → відповів людині.


Код

tools.py — інструменти, які реально виконуються

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}

Модель не має доступу до словників напряму. Вона може тільки попросити викликати ці функції.


executor.py — межа безпеки і виконання

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}

Тут головна ідея: навіть якщо модель просить щось дивне, система виконує тільки те, що явно дозволено.


llm.py — виклик моделі і опис доступних інструментів

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

Інструменти тут описані як schema. Модель бачить цей список і обирає серед нього.


main.py — агентний цикл (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()

Це класичний цикл: модель просить інструмент, система виконує, результат повертається моделі, потім наступне рішення.


requirements.txt

TEXT
openai>=1.0.0

Приклад виводу

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.

Чому це агент, а не один 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, циклом кроків і обробкою помилок. Якщо хочеш швидко запустити або розібрати код построково, відкрий приклад цілком.

Переглянути повний код на GitHub ↗
⏱️ 6 хв читанняОновлено Бер, 2026Складність: ★☆☆
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Дозволами на інструменти (allowlist / blocklist)
  • Kill switch та аварійна зупинка
  • Ідемпотентність і dedupe
  • Audit logs та трасування
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Автор

Цю документацію курують і підтримують інженери, які запускають AI-агентів у продакшені.

Контент створено з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Патерни та рекомендації базуються на постмортемах, режимах відмов і операційних інцидентах у розгорнутих системах, зокрема під час розробки та експлуатації governance-інфраструктури для агентів у OnceOnly.