TL;DR: CLI-агенты галлюцинируют даже с мощными моделями, потому что системные промпты раздуты лишними примерами, повторами и нерелевантными терминами. Это математически бьёт по вниманию модели и не даёт того эффекта, который обещают best-practices. Я форкнул Qwen Code, вычистил системные промпты, и на 4 моделях получил стабильный вызов нужных скиллов, меньше галлюцинаций и на 35-53% сократил расход токенов. Без потери качества.

Как разработчик, я должен пользоваться ИИ на работе. Я бы, конечно, предпочел безлимитный Claude, но мне доступен только Qwen Code, а он часто галлюцинирует. В этой статье я расскажу, как искал причины галлюцинаций, устранял их и к чему это привело.

Первая моя задача для ИИ-помощника – CLI-агента, была простой, её можно было бы решить инструментом «найти и заменить» в IDE минут за 20, а агент трудился – часа два, что непозволительно много. Он постоянно галлюцинировал, добавлял ненужные артефакты, а я продолжал попытки направить его в нужное русло уже из спортивного интереса. Только после этого замечательного опыта я наконец решил «прочитать инструкцию».

Я пошёл по списку best-practices: QWEN.md, rules, а потом и skills, и agents — всё согласно документации. Уделял большое внимание промптингу: описывал сценарии, требования и подробные примеры, но не наблюдал кардинальных улучшений. Особенно меня насторожили повсеместные рекомендации о подробных примерах в промптах. Известно, что LLM «обучаются» на миллионах примеров, так чем мои примеры ей помогут? Что если я укажу примеры для задачи А и Б, а потом попрошу решить задачу С?

В целом, глядя на такие md-файлы с примерами, меня не покидало ощущение, что от меня требуется написать готовый код, который модель просто скопирует и вставит по запросу. Но зачем тогда такой инструмент? Тогда я решил сменить стратегию: не я подстраиваюсь под инструмент, а он под меня. Пишу промты как мне удобно, а ИИ – умный, разберётся. А он не смог. Одно дело когда best-practices с просторов интернета не работают, и совсем другое когда не срабатывает твое «гениальное» решение. Тут разбираться нужно, а это значит – копнуть глубже, потому что невозможно эффективно пользоваться инструментом, если ты не понимаешь как он работает.

Почему ИИ-агент не слушается

Иллюзии

Чат-боты и CLI-агенты зависят от LLM, чей основной принцип работы – stateless processing, то есть, на самом деле у нас нет никакого диалога с моделью. Это опасная иллюзия.

…иллюзия общения с моделью вводит пользователей в заблуждение, заставляя их думать, что их инструкции понимаются и выполняются…

Do prompt-based models really understand the Meaning of their Prompts?
Albert Webson and Ellie Pavlick, Brown University

Анимация ниже дает общее представление о механизме «общения»:

иллюзия диалога с LLM
иллюзия диалога с LLM

Словами это можно выразить так: каждое наше новое сообщение в «диалоге» с LLM – это новый диалог, в который мы передаем всю предыдущую переписку, чтобы модель «вспомнила» о чем мы говорим. В случае с CLI-агентом, для простой команды вроде «добавь обработку ошибок в dir/file.ext» – реальный промпт, уходящий в модель, будет в десятки раз больше. Он включает системный промпт, структуру директории, список инструментов с их описанием, frontmatter всех скиллов и агентов (встроенных, проектных и глобальных), историю сообщений и описание MCP-серверов. Все это попадает в контекст, и влияет на качество ответа.

У упомянутого выше Qwen Code, только основной системный промпт (а их несколько), содержит около 25 тыс. символов, что примерно равно 5000 токенам, а со всеми инструментами уже около 20 тыс. токенов. Добавьте сюда ваши собственные md-файлы с какими-то инструкциями, пару прочитанных агентом файлов, и, с большой вероятностью ощутите одну из причин галлюцинаций: феномен – lost-in-the-middle.

Lost in the middle

Это такой эффект, при котором контекст в середине получает до 30-40% меньше «внимания» модели, что на 10-20% снижает качество ответа.

…модели распределяют внимание по U-образной кривой, отдавая предпочтение началу и концу контекста – независимо от их содержания…

Lost in the middle: How language models use long contexts
Liu et al., Stanford University, University of California, Samaya AI

Феномен был обнаружен в 2023 для моделей использующих RAG, тогда же было предложено первое решение, которое заключалось в сортировке документов (в результате векторного + контекстного поиска), таким образом, чтобы расположить менее релевантные документы – в середине. То есть, решение заключалось не в устранении этого феномена, а адаптацией под него. Годом позже появилась еще одна работа:

Found in the Middle: Calibrating positional attention bias improves long context utilization.
Hsieh et al. University of Washington, MIT, Google Cloud AI research

в ней авторы предложили способ калибровки механизма распределения внимания модели, который повышает качество ответа на 6-15%, но им не удалось устранить сам феномен. После калибровки, кривая распределения внимания остается U-образной.

Хотя размер контекстных окон растет, нельзя сказать, что lost-in-the-middle побежден, скорее наоборот. Однако, сам раздутый размер системного промтпа только часть проблемы. Мы не могли бы его раздуть настолько, просто перечисляя абстрактные инструкции, он раздувается из-за обилия терминов, которыми авторы пытаются охватить все потребности. Например, в том же системном промпте Qwen Code упоминаются разные технологии, области (такие как гейм-дев, бэкенд, фронтенд), языки программирования, пакетные менеджеры и т.д. Это создает контекстные помехи: еще одну причину галлюцинаций.

Contextual distraction

Помехи в контексте приводят к тому, что промпт «активирует» нерелевантные запросу «кластеры знаний» модели, в результате чего мы наблюдаем те самые галлюцинации.

...помехи в контексте могут привести к катастрофическому падению точности ответов (до 80%) даже у самых современных моделей, включая нарушение системных инструкций, и особенно в агентных системах...

Lost in the noise: How reasoning models fail with contextual distractors
Lee et al., Kaist AI, LG AI Research, University of Illinois

То есть, когда модель начинает смешивать синтаксис разных языков программирования, или придумывать несуществующие библиотеки, или как-то еще галлюцинировать – это не потому что она глупая. Это математически предсказуемый результат конфликта внимания, обилия терминов, ненужных примеров и противоречивых инструкций.

Противоречие инструкций возникает из-за архитектуры инструмента, потому что системные промпты разбросаны по всей кодовой базе, из-за чего не видно полной картины, то есть финального промпта который подается на модель. А иллюзия общения усугубляет ситуацию.

Ненужные примеры – следствие следования неподтвержденных best-practices, которые прямо опровергаются исследованиями, например, неэффективность очень популярного Few-Shot Prompting доказана минимум двумя работами. В первой – авторы исследовали влияние примеров, и получили контринтуитивный результат: если в примерах подменить правильные ответы на случайные — модель почти не теряет в качестве. Получается, что промпт не столько учит модель новому, сколько активирует то, что она уже умеет и подсказывает формат допустимых ответов, а не сам факт «как правильно». Логическое продолжение (уже моё) — раз даже корректный ответ в примере работает не как обучение, а как подсказка формата, то тем более наивно рассчитывать промптом перебить то, что модель усвоила на претрейнинге. Условно, если модель уверена, что 2+2=5, один пример с «2+2=4» не переубедит ее в обратном.

Rethinking the role of demonstrations: What makes in-context learning work?
Min et al., University of Washington, Allen Institute for AI

…промпты лишь активируют уже имеющиеся навыки модели, но не способны научить её чему-то новому…

