Розробник налаштував tool use, перевірив на тестових запитах — все працює. У production модель раптом відповідає без виклику інструменту, впевнено і зв'язно, але з даними річної давнини. Жодної помилки в логах. Просто неправильна відповідь. Спойлер: модель не «зламалась» — вона прийняла раціональне рішення не шукати, бо вважала себе достатньо обізнаною. І саме це є найнебезпечнішим режимом відмови.
⚡ Коротко
- ✅ Рішення про tool call приймається через внутрішній CoT: модель зважує намір запиту проти опису інструменту
- ✅ Description — це не документація, це промпт: погано написаний опис = модель не викличе tool
- ✅ Найнебезпечніший режим відмови: модель впевнена у відповіді з власних знань, але вони застаріли
- ✅ tool_choice: auto ≠ гарантія пошуку: модель може вирішити відповісти без retrieve навіть коли він потрібний
- ✅ self-conditioning: якщо модель нещодавно робила паралельні виклики — вона схильна їх повторювати
- 🎯 Ви отримаєте: конкретні шаблони для написання description і стратегії контролю рішення моделі
- 👇 Нижче — механіка, приклади коду і практичні патерни
📚 Зміст статті
- 📌 Три режими tool_choice: auto, required, none
- 📌 Як опис інструменту впливає на рішення моделі
- 📌 Chain-of-Thought всередині: як модель аналізує контекст
- 📌 Де рішення ламається: впевненість → відсутність пошуку → галюцинація
- 📌 Паралельні виклики: коли модель викликає кілька tools одночасно
- 💼 Практика: як писати description щоб модель викликала tool коли треба
- ❓ Часті питання (FAQ)
- ✅ Висновки
Три режими 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 + явної інструкції у системному промпті
значно підвищує надійність рішень моделі порівняно з кожним підходом окремо.