Function Calling и Tool Use с LLM: Практическо ръководство за 2026 г.

Практическо ръководство за function calling и tool use с LLM — от дефиниране на инструменти и Claude API, през Structured Outputs и паралелни извиквания, до MCP стандарта и продукционни практики за сигурност.

Въведение

Големите езикови модели (LLM) са впечатляващи генератори на текст. Но нека бъдем напълно честни — текстът сам по себе си рядко решава реални бизнес проблеми. Не можете да извършите банков превод с текст. Не можете да проверите наличност на продукт в склада с текст. Не можете да изпратите имейл, да обновите база данни или да проверите текущата цена на акции само с генериран текст.

Именно тук влиза function calling (извикване на функции) и tool use (използване на инструменти) — способността, която трансформира LLM от пасивен генератор на текст в активен участник, способен да взаимодейства с реалния свят. И ще ви кажа от опит — когато за пръв път видях как работи на практика, беше нещо като „ааа, значи така AI моделите стават наистина полезни".

Ако вече сте прочели нашите статии за мулти-агентна оркестрация, RAG пайплайни и промпт инженеринг, вероятно сте забелязали, че инструментите и извикванията на функции се появяват навсякъде. Агентите използват инструменти. RAG системите извличат данни чрез инструменти. Дори напредналите промпт техники работят ръка за ръка с дефинициите на инструменти. Това не е случайно — function calling е свързващата тъкан на съвременната AI архитектура.

През 2026 г. function calling вече не е експериментална функция. Това е стандартна инфраструктурна възможност, поддържана от всички водещи модели — Claude, GPT-4o, Gemini, Llama 3 и дори специализирани open-source модели. А с появата на Model Context Protocol (MCP) — отвореният стандарт на Anthropic за интеграция на инструменти — екосистемата навлезе в нова фаза на стандартизация.

В тази статия ще разгледаме function calling от основите до продукционните сценарии. Ще покажем как работи потокът на данни, как се дефинират инструменти, как да използвате Claude и OpenAI API-тата на практика, какво е Structured Outputs и защо е толкова важно, как MCP променя правилата на играта, и какви са ключовите съображения за сигурност. Всичко с код — обещавам.

Как работи Function Calling: Потокът на данни

Нека започнем с едно уточнение, което мнозина пропускат: LLM не извикват функции директно. Сериозно. Въпреки името „function calling", моделът никога не изпълнява код сам. Вместо това, когато определи, че потребителската заявка изисква използване на инструмент, той генерира структуриран JSON обект, описващ кой инструмент иска да използва и с какви параметри.

Вашият backend код е този, който действително изпълнява функцията. Моделът само „казва какво иска" — вие решавате дали и как да го направите.

Ето пълния цикъл:

  1. Изпращане на заявка — Вие изпращате потребителската заявка и списък с налични инструменти (с техните дефиниции) към API-то на модела.
  2. Анализ и решение — Моделът анализира заявката и решава дали някой от инструментите може да помогне.
  3. Генериране на извикване — Ако инструмент е необходим, моделът връща tool_use блок с името на инструмента и структурирани параметри (вместо обикновен текстов отговор).
  4. Изпълнение — Вашият backend код изпълнява реалната функция с предоставените параметри.
  5. Връщане на резултат — Изпращате резултата обратно към модела като tool_result.
  6. Финален отговор — Моделът интегрира резултата и генерира крайния отговор за потребителя.

Този цикъл може да се повтаря многократно в рамките на един разговор — моделът може да извика няколко инструмента последователно или дори паралелно (повече за това по-нататък).

# Опростена визуализация на потока
#
# Потребител → [Заявка + Инструменти] → LLM
#                                        ↓
#                               tool_use: get_weather(city="Sofia")
#                                        ↓
#                              Backend изпълнява функцията
#                                        ↓
#                              tool_result: {"temp": 22, "condition": "sunny"}
#                                        ↓
#                                       LLM
#                                        ↓
#                        "В София е слънчево, 22°C."  → Потребител

Тази архитектура има няколко ключови предимства. Първо, безопасност — моделът не изпълнява код директно, а само предлага действия, които системата ви може да валидира преди изпълнение. Второ, одитируемост — всяко извикване е структурирано и лесно се логва. И трето, гъвкавост — можете да подменяте backend имплементацията, без модела да знае или да му пука.

Дефиниране на инструменти: Практики за проектиране

Качеството на дефинициите на инструментите е критичен фактор за успеха на function calling. Лошо дефиниран инструмент означава, че моделът просто няма да знае кога и как да го използва. Ето ключовите принципи, на които се натъкнахме в практиката:

1. Ясни и описателни имена

Името на инструмента трябва да казва точно какво прави. Избягвайте генерични имена като process или handle — те не казват нищо полезно. Предпочитайте специфични като get_current_weather, search_products, create_invoice.

2. Подробни описания

Описанието на инструмента е това, което моделът чете, за да реши дали да го използва. Трябва да е кратко, но информативно — включете какво прави инструментът, кога е подходящо да се използва и какви ограничения има. Мислете за описанието като за мини-документация, предназначена за AI.

3. Стриктна JSON схема за параметрите

Всеки параметър трябва да има ясен тип, описание и индикация дали е задължителен. Ето пример за добре дефиниран инструмент:

{
    "name": "search_products",
    "description": "Търси продукти в каталога по ключови думи, категория или ценови диапазон. Използвай когато потребителят пита за наличност, цени или характеристики на продукти.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Ключови думи за търсене на продукт"
            },
            "category": {
                "type": "string",
                "enum": ["electronics", "clothing", "home", "sports"],
                "description": "Категория продукти за филтриране"
            },
            "min_price": {
                "type": "number",
                "description": "Минимална цена в лева"
            },
            "max_price": {
                "type": "number",
                "description": "Максимална цена в лева"
            },
            "in_stock_only": {
                "type": "boolean",
                "description": "Ако е true, показва само налични продукти"
            }
        },
        "required": ["query"]
    }
}

4. Групиране по бизнес логика

Когато имате десетки или стотици инструменти, организацията става наистина критична. Добра практика е да групирате инструментите по бизнес домейн и да ги предоставяте на модела селективно — само тези, които са релевантни за текущия контекст. Така намалявате консумацията на токени и подобрявате точността на избора.

5. Предоставяйте примери за използване

JSON схемите определят какво е структурно валидно, но не могат да изразят модели на използване — кога да се включват опционални параметри, кои комбинации имат смисъл, какви конвенции очаква API-то ви. Затова Claude поддържа tool use examples — и честно казано, разликата в качеството на извикванията е забележима, когато добавите дори един-два примера.

# Пример за tool use example в Claude API
tools = [
    {
        "name": "search_products",
        "description": "Търси продукти в каталога...",
        "input_schema": { ... },
        "examples": [
            {
                "input": "Искам да видя лаптопи до 2000 лева",
                "tool_input": {
                    "query": "лаптоп",
                    "category": "electronics",
                    "max_price": 2000,
                    "in_stock_only": True
                }
            }
        ]
    }
]

Claude Tool Use API: Практически примери

Claude от Anthropic предлага една от най-зрелите имплементации на tool use. Хайде да видим как се използва на практика с Python.

Основен пример

import anthropic
import json

client = anthropic.Anthropic()

# Дефиниране на инструменти
tools = [
    {
        "name": "get_weather",
        "description": "Връща текущото време за даден град. Използвай когато потребителят пита за времето.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "Името на града"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Единици за температура"
                }
            },
            "required": ["city"]
        }
    }
]

# Изпращане на заявка
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "Какво е времето в София днес?"}
    ]
)

# Проверка дали моделът иска да използва инструмент
for block in response.content:
    if block.type == "tool_use":
        tool_name = block.name
        tool_input = block.input
        tool_use_id = block.id
        print(f"Моделът иска да извика: {tool_name}")
        print(f"С параметри: {json.dumps(tool_input, ensure_ascii=False)}")

        # Изпълнение на реалната функция (симулация)
        weather_data = {"temperature": 22, "condition": "слънчево", "humidity": 45}

        # Връщане на резултата към модела
        final_response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=tools,
            messages=[
                {"role": "user", "content": "Какво е времето в София днес?"},
                {"role": "assistant", "content": response.content},
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "tool_result",
                            "tool_use_id": tool_use_id,
                            "content": json.dumps(weather_data, ensure_ascii=False)
                        }
                    ]
                }
            ]
        )
        print(final_response.content[0].text)

Нищо космическо, нали? Изпращате заявка, моделът казва „искам да извикам get_weather", вие изпълнявате функцията, връщате резултата — и моделът формулира хубав отговор за потребителя.

Агентски цикъл с множество инструменти

В продукционна среда обикновено имате множество инструменти и моделът може да извика няколко последователно в рамките на една заявка. Ето как се имплементира пълен агентски цикъл:

import anthropic
import json

client = anthropic.Anthropic()

# Множество инструменти
tools = [
    {
        "name": "search_database",
        "description": "Търси в базата данни за клиентска информация",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "SQL-подобна заявка"},
                "table": {"type": "string", "enum": ["customers", "orders", "products"]}
            },
            "required": ["query", "table"]
        }
    },
    {
        "name": "send_email",
        "description": "Изпраща имейл до клиент",
        "input_schema": {
            "type": "object",
            "properties": {
                "to": {"type": "string", "description": "Имейл адрес на получателя"},
                "subject": {"type": "string", "description": "Тема на имейла"},
                "body": {"type": "string", "description": "Съдържание на имейла"}
            },
            "required": ["to", "subject", "body"]
        }
    }
]

# Функции за изпълнение на инструментите
def execute_tool(name, input_data):
    if name == "search_database":
        # Симулация на заявка към база данни
        return {"results": [{"id": 1, "name": "Иван Петров", "email": "[email protected]"}]}
    elif name == "send_email":
        return {"status": "sent", "message_id": "msg_12345"}
    return {"error": "Непознат инструмент"}

# Агентски цикъл
def agent_loop(user_message):
    messages = [{"role": "user", "content": user_message}]

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )

        # Проверка дали моделът приключи (без повече tool calls)
        if response.stop_reason == "end_turn":
            return response.content[0].text

        # Обработка на tool use блокове
        messages.append({"role": "assistant", "content": response.content})
        tool_results = []

        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": json.dumps(result, ensure_ascii=False)
                })

        messages.append({"role": "user", "content": tool_results})

# Използване
result = agent_loop("Намери имейла на Иван Петров и му изпрати съобщение за новата ни промоция.")
print(result)

Забележете структурата на цикъла — той продължава, докато моделът реши, че е приключил работата си (stop_reason == "end_turn"). Моделът може да извика множество инструменти в множество стъпки, като всяка стъпка се базира на резултатите от предходната. По същество — моделът сам „мисли" кога е приключил.

Structured Outputs: Гарантирана валидация на схемата

Едно от най-важните подобрения в областта на function calling през последните години е Structured Outputs — механизъм, който гарантира, че моделът винаги генерира валиден JSON, съответстващ на зададена схема.

Защо това е толкова важно? Без Structured Outputs, моделът може да върне невалидни типове ("2" вместо 2), да пропусне задължителни полета или да добави несъществуващи свойства. В прототипна среда — досадно. В продукция — потенциално катастрофално, защото води до runtime грешки и неочаквано поведение.

Strict Mode в Claude

Claude поддържа strict: true в дефиницията на инструментите, което активира Structured Outputs за параметрите на tool calls:

