Як модель LLM вирішує коли шукати — механіка прийняття рішень

Оновлено:
Як модель LLM  вирішує коли шукати — механіка прийняття рішень

Розробник налаштував tool use, перевірив на тестових запитах — все працює. У production модель раптом відповідає без виклику інструменту, впевнено і зв'язно, але з даними річної давнини. Жодної помилки в логах. Просто неправильна відповідь. Спойлер: модель не «зламалась» — вона прийняла раціональне рішення не шукати, бо вважала себе достатньо обізнаною. І саме це є найнебезпечнішим режимом відмови.

⚡ Коротко

  • Рішення про tool call приймається через внутрішній CoT: модель зважує намір запиту проти опису інструменту
  • Description — це не документація, це промпт: погано написаний опис = модель не викличе tool
  • Найнебезпечніший режим відмови: модель впевнена у відповіді з власних знань, але вони застаріли
  • tool_choice: auto ≠ гарантія пошуку: модель може вирішити відповісти без retrieve навіть коли він потрібний
  • self-conditioning: якщо модель нещодавно робила паралельні виклики — вона схильна їх повторювати
  • 🎯 Ви отримаєте: конкретні шаблони для написання description і стратегії контролю рішення моделі
  • 👇 Нижче — механіка, приклади коду і практичні патерни

📚 Зміст статті

Три режими tool_choice: auto, required, none — і коли який використовувати

Параметр tool_choice — це не просто налаштування API. Це архітектурне рішення про те хто контролює flow: модель чи ваш код. Вибір режиму визначає де виникатимуть проблеми і де їх шукати.

auto: модель як суддя

Режим за замовчуванням коли передані tools. Модель самостійно вирішує: відповісти текстом з власних знань чи викликати інструмент. Це найгнучкіший режим — і найбільш непередбачуваний.

# auto — модель вирішує сама
response = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},   # можна не вказувати — це default
    messages=[{"role": "user", "content": query}]
)

# Перевіряємо що саме вирішила модель
if response.stop_reason == "tool_use":
    # модель викликала інструмент
    tool_block = next(b for b in response.content if b.type == "tool_use")
    print(f"Tool: {tool_block.name}, Args: {tool_block.input}")
elif response.stop_reason == "end_turn":
    # модель відповіла без пошуку — це може бути нормально або проблемою
    text = next(b for b in response.content if b.type == "text")
    print(f"Direct answer (no tool): {text.text[:100]}")

Логування stop_reason — обов'язкова практика у production. Це єдиний спосіб зрозуміти чи модель шукала інформацію чи відповіла з власних знань.

required / any: примусовий виклик

Модель зобов'язана викликати хоча б один інструмент незалежно від запиту. OpenAI називає це required, Anthropic — any.

# Anthropic: примусовий виклик будь-якого інструменту
tool_choice={"type": "any"}

# Anthropic: примусовий виклик конкретного інструменту
tool_choice={"type": "tool", "name": "search_documents"}

# OpenAI-сумісний синтаксис
tool_choice="required"

Коли виправданий: structured output де відповідь завжди має йти через інструмент, обов'язкове логування кожного запиту до зовнішньої системи, детермінований pipeline де retrieve — обов'язковий крок.

Коли шкідливий: conversational режим де користувач може запитати «привіт» або «дякую». Модель все одно сформує tool call, ваш код витратить токени і час на непотрібний retrieve.

Критичне обмеження Anthropic (станом на 2025): any і примусовий виклик конкретного tool несумісні з extended thinking. При увімкненому thinking доступні лише auto і none. Спроба використати any з thinking повертає HTTP 400. Актуальний статус — docs.anthropic.com.

none: заборона викликів

Модель не викликає жодного інструменту, генерує тільки текст. Корисний для фінального кроку після отримання всіх результатів — коли потрібно синтезувати відповідь з вже зібраного контексту без додаткових запитів.

# Фінальна синтезуюча відповідь після збору даних
final_response = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=2048,
    tools=tools,                      # tools все одно передаємо (вимога API)
    tool_choice={"type": "none"},     # але забороняємо їх викликати
    messages=[
        *conversation_history,
        {"role": "user", "content": "Тепер сформуй фінальний звіт на основі зібраних даних."}
    ]
)

Важливий нюанс: якщо у messages є блоки tool_result, tools у запиті все одно потрібно передати — інакше API поверне помилку валідації.

