16 KiB
Финальный баг-репорт: потеря начала ответа и сбои streaming/image path в platform-agent
Статус
Это финальный отчёт после полного аудита интеграции surfaces -> platform-agent_api -> platform-agent.
Итог:
- текущая реализация
surfacesрабочая, но проблемная из-за upstream-дефектов платформы - баг с пропадающим началом ответа на текущем состоянии не локализуется в
surfaces - в воспроизведённом сценарии повреждённый первый текстовый chunk рождается уже внутри
platform-agent - помимо этого подтверждены ещё два независимых platform-side дефекта:
- duplicate
END - некорректная обработка больших изображений (
data-uri > 10 MB,WS 1009)
- duplicate
Версии и состояние кода
Рантайм воспроизводился на vendored upstream-репозиториях без локальных platform patch’ей:
platform-agent:5e7c2df954cc3cd2f5bf8ae688e10a20038dde61platform-agent_api:aa480bbec5bbf8e006284dd03aed1c2754e9bbee
Со стороны surfaces transport layer был предварительно очищен:
- убрана локальная stream-semantics из
sdk/agent_api_wrapper.py sdk/real.pyпереведён на pinned upstreamplatform-agent_api.AgentApi- больше нет локального post-END drain, custom listener и wrapper-owned reclassification events
Это важно: баг воспроизводился после удаления наших транспортных костылей.
Контекст интеграции
- поверхность: Matrix
- транспорт к платформе: WebSocket через upstream
platform-agent_api.AgentApi chat_idна платформу: стабильный числовой surrogate id, выдаваемый со стороныsurfaces- файловый контракт: shared
/workspace, вложения передаются как относительные пути вattachments
Пользовательские симптомы
Наблюдались несколько классов сбоев:
- Начало ответа может пропасть
-
ожидалось:
Моя ошибка: ... -
фактически:
оя ошибка: ... -
ожидалось:
На двух изображениях: ... -
фактически:
двух изображениях: ...
- После tool/file flow ответы могут вести себя нестабильно
- следующий ответ стартует с середины фразы
- в некоторых сценариях после image/tool path платформа отвечает ошибкой или замолкает
- На больших изображениях image path падает совсем
- provider error
Exceeded limit on max bytes per data-uri item : 10485760 - websocket закрывается с
1009 (message too big)
Что было проверено на стороне surfaces
Ниже перечислено, что именно было перепроверено в нашем коде и почему это не выглядит корнем проблемы.
1. Мы больше не режем и не переклассифицируем stream локально
В текущем surfaces:
sdk/agent_api_wrapper.py— thin construction/factory shim над upstreamAgentApisdk/real.py— просто итерирует upstream events и склеиваетMsgEventTextChunk.textadapter/matrix/bot.py— отправляетOutgoingMessage.textв Matrix безstrip/lstrip
Наблюдение:
- в текущем коде не осталось места, где строка могла бы превратиться из
Моя ошибкавоя ошибкачерез локальный slicing
2. Сборка ответа у нас линейная и тупая
sdk/real.py делает только следующее:
- если пришёл
MsgEventTextChunk— добавляетevent.textвresponse_parts - если пришёл
MsgEventSendFile— превращает его вAttachment - не пытается “восстанавливать” поток после
END
Следствие:
- если начало уже отсутствует в
event.text, мы его не можем ни потерять, ни вернуть
3. Matrix sender не модифицирует текст
adapter/matrix/bot.py передаёт текст дальше как есть.
Следствие:
- Matrix renderer не является объяснением пропажи первого куска
Что было проверено в platform-agent_api
Upstream client всё ещё имеет спорную queue-архитектуру:
- одна активная
_current_queue MsgEventEndсъедается внутриsend_message()- в
finallyочередь отвязывается и дренится orphan messages
Это архитектурно хрупко и может быть источником других boundary bugs.
Но в конкретном воспроизведении этот слой не был точкой порчи текста.
Почему:
- в raw logs клиент получил ровно тот же первый text chunk, который сервер уже отправил
- queue/dequeue не изменили его содержимое
Что удалось доказать по raw logs
Для финальной проверки была временно добавлена точечная диагностика в:
external/platform-agent/src/agent/service.pyexternal/platform-agent/src/api/external.pyexternal/platform-agent_api/lambda_agent_api/agent_api.py
Эта диагностика не входила в рабочую реализацию и использовалась только для локализации бага.
Ключевое наблюдение
На проблемном запросе после tool/file flow сервер сам yield’ил уже обрезанный первый chunk:
platform-agent-1 | [raw-stream][server-yield] chat=1 event=TEXT text=' двух изображениях:\n\n**Первое изображение'
platform-agent-1 | [raw-stream][ws-send] chat=1 event=AGENT_EVENT_TEXT_CHUNK text=' двух изображениях:\n\n**Первое изображение' path=None
matrix-bot-1 | [raw-stream][client-listen] agent=matrix-bot chat=1 queue_active=True AGENT_EVENT_TEXT_CHUNK text=' двух изображениях:\n\n**Первое изображение'
matrix-bot-1 | [raw-stream][client-dequeue] agent=matrix-bot chat=1 request=2 AGENT_EVENT_TEXT_CHUNK text=' двух изображениях:\n\n**Первое изображение'
Это означает:
- порча произошла до websocket-клиента
surfacestransport layer не является источником именно этого дефектаplatform-agent_apiне исказил этот конкретный chunk по дороге
Дополнительно тот же паттерн виден и вне image-сценария:
platform-agent-1 | [raw-stream][server-yield] chat=1 event=TEXT text='сё работает напрямую'
...
matrix-bot-1 | [raw-stream][client-dequeue] ... text='сё работает напрямую'
То есть сервер уже выдаёт сё, а не Всё.
Наиболее вероятный root cause
Главный подозреваемый — external/platform-agent/src/agent/service.py.
Сейчас он делает следующее:
- читает
self._agent.astream_events(...) - обрабатывает только
kind == "on_chat_model_stream" - берёт
chunk = event["data"]["chunk"] - если
chunk.content, форвардитMsgEventTextChunk(text=chunk.content)
Проблема в том, что это очень грубое преобразование raw event stream в пользовательский текст.
Почему именно это место выглядит корнем
- Первый битый chunk уже рождается на server-side
- это подтверждено логами выше
- Код берёт только
chunk.content
- если начало ответа приходит в другой форме, поле или raw event, оно просто теряется
- Код не учитывает
ns/source
- после tool/vision flow у Deep Agents / LangChain может быть более сложная структура потока
- текущий adapter flatten’ит её слишком агрессивно
- Код никак не валидирует, что наружу уходит именно main assistant output
Итоговая гипотеза:
После tool/file/vision flow
platform-agentнеправильно адаптируетastream_events()вMsgEventTextChunk. Начало итогового пользовательского ответа может находиться не в том raw event, который сейчас читается черезchunk.content, либо теряться из-за упрощённой фильтрации потока.
Подтверждённый отдельный баг: duplicate END
Это отдельный platform-side дефект.
Сейчас:
external/platform-agent/src/agent/service.pyуже делаетyield MsgEventEnd(...)external/platform-agent/src/api/external.pyпосле завершения цикла дополнительно отправляет ещё одинMsgEventEnd(...)
По логам это выглядит так:
platform-agent-1 | [raw-stream][server-yield] chat=1 event=END
platform-agent-1 | [raw-stream][ws-send] chat=1 event=AGENT_EVENT_END text=None path=None
platform-agent-1 | [raw-stream][ws-send] chat=1 event=AGENT_EVENT_END duplicate_end=true
matrix-bot-1 | ... AGENT_EVENT_END tokens_used=0
matrix-bot-1 | ... AGENT_EVENT_END tokens_used=0
Независимая оценка:
- duplicate
END— реальный баг платформы - он делает границу ответа менее надёжной
- но в текущем воспроизведении он не объясняет сам факт потери первого text chunk
То есть это важный, но вторичный дефект.
Подтверждённый отдельный баг: большие изображения ломают image path
В отдельном воспроизведении платформа падала на анализе изображений с provider error:
Exceeded limit on max bytes per data-uri item : 10485760
И параллельно websocket рвался с:
received 1009 (message too big); then sent 1009 (message too big)
Это означает:
- image path отправляет в provider oversized
data:URI - безопасной предвалидации / деградации нет
- failure scenario сопровождается разрывом websocket-соединения
Независимая оценка:
- это отдельный platform-side bug
- он не объясняет потерю первого чанка в текстовом сценарии напрямую
- но подтверждает, что path
tool/file/image -> platform streamв целом сейчас нестабилен
Что мы считаем исключённым
С достаточной уверенностью можно исключить:
- Локальный slicing текста в
surfaces - Локальную “умную” реконструкцию потока, потому что она была удалена
- Matrix sender как источник потери первого чанка
platform-agent_apiqueue/dequeue как primary root cause именно в этом воспроизведении
Финальная независимая оценка
Текущая оценка вероятностей:
75%— ошибка вplatform-agent, в адаптереastream_events() -> MsgEventTextChunk15%— provider/model stream приносит начало ответа в другой raw event/field, аplatform-agentего некорректно интерпретирует10%— вторичные platform-side boundary defects (duplicate END, queue semantics и т.д.)~0-5%— ошибка вsurfaces
Итоговый вывод:
На текущем состоянии кода баг с пропадающим началом ответа следует считать platform-side дефектом. Воспроизведение после cleanup transport layer показывает, что первый повреждённый text chunk формируется уже внутри
platform-agentдо отправки в websocket.
Что нужно исправить в платформе
Обязательно
- Убрать duplicate
END
- один ответ должен завершаться ровно одним
MsgEventEnd
- Перепроверить адаптацию
astream_events()вservice.py
- логировать и проанализировать raw
event["event"] - проверить
event.get("name") - смотреть
event.get("ns") - сравнить
chunk.contentс тем, что реально лежит вchunk.text/ raw chunk repr
- Форвардить наружу только финальный main assistant output
- не flatten’ить весь поток без учёта
ns/source
Желательно
- Сделать image path устойчивым к oversized payload
- preflight check размера
- resize/compress или controlled error без разрыва WS
- Улучшить client/server protocol boundary
- более строгая корреляция запроса и ответа
- более однозначная semantics конца ответа
Что мы сделали со своей стороны
Со стороны surfaces уже выполнено следующее:
- transport layer очищен до thin adapter над upstream
AgentApi - локальные stream-workaround’ы удалены
- рабочая интеграция сохранена
- known issue задокументирован
То есть текущая интеграция не “идеальна”, но её поведение теперь достаточно чистое, чтобы platform bug было можно локализовать без смешения ответственности.
Приложение: короткий диагноз
Если нужна самая короткая формулировка для issue tracker:
После cleanup transport layer в
surfacesи воспроизведения на clean upstream platform repos видно, чтоplatform-agentиногда сам порождает первыйMsgEventTextChunkуже обрезанным, особенно после tool/file flow. Дополнительно подтверждены duplicateENDи отдельный image-path failure на большихdata:URI.