14 KiB
Баг-репорт: регрессия стриминга платформы после file/tool flow
Кратко
После обновления до текущих upstream-версий платформы стриминг ответов стал нестабильным в сценариях с вложениями и tool/file flow.
Наблюдаемые симптомы:
- первый текстовый chunk ответа может приходить уже обрезанным
- соседние ответы могут "протекать" друг в друга
- после некоторых запросов бот перестаёт присылать финальный ответ
- платформа присылает дублирующий
END
До обновления платформы этот класс ошибок у нас не воспроизводился.
Версии платформы
В рантайме используются upstream-репозитории без локальных правок:
platform-agent:5e7c2df954cc3cd2f5bf8ae688e10a20038dde61platform-agent_api:aa480bbec5bbf8e006284dd03aed1c2754e9bbee
Контекст интеграции
- поверхность: Matrix
- транспорт к платформе: websocket через
platform-agent_api chat_idна платформу отправляется как стабильный числовой surrogate id- shared workspace:
/workspace
Важно: vendored platform repos чистые, совпадают с upstream. Проблема воспроизводится без локальных patch'ей в платформу.
Пользовательские симптомы
Примеры из живого диалога:
-
ожидалось:
Моя ошибка: ... -
фактически пришло:
оя ошибка: ... -
ожидалось начало ответа вида
По фото IMG_3183.png ... -
фактически пришло:
IMG_3183.png**) — это ...
Также наблюдалось:
- после вопросов по изображениям бот иногда вообще перестаёт отвечать
- в том же чате, до attachment/tool flow, ответы приходят корректно
Шаги воспроизведения
- Поднять
platform-agentи Matrix surface на версиях выше. - Отправить несколько обычных текстовых сообщений.
- Убедиться, что начальные ответы стримятся корректно.
- Отправить изображения/файлы и задать вопросы вида:
что изображено на фото- уточняющие follow-up вопросы по тем же вложениям
- Затем отправить ещё одно обычное текстовое сообщение.
- Наблюдать один или несколько симптомов:
- первый chunk начинается с середины слова
- ответ начинается с середины фразы
- хвост прошлого ответа загрязняет следующий
- видимого финального ответа нет вообще
Что удалось доказать
По debug-логам Matrix surface видно, что обрезание присутствует уже в первом chunk, полученном от платформы.
Корректные первые chunk'и до attachment/tool flow:
Hey! HowЯПервый файл не найден — возможно, ...
Некорректные первые chunk'и после attachment/tool flow:
IMG_3183.png**) — это ю...оя ошибка: в первом запросе...
Это логируется сразу после десериализации websocket event на клиентской стороне, до Matrix rendering и до финальной сборки текста ответа. Из этого следует, что повреждение происходит на стороне платформенного стриминга, а не в Matrix sender.
Дополнительное наблюдение по протоколу
Платформа сейчас отправляет дублирующий END.
Релевантные места в upstream:
external/platform-agent/src/agent/service.py- уже
yield MsgEventEnd(...)
- уже
external/platform-agent/src/api/external.py- после завершения цикла дополнительно отправляет ещё один
MsgEventEnd(...)
- после завершения цикла дополнительно отправляет ещё один
В живых логах это видно как:
- первый
END - второй
END - клиентская suppression логика, которая гасит дубликат
Это само по себе делает границы ответа неоднозначными и повышает риск "протекания" поздних событий в следующий запрос.
Предполагаемая первопричина
Похоже, что на стороне платформы одновременно есть две проблемы.
1. Двойной сигнал завершения стрима
Для одного ответа генерируется два END.
Вероятные последствия:
- нечёткая граница ответа
- поздние события могут относиться не к тому запросу
- соседние ответы могут смешиваться
2. Некорректное извлечение текстового chunk'а
В текущем upstream platform-agent/src/agent/service.py текст форвардится из chunk.content внутри on_chat_model_stream.
Однако в документации LangChain для astream_events() примеры используют event["data"]["chunk"].text, а в Deep Agents stream дополнительно есть ns/source, которые важны для отделения main-agent output от tool/subagent/model-internal stream.
Потенциальные последствия:
- первый видимый chunk может быть неполным
- во внешний клиент может попадать не только финальный пользовательский текст
- attachment/tool flow сильнее деградирует поведение стрима
Почему проблема считается платформенной
С нашей стороны были проверены и исключены базовые причины:
- вложения корректно сохраняются в
/workspace - контейнер
platform-agentвидит эти файлы - Matrix surface получает уже обрезанный первый chunk от платформы
- обрезание происходит до сборки финального ответа
- эксперимент с reconnect на каждый запрос не исправил проблему
- платформенные vendored repos сейчас совпадают с upstream
Ожидаемое поведение
Для каждого пользовательского запроса:
- текстовые chunk'и должны начинаться с реального начала ответа модели
- должен приходить ровно один terminal
END - границы ответов должны быть однозначными
- file/tool flow не должен ломать следующий ответ
Фактическое поведение
После attachment/tool flow:
- первый text chunk может быть уже обрезан
ENDприходит дважды- следующий ответ может начаться с середины слова или фразы
- отдельные запросы могут не завершаться видимым ответом
Дополнительный failure mode: большие изображения
В отдельном воспроизведении текстовые запросы до файлового сценария работали корректно, а сбой возникал только после попытки анализа изображений.
По логам видно уже не только stream corruption, но и конкретный image-path failure:
platform-agentрвёт websocket с1009 (message too big)- провайдер возвращает
400с причиной:Exceeded limit on max bytes per data-uri item : 10485760
Характерный фрагмент:
websockets.exceptions.ConnectionClosedError: received 1009 (message too big); then sent 1009 (message too big)
...
Agent error (INTERNAL_ERROR): Error code: 400 - {
'error': {
'message': 'Provider returned error',
'metadata': {
'raw': '{"error":{"message":"Exceeded limit on max bytes per data-uri item : 10485760"...}}'
}
}
}
Из этого следует:
- текстовый path сам по себе работоспособен
- image-analysis path в платформе сейчас передаёт изображение как data URI
- для достаточно больших изображений это утыкается в provider limit
10,485,760байт на один data-uri item - параллельно websocket path между surface и platform-agent неустойчив к этому failure scenario и закрывается с
1009
То есть в платформе есть как минимум ещё одна отдельная проблема помимо некорректного стриминга:
- отсутствует безопасная обработка больших изображений до отправки в provider
- отсутствует аккуратная деградация без разрыва websocket-сессии
Что стоит исправить в платформе
- Отправлять ровно один
MsgEventEndна один ответ. - Перепроверить extraction текста из
on_chat_model_stream:- вероятно, должен использоваться
chunk.text, а неchunk.content.
- вероятно, должен использоваться
- Учитывать
ns/sourceи форвардить наружу только main assistant output. - Перед отправкой изображений в provider проверять размер payload и не пытаться слать oversized data-uri.
- Для больших изображений:
- либо делать resize/compression,
- либо возвращать контролируемую user-facing ошибку без разрыва websocket.
- В идеале добавить более жёсткую request boundary в websocket protocol, чтобы late events не могли прилипать к следующему запросу.
Наши временные mitigation'ы на стороне surface
Они не исправляют корень, только снижают ущерб:
- suppression duplicate
END - короткий post-
ENDdrain window - idle timeout для зависшего стрима
- transport failures нормализуются в
PlatformError, чтобы Matrix bot не падал процессом
Эти меры понадобились только потому, что в текущем сценарии platform stream contract нестабилен.
Приложение: характерный фрагмент логов
[matrix-bot] text chunk queue=True text='Первый файл не найден — возможно,'
[matrix-bot] ...
[matrix-bot] end event queue=True tokens=0
[matrix-bot] end event queue=True tokens=0
[matrix-bot] dropped duplicate END tokens=0
[matrix-bot] text chunk queue=True text='IMG_3183.png**) — это ю'
[matrix-bot] ...
[matrix-bot] end event queue=True tokens=0
[matrix-bot] end event queue=True tokens=0
[matrix-bot] dropped duplicate END tokens=0
[matrix-bot] text chunk queue=True text='оя ошибка: в первом запросе я случайно постав'
Этот фрагмент показывает две вещи:
- duplicate
ENDдействительно приходит от платформы - следующий первый chunk уже приходит в клиента обрезанным
Приложение: характерный фрагмент логов для больших изображений
platform-agent-1 | websockets.exceptions.ConnectionClosedError: received 1009 (message too big); then sent 1009 (message too big)
...
matrix-bot-1 | Agent error (INTERNAL_ERROR): Error code: 400 - {'error': {'message': 'Provider returned error', 'code': 400, 'metadata': {'raw': '{"error":{"message":"Exceeded limit on max bytes per data-uri item : 10485760","type":"invalid_request_error"...}}}}
Этот фрагмент показывает ещё две вещи:
- image path в платформе реально упирается в лимит провайдера на размер data URI
- при этом ошибка не изолируется корректно и сопровождается разрывом websocket-соединения