⚠️ Підводний камінь #1: ілюзія контролю через tool_choice

tool_choice: auto не означає що модель буде шукати коли треба. Це означає що модель буде шукати коли вона вважає за потрібне. Якщо ваша задача вимагає актуальних даних на кожному запиті — auto недостатньо. Або використовуйте required, або проектуйте description так, щоб модель завжди вважала пошук необхідним для вашого типу запитів.

Як опис інструменту впливає на рішення моделі: погане description = модель не викличе tool

Description — це не документація для розробника. Це частина промпту яку бачить модель. Саме через неї модель «розуміє» що інструмент робить і коли його варто викликати.

APXML (2025) описує це так: модель порівнює намір запиту з описами доступних інструментів, як людина порівнює завдання з наявними інструментами у ящику. Якщо молоток підписаний «предмет для фізичного впливу» — людина може не взяти його для забивання цвяха.

OpenAI офіційна документація рекомендує: пишіть чіткі й детальні назви функцій, описи параметрів та інструкції. Явно описуйте призначення функції та кожного параметра, і використовуйте системний промпт щоб пояснити коли (і коли не) використовувати кожну функцію.

Порівняння: поганий vs хороший description — кейс з практики

У одного з клієнтів виникла проблема: модель погано слідувала інструкціям і нерегулярно викликала пошук. Частина запитів про ціни та умови договорів проходила без retrieve — модель відповідала з власних знань впевнено, але неактуально. Коли ми розібрали причину — виявилось що опис інструменту був мінімальним. Після того як ми переписали description з тригерними сценаріями, поведінка моделі стабілізувалась.

# ❌ БУЛО: мінімальний description — модель часто не викликала tool
{
    "name": "search_docs",
    "description": "Шукає документи",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string"}
        },
        "required": ["query"]
    }
}
# ✅ СТАЛО: розгорнутий description з тригерами — модель розуміє коли і навіщо
{
    "name": "search_knowledge_base",
    "description": """Шукає актуальну інформацію у корпоративній базі знань.

    ВИКОРИСТОВУЙ цей інструмент коли:
    - Запит стосується умов договорів, цін, регламентів або внутрішніх процедур
    - Потрібна актуальна інформація яка могла змінитись після твого навчання
    - Запитується специфіка конкретного клієнта, продукту або проекту

    НЕ використовуй для:
    - Загальних питань про технології або загальновідомих фактів
    - Математичних обчислень
    - Форматування або редагування тексту""",

    "input_schema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Пошуковий запит природною мовою. Формулюй як питання або ключові поняття."
            },
            "top_k": {
                "type": "integer",
                "description": "Кількість фрагментів для повернення. За замовчуванням 5. Збільш до 10 для складних порівняльних запитів."
            }
        },
        "required": ["query"]
    }
}

Ключова різниця: хороший description містить тригерні сценарії — явний перелік ситуацій де tool потрібний. Модель використовує цей перелік як критерій у своєму внутрішньому рішенні.

Важливо якщо використовуєте Ollama з локальною моделлю: слабші моделі (7B–13B) можуть нестабільно слідувати навіть детальному description — це обмеження моделі, а не опису. У нашому production stack ми використовуємо AskYourDocs через OpenRouter з deepseek/deepseek-chat як default моделлю (${SPRING_AI_OPENAI_CHAT_MODEL:deepseek/deepseek-chat}) — і проблем з виконанням description на цьому рівні немає.

Для Ollama у dev/staging середовищі рекомендуємо додатково дублювати тригерні інструкції у системний промпт — це підвищує стабільність на слабших моделях. Детальніше про налаштування Ollama у production-конфігурації: RAG з Ollama: як навчити AI відповідати по твоїх документах .

Вплив description на вибір між кількома tools

Коли у системі кілька інструментів з подібною функціональністю, якість description стає ще критичнішою. APXML підкреслює: розмиті або перекриваючі описи змушують модель або вибрати неправильний інструмент, або не вибрати жодного.

# Проблема: два схожих інструменти з нечіткими описами
tools = [
    {"name": "search_contracts", "description": "Шукає контракти"},
    {"name": "search_documents", "description": "Шукає документи"}
]
# Модель не знає різниці — вибір непередбачуваний