tools = [
    {
        "name": "create_order",
        "description": "Създава нова поръчка в системата",
        "strict": True,  # Гарантирана валидация на схемата
        "input_schema": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "integer"},
                "items": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "product_id": {"type": "integer"},
                            "quantity": {"type": "integer"},
                            "unit_price": {"type": "number"}
                        },
                        "required": ["product_id", "quantity", "unit_price"]
                    }
                },
                "shipping_address": {"type": "string"},
                "priority": {
                    "type": "string",
                    "enum": ["standard", "express", "overnight"]
                }
            },
            "required": ["customer_id", "items", "shipping_address", "priority"]
        }
    }
]

С активиран strict mode, можете да бъдете сигурни, че customer_id винаги ще бъде число (не стринг), items винаги ще бъде масив с правилната структура, и priority ще бъде една от разрешените стойности. Целият клас от грешки просто изчезва.

Structured Outputs в OpenAI

OpenAI предлага подобна функционалност чрез strict: true в дефинициите на функции. Има обаче една важна разлика, която си заслужава да споменем: при OpenAI, Structured Outputs не са съвместими с паралелни tool calls. Когато използвате strict: true, трябва да зададете parallel_tool_calls: false. При Claude тази рестрикция не съществува.

Типобезопасност с Pydantic и Zod

За максимална типобезопасност в Python можете да комбинирате Structured Outputs с Pydantic модели. Честно казано, ако работите сериозно с function calling в Python и не използвате Pydantic — правите нещата по трудния начин.

from pydantic import BaseModel, Field
from typing import List, Literal

class OrderItem(BaseModel):
    product_id: int = Field(description="Уникален идентификатор на продукта")
    quantity: int = Field(ge=1, description="Количество (минимум 1)")
    unit_price: float = Field(gt=0, description="Единична цена в лева")

class CreateOrderInput(BaseModel):
    customer_id: int = Field(description="ID на клиента")
    items: List[OrderItem] = Field(description="Списък с продукти")
    shipping_address: str = Field(description="Адрес за доставка")
    priority: Literal["standard", "express", "overnight"] = Field(
        description="Приоритет на доставка"
    )

# Pydantic моделът може автоматично да генерира JSON Schema
schema = CreateOrderInput.model_json_schema()
print(json.dumps(schema, indent=2, ensure_ascii=False))

В TypeScript еквивалентът е Zod, който осигурява подобна типобезопасност по време на компилация и runtime.

Паралелни извиквания на инструменти

Едно от най-мощните подобрения в съвременния function calling е възможността за паралелни tool calls. Идеята е проста, но ефектът е огромен — вместо да извиква инструменти един по един, моделът може да генерира множество извиквания в едно съобщение, които вашият backend изпълнява едновременно.

Представете си следния сценарий: потребителят пита „Какво е времето в София, Пловдив и Варна?". Без паралелни извиквания моделът би трябвало да извика get_weather три пъти — всяко в отделен кръг. С паралелни извиквания — всичките три се генерират наведнъж:

# Отговор от модела с паралелни tool calls
# response.content съдържа три tool_use блока едновременно:
#
# tool_use: get_weather(city="София")       id="call_1"
# tool_use: get_weather(city="Пловдив")     id="call_2"
# tool_use: get_weather(city="Варна")       id="call_3"

# Вашият backend ги изпълнява паралелно
import asyncio

async def execute_parallel_tools(tool_calls):
    tasks = []
    for call in tool_calls:
        tasks.append(execute_tool_async(call.name, call.input))
    results = await asyncio.gather(*tasks)
    return results

# Връщате ВСИЧКИ резултати в едно съобщение
tool_results = [
    {"type": "tool_result", "tool_use_id": "call_1", "content": '{"temp": 22}'},
    {"type": "tool_result", "tool_use_id": "call_2", "content": '{"temp": 25}'},
    {"type": "tool_result", "tool_use_id": "call_3", "content": '{"temp": 28}'}
]

Паралелните извиквания драматично намаляват латентността при сценарии с множество независими операции. Вместо 3 кръга API извиквания (всеки по 1-2 секунди), получавате всичко в един кръг. За потребителя разликата е напълно осезаема — и тук говорим за реална разлика в потребителското изживяване, не за микрооптимизации.

