πŸŽ‰ 75% of content is free forever β€” Unlock Premium from $10/mo β†’
CW
Search courses…
πŸ’Ό Servicesℹ️ Aboutβœ‰οΈ ContactView Pricing Plansfrom $10

Contract Analysis Agent

AI AgentsLegal Document Agent🟒 Free Lesson

Advertisement

Contract Analysis Agent

Contract Analysis AgentDocument ParserClause ExtractorRisk ScorerCompliance CheckContract ComparatorReport GeneratorLegal Analysis Orchestrator

What is a Contract Analysis Agent?

Contract analysis agents automate legal document review by extracting key clauses, identifying risks, checking compliance with standards, and comparing contract terms. They accelerate due diligence and reduce the cost of legal review.

The key capabilities are: clause identification and classification, obligation extraction, risk scoring based on clause characteristics, deviation detection from standard templates, and compliance verification against regulatory requirements.

These agents serve as a first-pass review, flagging issues for human lawyers while handling the bulk of routine analysis.

Project Overview

We will build a contract analysis agent that:

  • Parses PDF and Word documents
  • Extracts and classifies contract clauses
  • Identifies obligations, rights, and conditions
  • Scores risk for non-standard clauses
  • Compares contracts against templates
  • Generates analysis reports with recommendations

Expected outcome: An agent that provides rapid contract analysis with risk scoring.

Difficulty: Advanced (requires understanding of legal document structure and risk assessment)

Architecture

Contract Analysis PipelineDocument LoaderPDF/Word/TextClause ExtractorLLM + rulesRisk EngineScoring rulesObligation TrackerCompliance CheckerReport BuilderLegal Analysis Orchestrator

Tools & Setup

ToolVersionPurpose
Python3.11+Core language
pymupdf1.23+PDF parsing
python-docx1.0+Word document parsing
openai1.0+LLM backbone
pydantic2.0+Data models

Step 1: Environment Setup

python -m venv venv
source venv/bin/activate
pip install pymupdf python-docx openai pydantic
export OPENAI_API_KEY="sk-your-key"

Step 2: Project Structure

Architecture Diagram
legal-agent/
β”œβ”€β”€ document/
β”‚   β”œβ”€β”€ __init__.py
β”‚   └── parser.py
β”œβ”€β”€ analysis/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ clause_extractor.py
β”‚   β”œβ”€β”€ risk_scorer.py
β”‚   └── compliance.py
β”œβ”€β”€ reporting/
β”‚   β”œβ”€β”€ __init__.py
β”‚   └── report.py
β”œβ”€β”€ agent.py
└── main.py

Step 3: Document Parser

# document/parser.py
import fitz
from docx import Document
from pathlib import Path
from typing import Dict

class DocumentParser:
    def parse(self, file_path: str) -> Dict:
        path = Path(file_path)
        suffix = path.suffix.lower()
        if suffix == ".pdf":
            return self._parse_pdf(path)
        elif suffix in (".docx", ".doc"):
            return self._parse_docx(path)
        elif suffix == ".txt":
            return {"text": path.read_text(encoding="utf-8"), "pages": 1}
        else:
            return {"error": f"Unsupported format: {suffix}"}

    def _parse_pdf(self, path: Path) -> Dict:
        doc = fitz.open(str(path))
        text_parts = []
        for page in doc:
            text_parts.append(page.get_text())
        doc.close()
        return {
            "text": "\n\n".join(text_parts),
            "pages": len(text_parts),
            "source": str(path),
        }

    def _parse_docx(self, path: Path) -> Dict:
        doc = Document(str(path))
        paragraphs = [p.text for p in doc.paragraphs if p.text.strip()]
        return {
            "text": "\n\n".join(paragraphs),
            "pages": 1,
            "source": str(path),
        }

    def extract_sections(self, text: str) -> Dict[str, str]:
        sections = {}
        current_section = "Preamble"
        current_content = []
        for line in text.split("\n"):
            if self._is_section_header(line):
                if current_content:
                    sections[current_section] = "\n".join(current_content)
                current_section = line.strip()
                current_content = []
            else:
                current_content.append(line)
        if current_content:
            sections[current_section] = "\n".join(current_content)
        return sections

    def _is_section_header(self, line: str) -> bool:
        import re
        patterns = [
            r"^\d+\.\s+[A-Z]",
            r"^[IVXLC]+\.\s+[A-Z]",
            r"^Article\s+\d+",
            r"^Section\s+\d+",
            r"^Article\s+\d+",
            r"^[A-Z][A-Z\s]{5,}$",
        ]
        return any(re.match(p, line.strip()) for p in patterns)