# Рішення: чітке розмежування сфер відповідальності
tools = [
    {
        "name": "search_contracts",
        "description": "Шукає ТІЛЬКИ підписані юридичні договори і додатки до них. "
                       "Використовуй для запитів про умови, терміни, зобов'язання сторін."
    },
    {
        "name": "search_documents",
        "description": "Шукає внутрішні регламенти, інструкції, технічну документацію і звіти. "
                       "НЕ містить юридичних договорів."
    }
]

⚠️ Підводний камінь #2: більше інструментів = більше помилок вибору

Laurent Kubaski (2025) зазначає: більшість провайдерів публікують максимальну кількість інструментів яку модель підтримує технічно, але не говорять що на практиці зі зростанням кількості tools зростає ймовірність неправильного вибору. Після 10-15 інструментів якість вибору помітно знижується. Після 50+ — потрібен Tool RAG (детально у TU-6).

Chain-of-Thought всередині: як модель аналізує контекст і приймає рішення

Рішення про tool call — не результат простого pattern matching. Всередині відбувається процес схожий на Chain-of-Thought reasoning, де модель послідовно зважує кілька факторів перед тим як повернути відповідь.

Raina (2025) описує це через призму нейронних шарів: вхідні embedding шари перетворюють запит і descriptions tools у числові вектори — про те як саме текст стає вектором і чому семантично схожі запити потрапляють в одну область простору читайте у Embeddings простими словами: як AI розуміє сенс, а не просто слова . Середні шари здійснюють абстрактне міркування і логіку вибору інструменту, а як пошук за цими векторами знаходить найрелевантніший інструмент або фрагмент — детально у Vector Search для початківців: як RAG знаходить потрібну інформацію . Вихідні шари генерують фінальне рішення — текст або tool call.

Внутрішній процес рішення (спрощена модель)

# Що відбувається всередині моделі при tool_choice: auto (концептуально):

# 1. Аналіз наміру запиту
intent = analyze_query(user_message)
# → "запит про умови розірвання договору"

# 2. Оцінка власних знань
confidence_in_own_knowledge = estimate_confidence(intent)
# → "є загальні знання про договори, але не про конкретний договір цього клієнта"

# 3. Порівняння з доступними tools
tool_relevance = match_intent_to_tools(intent, tool_descriptions)
# → search_knowledge_base: висока релевантність (тригерний сценарій: "умови договорів")

# 4. Рішення
if tool_relevance > threshold AND confidence_in_own_knowledge < threshold:
    return tool_call(name="search_knowledge_base", args={...})
else:
    return text_response(...)

Ключовий момент: модель не просто перевіряє чи є релевантний інструмент. Вона також оцінює чи достатньо власних знань. Якщо модель вважає себе обізнаною — вона може вибрати текстову відповідь навіть при наявності релевантного tool.

Як модель навчається вирішувати коли шукати

Здатність до правильного рішення — результат fine-tuning на синтетичних прикладах. Simplicity is SOTA (2025) описує підхід: провайдери генерують тисячі прикладів запит → CoT reasoning trace → tool call, де модель вчиться не просто викликати функцію, а обґрунтовувати навіщо. Приклад reasoning trace виглядає так:

# Внутрішній CoT reasoning trace (як він формується під час навчання):
"""
Запит: "Які умови дострокового розірвання нашого договору?"

Аналіз: Запит стосується конкретного договору ("нашого") —
це означає що потрібна специфічна інформація якої немає у моїх загальних знаннях.
Доступний інструмент search_knowledge_base описує пошук по корпоративній базі
з тригерним сценарієм "умови договорів". Це точне співпадіння.
Впевненість у власних знаннях: низька (специфіка конкретного договору).
Рішення: викликати search_knowledge_base.
"""
→ tool_call("search_knowledge_base", {"query": "умови дострокового розірвання договору"})

Саме тому reasoning-enabled варіанти моделей (Claude з extended thinking, o-series від OpenAI) показують кращі результати у складних tool use сценаріях — WildToolBench (2026) підтверджує: reasoning-enabled моделі стабільно перевершують non-reasoning варіанти у завданнях де потрібна правильна оркестрація послідовних tool calls.

Вплив системного промпту на рішення

Системний промпт — потужний важіль для керування рішенням моделі. Якщо в ньому явно вказати коли шукати, модель слідуватиме цій інструкції навіть при tool_choice: auto:

Поєднання якісного description + явної інструкції у системному промпті значно підвищує надійність рішень моделі порівняно з кожним підходом окремо.

