Не читается ответ от LLM¤
Проблема обнаружена 12.11.2025.
Взаимодействие¤
Фронтенд и бекенд¤
OpenWebUI состоит из двух частей, фронта и бека, которые являются отдельными компонентами и взаимодействуют между собой. Взаимодействие происходит:
- через HTTP запросы на бекенд
- через WebSocket
Бекенд и LLM¤
Интуитивный вариант работы с LLM (и, судя по коду, это возможно)
Код ориентирован на такой вариант: Бекенд посылает HTTP запрос, LLM модель обрабатывает его и возвращают ответы в виде SSE (Server-Sent Events).
При этом, сервер не закрывает соединение, как можно было бы ожидаеть, но посылает сигнал о завершении и ничего неделает.
Передача данных с LLM на Frontend¤
- Фронтенд посылает HTTP запрос в
chat/completionsна бекенд - Бекенд посылает HTTP запрос в LLM, получает HTTP ответ
- Бекенд передаёт соединение во внутреннюю задачу обработки
- Возвращает HTTP ответ на фронтенд, закрывает соединение с фронтом
- LLM пишет в соединение ответ по спецификации SSE (Server-Sent Events)
- Фоновая задача бекенда читает стрим SSE и тут же отправляет их на Websocket
- На фронте в пользовательском интерфейсе изменяется UI, согласно сообщениям Websocket
Полную схему можно изобразить таким образом
В виде диаграммы последовательности:
Предпологаемые места проблемы¤
Я предполагаю, что проблема начинается, когда перед LLM встаёт Proxy LLM. Nginx либо режет заголовки на входе и тем самым заставляет отвечать LLM не в режиме SSE,
либо режет соединение, мешая SSE.
Эта проблема имеет вариации. Например nginx режет внешине или внутренние заголовки, тем самым "путая карты" логике, которая работает с соединением (Coroutines), обработка ответа падает "молча", так как код OpenWebUI весьма плохо написан. Этот вариант выловить будет сложнее.
Поиск решения¤
13.11.2025
Обнаружил в коде, что в зависимости от заголовков ответа от LLM, выбирается способ обработки ответа.
r = await session.request(
method="POST",
url=request_url,
data=payload,
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
)
# Check if response is SSE
if "text/event-stream" in r.headers.get("Content-Type", ""):
streaming = True
return StreamingResponse(
r.content,
status_code=r.status,
headers=dict(r.headers),
background=BackgroundTask(
cleanup_response, response=r, session=session
),
)
else:
try:
response = await r.json()
except Exception as e:
log.error(e)
response = await r.text()
Это исключает вариант, в котором режутся заголовки на входе.
Необходимо посмотреть в тесте какие заголовки приходят со стороны Proxy LLM.
Ответ от Proxy LLM¤
На сложныз запросах к LLM через прокси становится понятно, что Nginx ожидает полного ответа от LLM и отдаёт
ответ без SSE, целиком, с заголовком Content-Type: application/json.
> curl -X POST 'https://s001tst-api-gchat.sibur.local/v1/chat/completions' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
--data '{"model":"GigaChat","messages":[{"role":"user","content":"Разработай бизнес план стартапа, который бы заинтересовал компанию СИБУР"}]}' \
-k -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.2.39.228:443...
* Connected to s001tst-api-gchat.sibur.local (10.2.39.228) port 443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=RU; ST=Moscow region; L=Skolkovo; O=LLCSIBUR; OU=IT; CN=s001tst-api-gchat.sibur.local
* start date: Mar 21 09:34:07 2025 GMT
* expire date: Mar 21 09:34:07 2026 GMT
* issuer: C=RU; ST=Moscow; L=Moscow; O=LLC SIBUR; OU=Information Security; CN=SiburInfrastructureCAG1
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.1
> POST /v1/chat/completions HTTP/1.1
> Host: s001tst-api-gchat.sibur.local
> User-Agent: curl/7.88.1
> Accept: */*
> Content-Type: application/json
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3WkpwSXBPT2lVdjQ3TzhWNFFWWGprSlNQRjFWTTZKcHdWaVE0NXh1YjQwIn0.eyJleHAiOjE3NjMwMjkwMjcsImlhdCI6MTc2MzAyNzIyNywianRpIjoiOGRhYzE1N2EtYTZhMS00NTg1LWFkNDgtNzkzMjRkZTBjZTc1IiwiaXNzIjoiaHR0cHM6Ly9pZC1lb3MtdHN0LnNpYnVyLmxvY2FsL3JlYWxtcy9zaWJ1ciIsImF1ZCI6WyJyYWctbGF5ZXItY2xpZW50IiwiYWNjb3VudCJdLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJyYWctbGF5ZXItY2xpZW50Iiwic2lkIjoiZjZhODVlNmYtOTYwNi00MWFkLWIwMzAtMWQ5ZWEwN2QyNWIyIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLXNpYnVyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSBvZmZsaW5lX2FjY2VzcyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiYTAwMS1haXNyY2gtZ2NodC10c3QiLCJncm91cHMiOlsiRzAwMWdnLXRzdC1naWdhY2hhdC10ZXN0LWNsaWVudCJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhMDAxLWFpc3JjaC1nY2h0LXRzdCIsImdpdmVuX25hbWUiOiJhMDAxLWFpc3JjaC1nY2h0LXRzdCJ9.mBsaCo0Ocvo-VVItZKqx-3LtWwCEbOWGj3MmFO04MVJQCJKxnzdHtDB_BC8gzdjuABYQxkPlCpx3vgBgMbT-Rrbd_Ser3x-DX06_EIqw7r2pv5P9mYGnzwOBc-omL_1zAmUksDufkeEiFoOrASl7a_m6s7qKNqmAJQrtbrRXBZmCm26994Nx7nUcRXBddGNyQ0IIhevtxLH9_rNGj8fBf7KNp8nbZeYSRauhk3CnTxhcoqQQ9tKGvPQGWeEDdIsiX4RnZJdCwIx1LP_nDU1P2tpMyxT2UA5tD176nQ5Ss-jdGOMv_TMZFOTpcoKJgmHtJt0OIAxt3tiLqvN0Rw4lOg> Content-Length: 197
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Server: nginx/1.26.3
< Date: Thu, 13 Nov 2025 09:53:30 GMT
< Content-Type: application/json; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization
< Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
< Access-Control-Allow-Origin: https://beta.saluteai.sberdevices.ru
< X-Request-Id: ed3b0b9f-d5ef-4a06-bb95-ffbd74fe82a2
< X-Session-Id: 35300513-3127-4aac-82b1-c887e68e4744
< Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
< X-Frame-Options: DENY
< X-Content-Type-Options: nosniff
< Content-Security-Policy: default-src 'self';
< X-Permitted-Cross-Domain-Policies: none
< Referrer-Policy: strict-origin-when-cross-origin
< Permissions-Policy: geolocation=(), microphone=(), camera=()
< Cross-Origin-Embedder-Policy: require-corp
< Cross-Origin-Opener-Policy: same-origin
< Cross-Origin-Resource-Policy: same-origin
< Cache-Control: no-store
<
{"choices":[{"message":{"content":"### Бизнес-план стартапа для компании СИБУР\n\n#### Описание проекта\nНазвание: **«ЭкоПолимер»**\nЦель: Разработка и производство инновационного биоразлагаемого полимера на основе возобновляемого сырья, способного заменить традиционные полимеры в упаковочной отрасли, сельском хозяйстве и строительстве.\n\n**Краткое описание идеи:**\nНа фоне усиливающегося экологического давления и законодательных ограничений (например, запрет одноразового пластика), рынок нуждается в устойчивых альтернативах традиционному пластику. Наша разработка — экологически чистый биополимер, произведенный из отходов сельского хозяйства и побочных продуктов переработки углеводородов. Мы предлагаем продукцию с уникальными свойствами: высокая прочность, эластичность, способность разлагаться естественным образом в течение нескольких месяцев после утилизации, безопасность для окружающей среды и человека.\n\n---\n\n## Резюме проекта\n\n- Название проекта: ЭкоПолимер\n- Направление деятельности: Производство инновационных биоразлагаемых полимеров.\n- Ключевые преимущества продукта: Устойчивость, биоразлагаемость, низкая стоимость производства благодаря использованию возобновляемых источников сырья.\n- Целевые рынки: Упаковка пищевых продуктов, сельское хозяйство, строительство, промышленность товаров повседневного спроса.\n- Потенциальный партнер/инвестор: Компания СИБУР.\n\n---\n\n## 1. Общее описание бизнеса\n\nКомпания «ЭкоПолимер» занимается разработкой и производством устойчивого материала на основе биоразлагаемых полимеров, сочетающих свойства традиционного полиэтилена и полипропилена, но имеющих минимальное воздействие на окружающую среду.\n\n**Продуктовая линейка:**\n- Биопластиковые пленки для упаковки продуктов питания и напитков.\n- Биоразлагаемые материалы для сельскохозяйственной промышленности (мешки, агротекстиль).\n- Материалы для строительства (влагоизоляционные покрытия, панели для внутренних стен и фасадов зданий).\n\n---\n\n## 2. Анализ рынка\n\n**Спрос и тенденции рынка:**\n- Рост потребления пластиковых изделий растет ежегодно на 4–6%.\n- Экологические ограничения ужесточаются во многих странах мира (Европа, США, Китай, Россия). Доля традиционных пластиков будет сокращаться.\n- Увеличение потребительского интереса к продукции, имеющей экологический сертификат.\n\n**Конкуренты:**\n- Основные конкуренты находятся в сегменте обычных полиэтиленовых и полипропиленовых пленок, пакеты, упаковка и товары массового потребления.\n- В сегменте био-разлагаемых материалов конкурентов немного, однако есть перспектива появления новых игроков.\n\n**Целевой сегмент клиентов:**\n- Крупные производители пищевой продукции и напитков.\n- Производители бытовой химии и косметики.\n- Строительные компании.\n- Сельскохозяйственные предприятия.\n\n---\n\n## 3. Концепция и технология производства\n\n**Сырье и технологическая цепочка:**\n- Сырьем станут отходы сельскохозяйственных культур (свекла, картофель, зерновые культуры), побочные продукты нефтепереработки (метанол, этанол).\n- Используемая технология предполагает переработку сырьевых компонентов в биологически активные добавки и непосредственно в готовый продукт через экструзию и формовку.\n\n**Уникальность технологии:**\n- Высокое качество конечного продукта при низкой себестоимости производства.\n- Быстрая деградация (от 3 до 18 месяцев в зависимости от условий утилизации).\n- Возможность полной вторичной переработки.\n\n---\n\n## 4. Маркетинговая стратегия\n\n**Позиционирование продукта:**\n- Высокая экологичность и соответствие международным стандартам (ISO, ГОСТ).\n- Сертификация продукции по стандарту ISO 14001 (экологический менеджмент).\n- Удобство интеграции в существующие производственные процессы клиентов.\n\n**Каналы сбыта:**\n- Прямые продажи крупным производителям.\n- Продажа через дистрибьюторов и оптовые сети.\n- Партнерства с крупными торговыми сетями и поставщиками.\n\n**Стратегия продвижения:**\n- Активное продвижение бренда через PR-акции, участие в выставках и форумах (AgroWorld, PackExpo, CEEF).\n- Сотрудничество с университетами и научно-исследовательскими институтами (совместные исследования и разработки).\n- Проведение презентаций и семинаров для потенциальных клиентов.\n\n---\n\n## 5. Финансовый план\n\n**Инвестиции и источники финансирования:**\n- Необходимый объем инвестиций для запуска производства — около ₽2 млрд рублей.\n- Источники финансирования: инвестиции венчурных фондов, собственные средства основателей, привлечение партнеров-инвесторов (СИБУР).\n\n**Прогноз доходов и расходов:**\n- Годовой объем продаж первого года работы — ₽500 млн.\n- Второй год — ₽1,2 млрд.\n- Третий год — ₽2,5 млрд.\n\n**Окупаемость проекта:**\n- Срок окупаемости проекта — 3–4 года.\n\n---\n\n## 6. Планы сотрудничества с СИБУР\n\n**Преимущества для СИБУРа:**\n- Доступ к возобновляемым источникам сырья, возможность снизить углеродный след.\n- Расширение ассортимента продукции компании.\n- Создание устойчивого имиджа компании как лидера в области экологически чистых технологий.\n\n**Партнерские условия:**\n- Совместная разработка технологических решений.\n- Использование производственных мощностей СИБУРа.\n- Совместный маркетинг и продвижение на международных рынках.\n\n---\n\n## Заключение\n\nПроект «ЭкоПолимер» представляет собой уникальный стартап, ориентированный на решение актуальных экологических проблем, связанных с использованием пластиковых материалов. Работая вместе с компанией СИБУР, мы сможем создать новый качественный продукт мирового уровня, отвечающий требованиям современного рынка и одновременно обеспечивающий стабильную прибыльность нашего совместного бизнеса.","role":"assistant"},"index":0,"finish_reason":"stop"}],"created":1763027609,"model":"GigaChat:latest","object":"chat.completion",* Connection #0 to host s001tst-api-gchat.sibur.local left intact"usage":{"prompt_tokens":28,"completion_tokens":1141,"total_tokens":1169,"precached_prompt_tokens":0}}
Значит не бекенде отрабатывает та ветка, которая ожидает полного ответа от LLM. И, судя по всему, эта ветка поломана. В деве работает, потому, что LLM отвечает в режиме SSE.
Предложение изменить настроки Nginx¤
В текущем варианте работы прокси мы не можем получать ответ в режиме SSE. То есть пользователь не сможет
получать ответ от LLM постепенно, но только весь ответ разом после долгого ожидания, поэтому было предложено
сменить настройки прокси. Я изучил текущие настройки и предлагаю
добавить в location /:
# SSE
proxy_buffering off; # Отключить прокси-буферизацию!
proxy_cache off; # Отключить кеширование
add_header 'Cache-Control' 'no-cache';
proxy_read_timeout 1h; # Для долгих SSE-соединений
proxy_send_timeout 1h; # Для долгих SSE-соединений
proxy_set_header Connection ''; # Чтобы не возникало проблем с keep-alive соединениями
- По большому счёту, нужно просто отключить буферизацию и стримить ответ:
proxy_buffering off;. Это основное. Уже этого, скорее всего хватит. - Затем заголовок
proxy_set_header Connection '';, может понадобится, может нет. - Осталось контроль кеширования и увеличение таймаутов. Напрямую это к нашей проблеме не относится, но это в принципе нужно для правильной работы с ответами LLM.
Предложение озвучено в общем чате разработки RAG-слоя.
Продолжение поиска решения¤
14.11.2025
Добавил прокси в проект¤
Реализовал прокси написанный на FastAPI в проекте. Прокси можно запустить через
Реализовал механизм, который позволяет в существующем деплое добавлять написанный прокси. Для этого добавил в start.sh запуск
этого прокси в зависимости от содержимого переменной окружения RUN_OWU_PROXY, добавил эту переменную в values.yml хелм чарта,
она теперь читается из ConfigMap. Механизм будет работать в деве и в тесте, в продуктовую среду не добавлял.
Прокси запускается автоматически при запуске пода, если выставлены нужные параметры.
Ближайшие цели¤
Прокси мне даёт:
- Воспроизводит поведение на тесте, которое ломает OpenWebUI
- Позволяет видеть все вызовы со стороны OpenWebUI, вместе с заголовками.
Продолжаю дебаг. Ближайшие цели:
- Увидеть реальные запросы со стороны OpenWebUI. Есть вероятность, что способы общения с моделью отличается от наших представлений.
- Локализация проблемы в коде OpenWebUI. Буду добавлять логирование в код OpenWebUI и локализовывать потерю ответа от LLM.
Изучение вызовов со стороны OpenWebUI¤
Во время работы прокси я вижу следующую картину в ответе:
2025-11-14 11:11:24,538 | INFO | REQUEST: POST v1/chat/completions
Headers: {'host': 'localhost:9999', 'content-type': 'application/json', 'x-openwebui-user-name': 'Pavel', 'x-openwebui-user-id': '0823afed-eecb-42c0-a353-b296208b6570', 'x-openwebui-user-email': 'basspv@sibur.ru', 'x-openwebui-user-role': 'admin', 'x-openwebui-chat-id': 'c162fee3-4562-4e55-99a9-1e686908e5c8', 'accept': '*/*', 'accept-encoding': 'gzip, deflate, br', 'user-agent': 'Python/3.11 aiohttp/3.12.15', 'content-length': '228'}
Query: {}
Body: {"stream": true, "model": "infidelis/GigaChat-20B-A3B-instruct:q8_0", "messages": [{"role": "user", "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option."}]}
2025-11-14 11:11:24,538 | INFO | Remove Host header
2025-11-14 11:11:24,538 | INFO | url='http://s001cd-0205:11434/v1/chat/completions'
2025-11-14 11:11:24,538 | INFO | Starting request...
2025-11-14 11:11:31,285 | INFO | RESPONSE: http://s001cd-0205:11434/v1/chat/completions
Status: 200
Headers: {'Content-Type': 'text/event-stream', 'Date': 'Fri, 14 Nov 2025 11:11:25 GMT', 'Transfer-Encoding': 'chunked'}
Body: data: {"id":"chatcmpl-490","object":"chat.completion.chunk","created":1763118685,"model":"infidelis/GigaChat-20B-A3B-instruct:q8_0","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"Cer"},"finish_reason":null}]}
data: {"id":"chatcmpl-490","object":"chat.completion.chunk","created":1763118685,"model":"infidelis/GigaChat-20B-A3B-instruct:q8_0","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"tain"},"finish_reason":null}]}
data: {"id":"chatcmpl-490","object":"chat.completion.chunk","created":1763118685,"model":"infidelis/GigaChat-20B-A3B-instruct:q8_0","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"ly"},"finish_reason":null}]}
data: {"id":"chatcmpl-490","object":"chat.completion.chunk","created":1763118685,"model":"infidelis/GigaChat-20B-A3B-instruct:q8_0","system_fingerprint":"fp_ollama","choices":[{"index":0,"delta":{"role":"assistant","content":"!"}...[TRUNCATED]
Просто перехваченный ответ на стороне прокси, полученный в полном объёме, будет содержать все переданные события SSE. В тесте я вижу уже собранный ответ от LLM в виде JSON. Получается, что Nginx не только работает как прокси, но и выполняет логику клиента.