n4nAI

AI agents for e-discovery: what's automated and what isn't

Practical guide to building AI agents e-discovery pipelines: what engineers can automate in legal document review and where human judgment stays required.

n4n Team3 min read745 words

Audio narration

Coming soon — every post will get a voice note here.

AI agents e-discovery are now common in legal tech stacks, but the marketing hides a blunt truth: most of the pipeline is still deterministic code with a thin LLM layer for judgment calls. This guide walks an engineering path from raw custodian data to produced documents, marking exactly what you can automate and where human judgment stays non-negotiable.

The e-discovery pipeline in one picture

E-discovery follows a known lifecycle: identification, preservation, collection, processing, review, analysis, production. The first four steps are bulk data tasks. Review and analysis are where AI agents e-discovery earn their keep, but even there automation is partial.

A typical matter involves terabytes of mailboxes, chat exports, and files. You will not point an agent at a zip file and call it done. You build a pipeline with explicit handoffs.

1. Collect and normalize with code, not agents

Ingestion is file I/O, parsing, and metadata extraction. Use battle-tested libraries: mail-parser for PST, tika for office docs, pdfplumber for PDFs. Agents add latency and cost here for zero benefit.

Deduplication is a deterministic hash on normalized text. Example:

import hashlib

def doc_fingerprint(raw_text: str) -> str:
    # strip whitespace, lowercase, remove common headers
    normalized = " ".join(raw_text.lower().split())
    return hashlib.sha256(normalized.encode()).hexdigest()

seen = set()
unique_docs = []
for doc in raw_docs:
    fp = doc_fingerprint(doc.text)
    if fp not in seen:
        seen.add(fp)
        unique_docs.append(doc)

This removes near-exact duplicates before any LLM sees the data. Skipping this step multiplies your inference bill and pollutes downstream stats.

2. Use AI agents e-discovery for responsiveness scoring

Responsiveness asks: does this document meet the request’s search terms or subject matter? A deterministic keyword filter catches the obvious. An LLM agent catches context: a contract mentioning “acquisition” may be responsive only if dated in the relevant window.

Build an agent that calls a model with a strict schema. Point it at an OpenAI-compatible endpoint. If you use a gateway such as n4n.ai, automatic fallback across providers keeps a single rate limit from stalling a nightly review batch.

import json
from openai import OpenAI

client = OpenAI(base_url="https://api.n4n.ai/v1", api_key="YOUR_KEY")

def score_responsiveness(doc_text: str, matter_topic: str) -> dict:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "You are a legal review assistant. Output JSON only."},
            {"role": "user", "content": f"Matter: {matter_topic}\n\nDocument:\n{doc_text[:8000]}\n\nResponsive? Give {{'responsive': bool, 'confidence': float, 'reason': str}}"}
        ],
        response_format={"type": "json_object"}
    )
    return json.loads(resp.choices[0].message.content)

Set a confidence threshold. Below 0.7, route to human. Above, auto-tag but keep the reason string for audit.

Pitfall: long documents exceed context. Chunk by section and aggregate votes. Do not silently truncate mid-clause.

3. Privilege detection is half-automated

Privilege (attorney-client, work product) is high-risk. An LLM can flag documents containing “privileged and confidential” or discussing legal strategy. But false negatives are malpractice.

Use a two-layer approach:

PRIVILEGE_TERMS = ["attorney-client", "privileged", "legal advice", "counsel"]

def quick_privilege_scan(text: str) -> bool:
    low = text.lower()
    return any(term in low for term in PRIVILEGE_TERMS)

Any hit goes to a human reviewer. The agent then summarizes why it thinks privilege applies, but the attorney decides. Never auto-release a privileged doc.

Tradeoff: this slows the pipeline. Accept it. The cost of a privilege waiver dwarfs API spend.

4. Redaction and production formatting

Production requires stamped PDFs, load files (DAT/OPT), and consistent numbering. Use pikepdf or PyPDF2 to apply redactions deterministically.

An agent can propose redaction boxes by detecting PII patterns, but apply them with code:

import re

def find_ssns(text: str):
    return [m.start() for m in re.finditer(r"\d{3}-\d{2}-\d{4}", text)]

Then map offsets to PDF coordinates via your parser. Human verifies the redaction set before burn-in. A mismapped coordinate leaks data.

5. Audit and traceability

Every document decision needs a log: who/what classified it, model version, prompt hash, token count. If you meter per-token usage, attribute cost to a matter and a stage. This is not optional under FRCP.

Store records in a simple table:

{
  "doc_id": "cust-001-msg-223",
  "stage": "responsiveness",
  "actor": "agent:gpt-4o-mini",
  "confidence": 0.91,
  "tokens": 1820,
  "timestamp": "2025-04-12T02:31:00Z"
}

Keep the raw model response alongside the parsed object. When a court asks why a doc was tagged non-responsive, you produce the reason string.

Common pitfalls and tradeoffs

Context truncation. Legal docs are long. Cutting off mid-sentence changes meaning. Chunk with overlap and merge scores by max confidence or weighted vote.

Hallucinated privilege. Models invent legal reasons. Constrain with few-shot examples and force citation to a span in the source text.

Rate limits. Bulk review at 100k docs will hit limits. Batch async with exponential backoff. A gateway with fallback hides individual provider outages but does not remove the need for local queueing.

Cost unpredictability. Long context plus high volume equals surprise bills. Cache embeddings of static corpora; reuse across matters where permitted.

Over-automation. Teams that auto-produce without human QC get sanctioned. Keep a human in the loop for any privileged or responsive-positive doc. The agent is a first-pass filter, not a signatory.

A minimal agent loop

Below is a sketch of the review loop that respects the above boundaries:

async def review_doc(doc, matter):
    if quick_privilege_scan(doc.text):
        return route_to_human(doc, reason="privilege_term")
    score = await score_responsiveness(doc.text, matter.topic)
    if score["confidence"] >= 0.7:
        log_agent_decision(doc.id, score)
        if score["responsive"]:
            redactions = propose_redactions(doc)
            return await human_qc(doc, redactions)
        return mark_non_responsive(doc)
    else:
        return route_to_human(doc, reason="low_confidence")

This is the practical boundary: AI agents e-discovery handle scoring and proposal; humans handle privilege, final redaction approval, and low-confidence cases.

Where to draw the line

Automate the repetitive, deterministic, and low-risk. Use agents for semantic judgment where you can measure confidence and fallback to humans. The remaining work—privilege calls, final production sign-off—stays with attorneys. Build the pipeline so the handoff is explicit, logged, and reversible.

Ship the code, keep the audit, and don’t trust a model with a privilege waiver.

Tagsai-legal-agentse-discoverylegal-techautomation

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All ai agents in legal tech posts →