当前位置: 首页 > news >正文

WPF + Semantic Kernel 实现流式输出

打字机效果完整代码

一、什么是流式输出

普通AI调用:
等待 → 等待 → 等待 → 一次性返回全部结果

流式输出:
开始返回 → 逐字/逐词输出 → 像打字机一样

用户体验差距非常明显。
这也是为什么 ChatGPT 用打字机效果,
而不是等全部生成完再显示。


二、Semantic Kernel 流式输出 API

核心方法是:

GetStreamingChatMessageContentsAsync

和普通调用的对比:

// 普通调用(一次性返回)
var result = await chatService
.GetChatMessageContentAsync(history);
string content = result.Content;

// 流式调用(逐块返回)
await foreach (var chunk in chatService
.GetStreamingChatMessageContentsAsync(history))
{
string piece = chunk.Content; // 每次一小块
}


三、在服务层封装流式方法

使用 IAsyncEnumerable 作为返回类型,
配合 yield return 逐块返回内容:

using System.Runtime.CompilerServices;

public async IAsyncEnumerable SummarizeStreamAsync(
string content,
[EnumeratorCancellation] CancellationToken ct = default)
{
var history = new ChatHistory();
history.AddUserMessage($“请总结以下内容:\n{content}”);

await foreach (var chunk in _chatService! .GetStreamingChatMessageContentsAsync( history, cancellationToken: ct)) { if (!string.IsNullOrEmpty(chunk.Content)) yield return chunk.Content; }

}

注意事项:

  1. 必须加 [EnumeratorCancellation] 特性
    才能正确处理取消操作
  2. 过滤空 chunk,避免UI无意义刷新
  3. CancellationToken 要透传,
    支持用户取消

四、在 WPF 里实时更新 UI

关键点:
流式输出在后台线程产生数据,
需要用 Progress 或者直接更新
绑定属性来刷新UI。

因为 ObservableCollection 和
INotifyPropertyChanged 的绑定属性
在 WPF 里可以跨线程更新
(只要是通过属性setter更新的):

// 在处理循环里
var sb = new StringBuilder();

await foreach (var chunk in
_aiService.SummarizeStreamAsync(content, ct))
{
sb.Append(chunk);

// 直接更新绑定属性,WPF自动刷新UI item.Result = sb.ToString();

}

item.Status = ProcessStatus.Completed;


五、完整测试代码(控制台版)

先用控制台验证流式输出效果:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
modelId: “deepseek-ai/DeepSeek-V3”,
apiKey: “你的硅基流动Key”,
endpoint: new Uri(“https://api.siliconflow.cn/v1”)
);

var kernel = builder.Build();
var chatService =
kernel.GetRequiredService();

var history = new ChatHistory();
history.AddUserMessage(“用200字介绍一下人工智能的发展历史”);

Console.WriteLine(“流式输出开始:”);

await foreach (var chunk in chatService
.GetStreamingChatMessageContentsAsync(history))
{
if (!string.IsNullOrEmpty(chunk.Content))
Console.Write(chunk.Content); // 不换行,逐字追加
}

Console.WriteLine(“\n完成”);


六、踩过的坑

坑1:忘记加 [EnumeratorCancellation]

IAsyncEnumerable 方法里如果有
CancellationToken 参数,
必须加 [EnumeratorCancellation] 特性,
否则取消操作不会正确传递。

// ❌ 错误
public async IAsyncEnumerable StreamAsync(
CancellationToken ct = default)

// ✅ 正确
public async IAsyncEnumerable StreamAsync(
[EnumeratorCancellation] CancellationToken ct = default)

坑2:chunk.Content 可能为 null 或空

流式返回的每个 chunk
Content 属性可能是 null 或空字符串,
必须过滤:

// ❌ 不过滤,可能空字符串刷新UI
yield return chunk.Content;

// ✅ 过滤空值
if (!string.IsNullOrEmpty(chunk.Content))
yield return chunk.Content;

坑3:StringBuilder 要在循环外声明

// ❌ 每次chunk都新建,结果只有最后一块
await foreach (var chunk in …)
{
var sb = new StringBuilder(); // 错误位置
sb.Append(chunk.Content);
item.Result = sb.ToString();
}

// ✅ 循环外声明,累积所有内容
var sb = new StringBuilder(); // 正确位置
await foreach (var chunk in …)
{
sb.Append(chunk.Content);
item.Result = sb.ToString();
}


七、效果对比

普通调用:
处理10秒钟 → 结果突然全部出现
用户:不知道有没有在运行,很焦虑

流式输出:
开始后立刻看到文字蹦出来
用户:直观感受到AI在工作,体验好很多


八、完整项目

这是我做的「文省事」AI文档批量处理工具
里用到的核心技术。

工具功能:
批量生成文档摘要
批量提取关键信息
支持PDF/Word/TXT
结果导出Excel

感兴趣的可以咸鱼搜索「文省事」。

如果本文对你有帮助,点个赞🙏
后续持续更新C# + AI实战内容。

http://www.jsqmd.com/news/1123824/

相关文章:

  • QT系统篇(5)(下)
  • 网盘下载慢到抓狂?这个开源浏览器脚本让你轻松获取高速直链
  • 从资产测绘到攻击链构建:一次SRC漏洞挖掘实战复盘
  • 零基础YOLO模型训练全流程:从环境配置到本地部署实战指南
  • 机械工程论文降AI工具免费推荐:2026年机械工程毕业论文降AI4.8元知网达标完整方案
  • 基于LTC6903与STM32的数字控制振荡器设计与优化
  • 学习记录 安装wrf大涡模拟(2026-6-29)
  • 车辆动力学中质心侧偏角的高精度估计方法与实践
  • Linux第05篇:文本处理三剑客——grep/sed/awk 从入门到实战
  • lu,足趾容积测量仪 足趾肿胀测量仪
  • 【下一代智慧养老:架构与实战连载】全书目录
  • PCF8591与PIC18F45K50的ADC/DAC信号处理实战
  • GDSDecomp技术实现:PCK文件极速修改与Godot逆向工程架构设计
  • 备战Java面试:核心知识点梳理
  • 蜜獾算法优化Transformer的单变量时序预测Matlab实现
  • Playwright MCP复用Chrome登录态:AI自动化测试与RPA新范式
  • Gemma 2深度实测:开源小模型中文实战选型指南
  • 网工笔记20260702
  • 架构评审数据化:别让评审会只剩观点碰撞
  • NVIDIA Profile Inspector:解锁显卡隐藏性能,让你的游戏体验飞起来
  • 华硕笔记本轻量级控制中心:释放硬件潜力的终极解决方案
  • 自己写一个《英雄无敌3》战斗AI
  • 免费分享最新IDEA安装及授权教程(附带文件)
  • 在Web应用中嵌入专业数学公式编辑:MathLive的技术实践
  • 49. OrCAD封装库中应该怎么删除Pin Group属性?I Cadence Allegro 电子设计 快问快答
  • 【私房菜集 HarmonyOS ArkTS 实战系列 01】从 0 到 1:单机菜谱应用的工程骨架
  • ORIN NX 16G + ubuntu22.04 环境安装及模型部署
  • 终极指南:40+经典DSGE模型库如何加速你的宏观经济研究
  • FigmaCN:5分钟快速汉化Figma界面,中文设计师的完整解决方案
  • Nutstore Sync 和 WebDAV 有什么区别?Obsidian 坚果云同步新旧方案完整对比