Today we are going to transfer a general-purpose LLM to a specialized legal analysis task without retraining any weights. We will build a contract clause analyzer that classifies text, extracts entities, and returns structured JSON using only prompt engineering and few-shot examples. This approach, often called in-context transfer learning, is fast to iterate on, and because we run inference on Oxlo.ai, the cost per request stays flat even as our system prompt grows.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
Step 1: Configure the Oxlo.ai client
First, instantiate the OpenAI-compatible client and verify connectivity. I am using llama-3.3-70b because it follows complex formatting instructions reliably, and Oxlo.ai serves it with no cold starts.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Confirm you are ready by responding with the word 'ready' and nothing else."},
],
)
print(response.choices[0].message.content)
Step 2: Design the system prompt for domain transfer
This is the transfer learning step. Instead of updating model weights, we compress domain expertise into a system prompt that defines the task, JSON schema, and reasoning style. Because Oxlo.ai uses request-based pricing, adding these few-shot examples does not increase inference cost. See https://oxlo.ai/pricing for plan details.
SYSTEM_PROMPT = """You are a legal contract analyst. Your job is to read a contract clause and output a JSON object with exactly these keys:
- clause_type: one of [Indemnification, Termination, Limitation of Liability, Confidentiality, Force Majeure]
- risk_level: one of [Low, Medium, High]
- entities: list of named parties, dollar amounts, and dates mentioned
- summary: one sentence summary of the clause
Rules:
1. Respond with valid JSON only, no markdown fences.
2. If the clause is ambiguous, flag risk_level as High.
3. Use terse, precise legal language in the summary.
Examples:
Input: "Party A shall indemnify and hold harmless Party B against all claims arising from negligence, up to a limit of $1,000,000."
Output: {"clause_type": "Indemnification", "risk_level": "Medium", "entities": ["Party A", "Party B", "$1,000,000"], "summary": "Party A indemnifies Party B for negligence claims capped at one million dollars."}
Input: "Either party may terminate this agreement with thirty days written notice without cause."
Output: {"clause_type": "Termination", "risk_level": "Low", "entities": ["thirty days"], "summary": "Either party may terminate without cause upon thirty days notice."}
Input: "Supplier shall not be liable for indirect, consequential, or punitive damages under any circumstances."
Output: {"clause_type": "Limitation of Liability", "risk_level": "High", "entities": [], "summary": "Supplier excludes liability for indirect, consequential, and punitive damages."}
"""
Step 3: Build the analysis function
Now we wrap the API call in a function that injects the system prompt and raw clause text, then parses the returned JSON. We keep the temperature low to protect the schema.
import json
def analyze_clause(clause_text: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
temperature=0.1,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": clause_text},
],
)
raw = response.choices[0].message.content.strip()
return json.loads(raw)
test = "Company X agrees to pay Company Y the sum of $50,000 within 10 business days of invoice receipt."
print(json.dumps(analyze_clause(test), indent=2))
Step 4: Run a batch of clauses
Transfer learning is only useful if it generalizes. We feed the model three unseen clauses to confirm the transferred behavior holds across different contract language.
clauses = [
"In no event shall Acme Corp's total liability exceed the amount paid by the customer in the preceding twelve months.",
"If performance is prevented by fire, flood, war, or acts of government, the affected party shall be excused for the duration of the event.",
"Recipient agrees to keep all source code, roadmaps, and pricing details strictly confidential for a period of 5 years.",
]
for text in clauses:
result = analyze_clause(text)
print(json.dumps(result, indent=2))
print("---")
Run it
Save the full script as contract_analyzer.py, set your key, and execute.
export OXLO_API_KEY="YOUR_OXLO_API_KEY"
python contract_analyzer.py
Expected output:
{
"clause_type": "Limitation of Liability",
"risk_level": "High",
"entities": ["Acme Corp", "twelve months"],
"summary": "Acme Corp limits liability to fees paid by the customer in the preceding twelve months."
}
---
{
"clause_type": "Force Majeure",
"risk_level": "Low",
"entities": [],
"summary": "Performance is excused during force majeure events including fire, flood, war, or government acts."
}
---
{
"clause_type": "Confidentiality",
"risk_level": "Medium",
"entities": ["5 years"],
"summary": "Recipient must keep source code, roadmaps, and pricing confidential for five years."
}
Wrap-up
We transferred a general chat model to a legal analysis domain using nothing but a detailed system prompt and three few-shot examples. If you want to push this further, try swapping llama-3.3-70b for qwen-3-32b or kimi-k2.6 on Oxlo.ai to see if reasoning-oriented models improve risk classification on ambiguous language. You could also add a function call that queries a vector database of precedent clauses whenever the model assigns a High risk level.
Top comments (0)