
Дисклеймер
У меня всё никак не было времени довести эту статью до публикации и уже можно было бы делать новое сравнение на новых моделях (финальное сравнительное ревью я делал 19 апреля 2026 года). К тому же Opus 4.6 тогда был прямо перед запуском Opus 4.7, и у меня есть субъективное ощущение, что перед выходом новой версии старая начинает тупеть, потому мои результаты могут сильно отличаться от ваших. Нужно также понимать, что claude и codex могут работать по-разному в зависимости от времени суток, региона, нагрузки, текущей версии модели и фазы луны. Такое сравнение может устаревать буквально за час. Результат будет другим для других задач, языка программирования, набора тулов, MCP, промптов, документации, размера проекта.
Предыстория
В статье «Вайбкодинг — это гемблинг» я обещал, что расскажу больше про свои эксперименты при разработке многомодульного монолитного java-проекта https://github.com/NGirchev/open-daimon с разными AI-тулами. Купив подписку claude за 100 баксов для личного использования, я заметил, что поначалу он работал хорошо, а затем токены стали заканчиваться слишком быстро, и я хотел альтернатив. В это время рабочая codex подписка казалась экономнее, и я захотел сравнить 2 подписки на своём личном проекте с личными аккаунтами. К счастью, в этот момент OpenAI предложила мне месяц бесплатной базовой подписки ($20).
<cut />
В тот момент в моём проекте я уже частично перевёл логику агента из библиотеки Spring AI на самописную FSM и ReAct-паттерн. Я решил понемногу отказываться от Spring AI, потому что memory, которую они поставляют, меня не устраивает, саммаризацию пришлось делать самому, их агентский loop скрытый, кеширование промптов не работает, часть метаинформации не пробрасывается, но отказ от Spring AI я опишу в будущих статьях, не об этом сейчас.
Так вот, код всё ещё не работал как надо, было много багов. И я решил, раз claude не справляется, почему бы не дать шанс codex.
Что именно сравнивалось
Я сделал так: целиком скопировал проект в соседнюю папку, создал отдельные ветки opus_4_6 и codex_5_3 и запустил агентов с одинаковым не слишком подробным промптом - чистый вайбкодинг. Я не читал промежуточных результатов, всегда говорил ему: "делай как считаешь нужным", потом запускал ревью, потом просил починить и так до тех пор, пока сценарий не был успешен и не были устранены все замечания, кроме minor. Просто ждал результата и указал как единственный критерий - прохождение всех тестов, включая e2e, которые заранее были написаны и работали со Spring AI loop.
Cross review Opus и Codex
Область |
|
| Кто сильнее |
|---|---|---|---|
Фильтрация thinking и | Отдельный | Часть логики размазана по stream-обработке и post-processing |
|
Защита от зависшего AI-stream | Нет timeout и fallback | Есть timeout + fallback на обычный |
|
Понятность Telegram-рендера: что отправить, отредактировать или откатить | Чистая модель | Больше мутаций прямо в context/actions |
|
Память после рестарта и длинные треды | Память проще, без нового recovery-сценария | Восстановление истории из БД поверх уже существующей summarization |
|
Разделение progress и final answer в проектном streaming-контракте | Регрессия | Контракт |
|
Тесты на chunk'и, tool calls и Telegram updates | Больше streaming-oriented тестов | Больше тестов вокруг новых фич |
|
Готовность к merge по | Зелёный | Падал в REST test slice |
|
Полезные фичи сверх самого streaming loop | Меньше | Больше: URL hygiene, history recovery, batching для Telegram progress |
|
Если совсем коротко по таблице: opus_4_6 лучше держит архитектуру streaming-пайплайна, а codex_5_3 быстрее делает прикладные фичи, но вместе с ними и дополнительную сложность, и concurrency-риски.
Но тут сразу важная и смешная оговорка. Когда я позже попросил вывести sequence diagram / цепочку вызовов, выяснилось, что часть красивого архитектурного кода в opus_4_6, которую он расхваливал в review, вообще не запускалась. Тесты проходили не потому, что новый ReAct/FSM streaming-flow был правильно встроен, а потому что старый Spring AI streaming всё ещё работал и закрывал e2e-сценарии.
Но давайте всё равно представим, что opus_4_6 смог бы найти свои проблемы вовремя и дописал бы свою классную архитектуру.
Рассмотрим подробнее, что же нашли ИИ на review.
Review opus_4_6 ветки
В opus_4_6 лучше всего получилась граница между сырым потоком от модели и текстом, который уже можно показывать человеку.
Вся история с <think> и <tool_call> должна быть удалена в некоторых режимах до того, как пользователь увидит текст. В таких режимах отображается только финальный ответ пользователю. И вот тут StreamingAnswerFilter был на правильном месте: перед эмитом события в Telegram.
LLM в stream mode не отдаёт аккуратные готовые предложения. Она может прислать <th, потом следующим чанком ink>, потом кусок внутреннего reasoning, потом закрывающий тег. Поэтому недостаточно просто в конце взять полный текст и удалить из него <think>. К этому моменту кусок уже мог улететь пользователю.
У opus_4_6 идея была примерно такая:
StreamingAnswerFilter filter = new StreamingAnswerFilter(); chatModel.stream(prompt) .map(chunk -> chunk.getResult().getOutput().getText()) .map(filter::feed) .filter(visibleText -> !visibleText.isBlank()) .doOnNext(visibleText -> ctx.emitEvent(AgentStreamEvent.partialAnswer(visibleText, iteration))) .blockLast(); String tail = filter.flush(); if (!tail.isBlank()) { ctx.emitEvent(AgentStreamEvent.partialAnswer(tail, iteration)); }
То есть дальше по цепочке идёт уже очищенный текст. Если модель прислала что-то такое:
"Ответ: " "<th" "ink>internal reasoning</think>" " результат" "<tool_call>...</tool_call>"
то Telegram/UI должен увидеть только:
Ответ: результат
А в codex_5_3 часть логики была ближе к обработке stream chunk'ов и частично добивалась уже post-processing'ом. Упрощённо опасное место выглядит так:
StringBuilder fullText = new StringBuilder(); chatModel.stream(prompt) .map(chunk -> chunk.getResult().getOutput().getText()) .doOnNext(delta -> { fullText.append(delta); ctx.emitEvent(AgentStreamEvent.partialAnswer(delta, iteration)); }) .blockLast(); String sanitized = sanitizeFinalAnswerText(fullText.toString());
sanitizeFinalAnswerText(...) потом может всё правильно вычистить, но для Telegram это уже поздно. delta уже могла попасть наружу.
opus_4_6 создал TelegramBuffer, построил renderer вокруг RenderedUpdate: сначала решаем, что надо сделать с сообщением, и только потом отдельный слой реально идёт в Telegram API.
RenderedUpdate update = renderer.render(event, context); telegramActions.apply(update, context);
В codex_5_3 больше логики было завязано на мутацию context/actions. На таком streaming-flow оно быстрее становится местом, где ты боишься трогать один сценарий, потому что можешь сломать другой, а второй поток просто всё сломает.
Review codex_5_3 ветки
Первое - timeout для stream-вызова. В opus_4_6 был обычный blockLast(). Если LLM-провайдер начал stream и не завершил его нормально, обработка могла зависнуть. В codex_5_3 стоял blockLast(Duration.ofMinutes(10)), а если stream не успел отдать ни одного чанка, код уходил в обычный chatModel.call(...). Если часть чанков уже пришла, ситуация сложнее. Но для пустого или не стартовавшего stream это полезная страховка.
Второе - восстановление истории после рестарта. codex_5_3 добавил сценарий, где пустая in-memory ChatMemory восстанавливается из уже сохранённой истории разговора в БД. Для пользователя Telegram это выглядит просто: бот не должен забывать разговор только потому, что приложение перезапустилось. Пользователь пишет в тот же чат и ожидает, что агент хотя бы примерно помнит прошлый контекст. Т.е. история была и раньше, но могла не подтянуться из бд в некоторых сценариях. А ещё если при восстановлении истории сообщения могли задублироваться, кодекс добавил дедупликацию и гарантировал загрузку истории.
Третье - проверка ссылок. В codex_5_3 появился UrlLivenessCheckerImpl, который отсекает явно нерабочие URL перед показом пользователю. Это не решает проблему галлюцинированных ссылок полностью: живая ссылка всё ещё может быть нерелевантной, но мёртвые ссылки теперь можно вычистить.
Четвёртое - правила ReAct prompt'а были вынесены из основного agent loop, а для простых запросов появился отдельный путь без tools и итераций. Это давало больше контроля над тем, какие инструкции получает модель: язык ответа, обязательные аргументы tool calls, история предыдущих шагов. Правда, тесты на этот слой были минимальными.
Итого
opus_4_6 был лучше именно в архитектуре streaming pipeline: как идут чанки, где режутся теги, где принимается решение по Telegram-сообщению, где делится длинный текст. А codex_5_3 быстрее тащил новые возможности и исправлял баги, ничего не переусложнял, но, возможно, со временем код бы был менее поддерживаемым. Почему opus_4_6 не дошёл до всех этих проблем? Вероятно они просто не воспроизвелись, ведь код работал по старому и не вызывал новый код.
Баги, которые ИИ нашли друг у друга
Что могло сломаться |
|
| Риск |
|---|---|---|---|
Stream может зависнуть без timeout, и пользователь просто не дождётся ответа | Да | Нет | Высокий |
История диалога может побиться при параллельных обращениях | Нет | Да | Высокий |
Telegram progress может потерять кусок ответа или показать его не в том порядке | Риск ниже | Риск выше | Высокий |
Thinking/status и финальный ответ могут смешаться в одном streaming-flow | Да | Нет | Высокий |
Полный | Нет | Да | Блокер |
Генерация слабо отменяется, даже если результат уже не нужен | Да | Да | Средний |
Чем больше recovery, URL-checking и Telegram batching, тем больше нового состояния | Меньше | Больше | Средний |
Вывод
Я не стал брать ни одну из двух веток целиком, а взял лучшее из обеих.
Второй эксперимент: codex_5_3 против gpt_5_5
После сравнения Codex и Claude я окончательно убедился, что Codex подходит для моих задач больше и выходит дешевле. Поэтому на следующем эксперименте я сфокусировался на сравнении двух моделей. К тому моменту вышла gpt_5_5, и раз уж codex_5_3 оказался неплох, то стоило сравнить его с новой моделью.
Я взял codex ветку после 1 эксперимента как основную, но тесты всё ещё были нестабильны: бот плохо справлялся со стримингом ответа в Telegram. Я снова целиком скопировал проект в соседнюю папку и сделал ветки codex_5_3 и gpt_5_5.
Review codex_5_3
codex_5_3 взялся за ту часть кода, которая принимает сообщение пользователя и отправляет ответ в Telegram. Поэтому результат был не самый аккуратный, но полезный.
В этой ветке бот уже не пытался редактировать Telegram-сообщение после каждого нового фрагмента мгновенно. Он копил промежуточный текст, отправлял обновления реже, а финальный ответ показывал отдельно, как и планировалось. Во второй итерации он ещё и начал обрабатывать часть случаев 429 Too Many Requests: если Telegram просил подождать несколько секунд, бот ждал и пробовал отправить сообщение снова, так как у Telegram есть лимиты.
Результат gpt_5_5
gpt_5_5 предложил более аккуратную идею: отделить ход работы, финальный ответ и ошибку от кода, выделяя абстракции. Но как и в прошлый раз, код считай не выполнялся и багов он не нашёл.
Вывод
Уже второй раз сильная модель создала бесполезный код за большее время, и это не просто так. Архитектурно эти модели мощнее, но так как подход был исключительно вайбкодерский, без нормального контроля модель скатывалась и не доводила дело до конца.
А codex_5_3, модель исключительно хорошая для разработческих задач, она шла и выполняла всё в цикле, как профессиональный разработчик, с фокусом на деталях. На работе я также использовал её в assistant coding и она также справлялась с большинством задач.
Так что сильная модель нужна для абстрактных задач, ресёрча, планинга, но сделать что-то рабочее без жёсткого контроля у неё получается хуже, несмотря на все тулы и документацию. С другой стороны, в моём другом frontend-проекте именно Opus создал работающий сайт без дополнительного контекста и контроля, а значит нужно больше экспериментов.
Это поведение, кстати, подтверждается документацией Anthropic opusplan-model-setting. opusplan в Claude Code: Opus используется для plan mode, а для execution Claude Code переключается на Sonnet. Также они добавили /adviser на модели Opus. Потому скажу, что мой эксперимент был для достаточно специфичного условия - запускать агента автономно с минимальным числом самописных сабагентов. И лично для меня это показательно.
Codex-5-3, запущенный в телеграм-боте без доп. контроля исключительно для автономного development'a для java пет-проекта в моём сетапе, подходит лучше. Для всего остального придётся экспериментировать, писать тестовые harness'ы.