Model Context Protocol (MCP): Новият стандарт

Ако function calling е „как моделът извиква инструменти", то Model Context Protocol (MCP) е „как стандартизираме и мащабираме тази способност". MCP е отворен стандарт, създаден от Anthropic и дарен на Agentic AI Foundation (под Linux Foundation) в края на 2025 г., с подкрепата на OpenAI, Block и други.

Най-добрата аналогия? USB-C за AI приложения.

Точно както USB-C стандартизира физическата връзка между устройства, MCP стандартизира начина, по който AI модели се свързват с външни инструменти и данни. Преди MCP всяка интеграция беше custom — всеки доставчик с различен формат, различен протокол, различна автентикация. MCP слага край на този хаос (или поне се опитва).

Как работи MCP

MCP архитектурата включва три компонента:

  • MCP Host — AI приложението (напр. Claude Desktop, IDE разширение, custom агент), което инициира връзките.
  • MCP Client — компонентът в host-а, който поддържа 1:1 връзка с MCP сървър.
  • MCP Server — лек програмен слой, който експозира възможности (инструменти, ресурси, промптове) чрез стандартизирания протокол.
# Пример за прост MCP сървър с Python SDK
from mcp.server import Server
from mcp.types import Tool, TextContent

server = Server("product-catalog")

@server.tool("search_products")
async def search_products(query: str, category: str = None) -> list[TextContent]:
    """Търси продукти в каталога по ключови думи."""
    # Реална имплементация на търсене
    results = await catalog_db.search(query, category=category)
    return [TextContent(
        type="text",
        text=json.dumps(results, ensure_ascii=False)
    )]

@server.tool("get_product_details")
async def get_product_details(product_id: int) -> list[TextContent]:
    """Връща подробна информация за конкретен продукт."""
    product = await catalog_db.get(product_id)
    return [TextContent(
        type="text",
        text=json.dumps(product, ensure_ascii=False)
    )]

# Стартиране на сървъра
if __name__ == "__main__":
    server.run()

Доста чисто, нали? Дефинирате инструменти с декоратори, SDK-то се грижи за протокола.

Защо MCP е важен

Преди MCP, ако искахте вашият AI агент да работи с 10 различни услуги (Slack, GitHub, база данни, имейл, календар...), трябваше да напишете 10 различни интеграции. С MCP — всяка услуга може да предостави MCP сървър, и всеки AI модел може да го използва без допълнителна конфигурация.

Екосистемата расте впечатляващо бързо. Към началото на 2026 г. са налични хиляди MCP сървъри — за бази данни, облачни услуги, инструменти за разработчици, бизнес приложения и още. SDK-та са достъпни за Python, TypeScript, Java, Go и други езици.

MCP срещу директен function calling

Важно е да разберете, че MCP не замества function calling. Function calling е механизмът, чрез който моделът решава да използва инструмент. MCP е стандартът, който определя как тези инструменти се откриват, описват и извикват. Те работят заедно — MCP сървърът експозира инструменти, а моделът ги извиква чрез function calling.

MCP също така допълва агентски фреймуърки като LangChain, LangGraph, CrewAI и LlamaIndex, но не ги замества. MCP не решава кога да бъде извикан инструмент или с каква цел — това остава работа на агентската логика.

Programmatic Tool Calling: Ефективност при мащаб

С нарастването на броя инструменти, стандартният подход за function calling среща проблеми с мащабируемостта. Когато имате стотици инструменти, зареждането на всички техни дефиниции в контекстния прозорец на модела консумира огромно количество токени и забавя отговорите. Това е реален проблем, който се усеща особено при по-сложни агентски системи.

Programmatic Tool Calling е подход, при който моделът извиква инструменти в среда за изпълнение на код, вместо чрез стандартния JSON протокол. Предимствата:

  • Зареждане на инструменти при поискване — вместо да предоставяте всички инструменти предварително, моделът ги зарежда динамично, когато са му необходими.
  • Филтриране на данни преди достигане на модела — кодът може да обработи и филтрира резултати, изпращайки само релевантната информация обратно.
  • Сложна логика в една стъпка — множество свързани операции в един блок код, вместо в отделни tool calls.
