Skip to content

Commit db67e7d

Browse files
author
Benjamin Michaelis
committed
fix: improve exception handling and update chat deployment name
- Replace UseExceptionHandler('/Error') with lambda handler that: - Logs exceptions via IExceptionHandlerFeature (was silently swallowed) - Returns JSON 500 for /api/* requests (fixes chat returning 404 HTML) - Redirects non-API requests to /Home/Error?statusCode=500 (route exists) - Add try/catch to ChatController.StreamMessage with: - OperationCanceledException guard for clean client disconnect handling - Pre-response-start catch: returns JSON 500 with CancellationToken.None - Mid-stream catch: best-effort SSE error event, wrapped in try/catch - Update ChatDeploymentName default: gpt-4o -> gpt-5 (matches ecs-cog-prod)
1 parent fdb9262 commit db67e7d

3 files changed

Lines changed: 63 additions & 19 deletions

File tree

EssentialCSharp.Web/Controllers/ChatController.cs

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,29 +72,53 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
7272
Response.Headers.CacheControl = "no-cache";
7373
Response.Headers.Connection = "keep-alive";
7474

75-
await foreach (var (text, responseId) in _AiChatService.GetChatCompletionStream(
76-
prompt: request.Message,
77-
systemPrompt: request.SystemPrompt,
78-
previousResponseId: request.PreviousResponseId,
79-
enableContextualSearch: request.EnableContextualSearch,
80-
cancellationToken: cancellationToken))
75+
try
8176
{
82-
if (!string.IsNullOrEmpty(text))
77+
await foreach (var (text, responseId) in _AiChatService.GetChatCompletionStream(
78+
prompt: request.Message,
79+
systemPrompt: request.SystemPrompt,
80+
previousResponseId: request.PreviousResponseId,
81+
enableContextualSearch: request.EnableContextualSearch,
82+
cancellationToken: cancellationToken))
8383
{
84-
var eventData = JsonSerializer.Serialize(new { type = "text", data = text });
85-
await Response.WriteAsync($"data: {eventData}\n\n", cancellationToken);
86-
await Response.Body.FlushAsync(cancellationToken);
84+
if (!string.IsNullOrEmpty(text))
85+
{
86+
var eventData = JsonSerializer.Serialize(new { type = "text", data = text });
87+
await Response.WriteAsync($"data: {eventData}\n\n", cancellationToken);
88+
await Response.Body.FlushAsync(cancellationToken);
89+
}
90+
91+
if (!string.IsNullOrEmpty(responseId))
92+
{
93+
var eventData = JsonSerializer.Serialize(new { type = "responseId", data = responseId });
94+
await Response.WriteAsync($"data: {eventData}\n\n", cancellationToken);
95+
await Response.Body.FlushAsync(cancellationToken);
96+
}
8797
}
8898

89-
if (!string.IsNullOrEmpty(responseId))
99+
await Response.WriteAsync("data: [DONE]\n\n", cancellationToken);
100+
await Response.Body.FlushAsync(cancellationToken);
101+
}
102+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested || HttpContext.RequestAborted.IsCancellationRequested)
103+
{
104+
_Logger.LogDebug("Chat stream cancelled for user {User}", User.Identity?.Name);
105+
}
106+
catch (Exception ex) when (!Response.HasStarted)
107+
{
108+
_Logger.LogError(ex, "Chat streaming error before response started for user {User}", User.Identity?.Name);
109+
Response.StatusCode = 500;
110+
Response.ContentType = "application/json";
111+
await Response.WriteAsJsonAsync(new { error = "Chat service unavailable" }, CancellationToken.None);
112+
}
113+
catch (Exception ex)
114+
{
115+
_Logger.LogError(ex, "Chat streaming error mid-stream for user {User}", User.Identity?.Name);
116+
try
90117
{
91-
var eventData = JsonSerializer.Serialize(new { type = "responseId", data = responseId });
92-
await Response.WriteAsync($"data: {eventData}\n\n", cancellationToken);
93-
await Response.Body.FlushAsync(cancellationToken);
118+
await Response.WriteAsync("data: {\"type\":\"error\",\"message\":\"Stream interrupted\"}\n\n", CancellationToken.None);
119+
await Response.Body.FlushAsync(CancellationToken.None);
94120
}
121+
catch { /* client already disconnected */ }
95122
}
96-
97-
await Response.WriteAsync("data: [DONE]\n\n", cancellationToken);
98-
await Response.Body.FlushAsync(cancellationToken);
99123
}
100124
}

EssentialCSharp.Web/Program.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Microsoft.AspNetCore.RateLimiting;
1616
using Azure.Monitor.OpenTelemetry.AspNetCore;
1717
using Microsoft.AspNetCore.DataProtection;
18+
using Microsoft.AspNetCore.Diagnostics;
1819
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
1920
using Microsoft.EntityFrameworkCore;
2021
using Microsoft.Extensions.Diagnostics.HealthChecks;
@@ -352,7 +353,26 @@ await context.HttpContext.Response.WriteAsync(
352353
// Configure the HTTP request pipeline.
353354
if (!app.Environment.IsDevelopment())
354355
{
355-
app.UseExceptionHandler("/Error");
356+
app.UseExceptionHandler(exceptionApp =>
357+
{
358+
exceptionApp.Run(async context =>
359+
{
360+
var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>();
361+
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
362+
logger.LogError(exceptionFeature?.Error, "Unhandled exception on {Path}", context.Request.Path);
363+
364+
if (context.Request.Path.StartsWithSegments("/api"))
365+
{
366+
context.Response.StatusCode = 500;
367+
context.Response.ContentType = "application/json";
368+
await context.Response.WriteAsJsonAsync(new { error = "An unexpected error occurred" });
369+
}
370+
else
371+
{
372+
context.Response.Redirect("/Home/Error?statusCode=500");
373+
}
374+
});
375+
});
356376
app.UseForwardedHeaders();
357377
app.UseSecurityHeadersMiddleware(new SecurityHeadersBuilder()
358378
.AddDefaultSecurePolicy());

EssentialCSharp.Web/appsettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
},
2121
"AIOptions": {
2222
"VectorGenerationDeploymentName": "text-embedding-3-large-v1",
23-
"ChatDeploymentName": "gpt-4o",
23+
"ChatDeploymentName": "gpt-5",
2424
"SystemPrompt": "You are a helpful AI assistant with expertise in C# programming and the Essential C# book content. You can help users understand C# concepts, answer programming questions, and provide guidance based on the Essential C# book materials. Be concise but thorough in your explanations.",
2525
"Endpoint": "",
2626
"ApiKey": ""

0 commit comments

Comments
 (0)