У меня какой-то странный зависающий запрос в приложении .NET/Core.C#

Место общения программистов C#
Ответить
Anonymous
 У меня какой-то странный зависающий запрос в приложении .NET/Core.

Сообщение Anonymous »

Вот настройка и проблема
У меня запущено два приложения: одно в .NET 4.7.2 (управление версиями не находится под моим контролем...) и (веб-приложение), а другое в dotnet 9 (прокси-служба LLM). Прокси-сервис принимает запрос, маршрутизирует его и использует семантическое ядро ​​для вызова OpenAI. Веб-приложение представляет собой внутренний инструмент с интерфейсом чат-бота.
Если я вызываю прокси-службу непосредственно из интерфейса JavaScript const res = await fetch(API_URL ..., все работает как надо, и запрос проходит. Если я вызываю прокси-сервер через веб-приложение, запрос поступает в прокси-службу, вплоть до вызова OpenAI, а затем просто... зависает там и раз вне. Иногда это работает, но затем при перезапуске снова зависает.
Соответствующий код
Веб-приложение (4.7.2), вызывающая сторона

Код: Выделить всё

/// Controller

[RoutePrefix("LLMProxy")]
public class LLMProxyController : Controller
{
[HttpPost]
[Route("Chat")]
public async Task Query()
{
string prefix = ConfigurationManager.AppSettings["ParameterStorePrefix"];
string parsedPref = TenantHelper.NormalizeParameterStorePrefix(prefix);

Request.InputStream.Position = 0;
using var reader = new System.IO.StreamReader(Request.InputStream);
var body = await reader.ReadToEndAsync().ConfigureAwait(false);

var dto = Newtonsoft.Json.JsonConvert.DeserializeObject(body);
if (dto == null || string.IsNullOrWhiteSpace(dto.Prompt))
return new HttpStatusCodeResult(400, "Missing prompt");

var raw = dto.JsonBlob?.ToString(Newtonsoft.Json.Formatting.None);

var proxyBusinessModel = new LLMProxyBusinessModel();
var result = await proxyBusinessModel.PostLLMChat(new Entity.ChatInputEntity
{
// ...
}, dto.ChatbotId).ConfigureAwait(false);

var json = JsonConvert.SerializeObject(result);

return Content(json, "application/json");
}
}

/// LLMProxyBusinessModel

public class LLMProxyBusinessModel
{

public string LLMProxyUrl
{
get => System.Configuration.ConfigurationManager.AppSettings["LLMProxyBaseUrl"].TrimEnd('/');
}

public async Task PostLLMChat(ChatInputEntity input, string chatbotId)
{
var url = string.IsNullOrEmpty(chatbotId)
? $"{LLMProxyUrl}/llm/chat"
: $"{LLMProxyUrl}/llm/chat?chatbotId={Uri.EscapeDataString(chatbotId)}";

var handler = new HttpClientHandler { UseProxy = false }; // == Also tried without ==
using (var http = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(3) })
{
http.DefaultRequestHeaders.ExpectContinue = false; // == Added this to try ==
http.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

var json = Newtonsoft.Json.JsonConvert.SerializeObject(input);
using var content = new StringContent(json, Encoding.UTF8, "application/json");

using var resp = await http.PostAsync(url, content).ConfigureAwait(false);
var raw = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
resp.EnsureSuccessStatusCode();

return Newtonsoft.Json.JsonConvert.DeserializeObject(raw);
}
}
Прокси-сервер LLM (получатель)

Код: Выделить всё

/// Controller

[ApiController]
[Route("llm")]
public class LLMController(RespondService respondService, ChatService chatService, ILogger logger) : Controller
{
private readonly ILogger _logger = logger;
private readonly RespondService _respondService = respondService;
private readonly ChatService _chatService = chatService;

[EndpointName("Chat")]
[EndpointSummary("Handles a chat request that maintains conversation state.")]
[EndpointDescription("Can receive a system prompt to set the context for the chat session.")]
[HttpPost("chat")]
public async Task Chat([FromBody] ChatInput input, [FromQuery] string chatbotId = "", CancellationToken ct = default)
{
var linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
linked.CancelAfter(TimeSpan.FromMinutes(3));  // == This was added as a test, no change ==

if (input is null)
{
_logger.LogWarning("Chat input is null.");
return BadRequest("Chat input cannot be null.");
}
_logger.LogInformation("Processing chat input: {Input}, chatbotId: {ChatbotId}", input, chatbotId);
try
{
var response = await _chatService.HandleChatInput(input, chatbotId, HttpContext, linked.Token);
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing chat request.");
return StatusCode(500, "Internal server error.");
}
}
}

/// Business logic

public async Task HandleChatInput(
ChatInput input,
string chatbotId,
HttpContext http,
CancellationToken ct = default)
{
// setup code, no HTTP, both go beyond this
// ...

var response = await GetChatResponseAsync(input, chatbotId, session, messageId, ct);
return response;
}

private async Task GetChatResponseAsync(
ChatInput input,
string chatbotId,
ChatSession session,
string messageId,
CancellationToken ct = default
)
{
var chatKernel = _router.GetKernelForModel(input, chatbotId);
var chat = chatKernel.GetRequiredService();

// ==== CRITICAL PART ====
// == In both cases, we reach this part. But when calling from the web app, I timeout here.
var reply = await chat.GetChatMessageContentAsync(
session.History,
executionSettings: ChatHelper.GetExecutionSettings(chatbotId),
kernel: chatKernel,
cancellationToken: ct
);

// == When calling from the web app, we never reach the return and just time out above ==

return new LLMOutput
{
Response = reply.Content,
ReturnValue = session.ReturnValue,
MessageId = messageId
};
}
Код внешнего интерфейса, который осуществляет вызов.

Код: Выделить всё

/// When I call via the web app, whos Razor page I am within:

const llmPostUrl = '@Url.Action("Chat", "LLMProxy", new { area = "" })';

const res = await fetch(llmPostUrl, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
UserId: userId,
Prompt: txt,
OrganisationId: organisationId,
WorkGroupId: workGroupId,
ChatbotId: chatbotId,
NewSession: createNewSession,
JsonBlob: jsonBlob, })
});