# Пример за programmatic tool calling
# Вместо: tool_use → result → tool_use → result → tool_use → result
# Моделът генерира код, който прави всичко наведнъж:

async def analyze_sales():
    # Зареждане на данни от множество инструменти
    sales = await mcp.call("get_sales_data", period="last_month")
    inventory = await mcp.call("get_inventory", warehouse="main")

    # Обработка на данните локално (не се изпраща обратно към модела)
    low_stock = [item for item in inventory if item["quantity"] < 10]
    top_products = sorted(sales, key=lambda x: x["revenue"], reverse=True)[:5]

    # Само обобщението се връща към модела
    return {
        "top_5_products": top_products,
        "low_stock_alerts": len(low_stock),
        "total_revenue": sum(s["revenue"] for s in sales)
    }

Anthropic и Google активно развиват тази концепция. Claude поддържа Tool Search Tool — специален мета-инструмент, който позволява на модела да търси сред хиляди инструменти, без да ги зарежда всички в контекстния прозорец. Това е наистина ключово за мащабиране на агентски системи в реални продукционни среди.

Function Calling срещу RAG: Кога кое да използвате

Ако сте прочели нашата статия за RAG пайплайни, може би се питате: каква е разликата между function calling и RAG? И двете свързват модела с външни данни. Отговорът е прост:

  • RAG е за четене на статични знания — извличане на информация от документи, бази знания, PDF-и. Данните обикновено са предварително индексирани и рядко се променят в реално време.
  • Function calling е за действия и динамични данни — изпращане на имейли, обновяване на бази данни, проверка на текущи цени, извикване на API-та. Данните са реални и актуални в момента на извикването.

В практиката обаче границата не е толкова ясна. Много продукционни системи комбинират и двете: RAG retriever може да бъде дефиниран като инструмент, който моделът извиква чрез function calling. Моделът решава кога да търси в документната база (function calling), а самото търсене се извършва чрез RAG пайплайн. Красива симбиоза, ако ме питате.

# RAG retriever като инструмент за function calling
tools = [
    {
        "name": "search_knowledge_base",
        "description": "Търси в корпоративната база знания за отговори на въпроси за политики, процедури и продукти.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Въпросът за търсене"},
                "top_k": {"type": "integer", "description": "Брой резултати (по подразбиране 5)"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "check_live_inventory",
        "description": "Проверява текущата наличност на продукт в реално време.",
        "input_schema": {
            "type": "object",
            "properties": {
                "product_id": {"type": "integer"}
            },
            "required": ["product_id"]
        }
    }
]
# Моделът решава: за въпрос за политики → search_knowledge_base (RAG)
#                  за въпрос за наличност → check_live_inventory (динамичен API)

Съображения за сигурност

Function calling отваря нов вектор за атаки и това е тема, която трябва да третирате изключително сериозно. Когато моделът може да извиква функции, които променят реалния свят — изтриване на данни, изпращане на съобщения, финансови транзакции — последствията от злоупотреба са значителни. Не е преувеличение.

1. Валидация на параметрите

Никога не доверявайте сляпо параметрите, генерирани от модела. Винаги валидирайте на ниво backend:

def execute_transfer(params):
    # ЗАДЪЛЖИТЕЛНА валидация преди изпълнение
    if params["amount"] <= 0:
        return {"error": "Сумата трябва да е положителна"}
    if params["amount"] > MAX_TRANSFER_LIMIT:
        return {"error": f"Максимален лимит: {MAX_TRANSFER_LIMIT}"}
    if not is_valid_account(params["to_account"]):
        return {"error": "Невалиден акаунт"}
    # ... едва тогава изпълняваме трансфера

2. Принцип на минималните привилегии