Як модель LLM  вирішує коли шукати — механіка прийняття рішень

Де рішення ламається: модель впевнена, але відповідь застаріла → не шукає → галюцинує

Це найнебезпечніший режим відмови у системах з tool use. Не помилка коду, не порожній результат — а впевнена, зв'язна, граматично правильна відповідь яка є неактуальною або невірною.

Механіка «тихої» галюцинації

# Сценарій: модель має parametric knowledge про продукт,
# але ціни змінились 3 місяці тому

user: "Яка ціна на Enterprise план?"

# Внутрішній процес:
# - модель бачить tool search_pricing
# - оцінює власні знання: "я знаю про Enterprise план, там було $500/місяць"
# - confidence: висока → вирішує відповісти без пошуку

assistant: "Enterprise план коштує $500 на місяць і включає..."
# stop_reason: "end_turn"  ← жодного tool_use

# Реальна ціна: $650/місяць після підвищення 3 місяці тому
# Результат: клієнт отримав невірну ціну, нічого в логах не сигналізує про проблему

OpenAI (2025) пояснює чому це відбувається: стандартне навчання винагороджує впевнені відповіді, а не визнання невизначеності. Модель навчена відповідати, а не утримуватись. Тому навіть frontier моделі схильні відповідати впевнено там де мали б запитати свіжі дані.

Огляд галюцинацій у LLM (2026) виділяє окремий тип: temporal misalignment — модель генерує відповідь яка була правильною в момент навчання, але застаріла на момент запиту. Це особливо критично для цін, регламентів, умов договорів, кадрових змін.

Типи ситуацій де це небезпечно

Тип даних Частота змін Ризик Рекомендація
Ціни, тарифи Висока 🔴 Критичний tool_choice: required або явна інструкція у промпті
Умови договорів Середня 🔴 Критичний tool_choice: required для всіх запитів про договори
Внутрішні регламенти Середня 🟠 Високий Тригерні сценарії у description + системний промпт
Кадрові дані Висока 🟠 Високий Пошук обов'язковий, ніколи не відповідати з memory
Технічна документація Низька 🟡 Середній auto + якісний description зазвичай достатньо
Загальні знання Дуже низька 🟢 Низький Відповідь без пошуку прийнятна

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

import anthropic

def safe_query(client, query, tools, require_search_keywords=None):
    """
    Запит з контролем чи модель використала пошук.
    require_search_keywords: список слів які вимагають обов'язкового пошуку
    """
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        tools=tools,
        messages=[{"role": "user", "content": query}]
    )

    used_tool = response.stop_reason == "tool_use"

    # Перевіряємо чи запит вимагав пошуку
    if require_search_keywords:
        needs_search = any(kw in query.lower() for kw in require_search_keywords)
        if needs_search and not used_tool:
            # Логуємо підозрілу відповідь без пошуку
            log_warning(f"Query requires search but model answered directly: {query[:100]}")
            # Опціонально: форсуємо повторний запит з required
            return retry_with_required(client, query, tools)

    return response

# Використання
response = safe_query(
    client, query, tools,
    require_search_keywords=["ціна", "вартість", "договір", "умови", "регламент"]
)

⚠️ Підводний камінь #3: впевнений тон = підозра, не довіра

Чим впевненіше модель відповідає на запит де очікується актуальна інформація — тим більше підстав перевірити чи вона взагалі шукала. Невпевнена відповідь з «я не впевнений» часто надійніша ніж впевнена відповідь без пошуку. У production: завжди логуйте stop_reason. Відповідь з end_turn без попереднього tool_use на «чутливому» запиті — привід для аудиту.

Паралельні виклики інструментів: коли модель викликає кілька tools одночасно

Сучасні моделі підтримують паралельні tool calls — кілька викликів в одному повороті для незалежних запитів. Це потужна можливість яка вимагає правильної обробки.

Коли модель генерує паралельні виклики

Модель вирішує зробити паралельні виклики коли:

  • Запит явно порівнює кілька сутностей («порівняй договори А і Б»)
  • Потрібні дані з кількох незалежних джерел одночасно
  • Підзапити не залежать один від одного і можуть виконуватись паралельно
# Запит: "Порівняй умови договорів з клієнтами Альфа і Бета"
# Модель повертає два паралельних виклики в одній відповіді:

