C#实战5分钟搞定AI模型API的SSE流式输出附完整代码最近在对接几个大语言模型的API时我发现一个挺有意思的现象很多开发者一听到“流式输出”就觉得头大尤其是用C#这种传统上被认为在Web实时通信领域不那么“敏捷”的语言。但实际上借助Server-Sent EventsSSE这个被低估的协议在C#里实现一个高效、稳定的AI流式响应接口可能比你想象的要简单得多。这篇文章就是写给那些正在赶项目、需要快速把AI模型的流式输出能力集成到自己.NET应用里的工程师。我们不谈太多理论直接上代码聊聊怎么用最少的代码处理最实际的问题比如连接管理、错误重试还有性能上的一些小坑。1. 为什么是SSEAI流式输出的技术选型在开始写代码之前我们得先搞清楚面对AI模型API比如那些生成文本、代码或者进行长对话的模型我们有哪些选择来实现“边生成边返回”的体验。主流方案无非三种WebSocket、长轮询以及我们今天的主角——Server-Sent Events。WebSocket功能最强大双向通信但复杂度也最高。你需要处理握手、帧协议、心跳维持对于仅仅是服务器向客户端推送AI生成结果这个场景来说有点杀鸡用牛刀。长轮询则是一种妥协它简单但效率低下会在每个请求-响应周期中引入不必要的延迟这对于要求实时性的AI对话体验来说是致命的。SSE恰恰站在一个完美的平衡点上。它基于普通的HTTP协议这意味着极低的接入成本无需像WebSocket那样引入额外的库或处理复杂的协议。任何能发HTTP请求的客户端都能用。天然的自动重连协议内置了重连机制网络波动时客户端会自动尝试重新连接。与现有基础设施完美兼容防火墙、负载均衡器通常对HTTP流量更友好。单向通信的完美匹配AI模型生成内容这个场景本质上就是服务器单向、持续地向客户端推送数据流。对于C#后端来说这意味着我们可以用最熟悉的HttpClient去消费上游AI模型的流式API同时用标准的ASP.NET Core控制器或Minimal API以SSE格式将数据“转发”给前端。整个架构清晰、稳定且易于调试。注意SSE的一个限制是它是单向的服务器到客户端。如果你的应用需要客户端频繁向服务器发送大量数据例如实时游戏那么WebSocket更合适。但对于AI对话客户端通常只是发送一个查询请求然后等待流式响应SSE完全胜任。2. 核心实战构建一个健壮的SSE代理端点让我们跳过“Hello World”级别的示例直接构建一个能在生产环境中使用的、具备基本容错能力的SSE代理。这个代理的核心工作是接收前端请求去调用真正的AI模型API如OpenAI的Chat Completions with streaming然后将收到的流式数据实时、原样地转发给前端。首先创建一个ASP.NET Core Web API项目。我们使用Minimal API的写法它更简洁。// Program.cs using System.Net.Http.Headers; using System.Text; var builder WebApplication.CreateBuilder(args); builder.Services.AddHttpClient(AIClient); // 注册一个命名的HttpClient var app builder.Build(); app.MapGet(/api/chat/stream, async (HttpContext context, IHttpClientFactory httpClientFactory) { // 1. 设置SSE响应头 context.Response.Headers.Append(Content-Type, text/event-stream); context.Response.Headers.Append(Cache-Control, no-cache); context.Response.Headers.Append(Connection, keep-alive); // 可选设置CORS头部如果前端跨域的话 // context.Response.Headers.Append(Access-Control-Allow-Origin, *); // 2. 获取前端传来的消息简化处理实际应从body读取JSON var userMessage context.Request.Query[message].ToString(); if (string.IsNullOrEmpty(userMessage)) { await context.Response.WriteAsync(data: [ERROR] Message is required\n\n); return; } // 3. 准备请求真实AI模型的API var client httpClientFactory.CreateClient(AIClient); client.DefaultRequestHeaders.Authorization new AuthenticationHeaderValue(Bearer, your-ai-api-key-here); var requestBody new { model gpt-3.5-turbo, messages new[] { new { role user, content userMessage } }, stream true // 关键参数要求流式响应 }; var jsonBody System.Text.Json.JsonSerializer.Serialize(requestBody); var content new StringContent(jsonBody, Encoding.UTF8, application/json); // 4. 发起流式请求并处理响应 try { using var response await client.PostAsync(https://api.openai.com/v1/chat/completions, content, context.RequestAborted); response.EnsureSuccessStatusCode(); using var stream await response.Content.ReadAsStreamAsync(); using var reader new StreamReader(stream); char[] buffer new char[8192]; StringBuilder messageBuffer new StringBuilder(); bool inDataSection false; while (!reader.EndOfStream !context.RequestAborted.IsCancellationRequested) { int charsRead await reader.ReadAsync(buffer, 0, buffer.Length); string chunk new string(buffer, 0, charsRead); // 这里是一个简化的SSE事件行解析逻辑 foreach (var line in chunk.Split(\n, StringSplitOptions.RemoveEmptyEntries)) { if (line.StartsWith(data: )) { var dataContent line.Substring(6).Trim(); if (dataContent [DONE]) { // AI流结束信号 await context.Response.WriteAsync(data: [DONE]\n\n); await context.Response.Body.FlushAsync(); return; } // 将AI API返回的SSE数据直接转发给前端 await context.Response.WriteAsync($data: {dataContent}\n\n); await context.Response.Body.FlushAsync(); } } } } catch (TaskCanceledException) { // 客户端断开连接正常终止 } catch (Exception ex) { // 记录日志 Console.Error.WriteLine($AI API调用失败: {ex.Message}); await context.Response.WriteAsync($data: [ERROR] Service temporarily unavailable\n\n); } }); app.Run();这段代码做了几件关键的事情设置正确的SSE响应头这是让浏览器识别为事件流的关键。使用IHttpClientFactory这是最佳实践它能高效管理HttpClient实例的生命周期避免套接字耗尽问题。处理取消令牌context.RequestAborted会在客户端断开连接时触发我们需要捕获这个信号及时停止向AI API请求数据并释放资源避免后台任务空转。简单的流式解析与转发我们逐块读取AI API返回的流解析出data:行然后立即以同样的SSE格式写回给前端响应流。这里做了简化实际AI API返回的JSON可能更复杂需要解析出choices[0].delta.content字段。基本的错误处理网络异常或AI服务错误时会向前端发送一个格式化的错误事件。3. 性能优化与高级错误处理上面的基础版本能跑起来但要用于生产我们还得考虑更多。下面这个表格对比了基础版和优化版需要关注的关键点关注点基础实现的风险优化策略连接管理客户端异常断开可能导致后端仍持有AI API连接浪费资源。紧密绑定HttpContext的取消令牌与AI API请求确保联动取消。缓冲区与刷新频繁调用FlushAsync可能影响性能但间隔太长又导致客户端延迟高。可以考虑使用BufferedStream或在内存中积累少量数据如攒够一个完整句子再刷新在实时性和吞吐量间权衡。AI API稳定性网络抖动或AI服务暂时不可用会导致整个请求失败。实现带退避机制的重试逻辑但注意SSE连接有超时限制重试需快速。对于非流式错误如鉴权失败应立即终止并报错。数据解析直接字符串分割解析SSE流在数据量巨大时可能效率不高且处理复杂JSON时易错。使用System.Text.Json的Utf8JsonReader进行流式JSON解析性能更高更安全。并发与资源大量并发请求可能耗尽HttpClient连接池或服务器资源。配置HttpClient的PooledConnectionLifetime和MaxConnectionsPerServer。考虑对AI API的调用进行速率限制防止因下游服务限制导致失败。让我们深入其中两点看看优化后的代码片段。优化点1更健壮的重试与错误处理我们不应该一遇到异常就立刻给前端返回错误。对于网络超时等临时性故障可以快速重试一次。// 在try-catch块内部发起请求的部分可以这样包装 int retryCount 0; int maxRetries 2; HttpResponseMessage? response null; while (retryCount maxRetries) { try { using var requestTokenSource CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted); requestTokenSource.CancelAfter(TimeSpan.FromSeconds(30)); // 设置单个请求超时 response await client.PostAsync(..., content, requestTokenSource.Token); response.EnsureSuccessStatusCode(); break; // 成功则跳出重试循环 } catch (HttpRequestException ex) when (retryCount maxRetries) { retryCount; // 等待一小段时间再重试指数退避 await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), context.RequestAborted); continue; } } if (response null || !response.IsSuccessStatusCode) { // 重试后仍失败 await context.Response.WriteAsync(data: [ERROR] Upstream service error\n\n); return; } // 后续使用response处理流...优化点2使用Utf8JsonReader进行高效解析AI API返回的通常是JSON格式的SSE事件。手动拼接字符串再解析效率低。我们可以边读流边解析。using var stream await response.Content.ReadAsStreamAsync(); using var reader new StreamReader(stream); var jsonBuffer new byte[4096]; while (!context.RequestAborted.IsCancellationRequested) { int bytesRead await stream.ReadAsync(jsonBuffer, 0, jsonBuffer.Length, context.RequestAborted); if (bytesRead 0) break; var jsonReader new Utf8JsonReader(new ReadOnlySpanbyte(jsonBuffer, 0, bytesRead)); while (jsonReader.Read()) { if (jsonReader.TokenType JsonTokenType.PropertyName jsonReader.ValueTextEquals(choices)) { // 简化逻辑定位到content字段 // ... 实际解析需要根据AI API的响应结构递归处理 if (TryExtractContent(ref jsonReader, out string? content)) { if (!string.IsNullOrEmpty(content)) { await context.Response.WriteAsync($data: {content}\n\n); await context.Response.Body.FlushAsync(); } } } } } // 需要实现TryExtractContent方法这里省略详细JSON遍历逻辑4. 前端如何消费这个SSE流后端准备好了前端也得跟上。用JavaScript消费SSE非常简单但也有一些细节需要注意。// 前端JavaScript示例 const eventSource new EventSource(/api/chat/stream?message你好请介绍一下你自己); eventSource.onmessage (event) { const data event.data; if (data [DONE]) { console.log(流式传输结束); eventSource.close(); return; } if (data.startsWith([ERROR])) { console.error(服务端错误:, data); eventSource.close(); return; } try { // 假设后端转发的是AI API原始的JSON字符串 const parsed JSON.parse(data); // 例如OpenAI的格式parsed.choices[0].delta.content const chunk parsed.choices[0]?.delta?.content || ; // 将chunk追加到你的UI元素中 document.getElementById(output).innerText chunk; } catch (e) { // 如果不是JSON可能是其他格式的数据 console.log(收到数据:, data); } }; eventSource.onerror (err) { console.error(EventSource failed:, err); // 错误发生时EventSource会自动尝试重连 // 如果需要处理不可恢复的错误可以在这里关闭 // eventSource.close(); };前端的关键在于错误处理监听onerror事件但注意SSE协议本身会尝试自动重连所以这个错误可能只是暂时的网络问题。解析数据根据你后端转发的具体格式是原始AI API的JSON还是你处理后的纯文本来解析event.data。连接管理在收到结束信号[DONE]或致命错误[ERROR]时主动调用eventSource.close()来释放资源。5. 部署与监控要点代码写完了本地也跑通了上线前还得过几关。部署配置超时设置确保你的反向代理如Nginx、IIS和负载均衡器为SSE连接配置了足够长的超时时间例如30分钟。否则连接可能在AI模型还在生成时就被掐断。# Nginx 示例配置 location /api/chat/stream { proxy_pass http://your_backend; proxy_set_header Connection ; proxy_http_version 1.1; proxy_buffering off; # 关键禁用代理缓冲数据才能实时推送 proxy_read_timeout 1800s; # 设置长超时 proxy_cache off; }连接数限制SSE是长连接每个活跃用户都会占用一个连接。评估你的服务器资源内存、文件描述符限制能支撑多少并发SSE连接。监控与调试日志在代理端记录关键事件如连接建立、AI API调用开始/结束、错误发生。但注意不要记录完整的AI响应内容可能包含敏感数据。指标监控活跃SSE连接数、AI API调用延迟、错误率。这些指标能帮你发现性能瓶颈或下游服务问题。客户端断连处理如前所述利用CancellationToken确保后端资源及时回收。可以在日志中记录连接持续时间分析用户行为。我在一个内部知识库问答项目中实际应用了这套方案。最初版本没有处理客户端突然关闭页面或刷新导致服务器后台堆积了不少僵尸请求。后来加上了RequestAborted的联动取消并配置了更合理的HttpClient超时系统就稳定多了。另一个教训是关于缓冲的一开始为了追求极致实时性每个字符都刷新在高并发下给服务器带来了不小压力。后来改为积累一小段文本比如遇到句号、换行符再刷新用户体验几乎没有感知但服务器负载显著下降。
C#实战:5分钟搞定AI模型API的SSE流式输出(附完整代码)
C#实战5分钟搞定AI模型API的SSE流式输出附完整代码最近在对接几个大语言模型的API时我发现一个挺有意思的现象很多开发者一听到“流式输出”就觉得头大尤其是用C#这种传统上被认为在Web实时通信领域不那么“敏捷”的语言。但实际上借助Server-Sent EventsSSE这个被低估的协议在C#里实现一个高效、稳定的AI流式响应接口可能比你想象的要简单得多。这篇文章就是写给那些正在赶项目、需要快速把AI模型的流式输出能力集成到自己.NET应用里的工程师。我们不谈太多理论直接上代码聊聊怎么用最少的代码处理最实际的问题比如连接管理、错误重试还有性能上的一些小坑。1. 为什么是SSEAI流式输出的技术选型在开始写代码之前我们得先搞清楚面对AI模型API比如那些生成文本、代码或者进行长对话的模型我们有哪些选择来实现“边生成边返回”的体验。主流方案无非三种WebSocket、长轮询以及我们今天的主角——Server-Sent Events。WebSocket功能最强大双向通信但复杂度也最高。你需要处理握手、帧协议、心跳维持对于仅仅是服务器向客户端推送AI生成结果这个场景来说有点杀鸡用牛刀。长轮询则是一种妥协它简单但效率低下会在每个请求-响应周期中引入不必要的延迟这对于要求实时性的AI对话体验来说是致命的。SSE恰恰站在一个完美的平衡点上。它基于普通的HTTP协议这意味着极低的接入成本无需像WebSocket那样引入额外的库或处理复杂的协议。任何能发HTTP请求的客户端都能用。天然的自动重连协议内置了重连机制网络波动时客户端会自动尝试重新连接。与现有基础设施完美兼容防火墙、负载均衡器通常对HTTP流量更友好。单向通信的完美匹配AI模型生成内容这个场景本质上就是服务器单向、持续地向客户端推送数据流。对于C#后端来说这意味着我们可以用最熟悉的HttpClient去消费上游AI模型的流式API同时用标准的ASP.NET Core控制器或Minimal API以SSE格式将数据“转发”给前端。整个架构清晰、稳定且易于调试。注意SSE的一个限制是它是单向的服务器到客户端。如果你的应用需要客户端频繁向服务器发送大量数据例如实时游戏那么WebSocket更合适。但对于AI对话客户端通常只是发送一个查询请求然后等待流式响应SSE完全胜任。2. 核心实战构建一个健壮的SSE代理端点让我们跳过“Hello World”级别的示例直接构建一个能在生产环境中使用的、具备基本容错能力的SSE代理。这个代理的核心工作是接收前端请求去调用真正的AI模型API如OpenAI的Chat Completions with streaming然后将收到的流式数据实时、原样地转发给前端。首先创建一个ASP.NET Core Web API项目。我们使用Minimal API的写法它更简洁。// Program.cs using System.Net.Http.Headers; using System.Text; var builder WebApplication.CreateBuilder(args); builder.Services.AddHttpClient(AIClient); // 注册一个命名的HttpClient var app builder.Build(); app.MapGet(/api/chat/stream, async (HttpContext context, IHttpClientFactory httpClientFactory) { // 1. 设置SSE响应头 context.Response.Headers.Append(Content-Type, text/event-stream); context.Response.Headers.Append(Cache-Control, no-cache); context.Response.Headers.Append(Connection, keep-alive); // 可选设置CORS头部如果前端跨域的话 // context.Response.Headers.Append(Access-Control-Allow-Origin, *); // 2. 获取前端传来的消息简化处理实际应从body读取JSON var userMessage context.Request.Query[message].ToString(); if (string.IsNullOrEmpty(userMessage)) { await context.Response.WriteAsync(data: [ERROR] Message is required\n\n); return; } // 3. 准备请求真实AI模型的API var client httpClientFactory.CreateClient(AIClient); client.DefaultRequestHeaders.Authorization new AuthenticationHeaderValue(Bearer, your-ai-api-key-here); var requestBody new { model gpt-3.5-turbo, messages new[] { new { role user, content userMessage } }, stream true // 关键参数要求流式响应 }; var jsonBody System.Text.Json.JsonSerializer.Serialize(requestBody); var content new StringContent(jsonBody, Encoding.UTF8, application/json); // 4. 发起流式请求并处理响应 try { using var response await client.PostAsync(https://api.openai.com/v1/chat/completions, content, context.RequestAborted); response.EnsureSuccessStatusCode(); using var stream await response.Content.ReadAsStreamAsync(); using var reader new StreamReader(stream); char[] buffer new char[8192]; StringBuilder messageBuffer new StringBuilder(); bool inDataSection false; while (!reader.EndOfStream !context.RequestAborted.IsCancellationRequested) { int charsRead await reader.ReadAsync(buffer, 0, buffer.Length); string chunk new string(buffer, 0, charsRead); // 这里是一个简化的SSE事件行解析逻辑 foreach (var line in chunk.Split(\n, StringSplitOptions.RemoveEmptyEntries)) { if (line.StartsWith(data: )) { var dataContent line.Substring(6).Trim(); if (dataContent [DONE]) { // AI流结束信号 await context.Response.WriteAsync(data: [DONE]\n\n); await context.Response.Body.FlushAsync(); return; } // 将AI API返回的SSE数据直接转发给前端 await context.Response.WriteAsync($data: {dataContent}\n\n); await context.Response.Body.FlushAsync(); } } } } catch (TaskCanceledException) { // 客户端断开连接正常终止 } catch (Exception ex) { // 记录日志 Console.Error.WriteLine($AI API调用失败: {ex.Message}); await context.Response.WriteAsync($data: [ERROR] Service temporarily unavailable\n\n); } }); app.Run();这段代码做了几件关键的事情设置正确的SSE响应头这是让浏览器识别为事件流的关键。使用IHttpClientFactory这是最佳实践它能高效管理HttpClient实例的生命周期避免套接字耗尽问题。处理取消令牌context.RequestAborted会在客户端断开连接时触发我们需要捕获这个信号及时停止向AI API请求数据并释放资源避免后台任务空转。简单的流式解析与转发我们逐块读取AI API返回的流解析出data:行然后立即以同样的SSE格式写回给前端响应流。这里做了简化实际AI API返回的JSON可能更复杂需要解析出choices[0].delta.content字段。基本的错误处理网络异常或AI服务错误时会向前端发送一个格式化的错误事件。3. 性能优化与高级错误处理上面的基础版本能跑起来但要用于生产我们还得考虑更多。下面这个表格对比了基础版和优化版需要关注的关键点关注点基础实现的风险优化策略连接管理客户端异常断开可能导致后端仍持有AI API连接浪费资源。紧密绑定HttpContext的取消令牌与AI API请求确保联动取消。缓冲区与刷新频繁调用FlushAsync可能影响性能但间隔太长又导致客户端延迟高。可以考虑使用BufferedStream或在内存中积累少量数据如攒够一个完整句子再刷新在实时性和吞吐量间权衡。AI API稳定性网络抖动或AI服务暂时不可用会导致整个请求失败。实现带退避机制的重试逻辑但注意SSE连接有超时限制重试需快速。对于非流式错误如鉴权失败应立即终止并报错。数据解析直接字符串分割解析SSE流在数据量巨大时可能效率不高且处理复杂JSON时易错。使用System.Text.Json的Utf8JsonReader进行流式JSON解析性能更高更安全。并发与资源大量并发请求可能耗尽HttpClient连接池或服务器资源。配置HttpClient的PooledConnectionLifetime和MaxConnectionsPerServer。考虑对AI API的调用进行速率限制防止因下游服务限制导致失败。让我们深入其中两点看看优化后的代码片段。优化点1更健壮的重试与错误处理我们不应该一遇到异常就立刻给前端返回错误。对于网络超时等临时性故障可以快速重试一次。// 在try-catch块内部发起请求的部分可以这样包装 int retryCount 0; int maxRetries 2; HttpResponseMessage? response null; while (retryCount maxRetries) { try { using var requestTokenSource CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted); requestTokenSource.CancelAfter(TimeSpan.FromSeconds(30)); // 设置单个请求超时 response await client.PostAsync(..., content, requestTokenSource.Token); response.EnsureSuccessStatusCode(); break; // 成功则跳出重试循环 } catch (HttpRequestException ex) when (retryCount maxRetries) { retryCount; // 等待一小段时间再重试指数退避 await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, retryCount)), context.RequestAborted); continue; } } if (response null || !response.IsSuccessStatusCode) { // 重试后仍失败 await context.Response.WriteAsync(data: [ERROR] Upstream service error\n\n); return; } // 后续使用response处理流...优化点2使用Utf8JsonReader进行高效解析AI API返回的通常是JSON格式的SSE事件。手动拼接字符串再解析效率低。我们可以边读流边解析。using var stream await response.Content.ReadAsStreamAsync(); using var reader new StreamReader(stream); var jsonBuffer new byte[4096]; while (!context.RequestAborted.IsCancellationRequested) { int bytesRead await stream.ReadAsync(jsonBuffer, 0, jsonBuffer.Length, context.RequestAborted); if (bytesRead 0) break; var jsonReader new Utf8JsonReader(new ReadOnlySpanbyte(jsonBuffer, 0, bytesRead)); while (jsonReader.Read()) { if (jsonReader.TokenType JsonTokenType.PropertyName jsonReader.ValueTextEquals(choices)) { // 简化逻辑定位到content字段 // ... 实际解析需要根据AI API的响应结构递归处理 if (TryExtractContent(ref jsonReader, out string? content)) { if (!string.IsNullOrEmpty(content)) { await context.Response.WriteAsync($data: {content}\n\n); await context.Response.Body.FlushAsync(); } } } } } // 需要实现TryExtractContent方法这里省略详细JSON遍历逻辑4. 前端如何消费这个SSE流后端准备好了前端也得跟上。用JavaScript消费SSE非常简单但也有一些细节需要注意。// 前端JavaScript示例 const eventSource new EventSource(/api/chat/stream?message你好请介绍一下你自己); eventSource.onmessage (event) { const data event.data; if (data [DONE]) { console.log(流式传输结束); eventSource.close(); return; } if (data.startsWith([ERROR])) { console.error(服务端错误:, data); eventSource.close(); return; } try { // 假设后端转发的是AI API原始的JSON字符串 const parsed JSON.parse(data); // 例如OpenAI的格式parsed.choices[0].delta.content const chunk parsed.choices[0]?.delta?.content || ; // 将chunk追加到你的UI元素中 document.getElementById(output).innerText chunk; } catch (e) { // 如果不是JSON可能是其他格式的数据 console.log(收到数据:, data); } }; eventSource.onerror (err) { console.error(EventSource failed:, err); // 错误发生时EventSource会自动尝试重连 // 如果需要处理不可恢复的错误可以在这里关闭 // eventSource.close(); };前端的关键在于错误处理监听onerror事件但注意SSE协议本身会尝试自动重连所以这个错误可能只是暂时的网络问题。解析数据根据你后端转发的具体格式是原始AI API的JSON还是你处理后的纯文本来解析event.data。连接管理在收到结束信号[DONE]或致命错误[ERROR]时主动调用eventSource.close()来释放资源。5. 部署与监控要点代码写完了本地也跑通了上线前还得过几关。部署配置超时设置确保你的反向代理如Nginx、IIS和负载均衡器为SSE连接配置了足够长的超时时间例如30分钟。否则连接可能在AI模型还在生成时就被掐断。# Nginx 示例配置 location /api/chat/stream { proxy_pass http://your_backend; proxy_set_header Connection ; proxy_http_version 1.1; proxy_buffering off; # 关键禁用代理缓冲数据才能实时推送 proxy_read_timeout 1800s; # 设置长超时 proxy_cache off; }连接数限制SSE是长连接每个活跃用户都会占用一个连接。评估你的服务器资源内存、文件描述符限制能支撑多少并发SSE连接。监控与调试日志在代理端记录关键事件如连接建立、AI API调用开始/结束、错误发生。但注意不要记录完整的AI响应内容可能包含敏感数据。指标监控活跃SSE连接数、AI API调用延迟、错误率。这些指标能帮你发现性能瓶颈或下游服务问题。客户端断连处理如前所述利用CancellationToken确保后端资源及时回收。可以在日志中记录连接持续时间分析用户行为。我在一个内部知识库问答项目中实际应用了这套方案。最初版本没有处理客户端突然关闭页面或刷新导致服务器后台堆积了不少僵尸请求。后来加上了RequestAborted的联动取消并配置了更合理的HttpClient超时系统就稳定多了。另一个教训是关于缓冲的一开始为了追求极致实时性每个字符都刷新在高并发下给服务器带来了不小压力。后来改为积累一小段文本比如遇到句号、换行符再刷新用户体验几乎没有感知但服务器负载显著下降。