Давайте на модела достъп само до инструментите, от които наистина се нуждае. Ако агентът е за клиентска поддръжка, не му давайте достъп до административни функции. Използвайте ролеви контрол (RBAC) за инструментите. Звучи очевидно, но ще се изненадате колко често се пренебрегва в практиката.

3. Защита от промпт инжекция

Промпт инжекцията е особено опасна при function calling. Злонамерен потребител може да се опита да манипулира модела да извика функция по неочакван начин. Изследователи по сигурност са документирали атаки чрез MCP, включително:

  • Промпт инжекция — вмъкване на злонамерени инструкции в потребителски вход
  • Комбиниране на инструменти за ексфилтрация на данни — използване на легитимни инструменти в комбинация за извличане на чувствителна информация
  • Lookalike инструменти — злонамерени инструменти, които имитират доверени такива

4. Потвърждение при критични действия

За необратими или високорискови операции, имплементирайте човешко потвърждение (Human-in-the-Loop). Моделът предлага действието, но то не се изпълнява, докато реален човек не го одобри:

def handle_tool_call(tool_name, params):
    # Класификация на риска
    risk_level = classify_risk(tool_name, params)

    if risk_level == "high":
        # Изпращане на заявка за одобрение
        approval = request_human_approval(
            action=f"{tool_name}({params})",
            reason="Високорисково действие, изискващо потвърждение"
        )
        if not approval.granted:
            return {"error": "Действието е отхвърлено от оператор"}

    return execute_tool(tool_name, params)

Добри практики за продукция

Нека обобщим ключовите препоръки за продукционно внедряване на function calling. Тези неща може да изглеждат очевидни, но по опит знам, че е лесно да ги пропуснете, когато бързате да пуснете нещо в продукция.

1. Мониторинг и логване

Логвайте всяко извикване на инструмент — кой инструмент, с какви параметри, какъв е резултатът, колко време е отнело. Това е критично за дебъгване, одит и оптимизация на разходите. Без логове ще летите на сляпо.

2. Обработка на грешки

Инструментите ще се провалят — API-та падат, бази данни не отговарят, мрежови проблеми се случват. Такъв е животът. Винаги връщайте структурирани грешки, които моделът може да интерпретира и комуникира на потребителя:

def safe_tool_execution(tool_name, params):
    try:
        result = execute_tool(tool_name, params)
        return {"status": "success", "data": result}
    except TimeoutError:
        return {"status": "error", "error": "Услугата не отговори навреме. Опитайте отново."}
    except PermissionError:
        return {"status": "error", "error": "Нямате права за това действие."}
    except Exception as e:
        # Не разкривайте вътрешни детайли на модела
        logger.error(f"Tool execution failed: {tool_name}, {params}, {e}")
        return {"status": "error", "error": "Възникна техническа грешка."}

3. Таймаути и лимити

Задайте максимално време за изпълнение на всеки инструмент и максимален брой извиквания на заявка. Без тези лимити, агентският цикъл може теоретично да работи безкрайно. А безкрайно работещ агент не е точно това, което искате в продукция.

4. Версиониране на инструментите

Третирайте дефинициите на инструментите като продукционен код — версионирайте ги, тествайте ги, поддържайте обратна съвместимост. Промяна в схемата на инструмент може да счупи съществуващи интеграции, а дебъгването на подобни проблеми не е забавно.

5. Оптимизация на разходите

Дефинициите на инструменти се включват в контекстния прозорец на всяко API извикване, което означава, че консумират токени. Бъдете кратки, но описателни. Ако имате много инструменти, разгледайте programmatic tool calling или tool search за динамично зареждане — портфейлът ви ще ви благодари.

Пълен пример: AI асистент за електронен магазин

Хайде да обединим всичко в един цялостен пример — AI асистент за електронен магазин, който може да търси продукти, проверява наличност, създава поръчки и изпраща потвърждения. Това е максимално близко до реален продукционен сценарий:

import anthropic
import json
from datetime import datetime

client = anthropic.Anthropic()

