feat: finalize matrix platform audit and docs
This commit is contained in:
parent
6422c7db58
commit
4524a6abc8
30 changed files with 3093 additions and 176 deletions
245
docs/reports/2026-04-21-platform-streaming-bug-report-ru.md
Normal file
245
docs/reports/2026-04-21-platform-streaming-bug-report-ru.md
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
# Баг-репорт: регрессия стриминга платформы после file/tool flow
|
||||
|
||||
## Кратко
|
||||
|
||||
После обновления до текущих upstream-версий платформы стриминг ответов стал нестабильным в сценариях с вложениями и tool/file flow.
|
||||
|
||||
Наблюдаемые симптомы:
|
||||
|
||||
- первый текстовый chunk ответа может приходить уже обрезанным
|
||||
- соседние ответы могут "протекать" друг в друга
|
||||
- после некоторых запросов бот перестаёт присылать финальный ответ
|
||||
- платформа присылает дублирующий `END`
|
||||
|
||||
До обновления платформы этот класс ошибок у нас не воспроизводился.
|
||||
|
||||
## Версии платформы
|
||||
|
||||
В рантайме используются upstream-репозитории без локальных правок:
|
||||
|
||||
- `platform-agent`: `5e7c2df954cc3cd2f5bf8ae688e10a20038dde61`
|
||||
- `platform-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, ответы приходят корректно
|
||||
|
||||
## Шаги воспроизведения
|
||||
|
||||
1. Поднять `platform-agent` и Matrix surface на версиях выше.
|
||||
2. Отправить несколько обычных текстовых сообщений.
|
||||
3. Убедиться, что начальные ответы стримятся корректно.
|
||||
4. Отправить изображения/файлы и задать вопросы вида:
|
||||
- `что изображено на фото`
|
||||
- уточняющие follow-up вопросы по тем же вложениям
|
||||
5. Затем отправить ещё одно обычное текстовое сообщение.
|
||||
6. Наблюдать один или несколько симптомов:
|
||||
- первый 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`
|
||||
|
||||
Характерный фрагмент:
|
||||
|
||||
```text
|
||||
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-сессии
|
||||
|
||||
## Что стоит исправить в платформе
|
||||
|
||||
1. Отправлять ровно один `MsgEventEnd` на один ответ.
|
||||
2. Перепроверить extraction текста из `on_chat_model_stream`:
|
||||
- вероятно, должен использоваться `chunk.text`, а не `chunk.content`.
|
||||
3. Учитывать `ns`/`source` и форвардить наружу только main assistant output.
|
||||
4. Перед отправкой изображений в provider проверять размер payload и не пытаться слать oversized data-uri.
|
||||
5. Для больших изображений:
|
||||
- либо делать resize/compression,
|
||||
- либо возвращать контролируемую user-facing ошибку без разрыва websocket.
|
||||
6. В идеале добавить более жёсткую request boundary в websocket protocol, чтобы late events не могли прилипать к следующему запросу.
|
||||
|
||||
## Наши временные mitigation'ы на стороне surface
|
||||
|
||||
Они не исправляют корень, только снижают ущерб:
|
||||
|
||||
- suppression duplicate `END`
|
||||
- короткий post-`END` drain window
|
||||
- idle timeout для зависшего стрима
|
||||
- transport failures нормализуются в `PlatformError`, чтобы Matrix bot не падал процессом
|
||||
|
||||
Эти меры понадобились только потому, что в текущем сценарии platform stream contract нестабилен.
|
||||
|
||||
## Приложение: характерный фрагмент логов
|
||||
|
||||
```text
|
||||
[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 уже приходит в клиента обрезанным
|
||||
|
||||
## Приложение: характерный фрагмент логов для больших изображений
|
||||
|
||||
```text
|
||||
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-соединения
|
||||
Loading…
Add table
Add a link
Reference in a new issue