В другой работе, авторы обнаружили, что если дать модели готовое и даже обфусцированное решение именно того вопроса, который нужно решить — она неплохо его восстанавливает. Но это неприменимо на практике: у вас никогда не будет готового ответа на вашу задачу, иначе незачем генерировать код. А когда в промпт подкладывают похожие решения – это не помогает, а часто даже немного вредит, в сравнении с промптом вообще без примеров.

What builds effective in-context examples for code generation?
Li et al., The Hong Kong University of Science and Technology

…похожие примеры не улучшают генерацию кода, а часто даже ухудшают. Они скорее отвлекают модель, чем помогают…


Из трёх разобранных выше причин – потери внимания, контекстных помех и переоценённости примеров — вытекает практический вывод именно для системного промпта: в отличие от пользовательского запроса, который меняется от задачи к задаче, системный промпт присутствует в каждом обращении к модели, а значит, любая нерелевантная инструкция в нём не разовый шум, а постоянная контекстная помеха для всех пользователей и задач без исключения. Отсюда я сформулировал требование: в системном промпте допустимы только инструкции, релевантные любой задаче, и начал тюнить CLI, следуя этому правилу.

Как я адаптировал Qwen Code под свои потребности

API Qwen Code позволяет переопределить системный промпт, но не описание встроенных инструментов: самих функций, их параметров и пр. Пришлось форкнуть. В форке провел такой «рефакторинг»:

  • Убрал любые упоминания языков программирования, библиотек, фреймворков и технологий;

  • Убрал все примеры;

  • Убрал повторы. В системных промтах Qwen Code это частое явление. Одна и та же инструкция повторяется или буквально, но в других местах, или там же другими словами.

  • Очистил output-language.md. Он слишком категорично и многословно требовал от модели отвечать на языке установленного в качестве основного на уровне ОС, а мне это не удобно, я с агентами «говорю» на английском.

  • Убрал метки скоупа для скилов (bounded, project, global). Это эмпирика, а не следствие теории выше. В результате разных экспериментов выяснилось, что модель отдает предпочтение скиллу уровня проекта, даже если он менее релевантен.

Это не все, что можно и нужно сделать, но достаточно для проверки гипотезы. Влияние этих изменений тестировал на четырех моделях: Claude Sonnet 4.6, Qwen 3.6 Plus, Qwen3 Coder Next и DeepSeek V4 Flash. Одна задача, по десять прогонов на каждую модель: пять с официальным CLI, и столько же для модифицированного. Конечно, этого маловато, но мои эксперименты никто не спонсирует, а модели платные.

Сценарий теста

Тест запускался в директории где не было ничего кроме папки .qwen с двумя скилами. Оба варианта CLI выполнялись с флагом --openai-logging в интерактивном режиме. Выяснилось, что даже в yolo режиме, если модель задает вопрос – ответить должен пользователь. При любых вопросах я всегда выбирал первый предложенный ответ (с меткой Recomended), даже если он явно был ошибочным. Имитировал автономный режим. Задача для агента звучала так:

Write a simple react application that fetches users from jsonplaceholder, shows a list of users and allow to navigate to a user page by clicking on row. Use latest react version and mobx for state management.

Из всех скилов релевантны задаче только два:

  • new-app
    description: Use it when you need to create a new application from scratch.
    встроен в CLI

  • routing
    description: Use it when you're working on a web app that requires client side routing/navigation.
    на уровне проекта

Второй скилл – stm, на уровне проекта – был специально добавлен, чтобы сбивать с толку. Его описание следующее: Use it ONLY if "kr-observable" is explicitly requested for state management. То есть, с одной стороны промпт и описание скила содержат ключевые слова – state management, а с другой – в запросе явно указано какой stm хочет пользователь.

Ожидаемое поведение

Предполагалось, что модель будет действовать примерно следующим образом: До первого вызова инструмента write_file, вызовет два скила, предложит план разработки (согласно инструкциям скила new-app), согласует его, сформирует todo list и приступит к разработке. При этом агент должен проигнорировать нерелевантный скил stm, а в качестве решения для роутинга использует то, что описано в скиле routing. Это идеальное поведение.