/// When I call via the same JS file but call the proxy directly

var API_URL = "http://localhost:5183/llm"

const res = await fetch(API_URL + "/chat?chatbotId=" + chatbotId, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
prompt: txt,
jsonBlob: jsonBlob,
reasoning: true,
model: null
})
});
JSON различается, поскольку веб-приложение добавляет их в исходящий запрос.
Пока и журналы
Тайм-ауты большие (веб-приложение 3 м, LLM Proxy 150 с). Вызов модели обычно выполняется быстро, когда он работает.
Журналы HTTP показывают:
Начало обработки HTTP-запроса POST https://api.openai.com/v1/chat/completions
Отправка HTTP-запроса POST https://api.openai.com/v1/chat/completions
…и затем ничего до истечения времени ожидания.
Предварительная проверка GET на https://api.openai.com/v1/models с использованием клиента с тем же именем немедленно возвращает 200 OK через ~1,3 секунды — даже если /chat/completions зависает.
Перезапуск прокси-сервера LLM может изменить поведение (работает > перезапуск > зависает).
  • Переключился на именованный клиент IHttpClientFactory («LLMProvider») и внедрился в SK.
  • Принудительный HTTP/1.1:

Код: Выделить всё

http.DefaultRequestVersion = HttpVersion.Version11;
http.DefaultVersionPolicy  = HttpVersionPolicy.RequestVersionOrLower;
  • Добавлена ​​перезапуск соединения + сбой быстрого подключения через SocketsHttpHandler:

Код: Выделить всё

ConnectTimeout = 10s
PooledConnectionLifetime = 5m
PooledConnectionIdleTimeout = 2m
DnsRefreshTimeout = 5m
MaxConnectionsPerServer = 256
ExpectContinue = false
UseProxy = false
Нет изменений, все еще зависает на /chat/completions.
  • Одноразовое соединение: закрыть (для обхода сокетов пула) — без изменений.
  • Временно отключены проверки отзыва TLS/закреплен TLS 1.2 — без изменений.
На этом этапе я исчерпал возможности Gemini, GPT и себя. ПОМОГИТЕ!

Подробнее здесь: https://stackoverflow.com/questions/797 ... pplication
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»