Concepts & Strategy7 min read

How to Create a Taxonomy That Improves Semantic Search and RAG Grounding

A retrieval-augmented generation system returns a confidently wrong answer.

Published August 26, 2026

A retrieval-augmented generation system returns a confidently wrong answer. Not because the model hallucinated, but because the vector search pulled three chunks about "returns" (product returns policy) when the user asked about "returns" (tax filings), and nothing in the content told the retriever the difference. This is the failure mode teams hit right after the demo works: semantic search feels magical on a clean corpus, then falls apart the moment real content, ambiguous terms, and overlapping topics arrive. Embeddings alone flatten meaning into distance, and distance is not the same as relevance.

The fix is not a bigger model or a fancier embedding. It is a taxonomy, a deliberate structure of categories, relationships, and controlled terms that tells your retrieval layer what a document is about before a single vector is compared. This is where Sanity earns its framing as the Content Operating System for the AI era: an intelligent backend where the taxonomy lives inside the content model itself, not bolted on as external metadata that drifts out of sync.

This guide covers how to design a taxonomy that measurably improves both semantic search precision and RAG grounding: how to model concepts, how to attach them as first-class references, and how to blend structured filters with vector similarity in a single query so retrieval stops guessing.

Why embeddings alone under-ground your RAG pipeline

Vector search works by projecting text into a high-dimensional space and measuring proximity. That is powerful for fuzzy recall, but it has no notion of authority, scope, or intent. Two passages can sit close in embedding space while belonging to entirely different domains, and the retriever has no signal to break the tie. The classic symptom is the polysemous term: "charge" (billing) versus "charge" (battery) versus "charge" (legal). Cosine distance treats all three as neighbors, so your top-k results become a lottery, and the language model dutifully grounds its answer in whichever chunk happened to rank first.

The deeper problem is that raw embeddings collapse the difference between what a document says and what a document is about. A press release that mentions your refund policy in passing will embed near genuine refund documentation, and without a categorical signal the retriever cannot down-weight the tangential mention. As corpora grow past a few thousand documents, precision degrades in exactly the way that erodes trust: not with obvious nonsense, but with plausible, on-topic, subtly-wrong answers.

A taxonomy restores the missing axis. When every document carries a controlled set of concept references (domain, product line, audience, lifecycle stage), you can constrain the candidate set before similarity scoring runs. You are no longer asking "what is closest?" but "what is closest among documents actually about this topic?" That single change turns retrieval from a probabilistic guess into a filtered, then ranked, operation. The taxonomy does the discriminating; the embedding does the ranking. Each does the job it is good at, and grounding improves because the model only ever sees candidates that belong.

Model concepts as first-class content, not free-text tags

The most common taxonomy mistake is storing categories as strings. A `category: "Billing"` field on each document feels convenient until someone types "billing", "Billing ", or "Payments", and your filter silently splits one concept into four. Free-text tags have no canonical identity, no hierarchy, and no place to hang synonyms or descriptions, which are exactly the affordances a retrieval layer needs.

The fix is to model each concept as its own document type and reference it. In Sanity, you define a `concept` schema with `defineType`, give it a human label, a stable slug, an optional parent reference for hierarchy, and an array of synonyms and related terms. Every content document then points at concepts through typed references rather than repeating strings. Because Content Lake resolves references natively, a GROQ query can walk from a document to its concepts and back with the `->` operator, so "find everything tagged with this concept or any of its children" is a single traversal, not a join you assemble in application code.

Modeling concepts as content pays off three ways. First, the taxonomy is queryable: you can ask which documents lack a required concept, or which concepts have gone orphaned. Second, it is governable: Content Releases and Roles & Permissions let a taxonomy owner curate the controlled vocabulary without a developer deploying a code change. Third, it is portable: because the concepts and the synonyms they carry live in structured fields, they travel into Portable Text annotations and into the embedding pipeline as clean metadata, so the same taxonomy that powers a faceted filter in your app also powers the pre-filter in your RAG retrieval. One structure, many surfaces, no drift between them.

Design the hierarchy: breadth, depth, and the granularity trap

