Як обмежити доступ до інструментів — Python (повна реалізація)

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

Це повна реалізація прикладу зі статті Як обмежити доступ до інструментів.

Якщо ти ще не читав статтю, почни з неї. Тут фокус на коді: як саме обмеження працюють у runtime.


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

  • Рівень 1 (tool access): які інструменти агент взагалі може викликати
  • Рівень 2 (action access): які дії дозволені всередині доступного інструменту
  • Policy gateway: централізована перевірка запитів перед виконанням
  • Fallback-поведінка: агент отримує помилку і обирає безпечний наступний крок

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

TEXT
examples/
└── foundations/
    └── tool-calling/
        └── python/
            ├── main.py           # agent loop
            ├── llm.py            # model + tool schemas
            ├── gateway.py        # policy checks + execution
            ├── tools.py          # tools (system actions)
            └── requirements.txt

Поділ важливий: модель пропонує дію, а gateway.py вирішує, чи ця дія взагалі буде виконана.


Як запустити

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

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

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

BASH
pip install -r requirements.txt

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

BASH
export OPENAI_API_KEY="sk-..."

4. Запусти:

BASH
python main.py

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

Ми робимо обережного робота, якому не можна робити все підряд.

  • AI може попросити будь-яку дію
  • спеціальний "охоронець" (gateway) перевіряє, чи це дозволено
  • якщо заборонено, робот нічого небезпечного не робить і пояснює безпечний варіант

Це як двері з замком: без дозволу команда не проходить.


Код

tools.py — реальні інструменти

PYTHON
from typing import Any

CUSTOMERS = {
    101: {"id": 101, "name": "Anna", "tier": "free", "email": "anna@gmail.com"},
    202: {"id": 202, "name": "Max", "tier": "pro", "email": "max@company.local"},
}


def customer_db(action: str, customer_id: int, new_tier: str | None = None) -> dict[str, Any]:
    customer = CUSTOMERS.get(customer_id)
    if not customer:
        return {"ok": False, "error": f"customer {customer_id} not found"}

    if action == "read":
        return {"ok": True, "customer": customer}

    if action == "update_tier":
        if not new_tier:
            return {"ok": False, "error": "new_tier is required"}
        customer["tier"] = new_tier
        return {"ok": True, "customer": customer}

    return {"ok": False, "error": f"unknown action '{action}'"}


def email_service(to: str, subject: str, body: str) -> dict[str, Any]:
    return {
        "ok": True,
        "status": "queued",
        "to": to,
        "subject": subject,
        "preview": body[:80],
    }

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


gateway.py — policy gateway (ключовий шар)

PYTHON
import json
from typing import Any

from tools import customer_db, email_service

TOOL_REGISTRY = {
    "customer_db": customer_db,
    "email_service": email_service,
}

# Level 1: which tools are visible to the agent
ALLOWED_TOOLS = {"customer_db"}

# Level 2: which actions are allowed inside each tool
ALLOWED_ACTIONS = {
    "customer_db": {"read"},  # update_tier is blocked
}


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

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

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

    if tool_name == "customer_db":
        action = args.get("action")
        if action not in ALLOWED_ACTIONS["customer_db"]:
            return {
                "ok": False,
                "error": f"action '{action}' is not allowed for tool '{tool_name}'",
            }

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

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

Саме тут enforce'яться правила. Модель не може обійти цей шар через промпт.


llm.py — модель і tool schemas

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 a support agent.
Use tools when data is missing.
If a tool or action is blocked, do not argue; suggest a safe manual next step.
Reply briefly in English.
""".strip()

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "customer_db",
            "description": "Customer data operations: read or update tier",
            "parameters": {
                "type": "object",
                "properties": {
                    "action": {"type": "string", "enum": ["read", "update_tier"]},
                    "customer_id": {"type": "integer"},
                    "new_tier": {"type": "string"},
                },
                "required": ["action", "customer_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "email_service",
            "description": "Sends an email to the customer",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"},
                },
                "required": ["to", "subject", "body"],
            },
        },
    },
]


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

Зверни увагу: llm.py може показувати моделі більше інструментів, але gateway.py все одно блокує недозволені.


main.py — агентний цикл із перевіркою політик

PYTHON
import json

from gateway import execute_tool_call
from llm import ask_model

MAX_STEPS = 6

TASK = (
    "For customer_id=101, check the profile, upgrade tier to pro, "
    "and send a confirmation email to anna@gmail.com. "
    "If any action is blocked, explain the safe manual next step."
)


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))

        if assistant.content and assistant.content.strip():
            print("Assistant:", assistant.content.strip())

        tool_calls = assistant.tool_calls or []
        if not tool_calls:
            print("\nDone: model finished the task.")
            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("Gateway 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()

Тут добре видно boundary: модель пропонує, gateway вирішує, чи дія взагалі відбудеться.


requirements.txt

TEXT
openai>=1.0.0

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

TEXT
=== STEP 1 ===
Tool call: customer_db({"action":"read","customer_id":101})
Gateway result: {'ok': True, 'tool': 'customer_db', 'result': {'ok': True, 'customer': {'id': 101, 'name': 'Anna', 'tier': 'free', 'email': 'anna@gmail.com'}}}

=== STEP 2 ===
Tool call: customer_db({"action":"update_tier","customer_id":101,"new_tier":"pro"})
Gateway result: {'ok': False, 'error': "action 'update_tier' is not allowed for tool 'customer_db'"}

=== STEP 3 ===
Tool call: email_service({"to":"anna@gmail.com","subject":"Tier updated","body":"..."})
Gateway result: {'ok': False, 'error': "tool 'email_service' is not allowed"}

=== STEP 4 ===
Assistant: I can only read the profile. Tier updates and email sending require manual operator action.

Done: model finished the task.

Чому це продакшен-підхід

Наївний tool callingЗ policy gateway
Модель вирішує все сама
Є централізований контроль доступу
Можна розділити read/write дії
Помилки перетворюються на керований fallback

Де далі копати

  • Додай ALLOWED_TOOLS_BY_ROLE (наприклад viewer, operator, admin)
  • Додай approval-flow для update_tier замість повної заборони
  • Додай max_tool_calls і max_cost поруч із MAX_STEPS
  • Логуй tool_name, args_hash, decision, reason для аудиту

💻 Повний код на GitHub

Дивись на GitHub

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

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

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

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