{
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_01A",
      "name": "search_knowledge_base",
      "input": {"query": "умови договору клієнт Альфа", "top_k": 5}
    },
    {
      "type": "tool_use",
      "id": "toolu_01B",
      "name": "search_knowledge_base",
      "input": {"query": "умови договору клієнт Бета", "top_k": 5}
    }
  ],
  "stop_reason": "tool_use"
}

Правильна обробка паралельних викликів

import anthropic
import json
from concurrent.futures import ThreadPoolExecutor

def handle_parallel_tool_calls(response, tools_map):
    """
    Обробляє паралельні tool calls і повертає результати для всіх.
    tools_map: dict {tool_name: callable}
    """
    tool_blocks = [b for b in response.content if b.type == "tool_use"]

    # Виконуємо всі виклики — можна паралельно якщо незалежні
    def execute_tool(block):
        fn = tools_map.get(block.name)
        if not fn:
            return {"tool_use_id": block.id, "content": f"Tool {block.name} not found", "is_error": True}
        try:
            result = fn(**block.input)
            return {
                "type": "tool_result",
                "tool_use_id": block.id,   # ← критично: id з tool_use блоку
                "content": json.dumps(result, ensure_ascii=False)
            }
        except Exception as e:
            return {
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": str(e),
                "is_error": True            # ← явно позначаємо помилку
            }

    with ThreadPoolExecutor() as executor:
        results = list(executor.map(execute_tool, tool_blocks))

    return results

# Другий запит з результатами ВСІХ паралельних викликів
tool_results = handle_parallel_tool_calls(response, {
    "search_knowledge_base": search_knowledge_base
})

follow_up = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=2048,
    tools=tools,
    messages=[
        {"role": "user", "content": original_query},
        {"role": "assistant", "content": response.content},
        {"role": "user", "content": tool_results}  # ← всі результати разом
    ]
)

Self-conditioning: прихована проблема довгих сесій

WildToolBench (2026) виявив важливий ефект: моделі демонструють self-conditioning — якщо в поточній сесії модель нещодавно використовувала паралельні виклики, вона схильна повторювати їх навіть коли це недоцільно. Причина: великий контекст попередніх повідомлень «забиває» увагу моделі до поточного запиту.

Практичний наслідок: у довгих агентних сесіях поведінка model може дрейфувати. Якщо бачите що модель робить зайві паралельні виклики у пізніх повідомленнях сесії — це не випадковість, а накопичений context bias. Рішення: periodically summarize and compress conversation history, або розбивати довгі агентні сесії на незалежні.

⚠️ Підводний камінь #4: незакриті паралельні виклики

Якщо у відповіді моделі два tool call блоки, але ви повернули tool_result тільки для одного — наступний API запит поверне помилку валідації. Кожен tool_use_id повинен мати відповідний tool_result. Якщо один з викликів завершився помилкою — передайте is_error: true, але не пропускайте відповідь для цього id.

Практика: як писати description щоб модель викликала tool коли треба

Statsig (2025) формулює це чітко: документація інструменту повинна читатись як контракт — рядок призначення, кілька конкретних прикладів, і типи аргументів які не залишають простору для здогадок.

Шаблон ефективного description

ШАБЛОН:
"""[Що робить інструмент — одне речення, конкретно]

ВИКОРИСТОВУЙ коли:
- [тригерний сценарій 1]
- [тригерний сценарій 2]
- [критерій актуальності: "коли потрібна свіжа/поточна інформація"]

НЕ використовуй для:
- [anti-use-case 1]
- [anti-use-case 2]

Приклади запитів які мають тригерити цей tool:
- "[конкретний приклад 1]"
- "[конкретний приклад 2]"
"""

Реальний приклад для AskYourDocs