A taxonomy is a set of decisions about how coarse or fine your categories should be, and both extremes fail. Too flat (a single list of two hundred sibling tags) and editors cannot find the right term, so they invent near-duplicates and precision collapses. Too deep (eight levels of nested subcategories) and no one can agree where a document belongs, so tagging becomes inconsistent and the hierarchy stops meaning anything. The workable middle is usually three to four levels: a small set of top-level domains, a manageable fan-out beneath each, and leaf concepts specific enough to discriminate but broad enough that editors apply them consistently.

Start from the questions your RAG system must answer, not from an abstract ontology. If users ask about a product across its lifecycle, model lifecycle as an explicit facet (evaluation, onboarding, troubleshooting, offboarding) rather than burying it inside topic names. Faceted taxonomies, where a document carries one concept from each of several independent axes, outperform a single deep tree because they let you filter on the dimension that matters for a given query. A support agent's retrieval constrains on product plus lifecycle; a marketing surface constrains on audience plus funnel stage. Same content, different projections.

Because the hierarchy is expressed as parent references between concept documents, you can restructure it without re-tagging every document. Move a concept under a new parent and every query that traverses the tree picks up the change immediately, since GROQ resolves the relationship at read time rather than baking it into a denormalized field. That read-time resolution is what makes an evolving taxonomy survivable: your categories will be wrong on day one, and the platform has to let you fix them on day one hundred without a migration.

Blend structured filters with vector similarity in one query

The point of the taxonomy is to constrain retrieval before ranking, and the cleanest place to do that is in the query itself. A naive RAG stack does this in two systems: a vector database returns semantic neighbors, then application code filters them by metadata fetched from somewhere else, discarding whatever the pre-filter removes and often re-querying to backfill. That round-trip is where latency and complexity accumulate, and where the filter and the content fall out of sync.

GROQ collapses the two operations. In a single query you can filter on concept references (`references($conceptId)`), score the survivors by semantic similarity with `text::semanticSimilarity()`, blend in lexical matching with `match()` and `score()` for exact terms the embedding might miss, and project exactly the fields your prompt template needs, all in one round trip against Content Lake. The candidate set is scoped by taxonomy, ranked by meaning, boosted by keyword, and shaped for the model, without stitching together a vector store, a metadata store, and a serialization step. Hybrid retrieval, filtered then semantically ranked then lexically boosted, is the pattern that most improves grounding, and here it is one expression rather than three services.

The grounding win is concrete. Because the taxonomy filter runs first, the model never receives a chunk from the wrong domain, so it cannot ground an answer in it. Because lexical scoring runs alongside vector scoring, exact identifiers (SKUs, error codes, statute numbers) that embeddings routinely blur are recovered precisely. And because the projection returns the concept labels alongside the text, you can pass provenance into the prompt, letting the model cite which category each retrieved passage came from and letting you audit why a given answer was grounded the way it was.

Automate tagging and keep the taxonomy from rotting

A taxonomy is only as good as its coverage, and manual tagging degrades the moment volume outpaces editors. Under-tagged documents fall out of filtered retrieval entirely, becoming invisible to the very RAG system they should feed. Over-tagged documents match too broadly and dilute precision. Either way, the gap between the taxonomy you designed and the taxonomy actually applied is where retrieval quality quietly leaks away.

The sustainable answer is to make tagging a workflow, not a chore. With Sanity Functions you can run serverless automation on content events: when a document is created or updated, a function proposes concept references by matching the document text against the controlled vocabulary and its synonyms, then writes the suggestions back for an editor to confirm. The App SDK lets you surface that assistance directly inside Sanity Studio as a custom input, so the person who knows the content approves the tags in context rather than in a separate tool. Automation proposes; a human governs; the taxonomy stays both complete and correct.

Governance is the other half of not rotting. Roles & Permissions restrict who can add or retire concepts, so the controlled vocabulary does not sprawl. Audit logs record who changed a concept and when, which matters when a taxonomy shift silently alters what your RAG system retrieves. Content Releases let you stage a taxonomy change, review its effect, and publish it as a unit rather than editing live and hoping. Treating the taxonomy as governed, versioned content, rather than a config file someone edits by hand, is what keeps semantic search trustworthy a year after launch, when the corpus has tripled and the original tagger has moved teams.

Measure grounding: precision, coverage, and traceability

You cannot improve what you do not instrument, and RAG grounding fails quietly, so the measurement has to be deliberate. Three metrics matter. Retrieval precision asks: of the passages returned for a query, how many actually belong to the answer's domain? A taxonomy pre-filter should raise this sharply, because off-domain candidates are excluded before ranking. Coverage asks: what fraction of documents carry the concepts a query would filter on? Gaps here mean relevant content is silently unreachable. Traceability asks: for any generated answer, can you name exactly which documents grounded it and which concepts scoped their retrieval?

Because the taxonomy lives in queryable content, all three are answerable without a separate analytics stack. A GROQ query counts documents missing a required facet, so coverage gaps surface as a list you can assign and fix. Because retrieval scopes on concept references, the same references come back in the projection, so every answer carries its provenance, which concepts filtered it and which passages ranked. That provenance is the difference between a RAG system you can debug and one you can only re-roll and hope.

The strategic reframe is this: semantic search quality is not primarily a model problem, it is a content-structure problem. Two teams running the identical embedding model and the identical language model will get very different grounding quality depending on whether their content carries a disciplined taxonomy. The lever most teams reach for (a better embedding, a larger context window) moves the metric a little. The lever that moves it a lot is the one closest to the content: a governed, hierarchical, reference-based taxonomy that constrains retrieval before similarity ever runs.

Taxonomy and hybrid retrieval: how the approaches compare

FeatureSanityContentfulStrapiPinecone + external CMS
Concepts as governed contentConcepts are their own document type via defineType, referenced with typed relations; hierarchy is parent references resolved at read time.Reference fields and content types support a taxonomy, though hierarchy traversal and synonym modeling are assembled in app code.Collection types plus relations can model concepts; hierarchy and controlled vocabulary are your schema and code to maintain.Taxonomy lives in the CMS; the vector store holds only vectors and flat metadata, so the two must be kept in sync manually.
Filter plus semantic rank in one querySingle GROQ query blends references(), text::semanticSimilarity(), and match()/score() against Content Lake in one round trip.GraphQL filters plus a separate vector service; hybrid ranking is composed across systems rather than in one query.REST/GraphQL filtering plus an external vector database; blending semantic and lexical scoring is application-side.Native vector similarity with metadata filters, but lexical scoring and content projection happen in a separate layer.
Traversing hierarchy in retrievalThe -> operator walks document to concept to parent in the query, so 'this concept or any child' is one traversal.Linked-entry traversal is possible but multi-level hierarchy typically resolves across several GraphQL requests.Nested relation population resolves hierarchy, with depth and performance tuned per query in application code.Hierarchy is not in the vector store; parent/child expansion must be precomputed and denormalized into metadata.
Automated tagging in the editorFunctions propose concept references on content events; App SDK surfaces suggestions as a custom input inside the Studio.App framework and webhooks enable automation, though in-editor suggestion UIs are custom builds or marketplace apps.Lifecycle hooks and plugins allow automation; editor-side suggestion UX is a custom plugin you build and host.Tagging automation runs outside the CMS entirely; editors reconcile suggestions in whichever tool holds the taxonomy.
Taxonomy governance and auditRoles & Permissions gate the vocabulary, Content Releases stage taxonomy changes, and Audit logs record who changed what.Roles, environments, and scheduled releases support governed changes across most plan tiers.Role-based access and draft/publish exist; staged multi-document taxonomy releases are self-assembled.Governance depends on the paired CMS; the vector store offers little native review or audit of taxonomy edits.
Coverage and drift auditingA GROQ query lists documents missing a required facet, so coverage gaps and orphaned concepts surface as fixable lists.Coverage checks run through the Content Management API or GraphQL queries built and scheduled by your team.Coverage auditing is a custom query or script against the content API you write and maintain.Coverage of taxonomy against the corpus must be reconciled across two systems, since neither holds the full picture alone.

Ready to try Sanity?

See how Sanity can transform your enterprise content operations.