using Spire.Agent.Office.AI; using Spire.Agent.Office.Extensions; using Spire.Doc; using Spire.Pdf; using Spire.Xls; using Spire.Presentation; using System.Collections.Concurrent;
string instruction = "Analyze this document and determine its type. " + "Return one of: Invoice, Contract, PurchaseOrder, " + "Receipt, BankStatement, Unknown. Include a confidence " + "score between 0 and 1. Save the result as JSON.";
string ext = Path.GetExtension(filePath).ToLowerInvariant(); AIResult? result = null;
if (ext == ".pdf") { using (PdfDocument doc = new PdfDocument()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, classifyPath, newstring[] { }); } } elseif (ext == ".xlsx" || ext == ".xls") { using (Workbook doc = new Workbook()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, classifyPath, newstring[] { }); } } elseif (ext == ".pptx" || ext == ".ppt") { using (Presentation doc = new Presentation()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, classifyPath, newstring[] { }); } } else { using (Document doc = new Document()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, classifyPath, newstring[] { }); } }
string instruction = documentType switch { "Invoice" => "Extract all invoice fields and write them as key-value " + "pairs in a sheet named 'Fields' with columns 'Field' and " + "'Value'. Use these exact field names: VendorName, " + "InvoiceNumber, IssueDate, DueDate, Subtotal, Tax, Total, " + "PONumber. Extract line items into a sheet named 'LineItems' " + "with columns: Description, Quantity, UnitPrice, Amount. " + "Write the extracted data to a structured Excel workbook.",
"Contract" => "Extract all contract fields and write them as key-value " + "pairs in a sheet named 'Fields' with columns 'Field' and " + "'Value'. Use these exact field names: Party1, Party2, " + "EffectiveDate, TerminationDate, ContractValue, " + "PaymentTerms, Signatory1, Signatory2. Extract key " + "obligations into a sheet named 'Obligations' with " + "columns: Description, Party, Deadline. Write the " + "extracted data to a structured Excel workbook.",
"PurchaseOrder" => "Extract all purchase order fields and write them as " + "key-value pairs in a sheet named 'Fields' with columns " + "'Field' and 'Value'. Use these exact field names: " + "PONumber, VendorName, IssueDate, ExpectedDeliveryDate, " + "ShippingAddress, Total. Extract requested items into a " + "sheet named 'LineItems' with columns: Description, " + "Quantity, UnitPrice, Amount. Write the extracted data " + "to a structured Excel workbook.",
_ => "Extract all key fields and values from this document. " + "Write the extracted data to a structured Excel workbook." };
string ext = Path.GetExtension(filePath).ToLowerInvariant(); AIResult? result = null;
if (ext == ".pdf") { using (PdfDocument doc = new PdfDocument()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, extractPath, newstring[] { }); } } elseif (ext == ".xlsx" || ext == ".xls") { using (Workbook doc = new Workbook()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, extractPath, newstring[] { }); } } elseif (ext == ".pptx" || ext == ".ppt") { using (Presentation doc = new Presentation()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, extractPath, newstring[] { }); } } else { using (Document doc = new Document()) { doc.LoadFromFile(filePath); result = doc.AI(agentOptions).ExecuteInstruction( doc, instruction, extractPath, newstring[] { }); } }
public ValidationResult Validate( ExtractionResult extracted, string documentType) { var errors = new List<string>(); var warnings = new List<string>(); double confidence = 1.0;
case"Contract": var party1 = GetField(extracted.Fields, "Party1"); var party2 = GetField(extracted.Fields, "Party2"); if (string.IsNullOrEmpty(party1) || string.IsNullOrEmpty(party2)) { errors.Add( "Contract must identify at least two parties"); confidence -= 0.25; }
// 警告:缺失可选合同元数据 string[] optionalContract = { "EffectiveDate", "ContractValue", "PaymentTerms" }; double contractPenalty = RoutingPolicy.OptionalFieldBudget / optionalContract.Length; foreach (var field in optionalContract) { varvalue = GetField(extracted.Fields, field); if (string.IsNullOrEmpty(value)) { warnings.Add( $"Optional field missing: {field}"); confidence -= contractPenalty; } } break;
default: // 未覆盖的文档类型需要人工审核 errors.Add( $"No validation rules for type '{documentType}'"); confidence -= 0.5; break; }
// 硬性兜底:零字段提取始终无效 if (extracted.Fields.Count == 0) { errors.Add("No fields were extracted from the document"); confidence -= 0.5; }
publicasync Task<BatchResult> ProcessBatchAsync( string inputDirectory, string outputDir, int maxConcurrency = 5) { var files = Directory.GetFiles(inputDirectory); var semaphore = new SemaphoreSlim(maxConcurrency); var results = new ConcurrentBag<PipelineResult>();
var tasks = files.Select(asyncfile => { await semaphore.WaitAsync(); try { var result = await RunPipelineAsync(file, outputDir); results.Add(result); } catch (Exception ex) { results.Add(new PipelineResult { Status = $"{PipelineResult.Failed}: {ex.Message}", Validation = new ValidationResult { IsValid = false, Errors = new List<string> { ex.Message } }, AuditLog = new List<string> { $"Error processing {file}: {ex}" } }); } finally { semaphore.Release(); } });
await Task.WhenAll(tasks);
int successful = results.Count( r => r.Status == PipelineResult.Routed); int errored = results.Count(r => r.Status.StartsWith( PipelineResult.Failed));
SemaphoreSlim 限制并发度,避免压垮 AI 服务或下游系统。每份文档独立走完四个阶段。批量报告把结果分成文档离开流水线的三种方式:Routed(通过校验并已发送下游)、Flagged(到达阶段 4 但需人工审核)、Errored(在产出结果前抛出异常)。Flagged 作为余数计算而非匹配状态字符串,因此三个计数器之和始终等于 Total——一份意外失败的结果会被报告为需要审核,而不是从报告中消失。并发上限的取值取决于 AI 服务的速率限制、文档大小和应用资源。
var w9 = w9Task.Result; var contract = contractTask.Result; var bank = bankTask.Result;
// 交叉校验:三份文档中的名称必须一致 var w9Name = GetField(w9.Extraction.Fields, "VendorName"); var contractName = GetField(contract.Extraction.Fields, "Party2"); var bankName = GetField(bank.Extraction.Fields, "AccountHolder");
if (w9Name == null || contractName == null || bankName == null) { returnnew OnboardingResult { Status = "Flagged", Issue = "Could not extract vendor name from one or more documents" }; }
string summaryInstruction = $"Create a vendor onboarding summary for {w9Name}. " + "Read the attached W-9, contract, and bank statement. " + "Compile the vendor's legal name, tax ID, contract terms, " + "and banking details into a formatted Word document. " + "Save the summary to the output path.";
using (Document summary = new Document()) { summary.LoadFromFile( Path.Combine(AppContext.BaseDirectory, "templates", "onboarding-summary.docx"));
AIResult result = summary.AI(agentOptions).ExecuteInstruction( summary, summaryInstruction, summaryPath, attachments);
if (!pipeline.Validation.IsValid) returnnew APResult { Status = "Requires review", Errors = pipeline.Validation.Errors };
// 与采购订单交叉比对 var poNumber = GetField(pipeline.Extraction.Fields, "PONumber"); if (string.IsNullOrEmpty(poNumber)) returnnew APResult { Status = "No PO reference" };
var poData = await _erpService.GetPurchaseOrderAsync(poNumber); if (poData == null) returnnew APResult { Status = "PO not found in ERP" };
// 三方匹配:发票 vs 采购订单 vs 收货单 var grData = await _erpService.GetGoodsReceiptAsync(poNumber); var matchResult = ThreeWayMatch( pipeline.Extraction, poData, grData);
if (matchResult.IsMatch) { await _erpService.PostInvoiceForPaymentAsync( pipeline.Extraction); returnnew APResult { Status = "Posted for payment" }; }
returnnew APResult { Status = "Three-way match failed", Discrepancies = matchResult.Discrepancies }; }
string instruction = "Analyze this contract and compare it to the attached " + "standard template. Identify non-standard clauses, unusual " + "risk terms, or missing provisions. Generate a redline " + "summary document highlighting the differences and save " + "it to the output path.";
string ext = Path.GetExtension(contractPath).ToLowerInvariant(); AIResult? result = null;
if (ext == ".pdf") { using (PdfDocument contract = new PdfDocument()) { contract.LoadFromFile(contractPath); result = contract.AI(agentOptions).ExecuteInstruction( contract, instruction, analysisPath, attachments); } } elseif (ext == ".pptx" || ext == ".ppt") { using (Presentation contract = new Presentation()) { contract.LoadFromFile(contractPath); result = contract.AI(agentOptions).ExecuteInstruction( contract, instruction, analysisPath, attachments); } } else { using (Document contract = new Document()) { contract.LoadFromFile(contractPath); result = contract.AI(agentOptions).ExecuteInstruction( contract, instruction, analysisPath, attachments); } }