☰
Github Copilot 实战:从零开始用 AI 写一个 OCR 工具(3)——WPF 客户端接入 TaoToken 统一 Key 的配置与验证
2026/9/27 19:24:42 网站建设 项目流程

1. 为什么 WPF OCR 工具需要统一 Key 通道

到这一步,你的 MiOcr 已经能截图、能跑 PaddleOCR 本地识别了。但真正用起来会发现一个尴尬:本地模型只认图不认语义。比如识别出一堆发票字段后,你想让 AI 帮忙判断"这张票据的金额和日期是否匹配",或者把识别结果整理成结构化 JSON,本地 OCR 引擎是做不到的。

这时候就得接大模型。而接大模型最烦的不是写代码,是 Key 管理。你可能会遇到这几种情况:项目里同时用 Claude、GPT、国产模型,每个平台一个 Key、一套计费、一份文档;团队协作时 Key 散落在各人机器上;调试阶段想快速换模型对比效果,改配置改到崩溃。

TaoToken 解决的就是这个:一个统一 Key,走一套 OpenAI 兼容协议,背后可以路由到不同模型。对 WPF 桌面端来说,你只需要在config.toml或settings.json里填一次 Key 和 BaseUrl,代码里用标准 HTTP 请求就能调通。这篇就聚焦"识别逻辑已完成之后"的那一段——怎么把 MiOcr 接到 TaoToken 统一通道上,并做一次真实的连通性验证。

适合谁看:已经跟着前两篇把 WPF + PaddleOCR 跑起来,现在想让工具具备"AI 后处理"能力的 C# 开发者。如果你还没到这一步,建议先把截图和识别跑通再回来。

2. TaoToken 前置准备:Key、BaseUrl 与配置文件定位

TaoToken 的接入点很清晰:官网https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,API 根地址是https://taotoken.net/api。注意 API 地址不带 UTM 参数,配置里就写这个干净的。

你需要先拿到 Key。登录后进控制台,在 API Keys 页面创建一个。这个 Key 就是后面所有请求的凭证,格式类似sk-开头的一串字符。创建后立刻复制保存,页面刷新后不一定能再看到完整值。

注意:Key 不要硬编码进源码提交到 Git。WPF 项目里推荐放在用户目录的配置文件,或者用环境变量注入。下面两种配置骨架都给你。

关于模型选择,TaoToken 支持在请求里指定模型名。OCR 后处理这种任务,通常用中等能力的模型就够,比如做文本纠错、字段抽取、格式转换。如果你要处理复杂版面理解,再换更强的模型。具体可用模型列表在控制台的模型对话页面能看到,也可以直接在那边试跑。

配置文件放哪?WPF 项目常见做法是放在%AppData%\MiOcr\下,或者跟 exe 同目录。我倾向放 AppData,避免安装目录权限问题。下面给出config.toml和settings.json两套骨架,你按项目习惯选一套。

3. 可复制配置:config.toml 与 settings.json 骨架

先看config.toml。Toml 的好处是可读性强,适合手改。C# 侧用Tomlyn这个 NuGet 包解析。

# %AppData%\MiOcr\config.toml [taotoken] api_key = "sk-你的Key" base_url = "https://taotoken.net/api" default_model = "claude-3-5-sonnet" timeout_seconds = 60 [ocr] model_dir = "models/paddleocr" enable_rotate = true enable_180_classify = true [ui] status_timeout_ms = 1500

对应的 C# 模型类:

using Tomlyn; using Tomlyn.Model; public class TaoTokenConfig { public string ApiKey { get; set; } = ""; public string BaseUrl { get; set; } = "https://taotoken.net/api"; public string DefaultModel { get; set; } = "claude-3-5-sonnet"; public int TimeoutSeconds { get; set; } = 60; } public static class ConfigLoader { public static TaoTokenConfig LoadToml(string path) { var text = File.ReadAllText(path); var model = Toml.ToModel(text); var section = (TomlTable)model["taotoken"]; return new TaoTokenConfig { ApiKey = section["api_key"].ToString()!, BaseUrl = section["base_url"].ToString()!, DefaultModel = section["default_model"].ToString()!, TimeoutSeconds = int.Parse(section["timeout_seconds"].ToString()!) }; } }

如果你更习惯 JSON,settings.json版本如下:

{ "taotoken": { "apiKey": "sk-你的Key", "baseUrl": "https://taotoken.net/api", "defaultModel": "claude-3-5-sonnet", "timeoutSeconds": 60 }, "ocr": { "modelDir": "models/paddleocr", "enableRotate": true, "enable180Classify": true } }

JSON 用System.Text.Json直接反序列化即可,不需要额外包:

using System.Text.Json; public static TaoTokenConfig LoadJson(string path) { var json = File.ReadAllText(path); using var doc = JsonDocument.Parse(json); var root = doc.RootElement.GetProperty("taotoken"); return new TaoTokenConfig { ApiKey = root.GetProperty("apiKey").GetString()!, BaseUrl = root.GetProperty("baseUrl").GetString()!, DefaultModel = root.GetProperty("defaultModel").GetString()!, TimeoutSeconds = root.GetProperty("timeoutSeconds").GetInt32() }; }

两套配置的字段含义一致,选一套就行。我实测下来 Toml 在手动改 Key 时更顺手,JSON 在程序生成配置时更方便。

4. CC Switch 切换配置:多环境 Key 的快速切换

开发时经常要在"测试 Key"和"正式 Key"之间切,或者在不同模型之间切。手动改配置文件太慢,这里介绍一个轻量做法:用 CC Switch 的思路管理多份配置。

CC Switch 本质是一个配置切换器,核心逻辑是维护多份 profile,切换时把选中的 profile 写入生效配置文件。你可以自己实现一个简化版:

public class ConfigProfile { public string Name { get; set; } = ""; public TaoTokenConfig Config { get; set; } = new(); } public class ConfigSwitcher { private readonly string _profileDir; private readonly string _activePath; public ConfigSwitcher(string profileDir, string activePath) { _profileDir = profileDir; _activePath = activePath; } public void SwitchTo(string profileName) { var profilePath = Path.Combine(_profileDir, $"{profileName}.json"); if (!File.Exists(profilePath)) throw new FileNotFoundException($"Profile 不存在: {profileName}"); var json = File.ReadAllText(profilePath); File.WriteAllText(_activePath, json); } public IEnumerable<string> ListProfiles() { return Directory.EnumerateFiles(_profileDir, "*.json") .Select(Path.GetFileNameWithoutExtension)!; } }

使用方式:在%AppData%\MiOcr\profiles\下放dev.json、prod.json、claude.json等文件,每个文件是一份完整的settings.json内容。切换时调SwitchTo("dev"),生效配置就被覆盖了。

提示:切换后需要重新加载配置。WPF 里可以在切换按钮的事件里调ConfigLoader.LoadJson刷新内存中的配置对象,不需要重启应用。

如果你不想自己写,也可以在控制台里直接管理多套 Key,按项目分配。CC Switch 的价值在于把"改配置"这个动作从手动编辑变成一次点击,减少出错。

5. 验证请求:一次 OCR 后处理的连通性测试

配置好了,接下来验证通道是否真的通。最直接的方式是发一个最小请求,看返回。这里用HttpClient调 TaoToken 的 chat completions 接口,把 OCR 识别出的一段文本丢进去做纠错。

using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; public class TaoTokenClient { private readonly HttpClient _http; private readonly TaoTokenConfig _config; public TaoTokenClient(TaoTokenConfig config) { _config = config; _http = new HttpClient { Timeout = TimeSpan.FromSeconds(config.TimeoutSeconds) }; _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", config.ApiKey); } public async Task<string> ChatAsync(string prompt) { var url = $"{_config.BaseUrl}/v1/chat/completions"; var payload = new { model = _config.DefaultModel, messages = new[] { new { role = "user", content = prompt } }, max_tokens = 512 }; var json = JsonSerializer.Serialize(payload); var content = new StringContent(json, Encoding.UTF8, "application/json"); var resp = await _http.PostAsync(url, content); var body = await resp.Content.ReadAsStringAsync(); if (!resp.IsSuccessStatusCode) throw new HttpRequestException($"HTTP {(int)resp.StatusCode}: {body}"); using var doc = JsonDocument.Parse(body); return doc.RootElement .GetProperty("choices")[0] .GetProperty("message") .GetProperty("content") .GetString()!; } }

调用验证:

var config = ConfigLoader.LoadJson( Path.Combine(Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData), "MiOcr", "settings.json")); var client = new TaoTokenClient(config); var ocrText = "发票金额:1O0.00元 日期:2O24年1月1日"; var result = await client.ChatAsync( $"请纠正以下OCR识别文本中的错误,只返回纠正后的文本:\n{ocrText}"); Console.WriteLine(result);

预期返回类似:发票金额:100.00元 日期:2024年1月1日。看到这个,说明 Key、BaseUrl、模型名、网络通道全部打通。

如果你想在 UI 里做这个验证,可以在主窗口加一个"测试连接"按钮,点击后调ChatAsync("回复OK"),把返回显示在状态栏。返回OK就说明通道正常。

6. 本篇常见错排查

报 401 Unauthorized:Key 错了或没带上。检查Authorization头是不是Bearer sk-xxx格式,注意 Bearer 后面有个空格。另外确认 Key 没有多余换行或引号。

报 404 Not Found:BaseUrl 拼错了。正确是https://taotoken.net/api,请求路径是/v1/chat/completions。如果你在 BaseUrl 末尾多加了/v1,就会变成/v1/v1/...。检查一下拼接逻辑。

报 model not found:模型名写错了。去控制台的模型对话页面确认可用模型名,注意大小写和版本号后缀。

请求超时:timeoutSeconds设太短,或者网络波动。OCR 后处理的 prompt 通常不长,60 秒足够。如果经常超时,先确认网络能正常访问taotoken.net。

配置改了不生效:WPF 应用启动时加载一次配置到内存,改文件后没重新加载。要么重启应用,要么在切换配置后手动调一次加载方法刷新内存对象。

中文乱码:StringContent的编码要显式指定Encoding.UTF8,否则可能按默认编码发送导致乱码。

截图后识别正常但 AI 后处理没反应:检查RunOcrAndDraw里是否在识别完成后调用了TaoTokenClient。识别和 AI 后处理是两个独立步骤,需要显式串联。

7. 下一步:把统一 Key 用到 Coding Plan

通道验证通过后,你可以把这个TaoTokenClient封装成服务,在 MiOcr 里做更多事:识别结果自动纠错、多语言翻译、结构化字段抽取、生成摘要。每次调用都走同一个 Key,不用为每个能力单独配。

如果你在开发 MiOcr 的过程中也想让 AI 辅助写代码,可以看看 Coding Plan,它把编码场景的模型调用也统一到同一个通道下。配置方式跟这篇一样,填 Key 和 BaseUrl 就能用。

接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite,里面有完整的接口说明和参数列表。API Keys 管理在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite。想先试试模型效果,直接去模型对话页面发一条消息就行,不用写代码。

到这里,MiOcr 的"本地识别 + 云端 AI 后处理"链路就完整了。下一步可以做的:把 AI 后处理做成可配置的 pipeline,识别完自动跑一遍纠错和字段抽取,结果直接填进 UI 的表格里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询