大多数企业软件仍然依赖某个人去读一份 PDF,然后把上面的数字手动输入到表单里。供应商发票、收银小票、前台扫描的身份证。如今的大语言模型已经能很好地读懂这些文档,但如果你问模型要“发票数据”,得到的往往是一段说明性文字,或者一个格式随它心意的 JSON,而且完全没有办法知道它对哪些数值没有把握。
sgcWebSockets 2026.10 为 Delphi、C++Builder 和 .NET 新增了 TsgcAIDocumentExtractor。你给它一个 PDF 或一张图像,它会通过 TsgcAIChat 询问你已经在用的模型,然后返回按 Schema 验证过的 JSON,每个字段都带有置信度。该组件不包含在当前的下载版本中,它将随 2026.10 版本一起发布。
TsgcAIDocumentExtractor 使用 Anthropic Claude 读取一张真实的发票 PDF,并返回带有每个字段置信度的经过验证的 JSON。YouTube 上也有。
输入 PDF,输出 JSON
在窗体上放置一个 TsgcAIChat 和一个 TsgcAIDocumentExtractor,把提取器指向这个聊天组件,选择一个预设,然后调用 ExtractFile。OpenAI、Anthropic 和 Gemini 可以直接读取 PDF 文件。DeepSeek、Grok、Mistral 和 Ollama 只接受图像,因此对于这些提供商,你需要把页面以 PNG 或 JPEG 格式发送。
uses
sgcAI, sgcAI_Chat, sgcAI_DocumentExtractor;
procedure TForm1.btnExtractClick(Sender: TObject);
begin
oChat := TsgcAIChat.Create(Self);
oChat.Provider := aicpAnthropic;
oChat.ChatOptions.ApiKey := 'your-api-key';
oExtractor := TsgcAIDocumentExtractor.Create(Self);
oExtractor.AIChat := oChat;
oExtractor.Preset := dpInvoice;
Memo1.Lines.Text := oExtractor.ExtractFile('invoice.pdf');
end;
这就是视频中展示的流程,并且已经针对 Anthropic Claude 在一张真实的发票 PDF 上进行了实机验证。ExtractFiles 会在一次请求中发送同一文档的多个页面,而 ExtractAttachments 接受你已经以 base64 形式保存在内存中的文档。
提取器的工作是通过 TsgcAIChat 完成的,而 TsgcAIChat 本身也获得了同样的能力,可用于你自己的提示词。ChatWithDocument 会随消息一起发送一个 PDF 或图像,ChatWithAttachments 一次发送多个,而 ChatJSONWithAttachments 会针对它们请求一个 JSON 格式的回答。新增的 TLSOptions 属性用于设置每个提供商所使用的 OpenSSL 版本、IOHandler 和证书。
Memo1.Lines.Text := oChat.ChatWithDocument('Summarize this contract', 'contract.pdf');
oFiles := TsgcAIChatAttachments.Create;
try
oFiles.AddFile('page1.png');
oFiles.AddFile('page2.png');
Memo1.Lines.Text := oChat.ChatJSONWithAttachments(
'List every line item as JSON', oFiles);
finally
oFiles.Free;
end;
预设与你自己的 Schema
三个预设涵盖了常见的文档类型:dpInvoice、dpReceipt 和 dpIDCard。每一个都自带 JSON Schema 和提取指令,因此不需要再配置任何东西。对于其他任何文档,把 Preset 设为 dpCustom,然后自己编写 Schema。
oExtractor.Preset := dpCustom;
oExtractor.SchemaName := 'purchase_order';
oExtractor.Schema.Text :=
'{"type": "object", "additionalProperties": false,' +
' "required": ["order_number", "supplier", "total"],' +
' "properties": {' +
' "order_number": {"type": "string"},' +
' "supplier": {"type": ["string", "null"]},' +
' "total": {"type": "number"}}}';
oExtractor.Instructions.Text := 'Write every amount without a currency symbol.';
使用 OpenAI 时,Schema 会以严格模式发送,因此必须遵守严格模式的规则:每个对象都要声明 "additionalProperties": false,每个属性都要出现在 required 中,可选值要使用可为空的类型,例如 ["string", "null"]。内置的预设已经遵循了这些规则。如果想从某个预设出发再添加一个字段,可以用 TsgcAIDocumentExtractor.GetPresetSchema(dpInvoice) 把它复制出来。
验证与重试
模型的回答永远不会未经检查就直接进入你的代码。提取器会在本地对照 Schema 进行验证:类型、必需属性、嵌套的对象和数组,以及枚举。如果不匹配,组件会告诉模型哪里出了问题,并再请求一次。ValidationRetries 设置重试的次数,默认值为 1。
如果重试之后数据仍然无效,提取器会抛出一个异常。把 RaiseOnInvalid 设为 False,可以照样拿到数据,并从 LastResult.ValidationErrors 中读取问题,每一行都是一条 path: message。
oExtractor.ValidationRetries := 1;
oExtractor.RaiseOnInvalid := False;
Memo1.Lines.Text := oExtractor.ExtractFile('invoice.pdf');
if not oExtractor.LastResult.Valid then
Memo1.Lines.AddStrings(oExtractor.LastResult.ValidationErrors);
置信度与人工复核
有效的 JSON 不等于正确的 JSON。一个模糊的总额或者一个被涂改过的日期,仍然可能生成一个格式良好的数字。所以模型还会报告它对每个提取字段的把握程度,提取器把这些值交给你。LastResult.Confidence 是其中最低的一个,LastResult.FieldConfidence('total') 返回某一条路径的值,而 OnLowConfidence 会针对每一个低于 MinConfidence 的字段触发一次。
oExtractor.MinConfidence := 0.8;
oExtractor.OnLowConfidence := OnLowConfidenceEvent;
Memo1.Lines.Text := oExtractor.ExtractFile('invoice.pdf');
if oExtractor.LastResult.FieldConfidence('total') < 0.8 then
ShowMessage('Check the total by hand');
procedure TForm1.OnLowConfidenceEvent(Sender: TObject; const aField: string;
aConfidence: Double);
begin
lstReview.Items.Add(Format('%s: %.2f', [aField, aConfidence]));
end;
这让复核队列变成了一个人能够真正处理完的东西:他们只需要检查模型没有把握的那两个字段,而不是整张发票。
本地 OCR,数据不离开本机
有些文档完全不能发送到任何云服务。把一个 OCR 组件关联到提取器,并挑选适合你的引擎。TsgcAIOCRTesseract 运行安装在本机上的 Tesseract OCR 引擎。TsgcAIOCRWindows 使用内置于 Windows 10 及更高版本中的文字识别功能,因此无需安装任何东西。把 OCRMode 设为 domAlways 后,图像永远不会发送给模型。OCR 引擎在本地读取文字,再由 Ollama 这样的文本模型完成提取,因此整条流程都在你自己的硬件上运行。
oOCR := TsgcAIOCRTesseract.Create(Self);
oOCR.Language := 'eng';
oChat.Provider := aicpOllama;
oChat.ChatOptions.Model := 'llama3.1';
oExtractor.OCR := oOCR;
oExtractor.OCRMode := domAlways;
oExtractor.Preset := dpReceipt;
Memo1.Lines.Text := oExtractor.ExtractFile('receipt.png');
Windows 引擎接受 BCP-47 语言标记,例如 en-US,也可以把 Language 留空以使用用户配置文件中的语言。
oWinOCR := TsgcAIOCRWindows.Create(Self);
oWinOCR.Language := 'en-US';
oExtractor.OCR := oWinOCR;
oExtractor.OCRMode := domFallback;
Memo1.Lines.Text := oExtractor.ExtractFile('invoice.png');
第二个示例中使用的 domFallback 是另一个选项。提取器会先尝试视觉请求,只有在该请求失败时才使用识别出的文字,并触发 OnOCRFallback,让你知道发生了这种情况。LastResult.UsedOCR 和 LastResult.OCRText 会在事后告诉你数据是由哪条路径产生的。OCR 步骤读取的是图像,因此扫描版 PDF 要以页面图像的形式输入。
.NET 版的 C#
sgcWebSockets 的 .NET 版本拥有同名的相同组件,位于 esegece.sgcWebSockets 命名空间中。预设、置信度值和事件的工作方式完全相同,两种 OCR 引擎 TsgcAIOCRTesseract 和 TsgcAIOCRWindows 也同样都有。
using esegece.sgcWebSockets;
var chat = new TsgcAIChat();
chat.Provider = TsgcAIChatProvider.aicpAnthropic;
chat.ChatOptions.ApiKey = "your-api-key";
var extractor = new TsgcAIDocumentExtractor();
extractor.AIChat = chat;
extractor.Preset = TsgcAIDocumentPreset.dpInvoice;
extractor.MinConfidence = 0.8;
extractor.OnLowConfidence += (sender, field, confidence) =>
Console.WriteLine($"review {field}: {confidence:0.00}");
string json = extractor.ExtractFile("invoice.pdf");
Console.WriteLine(json);
Console.WriteLine($"valid: {extractor.LastResult.Valid}, confidence: {extractor.LastResult.Confidence:0.00}");
可用性
TsgcAIDocumentExtractor、TsgcAIOCRTesseract、TsgcAIOCRWindows 以及 TsgcAIChat 新增的方法将随 sgcWebSockets 2026.10 一起发布,面向 Delphi、C++Builder 和 .NET。它们不在你今天可以下载的版本中。2026.10 发布后,会出现在下载页面上。
包含每个属性和事件、以及一份分步发票指南的完整参考文档,位于 TsgcAIDocumentExtractor 帮助中。关于 AI 组件所做的其他一切,请参阅 sgcAI 产品页面和 sgcWebSockets AI 页面。
延伸阅读
- 面向 Delphi 和 .NET 的 Server-Sent Events 客户端,同样是 2026.10 中的新特性
- 在 Delphi 中构建 AI 智能体:函数调用与 MCP 工具使用
有疑问、反馈或需要迁移方面的帮助?联系我们。回复你的会是真正编写这些代码的人。
