ГлавнаяБлогКак исправить асинхронный баг в чужом бенчмарке на Python
Python

Как исправить асинхронный баг в чужом бенчмарке на Python

Разбираем реальный баг с async with в Python-бенчмарке: как найти, исправить и не наступить на грабли. Читайте, применяйте!

Al
Редакция Algolitalgolit.ru
5 мин чтения24 августа 2026 г.

Почему стоит прочитать эту статью

Вы когда-нибудь запускали чужой код и получали TypeError ещё до первого запроса? Мы — да, и это стало отличным уроком. Разберём реальный баг в публичном бенчмарке Agent Memory Leaderboard: как он возник, как мы его нашли и исправили. Вы узнаете, почему async with не дружит с обычными файлами, и как избежать подобных граблей в своём коде.

Контекст: наш опыт с публичным бенчмарком

Мы разрабатываем систему памяти для ИИ-агентов и решили участвовать в открытом бенчмарке Agent Memory Leaderboard, где соревнуются Tencent, Mem0 и другие. По нашему правилу: перед отправкой результатов мы всегда запускаем официальный пайплайн оценки, а не полагаемся на собственную имитацию. И вот — пайплайн упал на первой же команде, даже не сделав ни одного API-вызова.

Суть бага: async with и синхронный файл

В трёх из пяти публичных пайплайнов бенчмарка встречается такой код:

async with httpx.AsyncClient(timeout=120) as client, \
             output.open("a", encoding="utf-8") as handle:
    for item in items:
        ...

В многоэлементном async with каждый контекстный менеджер должен быть асинхронным. Но Path.open() возвращает обычный синхронный файловый объект — у него нет __aenter__ и __aexit__. Поэтому Python гарантированно выбрасывает ошибку:

TypeError: '_io.TextIOWrapper' object does not support the asynchronous context manager protocol

Это не особенность окружения, а гарантия на уровне языка: такой код в принципе не мог работать. Ошибка возникает в функциях answer() и evaluate() в пайплайнах locomo-refined, beam и longmemeval-s. Два других набора тестов не используют этот паттерн и работают нормально.

Как мы убедились, что баг именно в их коде, а не в нашем

Прежде чем обвинять чужой код, мы проверили тремя независимыми способами:

  1. Точное воспроизведение: скачали их файл (md5-совпадение с текущим main) и запустили на однострочном вводе — он упал.
  2. Изоляция языка: воссоздали тот же паттерн с заглушкой асинхронного контекстного менеджера и без сторонних библиотек — ошибка воспроизвелась. Значит, дело не в httpx и не в нашем окружении, а в самом паттерне.
  3. Независимая проверка: передали наше утверждение ИИ-ревьюерам с задачей его опровергнуть — они не смогли.

После этого мы создали issue с минимальным воспроизведением и предложили фикс.

Исправление: разбиваем составной контекстный менеджер

Решение простое: разделяем составной оператор на вложенные менеджеры — асинхронный клиент снаружи, синхронный файл внутри. Семантика не меняется, только переиндентация тела:

# было — падает до первого запроса
async with httpx.AsyncClient(timeout=120) as client, \
             output.open("a", encoding="utf-8") as handle:
    for item in items:
        ...

# стало — работает
async with httpx.AsyncClient(timeout=120) as client:
    with output.open("a", encoding="utf-8") as handle:
        for item in items:
            ...

Мы исправили шесть блоков в трёх файлах. Изменения: +67/−61 строк, поведение не изменилось, кроме того, что код теперь вообще выполняется.

Верификация: главное — это результат

Мы не просто заменили строки, а проверили:

  • Все три файла проходят py_compile.
  • Та самая команда, которая падала, теперь работает end-to-end: пайплайн locomo-refined выполнил этап ответов и этап оценки на реальных данных и выдал вердикт.
  • Мы вручную просмотрели каждый дифф, особенно в файле beam, где блоки включают многострочные вызовы — каждая строка сдвинута ровно на один уровень отступа, и ничего больше не изменилось.

Почему мы чиним платформу, с которой соревнуемся

Весь смысл публичного бенчмарка в том, что каждый может его запустить. Лидерборд, чьи пайплайны падают из коробки, вредит всем участникам, включая тех, кого мы хотим обогнать. Наше правило простое: мы не соревнуемся с теми, кому не готовы помочь. И здесь есть эгоистичная выгода: запустив их настоящий пайплайн (через драйвер с чистым импортом, пока фикс ждёт апстрима), мы уверены в своих локальных результатах. Если бы мы молча обошли падение и никому не сказали, каждое наше сравнение имело бы звёздочку, которую никто другой не увидел бы.

Практический вывод: что делать прямо сейчас

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

#async with#Python#отладка#бенчмарк#контекстные менеджеры
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →