Metadata Extraction & Enrichment

Data Engineering & Processing beginner Building with AI 11 min read

Overview

Metadata extraction and enrichment is the process of pulling structured attributes from documents — author, creation date, source URL, language, document type — and then augmenting those attributes with derived information like entities, topics, summaries, and domain-specific tags. In an AI pipeline, metadata serves as the “index card” attached to every chunk of content, enabling precise filtering, ranking, and access control during retrieval.

Without metadata, a vector search can only rank results by semantic similarity to a query. With metadata, you can add powerful constraints: “find documents about API authentication written after January 2025” or “retrieve only content from the engineering team tagged as approved.” These filters dramatically improve precision by narrowing the search space before vector similarity is even computed.

Metadata enrichment is what transforms raw ingested documents from opaque blobs into richly annotated, filterable, and attributable data assets. It’s the difference between a pile of files and a structured knowledge base. For AI engineering teams, getting metadata right during the ETL phase prevents costly retrieval failures downstream.

How It Works

Types of Metadata

Metadata falls into two categories based on how it’s obtained:

Key Concept: Intrinsic vs. Derived Metadata

Intrinsic metadata is embedded in the document itself — file properties like author, creation date, last modified date, file size, and format. Derived metadata is computed from document content — extracted entities, topic classifications, summaries, sentiment scores, and language detection. Both types are critical for effective retrieval filtering.

Common metadata fields extracted and enriched in AI pipelines:

Extraction Techniques

File-Level Metadata Extraction

The simplest layer of metadata comes from file system properties and embedded document metadata:

# Extracting intrinsic metadata from different file types
import os
from datetime import datetime
from docx import Document
from PyPDF2 import PdfReader

# File system metadata
stat = os.stat("report.pdf")
file_meta = {
    "file_size": stat.st_size,
    "created": datetime.fromtimestamp(stat.st_ctime).isoformat(),
    "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
    "extension": os.path.splitext("report.pdf")[1],
}

# PDF embedded metadata
reader = PdfReader("report.pdf")
pdf_meta = reader.metadata
doc_meta = {
    "author": pdf_meta.author,
    "title": pdf_meta.title,
    "creator": pdf_meta.creator,
    "creation_date": str(pdf_meta.creation_date),
}

# DOCX core properties
doc = Document("report.docx")
props = doc.core_properties
docx_meta = {
    "author": props.author,
    "title": props.title,
    "created": str(props.created),
    "modified": str(props.modified),
    "category": props.category,
}

NER-Based Entity Extraction

Named Entity Recognition (NER) identifies and classifies proper nouns in text into categories like person, organization, location, date, and product. This is one of the most valuable enrichment techniques because entity metadata enables precise filtering:

# Entity extraction using spaCy
import spacy

nlp = spacy.load("en_core_web_lg")
text = "OpenAI released GPT-4 in March 2023. Sam Altman announced it at their San Francisco headquarters."

doc = nlp(text)
entities = {}
for ent in doc.ents:
    category = ent.label_  # PERSON, ORG, DATE, GPE, etc.
    if category not in entities:
        entities[category] = []
    entities[category].append(ent.text)

# Result: {
#   "ORG": ["OpenAI"],
#   "PRODUCT": ["GPT-4"],
#   "DATE": ["March 2023"],
#   "PERSON": ["Sam Altman"],
#   "GPE": ["San Francisco"]
# }

LLM-Based Enrichment

For higher-quality and more nuanced metadata, LLMs can generate structured enrichments from document content. This is more expensive but produces metadata that simple NLP models cannot:

# LLM-based metadata enrichment example
import openai

def enrich_chunk(chunk_text: str, doc_title: str) -> dict:
    response = openai.chat.completions.create(
        model="gpt-4o-mini",
        response_format={"type": "json_object"},
        messages=[{
            "role": "system",
            "content": """Extract metadata from this document chunk as JSON:
            {
              "summary": "1-2 sentence summary",
              "topics": ["topic1", "topic2"],
              "entities": ["entity1", "entity2"],
              "key_phrases": ["phrase1", "phrase2"],
              "document_type": "manual|policy|email|report|other",
              "questions_answered": ["question this chunk answers"]
            }"""
        }, {
            "role": "user",
            "content": f"Document: {doc_title}\n\nChunk:\n{chunk_text}"
        }]
    )
    return json.loads(response.choices[0].message.content)
Key Concept: Metadata Consistency

Inconsistent metadata tagging is one of the most common causes of retrieval failures in production RAG systems. If one document tags a topic as “auth” and another tags it as “authentication,” metadata filters will miss relevant results. Enforce a controlled vocabulary or taxonomy for categorical metadata fields, and use normalization (lowercasing, alias resolution) for entity metadata.

Metadata at the Chunk Level

A critical design decision is what level to attach metadata. Document-level metadata (author, date, source) applies to every chunk from that document. But chunk-level metadata can be much more specific:

Practical Applications

Pre-Retrieval Filtering in RAG

The most impactful use of metadata is pre-retrieval filtering — narrowing the search space before vector similarity search runs. A vector database with 10 million chunks can be filtered to 50,000 relevant chunks using metadata constraints (date range, department, document type) before computing any embeddings similarity. This is both faster and more precise than relying on vector similarity alone.

# Metadata filtering in a vector search query (Weaviate example)
results = client.query.get(
    "Document", ["content", "title"]
).with_near_text({
    "concepts": ["OAuth2 token refresh flow"]
}).with_where({
    "operator": "And",
    "operands": [
        {"path": ["department"], "operator": "Equal", "valueText": "engineering"},
        {"path": ["modified_date"], "operator": "GreaterThan", "valueDate": "2024-01-01T00:00:00Z"},
        {"path": ["document_type"], "operator": "Equal", "valueText": "technical_spec"},
    ]
}).with_limit(10).do()

Self-Query Retrievers

Advanced RAG systems use self-query retrievers that automatically extract metadata filters from natural language queries. When a user asks “What were the Q3 2024 revenue numbers from the finance team?”, the system decomposes this into a semantic query (“revenue numbers”) plus metadata filters (quarter=Q3, year=2024, department=finance). This requires well-defined metadata schemas that the LLM understands.

Access Control and Multi-Tenancy

In enterprise deployments, metadata-based access control ensures users only retrieve documents they’re authorized to see. Each chunk carries permission metadata (team, role, classification level), and retrieval queries automatically apply access filters based on the requesting user’s identity. Without this, a RAG system could leak confidential information across organizational boundaries.

Content Freshness and Provenance

Timestamp metadata enables freshness-aware retrieval — preferring recent documents over outdated ones. Source metadata provides provenance — answering “where did this information come from?” which is critical for trust and auditability. When an LLM generates an answer, the system can cite the specific document, author, and date for each piece of supporting evidence.

Key Takeaways

Further Reading