Advanced RAG Chunking Strategies: Semantic, Agentic, and Graph-Based Approaches

Advanced RAG Chunking Strategies for Production AI Systems

RAG chunking strategies are the foundation of effective Retrieval-Augmented Generation systems. The way you split, embed, and retrieve documents determines the quality of LLM responses more than any other factor. Therefore, mastering advanced chunking techniques is essential for building production-grade AI applications that deliver accurate, contextually relevant answers.

Traditional fixed-size chunking splits documents at arbitrary character or token boundaries, often breaking sentences mid-thought and losing semantic coherence. Moreover, naive chunking ignores document structure, treating headers, code blocks, and tables the same as regular paragraphs. Consequently, retrieval quality suffers because the chunks don’t represent meaningful units of information. This guide explores advanced strategies that dramatically improve retrieval accuracy.

RAG Chunking Strategies: Semantic Chunking

Semantic chunking uses embedding similarity to determine natural breakpoints in text. Instead of splitting at fixed intervals, it identifies where the topic shifts by comparing the semantic similarity of consecutive sentences. Furthermore, this approach preserves the natural flow of information, ensuring each chunk contains a complete thought or concept.

import numpy as np
from sentence_transformers import SentenceTransformer

class SemanticChunker:
    def __init__(self, model_name='all-MiniLM-L6-v2', threshold=0.3):
        self.model = SentenceTransformer(model_name)
        self.threshold = threshold

    def chunk(self, text, min_size=100, max_size=1000):
        sentences = self._split_sentences(text)
        embeddings = self.model.encode(sentences)

        chunks = []
        current_chunk = [sentences[0]]
        current_embedding = embeddings[0]

        for i in range(1, len(sentences)):
            similarity = np.dot(current_embedding, embeddings[i]) / (
                np.linalg.norm(current_embedding) * np.linalg.norm(embeddings[i])
            )

            chunk_text = ' '.join(current_chunk)
            if similarity < self.threshold and len(chunk_text) >= min_size:
                chunks.append(chunk_text)
                current_chunk = [sentences[i]]
                current_embedding = embeddings[i]
            elif len(chunk_text) >= max_size:
                chunks.append(chunk_text)
                current_chunk = [sentences[i]]
                current_embedding = embeddings[i]
            else:
                current_chunk.append(sentences[i])
                current_embedding = np.mean(
                    [current_embedding, embeddings[i]], axis=0
                )

        if current_chunk:
            chunks.append(' '.join(current_chunk))
        return chunks

    def _split_sentences(self, text):
        import re
        return [s.strip() for s in re.split(r'(?<=[.!?])\s+', text) if s.strip()]
AI semantic analysis and chunking visualization
Semantic chunking identifies natural topic boundaries for more coherent retrieval

Agentic RAG: Self-Improving Retrieval

Agentic RAG introduces an intelligent agent that decides how to retrieve, filter, and combine chunks based on the query complexity. Instead of a simple vector search, the agent can reformulate queries, perform multi-hop retrieval, and validate retrieved chunks before passing them to the LLM. Additionally, the agent learns from feedback to improve retrieval quality over time.

from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.tools import tool

@tool
def vector_search(query: str, top_k: int = 5) -> list:
    """Search the vector store for relevant document chunks."""
    results = vectorstore.similarity_search_with_score(query, k=top_k)
    return [(doc.page_content, score) for doc, score in results]

@tool
def graph_search(entity: str) -> list:
    """Search the knowledge graph for entity relationships."""
    query = f"""
    MATCH (e:Entity {{name: '{entity}'}})-[r]->(related)
    RETURN e.name, type(r), related.name, related.description
    LIMIT 10
    """
    return neo4j_driver.execute_query(query)

@tool
def rerank_results(query: str, documents: list) -> list:
    """Rerank documents using a cross-encoder model."""
    from sentence_transformers import CrossEncoder
    reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2')
    pairs = [[query, doc] for doc in documents]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, score in ranked[:3]]