Допустимое поведение, это если агент хотя-бы спрашивает, прежде чем принимать какое-то решение.

И совсем плохо, если агент игнорирует релевантные скилы, и вообще действует на свое усмотрение.

Результаты

Первое что стоит отметить: радикальное сокращение системных промптов с удалением всех примеров, не привело ни к деградации качества ответов, ни к некорректному использованию инструментов, ни к другим сбоям. Например, в официальном CLI есть очень настойчивое требование к модели использовать dedicated tools. Полное удаление этой инструкции никак не повлияло ни на одну из моделей, они все использовали релевантные инструменты а не shell.

Вызов скилов

Официальный → модифицированный CLI (фактическое/ожидаемое кол. вызовов)

new-app

routing

stm

Claude Sonnet 4.6

5/5 → 5/5

0/5 → 5/5

0/0 → 1/0

DeepSeek V4 Flash

0/5 → 5/5

0/5 → 4/5

0/0 → 0/0

Qwen 3.6 Plus

4/5 → 5/5

1/5 → 4/5

1/0 → 2/0

Qwen3 Coder Next

5/5 → 5/5

0/5 → 0/5

0/0 → 0/0

Можно сразу отметить катастрофическую непригодность специализированных моделей для кодинга. Возможно такие модели действительно быстро набирают код, но эта способность бесполезна в реальной работе. Qwen3 Coder Next категорически отказывается выполнять какие-либо инструкции системного промпта, она его попросту игнорирует, а модель того же семейства но общего назначения – ощутимо лучше.

Также можно отметить явную неспособность моделей работать с несколькими скилами одновременно (в официальном CLI), что довольно странно. В модифицированной версии ситуация кардинально лучше (кроме Qwen3 Coder Next).
Тут стоит отметить, что один ложный вызов скила stm у Claude Sonnet 4.6 закончился ожидаемым результатом. То есть, модель вызвала скилл, но сразу же отрапортовала что он нерелевантен задаче. Qwen 3.6 Plus «рассуждает» немного хуже. В модифицированной версии она вызвала нерелевантный скилл два раза, и в одном случае забыла про mobx; в немодифицированной – один раз, и тоже забыла. Это имеет значение, так как не вызвать релевантый скилл не так критично, как проигнорировать явный запрос пользователя.

Интересно, что доступные скилы в Qwen Code пакуются в некие специальные XML директивы (назначение которых мне неизвестно), и отправляются как сообщение пользователя а не системы (что похоже на баг). Теоретически, результаты могли бы быть лучше.

Расход токенов

Официальный → модифицированный CLI

Prompt

Completion

Result

Claude Sonnet 4.6

122 304 → 59 742

616 → 812

-50%

DeepSeek V4 Flash

117 364 → 74 863

1 211 → 1 645

-35%

Qwen 3.6 Plus

107 770 → 53 275

862 → 1 242

-50%

Qwen3 Coder Next

103 210 → 41 746

377 → 351

-53%

Сокращение системного промпа ожидаемо привело к меньшему потреблению токенов. Существенно. Но нужно отметить, что эти результаты неполноценные. Для тестов я применил ручной yolo режим, то есть подтверждал все без разбора. На реальных задачах, этот разворот моделей от «угадывания» к «уточняющим вопросам» , позволяет сразу скорректировать ее поведение и за меньшее количество шагов получить ожидаемый результат, в то время как с официальным CLI, приходится отменять принятое моделью решение, объяснять как надо, и ждать следующего. А уже принятое моделью решение, особенно неверное, влияет на качество следующих ответов. Я предполагаю, что реальный процент сэкономленных токенов будет больше.

Количество уточняющих вопросов

Официальный → модифицированный CLI

Любой

Роутинг

Тех-стек

Claude Sonnet 4.6

2/5 → 5/5

