WPF + Semantic Kernel + DeepSeek 实战:AI文档批量摘要工具开发全记录
WPF + Semantic Kernel 实战:AI文档批量处理工具开发记录
一、背景
做了快9年C# / WPF开发,最近开始探索AI应用方向。
第一个项目选了「AI文档批量处理工具」,
取名叫文省事,主要功能是:
- 批量生成文档摘要
- 批量提取关键信息
- 结果导出Excel / TXT
本文记录完整开发过程和踩过的坑。
二、技术选型
| 模块 | 选择 | 原因 |
|---|---|---|
| UI框架 | WPF .NET 8 | 擅长,开发效率高 |
| AI框架 | Semantic Kernel | 微软官方,.NET原生支持 |
| AI模型 | DeepSeek / 硅基流动 | 国内可用,价格低 |
| PDF读取 | Aspose.PDF | 文字提取稳定 |
| Word读取 | DocumentFormat.OpenXml | 官方库,免费 |
| Excel导出 | ClosedXML | API简洁,免费 |
| 架构 | MVVM | 代码解耦 |
为什么选 Semantic Kernel
Semantic Kernel 是微软专门为.NET开发者
做的AI框架,相比直接调用HttpClient有几个优势:
- 原生支持C#,API设计符合.NET习惯
- 自动管理对话上下文(ChatHistory)
- 支持多种模型无缝切换
- 内置流式输出支持
三、项目结构
AiDocTool/
├── Models/
│ └── DocumentItem.cs // 文档数据模型
├── Services/
│ ├── DocumentReader.cs // 文档读取(TXT/PDF/DOCX)
│ ├── AiDocumentService.cs // AI处理核心
│ └── BatchProcessor.cs // 批量处理+导出
├── ViewModels/
│ └── MainViewModel.cs // MVVM数据层
├── Converters.cs // 值转换器
├── MainWindow.xaml // 主界面
├── MainWindow.xaml.cs // 交互逻辑
├── ApiKeyDialog.xaml // API Key设置弹窗
└── ApiKeyDialog.xaml.cs
四、核心实现
4.1 接入 DeepSeek(兼容OpenAI格式)
DeepSeek 的API完全兼容 OpenAI 格式,
用 Semantic Kernel 接入只需要两行配置:
builder.AddOpenAIChatCompletion(
modelId: “deepseek-chat”,
apiKey: “你的APIKey”,
endpoint: new Uri(“https://api.deepseek.com”)
);
同样,硅基流动也是兼容 OpenAI 格式:
builder.AddOpenAIChatCompletion(
modelId: “deepseek-ai/DeepSeek-V3”,
apiKey: “你的APIKey”,
endpoint: new Uri(“https://api.siliconflow.cn/v1”)
);
这意味着只需要改两个参数就能切换AI服务商。
4.2 文档摘要的Prompt设计
Prompt设计对结果质量影响很大。
我最终用的版本:
var prompt = $“”"
请对以下文档内容进行总结,要求:
1. {lengthDesc}
2. 语言简洁,重点突出
3. 直接输出摘要内容,
不需要"摘要:"等前缀
文档内容:
{truncated}
""";
几个关键点:
- 明确说「直接输出,不要前缀」
否则AI会加「以下是摘要:」之类的废话 - 长度描述要具体
「200字」比「适中」效果好很多 - 文档内容要截断
避免超出token限制
4.3 PDF读取
使用 Aspose.PDF 提取文字:
using var doc = new Document(filePath);
var absorber = new TextAbsorber();
doc.Pages.Accept(absorber);
return absorber.Text;
注意:Aspose.PDF免费版对PDF页数有限制
但文字提取功能不受影响,对我们的场景够用。
4.4 批量处理的节流控制
批量处理时需要在每次请求之间加延迟
避免频繁调用被API限流:
// 避免频繁请求
if (i < list.Count - 1)
await Task.Delay(600, ct);
600ms是测试后的经验值
太短容易限流,太长影响处理速度。
4.5 WPF事件在初始化阶段提前触发
这是我踩的一个经典WPF坑。
RadioButton的Checked事件
会在InitializeComponent()过程中就触发,
这时候其他控件可能还没初始化完,
导致NullReferenceException。
解决方案:用IsLoaded判断拦截:
private void SummaryLength_Changed(
object sender, RoutedEventArgs e)
{
if (!IsLoaded || _vm == null ||
sender is not RadioButton rb)
return;
if (rb == RadioShort)
_vm.SummaryLength = SummaryLength.Short;
else if (rb == RadioDetailed)
_vm.SummaryLength = SummaryLength.Detailed;
else
_vm.SummaryLength = SummaryLength.Medium;
}
IsLoaded为true说明窗口已完全加载,
可以安全地访问所有控件。
五、踩过的坑
坑1:HTTP 402 余额不足
调用DeepSeek返回402错误。
原因:
- 新注册的免费额度有有效期
- 过期或用完后直接返回402
- 错误信息不够直观
解决:
- 充值,或者换用硅基流动
- 硅基流动新用户有14元免费额度
坑2:PDF扫描版无法提取文字
扫描版PDF本质上是图片,
Aspose.PDF提取出来是空字符串。
目前处理方式:
- 检测提取结果是否为空
- 是空则提示用户「暂不支持扫描版PDF」
- 后续考虑接入OCR解决
坑3:文档内容超出token限制
大文档直接传给AI会超出限制报错。
解决:截断到8000字符:
private static string TruncateContent(
string content, int maxLength)
{
if (content.Length <= maxLength)
return content;
return content[…maxLength] +
“\n\n[文档内容较长,已截取前段处理]”;
}
更好的方案是分块处理,
后续版本会优化。
坑4:API Key存储
第一版直接让用户输入Key,
保存到AppData目录的配置文件。
var configPath = Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData),
“AiDocTool”, “config.txt”);
注意不要把Key写死在代码里
更不要提交到GitHub。
六、打包发布
dotnet publish -c Release -r win-x64
–self-contained true
-p:PublishSingleFile=false
–self-contained true 让用户不需要
单独安装.NET运行时,开箱即用。
七、后续计划
- 支持超长文档分块处理
- 支持扫描版PDF(OCR)
- 支持更多格式(Excel/PPT)
- 流式输出(打字机效果)
- 本地模型支持(Ollama)
工具已发布,咸鱼搜索「文省事」可以找到。
如果本文对你有帮助,点个赞 🙏
后续会持续更新C# + AI实战内容。
源码
后续整理后开源到GitHub,届时更新链接。
更多推荐

所有评论(0)