Instructions to use litillabs/litil-contract-extractor-1.7b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use litillabs/litil-contract-extractor-1.7b with PEFT:
from peft import PeftModel from transformers import AutoModelForCausalLM base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-1.7B") model = PeftModel.from_pretrained(base_model, "litillabs/litil-contract-extractor-1.7b") - Notebooks
- Google Colab
- Kaggle
LiTiL Contract Extractor 1.7B
What this model does
LiTiL Contract Extractor answers a specific contract question by returning the relevant language from the agreement. It can capture a date, party, governing-law clause, renewal term, or another supported field, and it uses the stable value NOT_PRESENT when the requested term is not found.
In a contract-intelligence stack, place it after document parsing and retrieval. A classifier or search layer can identify the likely clause, the extractor can capture the exact value or supporting text, and the system can store that answer with its source location. Those structured fields can populate contract records, support comparisons, and provide the facts needed by a playbook or review workflow.
- Useful for: capturing dates, parties, governing law, renewal terms, and other contract fields
- Give it: contract text and a question about the term you need
- It returns: the supporting text or
NOT_PRESENT
Model description
| Field | Value |
|---|---|
| Hugging Face repository | litillabs/litil-contract-extractor-1.7b |
| Base model | Qwen/Qwen3-1.7B |
| Artifact type | PEFT LoRA adapter; the base model is required |
| Evaluated source revision | d8441e71a3dfa66b62d7b3ed1cce12da3e813294 |
| Adaptation | PEFT LoRA supervised fine-tuning |
| Input | Contract text, CUAD category, and one extraction question |
| Output | <answer>verbatim span</answer> or <answer>NOT_PRESENT</answer> |
| Developer | LiTiL Labs |
| Card date | September 11, 2026 |
The matching adapter_model.safetensors is 278,973,888 bytes with SHA-256 cf78d5ff2223fdbcb19202e4b55be352cf86fcc7173c6033f8199cf0e0c58e0c.
Intended use
Use one category-specific question at a time to:
- extract dates, parties, agreement names, governing law, and other CUAD fields;
- return a consistent absence value for missing terms;
- populate structured contract metadata; and
- send extracted text to a category-specific review step.
For long contracts, retrieve or chunk relevant text before asking the extraction question, then retain the source location beside the extracted span.
Input contract
The user message has three fields:
<context>
CONTRACT TEXT OR RETRIEVED PASSAGE
</context>
Category: Effective Date
Question: What is the effective date of this agreement?
Use a category and question from the CUAD taxonomy. The original preparation code contains the canonical question for all 41 categories. Keep one category per request.
Output contract
Present term:
<answer>Sept 29, 2004</answer>
Absent term:
<answer>NOT_PRESENT</answer>
Parse exactly one <answer> element and preserve the returned span before applying any normalization. Store NOT_PRESENT as a structured null value rather than literal contract text.
Evaluation
The retained test set contains 2,091 contract-question pairs from 51 contracts. The 459 training contracts and 51 test contracts are disjoint.
| Metric | Qwen3-1.7B base | LiTiL adapter | Change |
|---|---|---|---|
| Normalized exact match | 70.78% | 74.46% | +3.68 points |
| Token F1 | 0.7306 | 0.7690 | +0.0384 |
| Character Jaccard | 0.7460 | 0.7927 | +0.0467 |
Both models used greedy decoding and the same input construction. The adapter's largest saved gains were on document name, agreement date, effective date, parties, and insurance questions.
Training
The adapter was supervised-fine-tuned on public CUAD contracts and annotations.
| Setting | Value |
|---|---|
| Training rows | 18,819 |
| Training contracts | 459 |
| Test rows | 2,091 |
| Test contracts | 51 |
| Categories | 41 |
| Training span-present rows | 6,084 |
Training NOT_PRESENT rows |
12,735 |
| Epochs | 3 |
| Per-device batch size | 2 |
| Gradient accumulation | 8 |
| Effective batch size | 16 |
| Learning rate | 2e-4 |
| Warmup ratio | 5% |
| Weight decay | 0.01 |
| Maximum training length | 2,048 tokens |
| LoRA rank / alpha / dropout | 64 / 128 / 0.05 |
| Seed | 42 |
The retained source review found public CUAD post-training data and no private post-training source for this run.
Runtime guidance
The adapter is approximately 266 MiB. A 1.7B BF16 base requires roughly 3.4 GB for weights before the adapter, activations, and KV cache; 6–8 GB of accelerator memory is a practical starting point for short or retrieved passages. The full retained comparison ran with vLLM on one NVIDIA L40S at max_model_len=8192, greedy decoding, and 200 generated tokens. The training distribution used a 2,048-token envelope, so category-focused excerpts are the best-aligned input.
Limitations
- Results cover the 41 CUAD question categories and should not be generalized to arbitrary extraction schemas without testing.
- Long-contract quality depends on retrieval or chunking because the training examples were length-limited.
- Aggregate scores include both span extraction and
NOT_PRESENTcases; track those two behaviors separately in an application evaluation.
Use the model
Installation
The adapter records PEFT 0.19.1. Qwen3 support requires a current Transformers release.
python -m pip install \
"torch>=2.1" \
"transformers>=4.51" \
"peft>=0.19.1" \
"accelerate>=1.0" \
"safetensors>=0.5"
Loading and inference
import re
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
BASE_ID = "Qwen/Qwen3-1.7B"
ADAPTER_ID = "litillabs/litil-contract-extractor-1.7b"
SYSTEM_PROMPT = (
"You are a legal contract analysis assistant. "
"Given a contract excerpt, extract the specific clause or information requested. "
"If the information is present, output it verbatim inside <answer>...</answer> tags. "
"If the information is not present in the contract, output <answer>NOT_PRESENT</answer>. "
"Be precise and extract only the relevant text."
)
def build_messages(context: str, category: str, question: str):
return [
{"role": "system", "content": SYSTEM_PROMPT},
{
"role": "user",
"content": (
f"<context>\n{context.strip()}\n</context>\n\n"
f"Category: {category}\n"
f"Question: {question}"
),
},
]
def parse_answer(text: str):
text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL | re.IGNORECASE).strip()
match = re.search(r"<answer>(.*?)</answer>", text, flags=re.DOTALL | re.IGNORECASE)
if not match:
raise ValueError(f"Response did not contain one <answer> block: {text!r}")
value = match.group(1).strip()
return None if value.upper() == "NOT_PRESENT" else value
tokenizer = AutoTokenizer.from_pretrained(
ADAPTER_ID,
)
base = AutoModelForCausalLM.from_pretrained(
BASE_ID,
dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
model = PeftModel.from_pretrained(
base,
ADAPTER_ID,
).eval()
messages = build_messages(
context="THE EFFECTIVE DATE OF THIS RESELLER AGREEMENT SHALL BE: Sept 29, 2004",
category="Effective Date",
question="What is the effective date of this agreement?",
)
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
).to(model.device)
with torch.inference_mode():
generated = model.generate(
input_ids=input_ids,
do_sample=False,
max_new_tokens=200,
eos_token_id=tokenizer.eos_token_id,
pad_token_id=tokenizer.pad_token_id,
)
text = tokenizer.decode(generated[0, input_ids.shape[1]:], skip_special_tokens=True)
print(parse_answer(text))
Citation
@inproceedings{hendrycks2021cuad,
title = {CUAD: An Expert-Annotated NLP Dataset for Legal Contract Review},
author = {Hendrycks, Dan and Burns, Collin and Chen, Anya and Ball, Spencer},
booktitle = {NeurIPS Datasets and Benchmarks},
year = {2021}
}
- Downloads last month
- 17
Model tree for litillabs/litil-contract-extractor-1.7b
Dataset used to train litillabs/litil-contract-extractor-1.7b
Collection including litillabs/litil-contract-extractor-1.7b
Evaluation results
- Normalized exact match on CUAD contract-disjoint test splitself-reported0.745
- Token F1 on CUAD contract-disjoint test splitself-reported0.769
- Character Jaccard on CUAD contract-disjoint test splitself-reported0.793