tools = [
    {
        "name": "search_knowledge_base",
        "description": """Шукає актуальну інформацію у корпоративній базі знань компанії.
База містить: договори, додатки, специфікації, цінники, регламенти, внутрішні інструкції.

ВИКОРИСТОВУЙ коли:
- Запит про ціни, тарифи, вартість будь-яких послуг або продуктів
- Запит про умови договорів, зобов'язання, терміни, санкції
- Запит про внутрішні процедури, регламенти, інструкції
- Потрібна специфіка конкретного клієнта, проекту або продукту
- Запит містить слова: "наш", "ваш", "поточний", "актуальний", "останній"

НЕ використовуй для:
- Загальних питань про технології (що таке PDF, як працює API)
- Математичних обчислень
- Запитів про загальновідомі факти незалежні від компанії

Приклади запитів які МАЮТЬ тригерити цей tool:
- "Яка ціна на Enterprise план?"
- "Які умови дострокового розірвання?"
- "Хто відповідальний за онбординг нових клієнтів?"
- "Який дедлайн по договору з Альфа Corp?"
""",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Пошуковий запит природною мовою. "
                                   "Включай контекст: назви клієнтів, продуктів, типи документів."
                },
                "top_k": {
                    "type": "integer",
                    "description": "Кількість фрагментів (1-10). Default: 5. "
                                   "Використовуй 8-10 для порівняльних запитів."
                }
            },
            "required": ["query"]
        }
    }
]

Тестування якості description

Написати хороший description недостатньо — потрібно верифікувати що модель дійсно викликає tool на потрібних запитах і не викликає на зайвих.

def test_tool_description(client, tools, test_cases):
    """
    Тестує чи модель викликає tool на потрібних запитах.

    test_cases: список словників:
    [
        {"query": "Яка ціна?", "should_call_tool": True},
        {"query": "Що таке RAG?", "should_call_tool": False},
    ]
    """
    results = []
    for case in test_cases:
        response = client.messages.create(
            model="claude-opus-4-6",
            max_tokens=256,
            tools=tools,
            messages=[{"role": "user", "content": case["query"]}]
        )
        called_tool = response.stop_reason == "tool_use"
        passed = called_tool == case["should_call_tool"]
        results.append({
            "query": case["query"],
            "expected": case["should_call_tool"],
            "actual": called_tool,
            "passed": passed
        })
        if not passed:
            print(f"❌ FAIL: '{case['query']}' — "
                  f"expected tool={'yes' if case['should_call_tool'] else 'no'}, "
                  f"got {'yes' if called_tool else 'no'}")

    passed_count = sum(1 for r in results if r["passed"])
    print(f"\n{passed_count}/{len(results)} test cases passed")
    return results

# Запускай після кожної зміни description
test_tool_description(client, tools, [
    {"query": "Яка ціна на Enterprise план?", "should_call_tool": True},
    {"query": "Які умови розірвання договору?", "should_call_tool": True},
    {"query": "Хто займається онбордингом?", "should_call_tool": True},
    {"query": "Що таке векторна база даних?", "should_call_tool": False},
    {"query": "Привіт, як справи?", "should_call_tool": False},
    {"query": "Скільки буде 15% від 1000?", "should_call_tool": False},
])

Checklist для опису інструменту

  • ☑ Перше речення відповідає на «що робить цей tool?» конкретно і без загальних слів
  • ☑ Є явний перелік тригерних сценаріїв (ВИКОРИСТОВУЙ коли)
  • ☑ Є явний перелік anti-use-cases (НЕ використовуй для)
  • ☑ Є критерій актуальності («коли потрібна свіжа/поточна інформація»)
  • ☑ Якщо кілька tools — описи не перекриваються і чітко розмежовані
  • ☑ Description протестований на наборі запитів (both positive і negative cases)
  • ☑ Системний промпт підсилює description явними інструкціями коли шукати

❓ Часті питання

Як LLM вирішує чи викликати tool?

Через внутрішній CoT: модель зважує намір запиту проти опису інструменту і оцінює чи достатньо власних знань. Якщо description містить тригерний сценарій що збігається з запитом — і модель не вважає себе достатньо обізнаною — вона генерує tool call.

Чому модель не викликає tool навіть коли це потрібно?

Три причини: (1) нечіткий або відсутній тригерний сценарій у description; (2) модель вважає себе достатньо обізнаною з власних parametric знань; (3) tool_choice: auto за природою не гарантує виклик. Рішення: покращити description, додати явні інструкції у системний промпт, або використати required для критичних типів запитів.

Що таке галюцинація від впевненості?

Модель відповідає без виклику tool — впевнено і зв'язно — тому що parametric знання покривають запит, але ці знання застаріли. Особливо небезпечно для цін, умов договорів, регламентів. Діагностика: stop_reason == "end_turn" без попереднього tool_use на «чутливому» запиті.

Як правильно обробити паралельні tool calls?