0/0 → 0/0

0/5 → 5/5

DeepSeek V4 Flash

0/5 → 5/5

0/0 → 2/0

0/5 → 4/5

Qwen 3.6 Plus

0/5 → 3/5

0/0 → 2/0

0/5 → 2/5

Qwen3 Coder Next

0/5 → 2/5

0/0 → 0/0

0/5 → 2/5

Тут интересно поведение DeepSeek V4 Flash и Qwen 3.6 Plus в части вопросов про роутинг. Согласно контенту скила, использовать нужно было решение которое в нем указано, но в весах этих моделей связка React = React Router настолько сильна, что они даже после однозначной инструкции спрашивали что выбрать, скилл, или React Router. А поведение Qwen3 Coder Next наоборот, не нужно расценивать как корректное поведение. Эта модель ни разу не вызвала соответствующий скил, и вопросов не задавала. Просто молча выбирала React Router, что вообще говоря нарушение инструкций.

Второе интересное наблюдение это согласование тех стека. В немодифицированом CLI все модели собирали проект с помощью Vite, а в модифицированном, более продвинутые – Claude Sonnet 4.6 и DeepSeek V4 Flash – задавали уточняющие вопросы: JavaScript или TypeScript, npm или pnpm, WebPack или Vite. На мой взгляд, это более корректное поведение. Модели должны спрашивать, а не угадывать.

Количество запросов

В этой метрике цифры коррелируют с изменениями в поведении моделей. Среднее количество запросов у Claude Sonnet 4.6 и DeepSeek V4 Flash увеличилось на 60%, что естественно, так как эти модели стали больше задавать уточняющие вопросы. У Qwen 3.6 Plus всего на 15% больше запросов, что тоже соответствует ее поведению. Эта модель очень неохотно задает вопросы, а те что задает – больше формальные. Замыкает четверку Qwen3 Coder Next, чьё среднее количество запросов уменьшилось на 40%. Для не reasoning модели, это, наверное, естественная реакция. Она обучена быстро написать что-то вроде алгоритма пузырьковой сортировки, а не решать комплексные задачи.


Сырые логи всех запусков опубликованы в этом репозитории и доступны для самостоятельного анализа. Дифф изменений тут.

Выводы

Большие языковые модели – мощная технология, но по своей природе непредсказуемая. Мой эксперимент показал: то, что окружает модель – harness, – влияет на результат не меньше, чем сама модель. Вычищение шума в системном промпте дает ощутимый результат в части расхода токенов, и больший контроль над поведением. Нельзя вручить разработчику чёрный ящик и ожидать, что тот с помощью пары строк в md-файле подчинит его своей воле. Если harness сам по себе влияет на результат — он должен быть подконтрольным разработчику.

Комментарии (1)


  1. ToxaBes
    05.07.2026 11:37

    Можно сразу отметить катастрофическую непригодность специализированных моделей для кодинга. Возможно такие модели действительно быстро набирают код, но эта способность бесполезна в реальной работе. Qwen3 Coder Next категорически отказывается выполнять какие-либо инструкции системного промпта, она его попросту игнорирует, а модель того же семейства но общего назначения – ощутимо лучше.

    Ваши выводы про Qwen3 Coder Next не подтверждаются моей практикой. Подключите эту же модель через LM Studio к Claude Code, обозвав ее Claude Sonnet, и вы увидите, что она прекрасно работает, используя абсолютно все возможности самого Claude Code. В вашем случае проблема кроется не в самих моделях, а в Qwen Code и его настройках.

    Вообще Qwen3 Coder Next специально натренирована на агентные сценарии, взаимодействие со средой и использование инструментов. То, что у вас она наотрез отказывается работать, говорит только о том, что скорее всего сломан механизм передачи инструкций. Насколько я помню, у Qwen Code он завернут в специфические xml теги, то есть нестандартный. Вам определенно стоит покопать именно в эту сторону.