C# + WPF 做了一个 AI 文档批量处理工具,我是怎么从 0 到发布的

📅 发布时间:2026/7/14 5:31:33
C# + WPF 做了一个 AI 文档批量处理工具,我是怎么从 0 到发布的 一、背景做了快 9 年 C# / WPF / WinForm 开发最近开始认真尝试 AI 应用开发方向。与其一直想学 AI 以后能做什么不如先做一个真正能跑起来的小工具。所以我给自己定的思路很简单先做一个体量不大的工具先解决一个明确的问题先把东西发布出去再根据真实反馈慢慢迭代于是我做了这个工具文省事。它的定位很直接批量读取文档用 AI 生成摘要或提取关键信息。目前支持TXTPDFDOCX输出结果支持ExcelTXT这篇文章不只讲技术实现也记录一下从选题、开发到发布的完整过程。二、为什么做这个工具我一开始没有想做特别大的 AI 产品也没有打算一上来就做 Agent、知识库或者复杂工作流。因为对我来说现阶段更重要的是先把一个真正可用的小工具做出来。选择文档处理这个方向主要有几个原因1. 需求比较真实很多场景都会遇到文档太多根本没时间一个个看需要快速知道每份文档讲了什么想批量提取关键信息比如价格、日期、联系人、结论2. 技术实现难度适中这个方向刚好能把我现有的能力用上WPF 做界面C# 处理文件Semantic Kernel 接 AIExcel 导出结果3. 容易快速做出 MVP不需要复杂的用户系统也不需要部署服务端先做成桌面工具就可以验证。三、技术选型模块选择原因UI 框架WPF .NET 8自己最熟悉开发效率高AI 接入Semantic Kernel微软官方.NET 生态友好模型平台硅基流动 / DeepSeek国内可用成本低PDF 读取Aspose.PDF提取文字比较方便Word 读取DocumentFormat.OpenXml官方库免费Excel 导出ClosedXMLAPI 简洁够用架构简化版 MVVM项目不大控制复杂度为什么没用 Python因为我现在做的是AI 应用开发不是训练模型。对我来说现阶段重点是能不能把 AI 能力接起来能不能做成一个真实可用的工具能不能跑通从开发到发布的完整过程这些事情用 C# 完全可以做。四、项目结构项目结构比较简单AiDocTool/ ├── Models/ │ └── DocumentItem.cs ├── Services/ │ ├── DocumentReader.cs │ ├── AiDocumentService.cs │ └── BatchProcessor.cs ├── ViewModels/ │ └── MainViewModel.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ApiKeyDialog.xaml └── ApiKeyDialog.xaml.cs主要分成三层DocumentReader负责读取不同格式的文档内容txt、pdf、docxAiDocumentService负责和 AI 模型通信摘要、提取信息、流式输出BatchProcessor负责把文件读取和AI处理串起来并处理批量执行、导出结果这些事情。五、核心功能设计1. 文档统一读取入口不同文件格式底层处理方式不同但对上层来说只有一个统一入口publicstaticasyncTaskstringReadAsync(stringfilePath){varextPath.GetExtension(filePath).ToLower();returnextswitch{.txtawaitReadTxtAsync(filePath),.pdfawaitReadPdfAsync(filePath),.docxawaitReadDocxAsync(filePath),_thrownewNotSupportedException($不支持的格式{ext})};}这样上层不用关心具体格式只要拿到文本内容就行。2. AI 摘要和提取AI 层主要做两件事SummarizeAsync生成摘要ExtractAsync提取指定信息本质上就是 Prompt 模型调用。摘要 Prompt 核心代码varprompt$ 请对以下文档内容进行总结要求1.{lengthDesc}2.语言简洁重点突出3.直接输出摘要内容不需要任何前缀 文档内容{truncated};varhistorynewChatHistory();history.AddUserMessage(prompt);varresultawait_chatService!.GetChatMessageContentAsync(history,cancellationToken:ct);returnresult.Content?.Trim()??;提取信息 Prompt 核心代码varprompt$ 请从以下文档中提取{extractTarget}要求1.只提取文档中实际存在的信息2.找不到时明确说未找到相关信息3.结果格式清晰每条信息单独一行4.直接输出结果不需要额外解释 文档内容{truncated};Prompt 设计几个关键点明确说「直接输出不需要前缀」否则 AI 会加「以下是摘要」之类的废话长度描述要具体「200字」比「适中」效果好很多文档内容要截断避免超出 token 限制3. 流式输出v1.1 新增了流式输出就是像 ChatGPT 那样结果一个字一个字蹦出来而不是等全部生成完再一次性显示。核心实现publicasyncIAsyncEnumerablestringSummarizeStreamAsync(stringcontent,SummaryLengthlengthSummaryLength.Medium,[EnumeratorCancellation]CancellationTokenctdefault){EnsureInitialized();vartruncatedTruncateContent(content,8000);varhistorynewChatHistory();history.AddUserMessage(prompt);awaitforeach(varchunkin_chatService!.GetStreamingChatMessageContentsAsync(history,cancellationToken:ct)){if(!string.IsNullOrEmpty(chunk.Content))yieldreturnchunk.Content;}}批量处理时实时更新 UIvarsbnewStringBuilder();awaitforeach(varchunkin_aiService.SummarizeStreamAsync(content,summaryLength,ct)){sb.Append(chunk);// 直接更新绑定属性WPF 自动刷新 UIitem.Resultsb.ToString();}用户看到的效果是点击开始后右侧结果框立刻开始有内容出现一个字一个字地显示而不是一直等待。4. 批量处理与导出批量处理器负责把所有步骤串起来for(inti0;ilist.Count;i){if(ct.IsCancellationRequested)break;varitemlist[i];try{// 读取文档内容varcontentawaitDocumentReader.ReadAsync(item.FilePath);// AI 处理流式varsbnewStringBuilder();awaitforeach(varchunkin_aiService.SummarizeStreamAsync(content,summaryLength,ct)){sb.Append(chunk);item.Resultsb.ToString();// 实时更新 UI}item.StatusProcessStatus.Completed;progress.Report(newBatchProgressReport(i,true,));}catch(OperationCanceledException){throw;}catch(Exceptionex){item.ErrorMessageex.Message;item.StatusProcessStatus.Failed;progress.Report(newBatchProgressReport(i,false,ex.Message));}// 避免频繁请求被限流if(ilist.Count-1)awaitTask.Delay(600,ct);}导出 Excel 用 ClosedXML 实现每一行对应一个文件包含文件名、摘要内容、处理状态。六、踩过的坑坑1DeepSeek 返回 HTTP 402现象调用 DeepSeek API 返回HTTP 402: invalid_request_error原因免费额度有有效期过期直接返回 402错误信息不够直观容易误判为代码问题解决方案 ADeepSeek 充值10 元够用很久方案 B换用硅基流动推荐测试阶段使用新用户送 14 元免费额度API 格式完全兼容 OpenAI只需要改两个参数// DeepSeekbuilder.AddOpenAIChatCompletion(modelId:deepseek-chat,apiKey:你的Key,endpoint:newUri(https://api.deepseek.com));// 硅基流动builder.AddOpenAIChatCompletion(modelId:deepseek-ai/DeepSeek-V3,apiKey:你的Key,endpoint:newUri(https://api.siliconflow.cn/v1));坑2PDF 扫描版读取内容为空现象用 Aspose.PDF 提取某些 PDF 文字返回空字符串。原因扫描版 PDF 本质是图片没有真正的文字层文字提取库无法处理。解决提取后判断是否为空明确提示用户vartextabsorber.Text?.Trim();if(string.IsNullOrEmpty(text))thrownewException(PDF 内容为空或为扫描版图片暂不支持扫描版 PDF);坑3WPF 事件初始化阶段触发空引用现象程序启动时直接崩溃报NullReferenceException。原因RadioButton.Checked事件会在InitializeComponent()过程中就触发这时候其他控件可能还没有初始化完成直接访问就会报空引用。解决加IsLoaded判断拦截初始化阶段的事件触发privatevoidSummaryLength_Changed(objectsender,RoutedEventArgse){// IsLoaded 为 true 才说明窗口已完全加载if(!IsLoaded||_vmnull||senderisnotRadioButtonrb)return;if(rbRadioShort)_vm.SummaryLengthSummaryLength.Short;elseif(rbRadioDetailed)_vm.SummaryLengthSummaryLength.Detailed;else_vm.SummaryLengthSummaryLength.Medium;}坑4文档内容超出 Token 限制现象大文档处理时 API 直接报错。原因模型有 token 上限文档内容过长会超出限制。解决处理前截断内容超长部分提示用户privatestaticstringTruncateContent(stringcontent,intmaxLength8000){if(content.LengthmaxLength)returncontent;returncontent[..maxLength]\n\n[文档内容较长已截取前段进行处理];}后续版本计划做分段处理逐段调用 AI 再合并结果。坑5API Key 不能写死在代码里错误做法privateconststringApiKeysk-xxxx;// 危险正确做法让用户自己输入保存到本地 AppData 目录varconfigPathPath.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),AiDocTool,config.txt);// 保存File.WriteAllText(configPath,apiKey);// 读取if(File.Exists(configPath))ApiKeyFile.ReadAllText(configPath).Trim();同时把配置文件加入.gitignore避免提交到 GitHub 时泄露密钥。七、从开发完到发布出去是完全不同的阶段做完这个工具之后我最大的感受不是技术层面的而是“做出来和发出去”是两件完全不同的事情。开发阶段关注的是功能有没有跑通代码逻辑对不对界面有没有问题发布之后开始关注的是标题别人看得懂吗封面图够不够吸引人用户为什么没有点进来为什么曝光有了但浏览很低描述里的卖点够不够清晰这部分反而让我感觉比写代码更像是在学产品和运营。而且我发现一个很有意思的现象技术文章的数据比工具销售的数据更容易起来。写文章、讲技术、记录过程反而更容易被别人找到、认可。这也是为什么我决定继续坚持写技术文章把每一个功能点、每一个踩过的坑都记录下来。八、目前发布后的真实情况工具目前已经发布说说真实的数据情况。咸鱼曝光225浏览11成交0曝光到浏览的转化率偏低说明标题或者封面图不够吸引人用户看到了但没有点进去的欲望。这周已经调整了标题下周看数据变化。CSDN 技术文章第一篇阅读 198点赞 4收藏 3第二篇阅读 190点赞 9收藏 4这个数据比我预期的要好说明技术内容是有价值的方向继续就行。整体感受第一周没有成交很正常但数据告诉我问题在哪里这比什么都重要。知道问题在标题和封面那就改标题和封面。知道技术文章数据不错那就继续写技术文章。九、接下来怎么迭代工具层面流式输出v1.1已在做支持超长文档分段处理支持扫描版 PDF接 OCR支持更多格式Excel、PPT处理前后文件大小对比显示运营层面继续优化咸鱼标题和封面调整小红书内容风格从工具介绍改成真实记录每周持续发技术文章积累长尾流量等有用户咨询后主动问反馈内容层面每周一篇技术文章CSDN / 知乎 / 掘金每周三篇小红书工具类 / 生活类 / 复盘类技术文章和功能迭代同步做完就写十、写在最后这个工具不一定会很快带来多少收入但对我来说有一个很重要的意义我开始把会开发这件事往能做出可发布的产品这件事上走了。以前做项目交付给甲方就结束了。现在做工具要想的是用户是谁他们有什么问题我的东西能不能真正帮到他们他们会不会愿意为这个付钱这些问题比任何技术框架的学习都让我成长更快。如果你也是 C# / .NET 开发者也在考虑做 AI 方向的副业我的建议只有一句话先做一个小而完整的东西把它发出去。不用等完全准备好不用一上来就做很大先跑通一次完整的流程比想很多都重要。工具已发布咸鱼搜索「文省事」可以找到。如果本文对你有帮助点个赞 后续会持续更新 C# AI 实战内容欢迎关注。