Вопрос или проблема
Я создаю простой чат с приложением LLM. Я хочу, чтобы начальный POST-запрос (первое сообщение пользователя) был триггером, который приводит к ответу от LLM.
Итак, у меня есть контроллер ASP.net core web api, который имеет конечную точку для обработки соединения SSE и дополнительную конечную точку для обработки POST-запроса (это позволяет пользователю отправлять сообщения).
Angular-приложение, которое я создал (обслуживается node и использует proxy.conf), устанавливает соединение EventSource с моим контроллером. Если я жестко закодирую первоначальный вопрос пользователя на сервере, чтобы мне не пришлось делать свой POST-запрос, SSE отправляет ответ LLM, как и ожидалось.
// Конечная точка для потоковой передачи сообщений в чате
[HttpGet("stream")]
public async Task GetChatMessagesAsStreamAsync()
{
string prompt = """
Вот мой чудесный запрос
""";
await _agentGroupChatService.AddChatMessage(new ChatRequest { Message= prompt });
// Установить тип содержимого ответа для SSE
HttpContext.Response.ContentType = "text/event-stream";
// Потоковая передача сообщений из сервиса
await foreach (var message in _agentGroupChatService.GetChatMessagesAsStreamAsync())
{
var messageJson = JsonSerializer.Serialize(message);
// Записывать каждое сообщение в формате SSE
await HttpContext.Response.WriteAsync($"data: {messageJson}\n\n");
await HttpContext.Response.Body.FlushAsync(); // Сбросить, чтобы отправить сообщение немедленно
}
}
НО… Если я создаю соединение SSE, а затем полагаюсь на POST-запрос для отправки начального сообщения, соединение SSE немедленно разрывается (отмечено как отмененное на клиенте).
Это отмена происходит до того, как выполнится конечная точка контроллера POST – она всё равно отвечает “ок”!
[HttpPost("message")]
public async Task<IActionResult> SendMessage([FromBody] ChatRequest chatRequest)
{
return Ok(); // Ответ с кодом 200 OK
}
// Конечная точка для потоковой передачи сообщений в чате
[HttpGet("stream")]
public async Task GetChatMessagesAsStreamAsync()
{
// Установить тип содержимого ответа для SSE
HttpContext.Response.ContentType = "text/event-stream";
// Потоковая передача сообщений из сервиса
await foreach (var message in _agentGroupChatService.GetChatMessagesAsStreamAsync())
{
var messageJson = JsonSerializer.Serialize(message);
// Записывать каждое сообщение в формате SSE
await HttpContext.Response.WriteAsync($"data: {messageJson}\n\n");
await HttpContext.Response.Body.FlushAsync(); // Сбросить, чтобы отправить сообщение немедленно
}
}
На самом деле, если конечная точка контроллера POST просто возвращает OK, соединение SSE отменяется!
Кроме того, если данные сначала отправляются через поток событий, я могу отправить POST-запрос. Если никаких данных не было отправлено до POST, он не проходит. Есть идеи, что происходит?
Ответ или решение
Ваш вопрос касается проблемы с использованием однопоточного потокового SSE (Server-Sent Events) в микросервисе, реализованном на ASP.NET Core, где одновременно обрабатываются запросы Post для отправки сообщений. Давайте рассмотрим, почему происходит закрытие соединения SSE, когда вызывается POST
запрос, и возможные способы решения этой проблемы.
Общее описание проблемы
Вы реализовали два контроллера в ASP.NET Core:
- GET запрос для SSE (
GetChatMessagesAsStreamAsync
), который использует потоковую передачу для отправки сообщений клиенту. - POST запрос (
SendMessage
), который принимает сообщения от пользователя.
Основная проблема заключается в том, что при вызове отправки POST-запроса соединение SSE сразу закрывается, и клиент получает уведомление о том, что соединение отменено. Это происходит даже в тех случаях, когда вызов POST не возвращает никаких ошибок, а просто отдает статус 200 OK.
Возможные причины проблемы
-
Конфликт потоков: SSE использует единый поток для передачи данных. Если во время установления соединения выполняется другой запрос, это может привести к конфликтам состояния, так как оба запроса могут пытаться взаимодействовать с одним и тем же ресурсом.
-
Управление жизненным циклом соединения: Если сервер ожидает завершения одного запроса до начала другого, это может привести к закрытию одного из соединений. Особенно это касается ситуаций, когда сервер видит два активных дайджеста от одного и того же клиента.
-
Настройки сервера: Некоторые настройки в конфигурации сервера или в рамках middleware могут вызывать отмену соединений при определенных условиях, например, при изменении состояния контекста
HttpContext
.
Рекомендации по решению проблемы
-
Асинхронное ожидание: Убедитесь, что ваши методы обработки запросов реализованы как асинхронные, правильно используя ключевое слово
await
. Это позволит избежать блокировок и конфликтов в потоках. -
Проверка состояния соединения: Перед тем как выполнять POST-запрос, убедитесь, что соединение SSE действительно активно. Это поможет определить, не происходит ли автоматическое закрытие соединения из-за потери состояния.
-
Использование Middleware для SSE: Попробуйте обернуть ваши потоковые запросы в middleware, который будет контролировать состояние соединения и управлять событиями. Это может помочь избежать неожиданных закрытий соединения.
-
Изменение порядка событий: Рассмотрите возможность отправки первого сообщения через SSE перед отправкой POST-запроса. Вы уже упомянули, что это работает, и это может стать временным решением.
Заключение
Проблема с закрытием соединения SSE при отправке POST-запроса может быть связана с конфликтами в управлении потоками, состоянием соединения или неправильной конфигурацией сервера. Принятие мер, таких как асинхронная реализация ваших методов, лучшее управление состоянием соединения и использование middleware для контроля событий, может помочь в решении этой проблемы.
Рекомендуется протестировать каждое исправление на предмет того, принесло ли это улучшение, а также документировать изменения для будущих ссылок и для обеспечения более легкой отладки проблем в будущем.