cookbook · vertical · legal-clause-extract
Recipe · vertical

Contracts in, labeled clauses out — with spans.

A local .kolm file that takes a contract (PDF or DOCX) and returns every clause labeled with type, parties, governing law, monetary thresholds, and the exact character span. Verifier rejects any clause label whose span doesn't byte-match a substring of the input. Trained on 1,000 paired contracts/markups from your firm.

base modelqwen2.5-7b-instruct
gold contracts1,000 (700 train / 300 eval)
k-score floor0.86
artifact size2.6 GB
compile time~84 min
spec sourceclosed-vocab + span-grounded

What this recipe does

The "ChatGPT, summarize this contract" workflow has the failure mode you'd expect: a number that isn't in the contract appears in the summary. This recipe makes that mode unreachable. Every label points to a byte-exact span in the input. If the model hallucinates a "liability cap of $5M" that isn't in the contract, the verifier rejects it before the artifact returns.

The output schema is closed-vocab on clause type (32 categories from your firm's playbook) and free-text on span. Atypical clauses get bucketed to "other" with a span and a free-text label — these are the rows your associates should look at first.

The spec

{
  "output_kind": "json",
  "schema": {
    "required": ["clauses", "governing_law", "parties"],
    "properties": {
      "clauses": { "type": "array", "items": {
        "required": ["type", "span", "start", "end"],
        "properties": {
          "type": { "$ref": "clause-types.json" },
          "span": { "type": "string" },
          "start": { "type": "integer" },
          "end": { "type": "integer" },
          "monetary_thresholds": { "type": "array", "items": {"type":"string"} },
          "label_if_other": { "type": "string" }
        }
      } },
      "governing_law": { "type": "string" },
      "parties": { "type": "array", "items": {"type":"string"} }
    }
  },
  "verifier": {
    "span_must_byte_match_input": true,
    "clause_type_must_be_in_taxonomy": true,
    "monetary_threshold_must_appear_in_span": true
  }
}

Compile

kolm compile "contract clause extractor with span-grounded labels" \
  --base qwen2.5-7b-instruct \
  --pairs ./gold-contracts/ \
  --taxonomy ./clause-types.json \
  --verifier span-byte-grounded,closed-vocab \
  --k-floor 0.86 \
  --output legal-clause-extract.kolm

ok wrote legal-clause-extract.kolm
   k_score=0.89  signature=hmac-sha256

K-score gate

K-score 0.89 held-out 300 contracts · span byte-match 100% · clause-type accuracy 91% · missed-clause rate 6% · "other" bucket precision 84%

Run-time profile

Mac Studio
3.6s
/avg contract
RTX 5090
820ms
Hospital server
4.2s
CPU x86
5.6s

Deploy

# DMS hook — every contract uploaded gets a clause-list as a sidecar:
on_contract_upload = (file) => {
  const x = kolm.run('legal-clause-extract.kolm', pdf.text(file));
  dms.attach(file.id, 'clause-list.json', x);
  if (x.clauses.some(c => c.type === 'liability_cap' && c.monetary_thresholds.length === 0)) {
    review.queue(file.id, 'liability cap clause has no number — review');
  }
};