# Create agentic RAG pipeline
agent = create_openai_tools_agent(
    llm=ChatOpenAI(model="gpt-4-turbo"),
    tools=[vector_search, graph_search, rerank_results],
    prompt=rag_agent_prompt
)
executor = AgentExecutor(agent=agent, tools=[vector_search, graph_search, rerank_results])

Graph-Based Chunking and Retrieval

Graph-based approaches represent documents as interconnected nodes, preserving relationships between concepts, entities, and sections. This technique excels when answers require synthesizing information from multiple parts of a document or across multiple documents. Furthermore, graph structures naturally capture hierarchical relationships like chapter-section-paragraph that flat vector stores lose.

from neo4j import GraphDatabase

class GraphRAG:
    def __init__(self, uri, auth):
        self.driver = GraphDatabase.driver(uri, auth=auth)

    def ingest_document(self, doc_id, chunks, entities):
        with self.driver.session() as session:
            # Create document node
            session.run(
                "CREATE (d:Document {id: $id, title: $title})",
                id=doc_id, title=chunks[0].metadata.get('title', '')
            )
            # Create chunk nodes with embeddings
            for i, chunk in enumerate(chunks):
                session.run("""
                    MATCH (d:Document {id: $doc_id})
                    CREATE (c:Chunk {
                        id: $chunk_id, content: $content,
                        embedding: $embedding, position: $pos
                    })
                    CREATE (d)-[:CONTAINS]->(c)
                """, doc_id=doc_id, chunk_id=f"{doc_id}_chunk_{i}",
                   content=chunk.page_content,
                   embedding=chunk.metadata['embedding'],
                   pos=i)

            # Create entity relationships
            for entity in entities:
                session.run("""
                    MERGE (e:Entity {name: $name, type: $type})
                    WITH e
                    MATCH (c:Chunk {id: $chunk_id})
                    CREATE (c)-[:MENTIONS]->(e)
                """, name=entity['name'], type=entity['type'],
                   chunk_id=entity['chunk_id'])

    def hybrid_search(self, query, top_k=5):
        with self.driver.session() as session:
            # Combine vector similarity with graph traversal
            results = session.run("""
                CALL db.index.vector.queryNodes('chunk_embeddings', $k, $embedding)
                YIELD node, score
                OPTIONAL MATCH (node)-[:MENTIONS]->(e:Entity)<-[:MENTIONS]-(related:Chunk)
                WHERE related <> node
                RETURN node.content AS content, score,
                       collect(DISTINCT related.content)[..2] AS related_chunks,
                       collect(DISTINCT e.name) AS entities
                ORDER BY score DESC
                LIMIT $k
            """, k=top_k, embedding=self.embed(query))
            return list(results)
Knowledge graph visualization for RAG systems
Graph-based RAG captures entity relationships that flat vector stores miss

Hybrid Chunking: Combining Multiple Strategies

Production RAG systems rarely rely on a single chunking strategy. Instead, they combine semantic chunking for prose content, structure-aware chunking for technical documents, and graph-based retrieval for entity-rich queries. Additionally, a reranking step using cross-encoder models ensures the most relevant chunks are selected regardless of the initial retrieval method.

Key Takeaways

  • Start with a solid foundation and build incrementally based on your requirements
  • Test thoroughly in staging before deploying to production environments
  • Monitor performance metrics and iterate based on real-world data
  • Follow security best practices and keep dependencies up to date
  • Document architectural decisions for future team members

The key insight is matching the chunking strategy to the content type. Code documentation benefits from function-level chunks with docstrings preserved. Legal documents need paragraph-level chunks with section references maintained. Conversational content works best with dialogue-turn chunks. Therefore, a content-aware chunking pipeline that automatically selects the right strategy yields the best results. See LlamaIndex RAG optimization docs for more patterns.

Data processing pipeline architecture
Hybrid chunking pipelines combine multiple strategies for optimal retrieval accuracy

In conclusion, advanced RAG chunking strategies are the differentiator between AI applications that deliver accurate answers and those that hallucinate or miss context. Start with semantic chunking as your baseline, add graph-based retrieval for entity-heavy domains, and implement agentic RAG for complex multi-hop queries. Monitor retrieval quality metrics continuously and iterate on your chunking pipeline.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top