Виконати функцію для кожного tool_use блоку і передати tool_result для кожного tool_use_id у наступному запиті. Пропуск хоча б одного id → помилка валідації API. Якщо виклик завершився помилкою — передайте is_error: true, але не пропускайте відповідь.

✅ Висновки

  • tool_choice: auto — не гарантія пошуку. Це право моделі вирішити. Логуйте stop_reason.
  • Description — частина промпту, не документація. Тригерні сценарії і anti-use-cases критичні.
  • Найнебезпечніший режим відмови: впевнена відповідь без пошуку на застарілих parametric знаннях.
  • Reasoning-enabled моделі кращі у складних tool use сценаріях — WildToolBench (2026) підтверджує.
  • Паралельні виклики вимагають повернення результату для кожного tool_use_id.
  • Self-conditioning у довгих сесіях може спотворювати рішення моделі — стежте за context drift.
  • Тестуйте description на наборі позитивних і негативних прикладів після кожної зміни.

Наступний крок: як модель оцінює якість результатів які повертає інструмент — і чому навіть правильний виклик може дати неправильну відповідь — у TU-3: Grounding та довіра до джерел.

Джерела

WildToolBench — Benchmarking LLM Tool-Use in the Wild (2026) · Simplicity is SOTA — How LLMs are trained for function calling (2025) · OpenAI — Why language models hallucinate (2025) · Raina — Inside the Black Box: LLM Neural Layers and Tool Calling (2025) · APXML — Agent Tool Selection Logic · OpenAI Docs — Function Calling Best Practices · Kubaski — Tool Calling Best Practices (2025) · Statsig — Tool calling optimization (2025) · Survey — Large Language Models Hallucination (2026) · Anthropic Docs — How to implement tool use

Останні статті

Читайте більше цікавих матеріалів

Як модель LLM  вирішує коли шукати — механіка прийняття рішень

Як модель LLM вирішує коли шукати — механіка прийняття рішень

Розробник налаштував tool use, перевірив на тестових запитах — все працює. У production модель раптом відповідає без виклику інструменту, впевнено і зв'язно, але з даними річної давнини. Жодної помилки в логах. Просто неправильна відповідь. Спойлер: модель не «зламалась»...

Tool Use vs Function Calling: механіка, JSON schema і зв'язок з RAG

Tool Use vs Function Calling: механіка, JSON schema і зв'язок з RAG

Коли розробник вперше бачить як LLM «викликає функцію» — виникає інтуїтивна помилка: здається що модель сама виконала запит до бази або API. Це не так, і саме ця помилка породжує цілий клас архітектурних багів. Спойлер: LLM лише повертає структурований JSON з назвою...

Core Update березень 2026: трафік падає, але ви нічого не порушили

Core Update березень 2026: трафік падає, але ви нічого не порушили

27 березня 2026 року Google запустив перший широкий Core Update року. Офіційне формулювання — «регулярне оновлення для покращення релевантності результатів». Але за лаштунками тисячі сайтів побачили падіння кліків і показів у Google Search Console. При цьому Google прямо каже: штрафів...

Як навчають LLM: від pre-training до RLVR — повний гайд 2026

Як навчають LLM: від pre-training до RLVR — повний гайд 2026

Якщо ви досі думаєте, що LLM навчають так: "скопіювали весь інтернет → натиснули кнопку Train" – ви помиляєтесь на сотні мільйонів доларів. ChatGPT, Claude і Gemini проходять три принципово різних етапи навчання. І найважливіший з них – не pre-training. Спойлер: у 2025–2026...

AI coding не принесе вам грошей. І ось чому

AI coding не принесе вам грошей. І ось чому

Кілька днів тому мій друг написав мені в месенджер: «Слухай, я тут роблю проєкт через Gemini. Код сам пишеться, все швидко. Думаю за 3-4 дні запущу і почну заробляти.» Я — розробник. І я знав, що зараз почнеться той самий розмова, яку я вже мав десятки разів. Але цього разу я вирішив не...

Я додав BM25 до свого RAG-сервісу — і vector search перестав губити точні запити

Я додав BM25 до свого RAG-сервісу — і vector search перестав губити точні запити

Чистий vector search втрачає точні терміни, ціни і номери документів. Я це виправив за один день — без зміни LLM, без GPU, без нових залежностей. Мій RAG-сервіс працював. Vector search знаходив релевантні чанки, LLM генерувала відповіді українською. Але коли клієнт запитав "консультація...