Step 4: Clause Extractor and Risk Scorer

# analysis/clause_extractor.py
from openai import OpenAI
from typing import Dict, List
import json

class ClauseExtractor:
    def __init__(self, model: str = "gpt-4-turbo-preview"):
        self.client = OpenAI()
        self.model = model

    def extract_clauses(self, text: str) -> List[Dict]:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": """Extract key clauses from this legal document.
                Return JSON array:
                [{
                    "clause_type": "type (indemnity|limitation|termination|confidentiality|ip|warranty|governing_law|force_majeure|non_compete|other)",
                    "title": "clause title",
                    "text": "full clause text",
                    "summary": "plain English summary",
                    "obligations": ["list of obligations"],
                    "rights": ["list of rights"]
                }]"""},
                {"role": "user", "content": text[:8000]},
            ],
            temperature=0.0,
        )
        try:
            return json.loads(response.choices[0].message.content)
        except:
            return []

    def extract_obligations(self, clauses: List[Dict]) -> List[Dict]:
        obligations = []
        for clause in clauses:
            for obs in clause.get("obligations", []):
                obligations.append({
                    "clause_type": clause["clause_type"],
                    "obligation": obs,
                    "text": clause["text"][:200],
                })
        return obligations

# analysis/risk_scorer.py
from typing import Dict, List

class RiskScorer:
    RISK_RULES = {
        "indemnity": {"base": 7, "keywords": ["unlimited", "gross negligence", "willful misconduct"]},
        "limitation": {"base": 4, "keywords": ["no limitation", "uncapped", "unlimited liability"]},
        "termination": {"base": 3, "keywords": ["immediate", "without cause", "convenience"]},
        "confidentiality": {"base": 5, "keywords": ["perpetual", "indefinite", "no exceptions"]},
        "ip": {"base": 6, "keywords": ["work for hire", "all rights", "exclusive"]},
        "warranty": {"base": 5, "keywords": ["as is", "no warranty", "disclaims all"]},
        "governing_law": {"base": 3, "keywords": ["arbitration", "waive jury", "exclusive jurisdiction"]},
        "non_compete": {"base": 7, "keywords": ["perpetual", "worldwide", "unlimited scope"]},
    }

    def score_clause(self, clause: Dict) -> Dict:
        clause_type = clause.get("clause_type", "other")
        text = clause.get("text", "").lower()
        rule = self.RISK_RULES.get(clause_type, {"base": 3, "keywords": []})
        score = rule["base"]
        risk_factors = []
        for keyword in rule["keywords"]:
            if keyword in text:
                score += 1
                risk_factors.append(f"Contains '{keyword}'")
        score = min(score, 10)
        return {
            "clause_type": clause_type,
            "risk_score": score,
            "risk_level": self._level(score),
            "risk_factors": risk_factors,
        }

    def _level(self, score: int) -> str:
        if score >= 8:
            return "HIGH"
        elif score >= 5:
            return "MEDIUM"
        return "LOW"

    def score_contract(self, clauses: List[Dict]) -> Dict:
        scored = [self.score_clause(c) for c in clauses]
        avg_score = sum(s["risk_score"] for s in scored) / len(scored) if scored else 0
        high_risk = [s for s in scored if s["risk_level"] == "HIGH"]
        return {
            "overall_risk_score": round(avg_score, 2),
            "overall_risk_level": self._level(int(avg_score)),
            "high_risk_clauses": high_risk,
            "all_scores": scored,
            "total_clauses": len(scored),
        }

Step 5: Compliance Checker and Agent

# analysis/compliance.py
from openai import OpenAI
from typing import Dict, List
import json

class ComplianceChecker:
    def __init__(self, model: str = "gpt-4-turbo-preview"):
        self.client = OpenAI()
        self.model = model

    def check_compliance(
        self, text: str, standards: List[str]
    ) -> Dict:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": f"""Check this contract against standards: {', '.join(standards)}.
                Return JSON:
                {{
                    "compliant": true/false,
                    "issues": [{{"standard": "...", "issue": "...", "severity": "high|medium|low"}}],
                    "recommendations": ["list of recommendations"]
                }}"""},
                {"role": "user", "content": text[:8000]},
            ],
            temperature=0.0,
        )
        try:
            return json.loads(response.choices[0].message.content)
        except:
            return {"compliant": False, "issues": [], "recommendations": []}

# agent.py
from document.parser import DocumentParser
from analysis.clause_extractor import ClauseExtractor
from analysis.risk_scorer import RiskScorer
from analysis.compliance import ComplianceChecker
from typing import Dict

class ContractAnalysisAgent:
    def __init__(self, model: str = "gpt-4-turbo-preview"):
        self.parser = DocumentParser()
        self.extractor = ClauseExtractor(model)
        self.risk_scorer = RiskScorer()
        self.compliance_checker = ComplianceChecker(model)

    def analyze(self, file_path: str, standards: list = None) -> Dict:
        doc = self.parse(file_path)
        clauses = self.extractor.extract_clauses(doc["text"])
        risk = self.risk_scorer.score_contract(clauses)
        obligations = self.extractor.extract_obligations(clauses)
        compliance = None
        if standards:
            compliance = self.compliance_checker.check_compliance(doc["text"], standards)
        return {
            "source": file_path,
            "pages": doc.get("pages", 0),
            "total_clauses": len(clauses),
            "clauses": clauses,
            "risk_assessment": risk,
            "obligations": obligations,
            "compliance": compliance,
        }

    def compare(self, file1: str, file2: str) -> Dict:
        analysis1 = self.analyze(file1)
        analysis2 = self.analyze(file2)
        return {
            "contract_1": {"file": file1, "risk": analysis1["risk_assessment"]["overall_risk_score"]},
            "contract_2": {"file": file2, "risk": analysis2["risk_assessment"]["overall_risk_score"]},
            "clause_counts": {file1: analysis1["total_clauses"], file2: analysis2["total_clauses"]},
        }

Mathematical Foundation

Contract Risk Score:

Where each parameter means:

  • β€” risk score for clause (1-10)
  • β€” importance weight for clause type
  • β€” total number of clauses

Intuition: Weighted average risk across all clauses, with higher weights for critical clause types.

Risk Level Thresholds:

Testing & Evaluation

import pytest
from document.parser import DocumentParser
from analysis.risk_scorer import RiskScorer

def test_parser():
    parser = DocumentParser()
    result = parser.parse("test_contract.pdf")
    assert "text" in result

def test_risk_scorer():
    scorer = RiskScorer()
    clause = {"clause_type": "indemnity", "text": "Unlimited indemnification for all claims"}
    result = scorer.score_clause(clause)
    assert result["risk_level"] == "HIGH"

Performance Metrics

MetricValueNotes
Document Parsing1-5sDepends on size
Clause Extraction3-8sGPT-4 per document
Risk Scoring<100msRule-based
Compliance Check5-10sPer standard set
Comparison Analysis10-20sTwo contracts

Deployment

# main.py
from agent import ContractAnalysisAgent
import json

def main():
    agent = ContractAnalysisAgent()
    file_path = input("Contract file path: ").strip()
    result = agent.analyze(file_path)
    print(f"\nClauses: {result['total_clauses']}")
    print(f"Risk Level: {result['risk_assessment']['overall_risk_level']}")
    print(f"Score: {result['risk_assessment']['overall_risk_score']}/10")
    if result["risk_assessment"]["high_risk_clauses"]:
        print("\nHIGH RISK CLAUSES:")
        for clause in result["risk_assessment"]["high_risk_clauses"]:
            print(f"  - {clause['clause_type']}: {clause['risk_factors']}")

if __name__ == "__main__":
    main()

Real-World Use Cases

  • Due Diligence: Rapid contract review in M&A transactions
  • Vendor Management: Analyze supplier agreements at scale
  • Employment Law: Review employment contracts and policies
  • Real Estate: Analyze lease agreements and purchase contracts
  • Compliance: Verify contracts meet regulatory requirements

Common Pitfalls & Solutions

PitfallSolution
PDF parsing errorsUse multiple parsers, validate output
Legal jargon variationTrain on domain-specific clause types
False positives in riskCalibrate thresholds with legal review
Missing contextAnalyze related clauses together
Jurisdiction differencesInclude governing law in analysis

Summary with Key Takeaways

  • Automated clause extraction accelerates contract review by 10x
  • Risk scoring provides consistent, objective assessment across contracts
  • Compliance checking ensures contracts meet regulatory standards
  • Contract comparison identifies material differences quickly
  • Always have legal experts validate automated analysis before action

Need Expert AI Agents Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement