C# 实现AI合同审查:从读取、风险标注到批量签发

合同审查是企业采购、人事、法务中高频却重复的工作:从 Word、PDF 中提取签约方、有效期、付款条件与违约条款,再逐条比对企业标准。传统做法要么人工逐页通读,要么为每个模板编写字段映射代码,难以应对格式各异、数量波动的合同流入。本文以供应商协议审查为例,演示如何借助 Spire.Agent.Office 在 .NET 中用自然语言指令完成合同读取、风险标注与摘要生成,并对比三种自动化方案与能力边界。


一、场景与痛点:合同审查为何适合交给 AI

合同相关的自动化需求可归结为三类重复劳动:

  • 读取:从格式各异的协议中提取签约方、日期、付款条件与义务条款
  • 核查:发现缺失条款或异常表述
  • 生成:将清单数据转化为可签署的合同文本

这三类任务适合交给语言模型而非手写规则,原因如下:

  1. 输入非结构化:对方合同格式千差万别,针对单一排版写的规则下一份就失效,而语言模型直接阅读文本。
  2. 输出是文档形态 :最终交付物是格式正确的 .docx / .pdf,而非纯文本字符串,这正是文档处理组件的价值所在。
  3. 数量持续波动:单月入职 50 人或审查 200 份供应商协议,需要的是配置驱动而非逐模板编码。
  4. 审查与生成一体:摘要风险标注与批量生成新合同可由同一套能力承载。

同一套"指令 + 模板 + 可选数据"的模式,可覆盖团队常见需求:

  • 供应商协议审查:标注付款条件、责任上限与终止条款中与我方标准不一致之处。
  • 劳动合同批量生成:按 employees.xlsx 每行生成一份劳动合同,使用指定模板并保留版式。
  • 保密协议处理:摘要 NDA 的保密期限、允许披露范围与违约救济方式。
  • 租赁合同分析:提取租金、租期、续约选项与维修义务,输出为结构化表格。

二、能力边界:AI 合同代理能做什么与不能做什么

在把 AI 引入合同流程前,明确能力边界比追求"全自动"更重要。以下基于工程实践梳理 Spire.Agent.Office 在合同场景中的实际能力与局限:

能力项 说明
提取结构化信息 签约方、生效日期、付款条件、义务条款等
生成摘要简报 长篇协议浓缩为单页摘要
批量生成合同 基于模板与数据源批量生成
保留原版式 通常保持表格样式、字体与版式(不保证100%)
标记异常条款 偏离标准模板表述的风险点
法律审查判断 高风险协议的专业判断需人工完成
合规性保证 不保证符合各地法律法规
条款谈判 不代表企业进行谈判或接受要约
零差错输出 需人工复核,不保证无人参与时零差错
法规解读 新颖或模糊条款需转交法务
识破隐藏风险 刻意模糊表述背后的风险仍需人工识别

分工原则: 代理负责读取、抽取与起草------即律师助理原本要花数小时完成的机械工作;最终的法律判断由人类律师掌握。这条边界既保证工具的实用性,也确保流程的可辩护性。

数据流向: 文档读取、生成与版式处理在 .NET 应用本地完成;但 AI 理解所需的正文文本会发往你配置的模型------是否出网取决于模型是私有 / 本地部署还是托管 API。组件不会把合同文件另存到第三方文档 SaaS,但合同正文会随模型调用离开应用。若合规要求合同文本不得离开内网,应使用私有化模型端点,而非默认云端 API。


三、方案对比:在 .NET 中自动化合同处理的三种方法

.NET 中做合同自动化常见三条路线,代码量、格式保真度与维护成本差异明显:

方案 代码量 格式保真度 维护成本 推荐场景
文档 AI 代理 (大模型 + 文档层) 一条指令 + 约 10 行配置 高(真实 Word/PDF,通常保持版式) 低(改指令即可) 不想自建大模型管线的团队
裸 LLM API (OpenAI/Claude + 自有代码) 高(提示、解析、文件 I/O 全要写) 低(模型本身不读写 Office 文件) 高(RAG、路由、错误处理自己扛) 已在运行大模型技术栈的团队
传统 SDK (Spire.Office 或同类) 每类文档数十行 高(确定性) 高(每个映射都是代码) 固定、明确、很少变动的文档

关键点: 大模型无法在没有文档处理层的情况下编辑合同模板,传统 SDK 又无法理解自然语言请求------文档 AI 代理把两者结合:模型决定提取或填充什么,文档层保证文件真实且格式良好。对于固定、明确、很少变动的文档,确定性 SDK 仍是正确选择;只有当模板、输入和需求变化频繁到"重新编码成为瓶颈"时,代理才真正赢得一席之地。


四、技术实现:用 C# 完成合同审查、审批与批量签发

4.1 环境准备与配置

① 目标框架硬性要求。 Spire.Agent.Office 当前版本(11.6.2)仅支持 .NET 10.0 。使用 .NET 8 / 6 / Framework 的项目执行 dotnet add package 会直接报不兼容。请先确认或新建 .NET 10 项目:

bash 复制代码
# 新建 .NET 10 控制台项目(后续可改造成类库 / Web 项目)
dotnet new console -n ContractReview -f net10.0
cd ContractReview

② 获取商业授权。 Spire.Agent.Office 的 AI 能力由 SpireToken 激活,正式生产环境须使用商业授权------免费版仅限功能评估,存在水印与用量限制。授权激活后才可在项目中安装对应版本的 SDK:

  • 申请入口:通过 Spire.Agent.Office 官方授权渠道获取商业版 SpireToken,说明企业使用场景与并发量即可获取正式 Key
  • 拿到 SpireToken 后 ,在代码里定义为变量,供后续 AIOptions 引用:
csharp 复制代码
// 在此粘贴你从销售处获取的 SpireToken(生产环境须使用商业版授权)
string spireToken = "在此粘贴你的SpireToken";

③ 在本地项目安装 SDK。 授权就绪后,在 .NET 10 项目中安装 Spire.Agent.Office(已通过传递依赖包含 Spire.DocSpire.PDF 等文档 SDK,通常无需单独安装):

bash 复制代码
dotnet add package Spire.Agent.Office
# 传递依赖会自动带入 Spire.Doc / Spire.PDF;
# 仅在需要单独引用某个文档 SDK 时才追加安装,例如:
# dotnet add package Spire.Doc
# dotnet add package Spire.Pdf

以下示例假设授权已激活、SDK 已安装。发布前请对照所用 SDK 版本核实 API 名称与签名。

4.2 审查工作流是怎样的

合同审查在落地时是一条清晰的工作流:配置代理一次,遍历收件箱里的每份供应商协议(Word 或 PDF),用自然语言指令生成 Markdown 摘要简报并标记异常条款;随后由确定性代码决定通过名单;最后为通过者批量签发合同。在 .NET 代码里对应几个关键步骤------加载文档、连接 AI 处理器、执行指令、检查结果。

关键 API 说明:

API 说明
AIOptions 承载工作目录、SpireToken 与超时等配置
doc.AI(options) 返回 AI 文档处理器实例(AIDocumentProcessor
ExecuteInstruction(doc, instruction, savePath, attachments) 接收文档、自然语言指令、保存路径与附件路径四个参数,返回 AIResult。注意:第一个参数 doc 必须是文档对象本身(即调用 .AI(options) 的那个对象),是固定签名------不要省略,也不要误以为它和指令是同一类东西。
result.Success / result.ErrorMessage 务必先判断 result.Success 后再继续,失败时可从 result.ErrorMessage 读取原因

发布前必核: 文中 API 名称、参数名与返回值结构(AIOptionsdoc.AI()ExecuteInstructionAIResult.SuccessAIResult.ErrorMessageAIResult.TokenUsage 等)均为示意性名称。Spire.Agent.Office 的实际 API 可能采用不同命名(例如方法可能是 ExecuteAsyncRunInstruction,返回类型可能叫 AgentResult,扩展方法命名空间也可能不同)。在你自己的项目中编译前,请务必对照你所安装版本的官方 SDK 文档与 IntelliSense 逐项核对,不可直接照抄下文代码。

4.3 控制台应用集成合同审查与签发流程

法律和采购团队每周重复的任务通常是三件:审查 新达成的供应商协议 → 由确定性规则审批 通过名单 → 为获批供应商签发合同。下面把这条链路落到控制台代码,可作为企业内部合同处理服务的参考骨架。

场景一:审查本周到达的每份协议

配置代理一次,读取收件箱文件夹,把每份协议汇总为一份 Markdown 简报,可直接粘贴进审核追踪系统:

csharp 复制代码
using System.IO;
using Spire.Agent.Office.AI;       // AIOptions / AIResult
using Spire.Agent.Office.Extensions; // .AI(options) 扩展方法
using Spire.Doc;                     // Document(Word 模板需要)
using Spire.Pdf;                     // PdfDocument(PDF 审查需要)
// 注:以上命名空间以实际 SDK 为准;若 .AI() 方法无法解析,请查看官方文档

string spireToken = "在此粘贴你的SpireToken"; // 来自 4.1 的官网申请

// 批量任务可能耗时数分钟,显式设置超时避免默认超时中断
AIOptions agentOptions = new AIOptions
{
    WorkDir = @"C:\legal-ops\output",
    SpireToken = spireToken,
    TimeoutMs = 300_000
};

// 指令用英文书写------对大多数 LLM 而言英文 prompt 表达更稳定;
// 你也可以用中文,只要把输出格式与关注维度说清楚即可
string reviewPrompt =
    "Review this supplier agreement and write a Markdown brief: a one-row table with " +
    "the parties, effective date, payment terms, and termination clause, then a bullet " +
    "list of any clauses that look unusual for a standard supplier agreement. " +
    "Save the brief to the specified output path as Markdown.";

Directory.CreateDirectory(@"C:\legal-ops\output");

foreach (string file in Directory.GetFiles(@"C:\legal-ops\inbox", "*.pdf"))
{
    string briefPath = Path.Combine(
        @"C:\legal-ops\output", Path.GetFileNameWithoutExtension(file) + ".md");

    using (PdfDocument agreement = new PdfDocument())
    {
        agreement.LoadFromFile(file);
        // 第一个参数 agreement 即文档对象本身,是固定签名
        AIResult result = agreement.AI(agentOptions).ExecuteInstruction(
            agreement, reviewPrompt, briefPath, new string[] { });

        if (result is null || !result.Success)
        {
            throw new InvalidOperationException(
                $"Review failed for {Path.GetFileName(file)}: {result?.ErrorMessage}");
        }
    }
}

关键 API 调用:

API 作用
PdfDocument.LoadFromFile() 打开供应商协议 PDF
agreement.AI(agentOptions) 连接 AI 文档处理器
ExecuteInstruction(doc, instruction, savePath, attachments) 执行审查并写出 Markdown 简报(首个参数为 agreement 本身)
AIResult.Success / AIResult.ErrorMessage 验证结果并暴露错误
场景二:用确定性代码决定通过名单

决策必须由确定性、可审计的代码完成,这正是第二节"可辩护性"的落地------LLM 只负责抽取,判定权力留在代码里。下面的伪代码示意说明这条审批闸门如何运作(ParseBriefs 解析场景一产出的 Markdown 简报,WriteVendorList 导出 Excel,两者由你用现有解析 / 导出代码实现):

csharp 复制代码
// ── 伪代码示意:以下代码展示审批闸门的结构,不可直接编译 ──
// ParseBriefs / WriteVendorList 由你用现有解析 / 导出代码实现;
// briefsPath 指向场景一输出的 Markdown 简报目录。

// 数据结构:审查结果与获批供应商
record ReviewResult(string Vendor, int PartyCount, decimal LiabilityCap, int TerminationNoticeDays);
record ApprovedVendor(string SupplierName, decimal Amount, string PaymentTerms, DateOnly EffectiveDate);

string briefsPath = @"C:\legal-ops\output"; // 场景一的输出目录

// 审批闸门:规则写在代码里,可版本化、可审计,不交给 LLM
var approved = new List<ApprovedVendor>();
foreach (ReviewResult r in ParseBriefs(briefsPath))
{
    bool passes =
        r.PartyCount >= 2 &&
        r.LiabilityCap >= 1_000_000m &&   // 我方责任上限下限
        r.TerminationNoticeDays <= 60;    // 终止通知天数上限
    if (passes)
    {
        // Amount / PaymentTerms / EffectiveDate 来自供应商主数据(由你接入)
        approved.Add(new ApprovedVendor(
            r.Vendor,
            Amount: 10_000m,
            PaymentTerms: "Net 30",
            EffectiveDate: DateOnly.FromDateTime(DateTime.Today)));
    }
}

// 将通过名单写成 approved-vendors.xlsx
// 列需与模板占位符对应:SupplierName, Amount, PaymentTerms, EffectiveDate
WriteVendorList(@"C:\legal-ops\data\approved-vendors.xlsx", approved);

只有通过的供应商才进入下一步;这份 Excel 正是场景三要读取的数据源。

场景三:为获批供应商签发合同

一份模板加一份批准列表即可驱动批量签发。模板里用 {``{SupplierName}}{``{Amount}}{``{PaymentTerms}}{``{EffectiveDate}} 标记供应商数据(指令里的占位符名必须与模板、以及下方传统 SDK 示例完全一致),把输出路径传为 null,代理会把每个供应商的一份独立 PDF 写入工作目录:

csharp 复制代码
using System.IO;
using System.Linq;                   // .Where() 用于事后回收生成文件
using Spire.Agent.Office.AI;        // AIOptions / AIResult
using Spire.Agent.Office.Extensions; // .AI(options) 扩展方法
using Spire.Doc;                      // Document

AIOptions agentOptions = new AIOptions
{
    WorkDir = @"C:\legal-ops\output",
    SpireToken = "在此粘贴你的SpireToken",
    TimeoutMs = 300_000
};
string[] attachments = { @"C:\legal-ops\data\approved-vendors.xlsx" };

using (Document contract = new Document())
{
    contract.LoadFromFile(@"C:\legal-ops\templates\supplier-contract.docx");
    DateTime started = DateTime.Now; // 用于事后回收本次生成的文件

    // 占位符名与模板/SDK 示例统一为具体字段名
    AIResult result = contract.AI(agentOptions).ExecuteInstruction(
        contract, // 第一个参数是文档对象本身
        "Issue one purchase contract per approved vendor: read 'approved-vendors.xlsx' " +
        "row by row, fill the {{SupplierName}}, {{Amount}}, {{PaymentTerms}}, {{EffectiveDate}} " +
        "fields in this template with each vendor's data, preserve the template layout and " +
        "styling, and save each contract as an independent PDF in the work directory.",
        null, // null output path -> 代理把每份合同写入 WorkDir 的会话子目录
        attachments);

    if (result is null || !result.Success)
    {
        throw new InvalidOperationException(
            $"Contract issuing failed: {result?.ErrorMessage}");
    }

    // AIResult 不含多文件清单(其成员为 Success / ErrorMessage / Duration / TokenUsage),
    // 多份 PDF 落在 WorkDir 下的会话子目录;执行后扫描收集,供归档与审计:
    var generated = Directory.EnumerateFiles(agentOptions.WorkDir, "*.pdf", SearchOption.AllDirectories)
        .Where(f => File.GetLastWriteTime(f) >= started)
        .ToList();
    foreach (var pdf in generated)
        Console.WriteLine($"已生成合同:{pdf}"); // TODO: 写入审计日志 / 归档
}

生产环境提示: 上面的时间窗扫描(>= started)在连续多次运行或有人手动拷文件进 WorkDir 时可能误收旧文件。生产环境应直接记录代理本次创建的会话目录路径并只扫描该目录,而非对整个 WorkDir 做全局扫描。

关键 API 调用:

API 作用
Document.LoadFromFile() 加载合同模板
contract.AI(agentOptions) 连接 AI 文档处理器
ExecuteInstruction(doc, instruction, savePath, attachments) 为每个供应商行签发一份独立合同(首个参数为 contract 本身)
AIResult.Success / AIResult.ErrorMessage 验证结果并暴露错误

每份合同都会写入代理在 WorkDir 下管理的会话子文件夹(例如 output\.office_use_tmp\Word\<session>\output_contracts,具体路径随版本变化,以官方文档为准),因此建议把 WorkDir 指向存档文件夹。输出可保存为 DOCX、DOC、HTML、OFD、Markdown、XPS 等格式(本示例以 PDF 交付;具体支持列表以官方文档为准)。

4.4 传统 SDK vs AI Agent:代码量对比

传统 SDK 的写法是定位每个 {``{占位符}} 并手动替换,每个字段一行代码,还要为每个表格列做映射,然后循环每一行导出一个文件:

csharp 复制代码
// using Spire.Doc; 需在文件顶部引入
// 数据模型(若项目别处已声明可省略)
record ApprovedVendor(string SupplierName, decimal Amount, string PaymentTerms, DateOnly EffectiveDate);

// 示意:单个供应商循环体,vendor 来自 approved-vendors.xlsx 的一行
ApprovedVendor vendor = new ApprovedVendor(
    SupplierName: "示例供应商",
    Amount: 50_000m,
    PaymentTerms: "Net 45",
    EffectiveDate: new DateOnly(2026, 8, 15));

using (Document doc = new Document())
{
    doc.LoadFromFile(@"C:\legal-ops\templates\supplier-contract.docx");

    // 占位符名与场景三指令、模板保持一致
    doc.Replace("{{SupplierName}}", vendor.SupplierName, caseSensitive: false, wholeWord: true);
    doc.Replace("{{Amount}}", vendor.Amount.ToString(), caseSensitive: false, wholeWord: true);
    doc.Replace("{{PaymentTerms}}", vendor.PaymentTerms, caseSensitive: false, wholeWord: true);
    doc.Replace("{{EffectiveDate}}", vendor.EffectiveDate.ToString("yyyy-MM-dd"), caseSensitive: false, wholeWord: true);

    doc.SaveToFile(@"C:\legal-ops\output\PO-001.pdf"); // ... 对每个供应商行重复此块
}

而 AI Agent 用一条指令就替代了上述编排(一次性读取整个 Excel、批量输出多份 PDF):

csharp 复制代码
// using Spire.Doc; using Spire.Agent.Office.AI; 需在文件顶部引入
AIOptions agentOptions = new AIOptions
{
    WorkDir = @"C:\legal-ops\output",
    SpireToken = "在此粘贴你的SpireToken",
    TimeoutMs = 300_000
};
string[] attachments = { @"C:\legal-ops\data\approved-vendors.xlsx" }; // 场景二的产出

using (Document contract = new Document())
{
    contract.LoadFromFile(@"C:\legal-ops\templates\supplier-contract.docx");

    AIResult result = contract.AI(agentOptions).ExecuteInstruction(
        contract,
        "Issue one purchase contract per approved vendor: read 'approved-vendors.xlsx' " +
        "row by row, fill the {{SupplierName}}, {{Amount}}, {{PaymentTerms}}, {{EffectiveDate}} " +
        "fields in this template with each vendor's data, preserve the template layout and " +
        "styling, and save each contract as an independent PDF in the work directory.",
        null,
        attachments);

    // 生产环境务必像场景三那样判空 + 检查 result.Success 并记录日志
    if (result is null || !result.Success)
        throw new InvalidOperationException($"签发失败:{result?.ErrorMessage}");
}

两者产出同样的合同。区别在于:SDK 为每个占位符增加一个 Replace 调用、为每个列增加一个映射;而 Agent 把同样的工作吸收进一条指令。当模板或数据布局变化时,你改的是指令而非代码。

4.5 工程实践要点

要点 说明
指令要具体 自然语言指令应明确输出格式(如 Markdown 表格、要点列表)与关注维度(如付款条件、终止条款),模糊指令会导致结果不可控
保留人工复核 AI 负责提取与初筛,律师负责风险判断与最终决策,二者分工不可省略,尤其涉及责任上限、管辖法律等条款时
及时记录日志 result 的状态、耗时与输出路径写入日志,便于事后追溯与审计(场景三已演示如何回收生成文件清单)
资源及时释放 Document / PdfDocument 使用 using 包裹,避免批量处理时产生文件句柄残留
超时配置 批量生成可能耗时数分钟,AIOptions.TimeoutMs 建议显式设置(如 300_000),避免默认超时中断长任务

五、技术选型:文档 AI 代理的三个判据

从前面的方案对比可以看出,"文档 AI 代理"之所以能介于裸 LLM 与传统 SDK 之间,是因为它同时满足三个技术判据。选型时可据此评估任意一个候选实现(包括 Spire.Agent.Office):

判据 技术含义 不满足时的代价
直接读写 Office 结构 代理操作的是 .docx / .pdf 的真实结构(段落、表格、域),而非把它们降级为纯文本再交给模型 需自建文本提取与重建管道,格式信息丢失,等同于裸 LLM 路线
保留版式与样式 输出文档保持模板的条款编号、表格、字体;指令中可标注"preserve layout" 法务 / HR 拿到的是无格式文本,需人工二次排版,自动化失去意义
作为 .NET 对象内嵌 Document.AI() 这类扩展方法形式存在,无需独立微服务或跨进程管道 需引入额外服务层,部署、鉴权、超时都要自己编排

Spire.Agent.Office 是同时满足这三个判据的实现之一:Word、Excel、PowerPoint、PDF 对它而言是同一套对象模型,Document 对象既可被传统 SDK 操作,也可调用 AI() 处理器------这正是前文场景代码得以成立的前提。


六、总结:让合同处理回归业务判断本身

AI 合同审查的本质,是将语言理解与文档处理能力结合到 .NET 应用内部,让开发者用描述任务的自然语言替代为每个模板编写的字段映射代码。Spire.Agent.Office 承担语言理解与编排,确定性的文档处理层负责将模型产出落地为格式正确的 Word 与 PDF。这种方式尤其适合合同数量波动大、格式不统一的团队------配置驱动比逐模板编码更务实,业务可维护性比代码灵活性更重要。

从供应商协议审查、劳动合同批量生成到保密协议摘要,合同以 Word 或 PDF 形态流入应用,便可由 Spire.Agent.Office 自动完成读取、抽取与初稿生成;而是否通过、风险如何裁定这类判断,始终留在确定性代码与人类手中。作为面向企业的文档 AI 代理组件,它以自然语言为纽带,将合同处理能力嵌入 .NET 应用架构,让机械的读取与起草交给自动化,让法务团队聚焦于真正需要专业判断的风险决策。

相关推荐
腾视科技-AIoT2 小时前
私有云时代来临:AI NAS如何重塑你的数字生活?
人工智能·ai·生活·nas·ai算力模组·ainas·腾视科技
fthux2 小时前
装闭 RenoPit 源码解析(12):从AI分析结果到React避坑报告
人工智能·ai·开源·github·open source·renopit
安_3 小时前
langgrah使用pgsql做会话存储会自己创建表吗
数据库·ai
DS随心转APP3 小时前
生成word文档的ChatGPT格式乱码终结者:AI导出鸭横向测评与工程化架构解析 摘要
人工智能·ai·chatgpt·word·deepseek·ai导出鸭
今天你AiPy了吗4 小时前
AI桌面助手的隐私底线 数据可落本地不出域,还能一键切换云端,全能智能体
人工智能·python·ai·ai编程·智能体
spencer_tseng4 小时前
Deepseek has increased its price 2026.08.17
ai·deepseek
JaydenAI5 小时前
[基于AgentEvals的自动化评估-06]以LLM-as-a-Judge评估LangGraph执行轨迹
ai·langchain·agent·evaluation·openevals·agentevals
阿标在干嘛5 小时前
3种主流NLP模型在招标信息匹配中的效果实测
人工智能·自然语言处理