Исправление tool calling в Qwen 3.5: Jinja-шаблон и теги <think> | AiManual
AiManual Logo Ai / Manual.
11 Апр 2026 Инструмент

Исправление tool calling в Qwen 3.5: исчерпывающий Jinja-шаблон и разбор нативных тегов <think>

Полный гайд по починке сломанного tool calling в Qwen 3.5. Рабочий Jinja-шаблон, разбор нативных тегов <think> и сравнение с альтернативами на 11.04.2026.

Почему ваш Qwen 3.5 отказывается звонить?

Вы настраиваете агента, пишете красивый системный промпт, описываете инструменты — а модель молчит. Или, что хуже, начинает галлюцинировать, выдавая XML-мусор вместо четкого вызова функции. Знакомая история? На 11 апреля 2026 года эта проблема живее всех живых, даже несмотря на выход Qwen 4.0. Многие команды остаются на Qwen 3.5 из-за проверенной стабильности и отлаженных пайплайнов, но с tool calling тут настоящая лотерея.

Корень зла — в чат-шаблоне. Том самом Jinja-файле, который превращает ваш диалог в последовательность токенов. Официальный шаблон от Hugging Face содержит тонкую ошибку, которая ломает логику нативных тегов модели. А без понимания этих тегов любая настройка превращается в стрельбу из пушки по воробьям.

💡
Эта статья — не просто набор советов. Это результат разбора десятков сломанных продовшен-окружений, где tool calling работал через раз. Если вы уже читали базовые гайды и ничего не помогло — вы по адресу.

1 Тег : нераскрытая карта модели

Qwen 3.5 — не глупая модель. У нее есть внутренний механизм рассуждений, который она обозначает специальными тегами и . Это не просто украшение. Это нативный формат, в котором модель ведет внутренний диалог перед тем, как выдать финальный ответ или вызвать инструмент.

Вот как это выглядит в сыром выводе токенизатора:


Пользователь просит узнать погоду. У меня есть инструмент get_weather.
Нужно вызвать его с параметром location: "Москва".


{"name": "get_weather", "arguments": {"location": "Москва"}}

Проблема в том, что большинство фреймворков и клиентов (взгляните на баги парсера LM Studio) считают все, что между и , частью ответа пользователю. Они тупо склеивают этот текст с финальным выводом. В результате ваш агент думает вслух, а система логирования сходит с ума.

Важно: На 11.04.2026 некоторые квантования Qwen 3.5 (особенно созданные старыми скриптами) могут вообще игнорировать теги . Если ваш tool calling не работает даже с правильным шаблоном — проверьте, не сломана ли сама модель на уровне весов.

2 Сломанный шаблон vs исправленный: где собака порылась

Официальный шаблон для Qwen 3.5 делает грубейшую ошибку: он обрабатывает теги как обычный текст. Вот фрагмент того, как НЕ надо делать:

{# Старый, сломанный подход #}
{% for message in messages %}
  {% if message['role'] == 'system' %}
    {{ '<|im_start|>system\n' + message['content'] + '<|im_end|>\n' }}
  {% else %}
    {{ '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>\n' }}
  {% endif %}
{% endfor %}

Видите? Нет никакого разделения. Модель генерирует -блок, а шаблон считает его частью контента сообщения assistant. В результате при следующем раунде диалога этот внутренний монолог подается модели на вход как история. Она путается, теряет контекст и начинает галлюцинировать. Это та же самая проблема, что ломает кэширование в llama.cpp.

3 Jinja-шаблон, который работает. Берите и пользуйтесь

Вот исправленная версия. Она делает одну простую вещь: отделяет reasoning-блок от финального ответа модели, используя те же нативные теги, которые понимает Qwen 3.5.

{# Исправленный шаблон для Qwen 3.5 Tool Calling (актуален на 11.04.2026) #}
{% for message in messages %}
  {% if message['role'] == 'system' %}
    {{ '<|im_start|>system\n' + message['content'] | trim + '<|im_end|>\n' }}
  {% elif message['role'] == 'user' %}
    {{ '<|im_start|>user\n' + message['content'] | trim + '<|im_end|>\n' }}
  {% elif message['role'] == 'assistant' %}
    {% set content = message['content'] | trim %}
    {% if '' in content and '' in content %}
      {# Разделяем reasoning и финальный ответ/action #}
      {% set think_end = content.find('') + 9 %}
      {{ '<|im_start|>assistant\n' + content[:think_end] + '<|im_end|>\n' }}
      {{ '<|im_start|>assistant\n' + content[think_end:] | trim + '<|im_end|>\n' }}
    {% else %}
      {{ '<|im_start|>assistant\n' + content + '<|im_end|>\n' }}
    {% endif %}
  {% elif message['role'] == 'tool' %}
    {{ '<|im_start|>tool\n' + message['content'] | trim + '<|im_end|>\n' }}
  {% endif %}
{% endfor %}
{% if add_generation_prompt %}
{{ '<|im_start|>assistant\n' }}
{% endif %}

Что изменилось? Когда шаблон видит сообщение от assistant, он проверяет, содержит ли оно теги . Если да — он разбивает это сообщение на две отдельные записи в истории диалога. Первая — это сам reasoning-блок (с тегами). Вторая — финальный ответ или tool call. Для модели это выглядит как два последовательных сообщения от assistant, что полностью соответствует ее внутренней логике генерации.

Этот шаблон решает не только проблему tool calling. Он также фиксит баги с повторной обработкой промптов, которые убивают производительность в llama.cpp и подобных бэкендах. Один файл — и десятки часов отладки.

4 Как встроить этот шаблон в ваш проект

Не нужно пересобирать мир. Если вы используете Hugging Face Transformers (актуальная версия на 11.04.2026 — 4.45.0), просто установите этот шаблон для токенизатора:

from transformers import AutoTokenizer

# Загружаем модель и токенизатор
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3.5-7B-Instruct", trust_remote_code=True)

# Ваш исправленный шаблон в виде строки
correct_chat_template = """
{# Вставьте сюда Jinja-шаблон из шага 3 #}
"""

# Устанавливаем его
tokenizer.chat_template = correct_chat_template

# Теперь tokenizer.apply_chat_template будет работать правильно
messages = [
    {"role": "system", "content": "Ты полезный ассистент с инструментами."},
    {"role": "user", "content": "Какая погода в Берлине?"}
]
input_ids = tokenizer.apply_chat_template(messages, return_tensors="pt")

Для Ollama, llama.cpp или LM Studio процесс сложнее — нужно править конфигурационные файлы модели. Но принцип тот же: заменить стандартный шаблон на исправленный. В гайде по LM Studio есть детали.

А другие модели? Они тоже так больны?

Qwen 3.5 — не уникальна в своих проблемах. Механизм reasoning-тегов есть у многих современных моделей. Например, Minimax M2.1 использует похожий подход, но с другими тегами. Близкий родственник, Qwen 2.5 27B, страдает от похожих симптомов. Даже быстрые модели вроде Step 3.5 Flash могут галлюцинировать tool calls без правильного шаблона.

Альтернатива? Можно перейти на модели с нативным support tool calling API, как GPT-4o или Claude 3.7. Но это значит платить за API, терять контроль над данными и зависеть от чужой инфраструктуры. Наш исправленный шаблон дает бесплатный и полный контроль.

Кому стоит заморачиваться с этим шаблоном?

Это решение — для инженеров, которые уже увязли в Qwen 3.5 по уши. У вас есть прод-окружение, агенты на продакшене, и переезд на другую модель стоит как минимум месяц работы. Вы готовы потратить час на настройку, чтобы получить стабильный tool calling.

Не тратьте время, если:

  • Вы только начинаете проект и можете выбрать любую модель. Возьмите что-то с менее кривым чат-шаблоном.
  • Вам не нужны инструменты. Тогда баг с вас не заденет.
  • Вы работаете исключительно через официальный API от Alibaba Cloud. Там шаблон уже исправлен (надеюсь).

Всем остальным — сохраняйте статью в закладки. Этот шаблон сэкономит вам недели нервов. И помните: в мире opensource-моделей стабильность — это не данность, а результат точечных исправлений. Иногда одно изменение в 20 строк кода оживляет целый стек технологий.

Подписаться на канал