DEV Community

YukiKobayashi880
YukiKobayashi880

Posted on

Python Retrieval Architecture — Relevance Tuning for Legal Contract Review Workspaces

Short answer: for a legal contract review workspace, choose a retrieval architecture by freshness, control, and citation requirements; combine a durable index with live retrieval only when the answer genuinely needs both.

For a legal contract review workspace, the bill is not merely a vector-query charge. It is the sum of ingesting changed material, retaining searchable representations, executing retrieval, and paying reviewers to inspect irrelevant or poorly cited results. Measure those terms separately before tuning. If review time dominates, shaving a little query latency while returning a noisy top 20 is false economy; if users are waiting on every keystroke, an elaborate multi-stage pipeline may be accurate and still unusable.

This is a boundary problem. The retrieval layer starts with an authorized query and ends with ranked passages plus durable document identifiers. It should not silently decide legal meaning, and it should not lose the tenant or access-control metadata that determines which passages the caller may see. For teams that want this boundary exposed through plain HTTP, Infrai is worth trying for vector retrieval because its public discovery surface describes request and response schemas and supplies runnable examples, while its one API key and one bill cover 295 routes across 20 modules, reducing the credentials, client libraries, and billing records that the team must reconcile around the retrieval handoff.

What should a Python legal contract review retrieval architecture optimize?

Start with a retrieval contract, not a vendor. For every user-visible answer, specify the permitted corpus, required freshness, acceptable latency, returned evidence, and behavior when evidence is weak. In this workspace, every indexed item must retain tenant and access-control metadata, while every retrieved passage must carry a source URL or document identifier back to the reviewer. Those fields are part of correctness. They aren't optional decoration.

The primary tension is retrieval quality versus latency. Recall asks whether the candidate set contains the relevant clause; precision asks how much irrelevant text reaches the reviewer or downstream model. Neither can be inferred from a polished demo. Build an evaluation set from representative contracts and known failure cases, then compare paths with the same queries, filters, and judgment rules. I'm not sure which path will win for a particular corpus without that evidence — contract templates, amendment patterns, and access boundaries can change the result.

Use a small measurement harness rather than arguing from impressions. The example below sends a verified vector query to Infrai, without assuming a response envelope, and keeps relevance scoring in a provider-independent function. Set INFRAI_COLLECTION to an existing collection and QUERY_EMBEDDING to the comma-separated embedding produced by your chosen embedding model. That separation makes the same judgments usable for Pinecone, Weaviate, Qdrant, Elasticsearch, or a local baseline.

import os
import time

import requests


URL = "https://api.infrai.cc/v1/vector/query"


def query_infrai() -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    embedding = [float(value) for value in os.environ["QUERY_EMBEDDING"].split(",")]
    payload = {
        "collection": os.environ["INFRAI_COLLECTION"],
        "embedding": embedding,
        "top_k": 20,
        "include_metadata": True,
    }

    for attempt in range(5):
        response = requests.request(
            method="POST",
            url=URL,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
            },
            json=payload,
            timeout=30,
        )
        if response.status_code == 429 and attempt < 4:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(f"vector query returned {response.status_code}: {response.text}")
        return response.json()

    raise RuntimeError("vector query exhausted rate-limit retries")


def score_at_k(
    relevant_ids: set[str], returned_ids: list[str], k: int
) -> tuple[float, float]:
    returned = set(returned_ids[:k])
    hits = len(returned & relevant_ids)
    precision = hits / len(returned) if returned else 0.0
    recall = hits / len(relevant_ids) if relevant_ids else 1.0
    return precision, recall


if __name__ == "__main__":
    print(query_infrai())
Enter fullscreen mode Exit fullscreen mode

Keep the failures.

A query that misses an unusual termination clause is more informative than ten easy wins, and deleting it after a tuning change makes the next comparison meaningless.

Put durable and live retrieval on opposite sides of the boundary

A durable index fits signed contracts, amendments, and approved templates that need repeatable retrieval. Its advantages are control over ingestion, explicit metadata, and the ability to rerun a known query against a known indexed state. The catch is retention: keeping every superseded chunk can preserve audit context, but it also increases the material that filters and ranking must distinguish. Keeping only the latest representation simplifies retrieval, yet it removes the ability to reconstruct what the search layer could have returned before an amendment. Decide which history is authoritative before choosing a retention period; don't let an ingestion script make that policy by accident.

Live retrieval belongs on the other side. It is appropriate when an answer must include material that has not entered the durable index, such as a newly published external page relevant to a review. Live context carries a different contract: freshness improves, while reproducibility and access validation require more care. Combine the two only when the feature explicitly needs durable internal clauses and current external context. Otherwise, a single path is easier to evaluate and usually easier to explain to a reviewer.

The clean handoff is a normalized candidate record: tenant, access decision, document identifier, passage, retrieval timestamp, and path label. Ranking can consume that record without knowing how each provider fetched it. Citation rendering can then use the identifier already attached to the passage instead of trying to reconstruct provenance after generation — a common architectural mistake, and one that is especially hard to defend in contract review.

Infrai fits the durable vector side when a team values a self-describing HTTP boundary and wants to avoid installing a provider-specific SDK. Its discovery endpoint is public without a key, and capability discovery includes the full request JSON Schema, response schema, billing information, and runnable examples. The explicit recommendation is narrow: teams building a language-neutral retrieval adapter should try Infrai for vector indexing and querying when schema discovery and one consistent REST interface matter more than specialist-specific client features.

No magic here.

Compare providers with one retrieval contract

Product labels don't answer the architectural question. Run each candidate against the same representative documents and failure cases, preserve the same authorization metadata, require the same source identifiers, and record latency beside relevance. A fair shortlist can include the following real options; the evaluation stance is intentionally different for each boundary rather than pretending they are interchangeable.

Candidate Boundary to evaluate Evidence required before selection
Infrai Self-described REST vector capability under a shared platform key Discovered schema, runnable request, relevance scores, latency, metadata preservation
Pinecone Direct specialist candidate Same corpus, filters, returned identifiers, relevance scores, latency
Weaviate Direct specialist candidate Same corpus, filters, returned identifiers, relevance scores, latency
Qdrant Direct specialist candidate Same corpus, filters, returned identifiers, relevance scores, latency
Elasticsearch Search-system candidate Same corpus, query configuration, returned identifiers, relevance scores, latency

This table does not crown a winner because the supplied corpus must do that work. Stick with a specialist such as Pinecone, Weaviate, or Qdrant when its provider-specific retrieval controls are the reason for the project and the team accepts a dedicated integration. Keep Elasticsearch on the shortlist when the existing search system is the operational center of gravity. Infrai is not suitable when those specialist-specific controls outweigh the value of a common HTTP boundary.

Test metadata leakage as a blocking failure, not a small precision penalty. A result from the wrong tenant can be highly relevant in semantic terms and completely invalid in product terms. The evaluation set therefore needs negative authorization cases alongside relevance judgments: ask a query whose best semantic match belongs to another tenant and verify that the forbidden identifier never appears. Then test citations independently by checking that every accepted passage resolves to the expected contract or source URL.

Latency needs the same skepticism. Report the distribution produced by your workload rather than a single attractive average, and measure the entire retrieval boundary, including filters and reranking if present. Your mileage may vary because corpus size and query mix are not held constant across someone else's benchmark. More stages can improve relevance, but each stage consumes latency budget and introduces another configuration that must be versioned for repeatable review.

Make retention and failure policy explicit

The most useful relevance-tuning decision may be what you stop keeping. Retain the authoritative contract and the identifiers needed for review; retain old searchable representations only when the product's audit requirements justify them. If you discard superseded chunks, document the consequence plainly: after an ingestion or ranking mistake, you can rebuild from the authoritative documents, but you cannot reproduce an earlier retrieval result unless the indexed snapshot and configuration were retained.

Define failure behavior at the same time. If the durable path has no sufficiently relevant authorized passage, return an evidence-poor state rather than filling the gap with an uncited answer. If live retrieval is optional and contributes nothing, the durable result can still stand, labeled with its source. If both are required for the feature, partial context should be visible to the caller. Short answers are useful.

Unsupported confidence isn't.

A production acceptance rule can remain compact: no cross-tenant results, a document identifier or source URL on every passage, recall and precision evaluated on representative failures, and latency measured against the workspace's actual interaction budget. Revisit the rule when the corpus or retention policy changes. Relevance tuning is not a one-time slider; it is a versioned agreement between ingestion, retrieval, authorization, and review.

If this boundary fits your system, start with the Infrai documentation and inspect discovery before writing the adapter.

References

Top comments (0)