合同管理系统案例开发AI自动提取合同风险条款实战_技术版

实战:用 LLM + 结构化输出自动提取合同风险条款并标注红线

面向开发者 / 架构师 | 开发者实战教程 | 适配 CSDN / 掘金 / 博客园

一份 50 页采购合同,法务平均 4 小时才能看出哪里有坑。本文带你从零写一个可运行的风险条款提取器:把合同分块、用 LLM 做结构化输出、给每条风险打分、按三档策略标红,并强制回贴原文防幻觉。核心参考 draft-legal 的 Redline Agent 思路。


一、整体流水线

复制代码
合同文本
   │  按条款分块(保留"第X条"语义边界)
   ▼
clause chunks
   │  逐块送 LLM + 结构化 schema
   ▼
RiskClause[] (category / severity / quote / suggestion)
   │  按 RED_LINE_POLICY 过滤
   ▼
redline 清单(高亮原文 + 风险评分)

分块策略很关键:按条款("第 X 条"或编号)切,而非固定字数,保证一个 chunk 是一条完整义务,模型才不会断章取义。


二、结构化输出:用 schema 锁住格式

单靠"请提取风险"会拿到一段自由文本,没法做红线标注。正确做法是用 structured output(function calling / JSON mode)强制模型返回固定 schema。

python 复制代码
from pydantic import BaseModel
from openai import OpenAI

class RiskClause(BaseModel):
    category: str          # PAYMENT / DELIVERY / WARRANTY / LIABILITY
    severity: int          # 1-3, 3 最高
    quote: str             # 必须回贴原文片段
    reason: str            # 为什么有风险
    suggestion: str | None # 修改建议

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")  # 自托管

def extract_risk(clause_text: str) -> RiskClause:
    resp = client.chat.completions.create(
        model="qwen2.5:32b",
        messages=[{
            "role": "system",
            "content": "你是合同风控工程师,只输出结构化风险,quote 必须原样来自文本。",
        }, {
            "role": "user",
            "content": f"审查以下条款:\n{clause_text}",
        }],
        response_model=RiskClause,   # instructor / structured output
    )
    return resp

三、三档红线策略:给提示词换尺度

保守 / 中等 / 激进不是三个模型,而是三个审查尺度。把它做成一个 policy 开关,注入 system prompt。

python 复制代码
RED_LINE_POLICY = {
    "conservative": "仅标注明确违法或重大金额偏差的条款。",
    "moderate":     "标注违法 + 不公平 + 模糊义务条款。",
    "aggressive":   "标注一切对己方不利的表述,含隐含风险。",
}

def redline(clause: str, level: str) -> list[RiskClause]:
    policy = RED_LINE_POLICY[level]
    return extract_risk_with_policy(clause, policy)

实测口径(52 页汽配合同,本地 qwen2.5:32b):中等档召回率 92%,误标率经人工门压到 3% 以下;单份约 12 分钟,Token 成本约 0.4 元(自托管零 API 费)。


四、风险评分与引用溯源(防幻觉核心)

最危险的不是漏标,是模型"编"出一条不存在的风险。强制每条输出回贴 quote,无出处一律不显示------这和 draft-legal 的引用溯源纪律一致。

python 复制代码
def safe_redlines(clauses: list[str], level: str) -> list[RiskClause]:
    out = []
    for c in clauses:
        r = redline(c, level)
        if not r.quote or r.quote.strip() not in c:   # 出处校验
            continue                                    # 无原文字段段,丢弃
        out.append(r)
    return out

# 风险评分加权:severity * 权重(category)
RISK_WEIGHT = {"LIABILITY": 3, "PAYMENT": 2, "WARRANTY": 2, "DELIVERY": 1}
def score(risks: list[RiskClause]) -> float:
    return sum(RISK_WEIGHT.get(r.category, 1) * r.severity for r in risks)

五、标红渲染:把清单贴回原文

提取完直接在编辑器里高亮。TipTap(draft-legal / killua2312 采用)可用 decoration 标红:

typescript 复制代码
// 用 TipTap decoration 高亮风险片段
import { Decoration } from '@tiptap/pm/view';

function buildRedlineDecorations(risks: RiskClause[]) {
  return risks.map(r => {
    const from = doc.textBetween(0, doc.content.size).indexOf(r.quote);
    return Decoration.inline(from, from + r.quote.length, {
      class: r.severity >= 3 ? 'redline-high' : 'redline-mid',
    });
  });
}

六、踩坑经验

  • 中文术语被切坏:"连带责任""质保金"常被拆成普通词导致漏标。Redline 前加制造业词典做关键词锚定,召回率 +5 点。
  • 小模型幻觉:本地 7B 误标率约 12%,32B 降到 3% 以下。取舍是 Redline 用 32B 保准,Ask 用 7B 保快。
  • PDF 表格丢字段:参数在表格里被揉成一团,准确率 71% → 换带版面分析解析器 → 94%。解析质量决定上层一切。
  • 出处造假 :模型有时把融合结果编成不存在条款。强制回贴 quote 且做 in 原文校验,无 id/无原文不展示。

开放性问题

如果让你给"风险评分"设计一套加权公式,你会按条款类型(付款/违约/质保)加权,还是按交易对手风险等级动态加权?为什么?


动手试试: 想跑通这套提取器?拉取 合同管理系统开源 Demo ,或到 官网免费试用 ,上传一份合同看红线清单。也欢迎 加入技术社群 交流 LLM 结构化输出在合同场景的调参经验。

相关推荐
vx158897262011 天前
合同管理系统之AI多代理如何重构制造业合同审查流程
合同管理系统·合同归档管理·合同ai多代理审查