# Пълна дефиниция на инструментите
TOOLS = [
    {
        "name": "search_products",
        "description": "Търси продукти по ключови думи, категория или ценови диапазон.",
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Ключови думи"},
                "category": {"type": "string", "enum": ["electronics", "clothing", "home"]},
                "max_price": {"type": "number", "description": "Максимална цена в лева"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "check_availability",
        "description": "Проверява наличността на конкретен продукт в реално време.",
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "product_id": {"type": "integer"}
            },
            "required": ["product_id"]
        }
    },
    {
        "name": "create_order",
        "description": "Създава нова поръчка. Използвай само след потвърждение от потребителя.",
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "integer"},
                "product_id": {"type": "integer"},
                "quantity": {"type": "integer"},
                "shipping_address": {"type": "string"}
            },
            "required": ["customer_id", "product_id", "quantity", "shipping_address"]
        }
    },
    {
        "name": "send_confirmation",
        "description": "Изпраща имейл потвърждение за поръчка на клиента.",
        "strict": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string"},
                "customer_email": {"type": "string"}
            },
            "required": ["order_id", "customer_email"]
        }
    }
]

SYSTEM_PROMPT = """Ти си AI асистент за електронен магазин TechShop.bg.

Правила:
- Винаги бъди учтив и професионален
- Преди създаване на поръчка, потвърди детайлите с клиента
- Ако продуктът не е наличен, предложи алтернативи
- За въпроси извън темата, учтиво пренасочи разговора
"""

def run_shop_assistant(user_message, customer_id=1):
    messages = [{"role": "user", "content": user_message}]

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=4096,
            system=SYSTEM_PROMPT,
            tools=TOOLS,
            messages=messages
        )

        if response.stop_reason == "end_turn":
            for block in response.content:
                if hasattr(block, "text"):
                    return block.text
            return ""

        messages.append({"role": "assistant", "content": response.content})
        tool_results = []

        for block in response.content:
            if block.type == "tool_use":
                result = handle_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": json.dumps(result, ensure_ascii=False)
                })

        messages.append({"role": "user", "content": tool_results})

# Използване
response = run_shop_assistant("Търся лаптоп до 2000 лева, какво имате?")
print(response)

Заключение

Function calling и tool use превръщат големите езикови модели от впечатляващи, но пасивни генератори на текст в активни участници, способни да взаимодействат с реални системи и данни. Това е фундаменталната способност, която превръща LLM в наистина полезни бизнес инструменти.

Ето какво да запомните от тази статия:

  • Function calling е посредник — моделът не изпълнява код директно, а генерира структурирани заявки, които вашият backend изпълнява. Безопасност и контрол на първо място.
  • Качеството на дефинициите е критично — ясни имена, подробни описания и стриктни JSON схеми са фундаментът на надежден tool use.
  • Structured Outputs елиминират цял клас грешки — активирайте strict: true за гарантирана валидация на схемата в продукция. Няма причина да не го направите.
  • MCP стандартизира екосистемата — вместо custom интеграции за всяка услуга, MCP предоставя универсален протокол за свързване на AI с инструменти.
  • Сигурността не е опционална — валидация на параметри, минимални привилегии, човешко потвърждение за критични действия и защита от промпт инжекция са задължителни.

С непрекъснатото развитие на MCP екосистемата, programmatic tool calling и structured outputs, function calling продължава да еволюира. Следващата стъпка е agent-to-agent комуникация, където MCP сървърите сами стават агенти, способни да преговарят и координират автономно.

Ако сте нови в темата, започнете с прост агент с 2-3 инструмента и Structured Outputs. Тествайте, итерирайте, мониторирайте. И постепенно добавяйте сложност — MCP интеграции, паралелни извиквания, programmatic tool calling. Фундаментът, който изграждате днес, ще ви служи добре утре. А ако нещо не работи от първия опит — не се притеснявайте, при никого не работи от първия опит.

За Автора Editorial Team

Our team of expert writers and editors.