<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Your Rag demo works. But is it production-ready ?]]></title><description><![CDATA[Building a RAG demo is easy. Building a reliable, secure, and scalable RAG system is a different challenge.
Let's evolve BeyondRAG beyond a basic proof of conce]]></description><link>https://metemrtkan.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 02 Nov 2026 08:42:56 GMT</lastBuildDate><atom:link href="https://metemrtkan.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Your RAG demo works. But is it production-ready ?]]></title><description><![CDATA[Explore how to evolve RAG into production-ready AI with modular architecture, configurable retrieval, cross-lingual search, and enterprise security]]></description><link>https://metemrtkan.hashnode.dev/your-rag-demo-works-but-is-it-production-ready</link><guid isPermaLink="true">https://metemrtkan.hashnode.dev/your-rag-demo-works-but-is-it-production-ready</guid><category><![CDATA[RAG ]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[chunking]]></category><category><![CDATA[elasticsearch]]></category><category><![CDATA[rbac]]></category><category><![CDATA[crosslanguage]]></category><dc:creator><![CDATA[Mete Mertkan]]></dc:creator><pubDate>Sat, 31 Oct 2026 14:54:31 GMT</pubDate><content:encoded><![CDATA[<h1>Beyond RAG: Evolving an Enterprise Retrieval Engine Through Better Architecture</h1>
<p>A RAG demo is relatively easy to build.</p>
<p>Load a few documents, split them into chunks, generate embeddings, store those vectors, retrieve the most relevant passages, and ask a language model to produce an answer. With a small dataset and a carefully chosen question, the result can look impressive.</p>
<p>The difficult part starts when that demo needs to become a system that other teams can depend on.</p>
<p>Real-world enterprise retrieval systems need to deal with questions that are rarely addressed in tutorials:</p>
<ul>
<li><strong>Can users retrieve only the information they are authorized to see?</strong></li>
<li><strong>Can someone ask a question in one language and retrieve reliable answers written in another?</strong></li>
<li><strong>Can different use cases use different retrieval profiles without rewriting application code?</strong></li>
<li><strong>Can domain logic evolve without being coupled to a specific search engine, embedding provider, or document parser?</strong></li>
<li><strong>What happens when document ingestion fails halfway through?</strong></li>
<li><strong>Can we explain why a result was retrieved, measure its quality, and improve it systematically?</strong></li>
</ul>
<p>These are architecture and software engineering questions as much as they are AI questions.</p>
<p>I have been exploring these challenges in <a href="https://github.com/metemertkan/BeyondRAG"><strong>BeyondRAG</strong></a>, an evolving Python-based retrieval project built around Elasticsearch and configurable RAG capabilities. Rather than treating it as a finished framework, I use it as a practical engineering exercise: take a working retrieval implementation and improve the boundaries, configuration, and responsibilities that matter when moving beyond a proof of concept.</p>
<p>This article walks through the architectural evolution, the trade-offs, and the reasoning behind each decision.</p>
<blockquote>
<p><strong>The central premise:</strong> Production-oriented RAG is not just about picking a larger model or tweaking a prompt. It is about building a retrieval engine whose behavior, dependencies, security boundaries, and failure modes can be understood, tested, and evolved.</p>
</blockquote>
<hr />
<h2>1. The Gap Between a RAG Demo and a Dependable System</h2>
<p>A basic RAG pipeline typically follows five linear steps:</p>
<ol>
<li>Receive a user query.</li>
<li>Convert the query into an embedding vector.</li>
<li>Perform an unconstrained <code>top-K</code> vector similarity search.</li>
<li>Inject retrieved chunks into a prompt template.</li>
<li>Call an LLM to generate an answer.</li>
</ol>
<p>That is a great proof of concept. But this naive pipeline hides critical assumptions inside implementation details:</p>
<ul>
<li><strong>Rigid retrieval parameters:</strong> Fixed top-K limits, fixed chunk sizes, and hardcoded similarity metrics.</li>
<li><strong>Leaky abstractions:</strong> The core service orchestrating the workflow often directly instantiates Elasticsearch clients, Ollama or OpenAI SDKs, PyPDF extractors, and embedding models.</li>
<li><strong>Single-user assumptions:</strong> No isolation between departments, roles, or tenants.</li>
</ul>
<p>In an enterprise, one size never fits all:</p>
<ul>
<li>A <strong>Customer Support Bot</strong> prioritizes broad recall over a large corpus.</li>
<li>An <strong>Executive Assistant</strong> demands high precision and concise answers over a tight set of high-confidence policy memos.</li>
<li>A <strong>Compliance Auditor</strong> requires deterministic, verbatim source citations with zero hallucination tolerance.</li>
</ul>
<p>These are not separate applications; they are different <strong>retrieval profiles</strong> over the same organizational knowledge base.</p>
<p>Furthermore, access control cannot be an afterthought. If an unauthorized document enters an LLM context window, that is not a ranking error—it is a critical security failure.</p>
<hr />
<h2>2. Decouple Domain Behavior from Infrastructure</h2>
<p>The first architectural refactor in BeyondRAG was applying <strong>Dependency Inversion</strong> (Hexagonal / Ports and Adapters architecture) to isolate domain logic from concrete infrastructure.</p>
<p>In early prototypes, it is tempting to let the central knowledge base service directly call <code>elasticsearch.Elasticsearch</code>, <code>google.genai</code>, or <code>ollama</code>. While fast to write, this binds your business logic to third-party APIs, making unit testing slow, brittle, and dependent on live external services.</p>
<h3>Inverting the Dependencies</h3>
<p>We separated the retrieval engine into explicit domain interfaces (Protocols) and pluggable infrastructure adapters:</p>
<ul>
<li><strong>Domain Layer (</strong><code>src/rag/ports.py</code><strong>):</strong> Pure Python type contracts defining <em>what</em> the system needs.</li>
<li><strong>Infrastructure Adapters:</strong> Concrete implementations for Elasticsearch 8.15, Ollama, Gemini, PyPDF, and Markdown.</li>
<li><strong>Service Facade (</strong><code>src/rag/service.py</code><strong>):</strong> Thin coordinator delegating strictly to injected port implementations.</li>
</ul>
<pre><code class="language-mermaid">flowchart TD
    subgraph AppLayer["Application &amp; API Layer"]
        API["FastAPI Endpoints&lt;br/&gt;(/api/chat, /api/upload)"]
        CLI["CLI &amp; Benchmarks"]
    end

    subgraph DomainOrch["Domain Orchestration"]
        Facade["RBACKnowledgeBaseService (Facade)"]
        Ingest["Document Ingestor"]
        Retriever["Hybrid Retriever"]
        Synth["Answer Synthesizer"]
    end

    subgraph DomainPorts["Domain Port Protocols (src/rag/ports.py)"]
        P_Search["SearchIndexPort"]
        P_Embed["EmbeddingProviderPort"]
        P_LLM["LLMPort"]
        P_Extract["DocumentExtractorPort"]
        P_ACL["ACLPolicyPort"]
    end

    subgraph InfraAdapters["Infrastructure Adapters"]
        A_ES["Elasticsearch 8.15 Adapter&lt;br/&gt;(int8_hnsw + BM25)"]
        A_Ollama["Ollama / Gemini Adapter"]
        A_Embed["Embedding Providers&lt;br/&gt;(FeatureHash / Ollama / Google)"]
        A_PDF["PyPDF &amp; PlainText Parsers"]
    end

    API --&gt; Facade
    CLI --&gt; Facade
    Facade --&gt; Ingest
    Facade --&gt; Retriever
    Facade --&gt; Synth

    Ingest --&gt; DomainPorts
    Retriever --&gt; DomainPorts
    Synth --&gt; DomainPorts

    P_Search -.implements.- A_ES
    P_Embed -.implements.- A_Embed
    P_LLM -.implements.- A_Ollama
    P_Extract -.implements.- A_PDF
</code></pre>
<h3>Strongly Typed Domain Protocols</h3>
<p>Rather than importing client libraries, domain services depend on pure Python protocols:</p>
<pre><code class="language-python"># src/rag/ports.py
from typing import Protocol, List, Dict, Any, Optional

class EmbeddingProviderPort(Protocol):
    """Domain contract for vector generation."""
    @property
    def dimensions(self) -&gt; int: ...
    
    def embed_query(self, text: str) -&gt; List[float]: ...
    def embed_documents(self, texts: List[str]) -&gt; List[List[float]]: ...

class SearchIndexPort(Protocol):
    """Domain contract for search engine persistence &amp; retrieval."""
    def search(
        self,
        query_text: str,
        query_vector: List[float],
        acl_filter: List[str],
        top_k: int,
    ) -&gt; List[Dict[str, Any]]: ...
</code></pre>
<h3>Why This Matters</h3>
<ol>
<li><strong>Deterministic Testing:</strong> We can run full end-to-end integration tests using in-memory mock adapters without starting Docker, Elasticsearch, or an LLM daemon.</li>
<li><strong>Zero Vendor Lock-in:</strong> Swapping Elasticsearch for OpenSearch or switching embedding providers requires implementing an adapter—the core ingestion and ACL logic remains untouched.</li>
<li><strong>Clean Code Boundaries:</strong> Search query DSL belongs inside the search adapter; document parsing belongs in the extractor; retrieval policy belongs in the domain.</li>
</ol>
<hr />
<h2>3. Single Responsibility: Breaking the God Service</h2>
<p>Dependency inversion alone does not guarantee maintainability. A service can depend on interfaces and still be a bloated "God Class" that does too much.</p>
<p>In BeyondRAG, we split the monolithic knowledge base service into six single-responsibility components:</p>
<table>
<thead>
<tr>
<th align="left">Component</th>
<th align="left">Dedicated Responsibility</th>
<th align="left">Source File</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Ingestor</strong></td>
<td align="left">Manages document lifecycle, validation, and atomic chunk indexing</td>
<td align="left"><a href="https://github.com/metemertkan/BeyondRAG/blob/main/src/rag/ingestor.py"><code>src/rag/ingestor.py</code></a></td>
</tr>
<tr>
<td align="left"><strong>Chunker</strong></td>
<td align="left">Breaks documents into semantic retrieval units</td>
<td align="left"><a href="https://github.com/metemertkan/BeyondRAG/blob/main/src/rag/chunker.py"><code>src/rag/chunker.py</code></a></td>
</tr>
<tr>
<td align="left"><strong>ACL Engine</strong></td>
<td align="left">Normalizes roles and computes effective authorization filters</td>
<td align="left"><a href="https://github.com/metemertkan/BeyondRAG/blob/main/src/rag/acl.py"><code>src/rag/acl.py</code></a></td>
</tr>
<tr>
<td align="left"><strong>Hybrid Retriever</strong></td>
<td align="left">Executes pre-filtered vector and lexical search</td>
<td align="left"><a href="https://github.com/metemertkan/BeyondRAG/blob/main/src/rag/retriever.py"><code>src/rag/retriever.py</code></a></td>
</tr>
<tr>
<td align="left"><strong>Cross-Lingual Handler</strong></td>
<td align="left">Discovers domain concepts and aligns multilingual queries</td>
<td align="left"><a href="https://github.com/metemertkan/BeyondRAG/blob/main/src/rag/cross_lingual.py"><code>src/rag/cross_lingual.py</code></a></td>
</tr>
<tr>
<td align="left"><strong>Synthesizer</strong></td>
<td align="left">Produces grounded citations or RBAC barrier notifications</td>
<td align="left"><a href="https://github.com/metemertkan/BeyondRAG/blob/main/src/rag/synthesizer.py"><code>src/rag/synthesizer.py</code></a></td>
</tr>
</tbody></table>
<p>The central <code>RBACKnowledgeBaseService</code> remains as a lightweight facade that coordinates these components, providing a single clean API to web controllers and background workers.</p>
<hr />
<h2>4. Configurable Retrieval Profiles</h2>
<p>Hardcoding <code>top_k = 4</code> and <code>min_score = 0.7</code> across an entire application forces different use cases to make painful compromises.</p>
<p>BeyondRAG introduces <strong>Retrieval Profiles</strong> to make retrieval behavior an explicit, validated configuration:</p>
<pre><code class="language-yaml"># config/retrieval_profiles.yaml
profiles:
  precision:
    top_k: 2
    min_should_match: "75%"
    hybrid_dense_boost: 2.5
    hybrid_sparse_boost: 1.0
    synthesis_mode: "strict_grounded"
    
  high_recall:
    top_k: 8
    min_should_match: "40%"
    hybrid_dense_boost: 1.0
    hybrid_sparse_boost: 2.0
    num_candidates_multiplier: 5
    synthesis_mode: "comprehensive"
    
  executive_summary:
    top_k: 3
    min_should_match: "60%"
    hybrid_dense_boost: 2.0
    hybrid_sparse_boost: 1.2
    synthesis_mode: "fast_bullets"
</code></pre>
<h3>Precedence Resolution</h3>
<p>Configurability only works if precedence is predictable. BeyondRAG enforces a strict resolution hierarchy:</p>
<pre><code class="language-text">Default Profile (Baseline)
    └──&gt; Named Profile Override (e.g., 'precision')
            └──&gt; Request-Level Override (e.g., per-query top_k)
</code></pre>
<p>Every parameter is validated at startup so typos fail fast rather than silently falling back to defaults.</p>
<hr />
<h2>5. Chunking is a Retrieval Decision, Not a Preprocessing Detail</h2>
<p>Chunking is often treated as simple text splitting (<code>text[:500]</code>). In practice, chunking determines the granular boundary of semantic relevance.</p>
<ul>
<li><strong>Chunks that are too large:</strong> Dilute relevance with unrelated paragraphs and exhaust token context.</li>
<li><strong>Chunks that are too small:</strong> Fragment thoughts, causing the LLM to misinterpret incomplete context.</li>
</ul>
<p>BeyondRAG supports pluggable, deterministic chunking strategies:</p>
<ul>
<li><strong>Sliding Window:</strong> Fixed token windows with configurable overlap for dense unstructured text.</li>
<li><strong>Paragraph-Oriented:</strong> Preserves natural section headings and narrative boundaries.</li>
<li><strong>Sentence-Oriented:</strong> Chunks across grammatical clause boundaries, preventing sentence truncation.</li>
</ul>
<h3>The Token vs. Character Distinction</h3>
<p>A common bug in multilingual RAG is defining chunk size in characters. In English, 500 characters is roughly <del>100 words (</del>130 tokens). In Chinese, 500 characters can represent ~400 dense tokens! Chunk size must be measured in <strong>tokens</strong> using a tokenizer appropriate for your target embedding model.</p>
<hr />
<h2>6. Access Control Belongs in the Storage Engine (Pre-Filtering)</h2>
<p>In enterprise RAG, authorization is a core retrieval constraint, not a prompt instruction.</p>
<h3>Why Naive Python Post-Filtering Drops Recall to 0%</h3>
<p>The most common architectural mistake in early RAG systems is <strong>post-filtering</strong>: running an unconstrained vector search first, then dropping unauthorized chunks in application code:</p>
<pre><code class="language-python"># ❌ DANGEROUS ANTI-PATTERN: Naive Python Post-Filtering
raw_hits = vector_db.search(query_vector, top_k=5)
authorized_hits = [hit for hit in raw_hits if user.can_access(hit.role)]
</code></pre>
<p>When restricted documents (such as confidential M&amp;A memos or executive salary sheets) have high semantic similarity to a question, they saturate all <code>K</code> candidate slots in the vector graph. When Python discards them afterward, the user receives <strong>0 results (</strong><code>0% recall</code><strong>)</strong>—even though perfectly valid public documents exist in the index!</p>
<pre><code class="language-mermaid">flowchart TD
    subgraph Naive["❌ Naive RAG: Post-Filtering"]
        N1["User Query: 'Minimum cash reserve buffer?'"] --&gt; N2["Unconstrained Top-5 kNN Search"]
        N2 --&gt; N3["Top-5 Slots Filled by Restricted Finance Memos"]
        N3 --&gt; N4["Python Checks Role: User is Public-Only"]
        N4 --&gt; N5["Python Discards All 5 Hits&lt;br/&gt;❌ 0% Recall Starvation (Zero Results Returned)"]
    end

    subgraph Beyond["✅ BeyondRAG: Storage-Engine Pre-Filtering"]
        B1["User Query + Active Roles: ['__public__']"] --&gt; B2["Lucene Index Pre-Filters by acl_tags"]
        B2 --&gt; B3["kNN Graph Explores Only Authorized Nodes"]
        B3 --&gt; B4["Returns Authorized Public Documents&lt;br/&gt;✅ 100% Recall &amp; Zero Data Leakage"]
    end
</code></pre>
<h3>The Solution: Lucene-Level Pre-Filtering</h3>
<p>In BeyondRAG, access control is enforced directly inside the Elasticsearch 8.15 query engine:</p>
<pre><code class="language-python"># src/rag/retriever.py
acl_filter_clause = {"terms": {"acl_tags": effective_acl_filter}}

es_query = {
    "size": profile.top_k,
    "query": {
        "bool": {
            "should": [
                {"match": {"content": {"query": expanded_query, "boost": 2.0}}},
                {"match": {"title": {"query": expanded_query, "boost": 1.2}}}
            ],
            "filter": [acl_filter_clause]  # &lt;-- Lexical pre-filter
        }
    },
    "knn": {
        "field": "embedding",
        "query_vector": query_vector,
        "k": profile.top_k,
        "num_candidates": profile.num_candidates,
        "filter": acl_filter_clause        # &lt;-- Vector HNSW pre-filter
    }
}
</code></pre>
<p>Because permissions are evaluated during graph traversal, unauthorized content never enters memory and cannot starve authorized results.</p>
<hr />
<h2>7. Cross-Lingual Retrieval Needs Explicit Language Boundaries</h2>
<p>In global organizations, policy documents are frequently written in <strong>English</strong>, while employees ask questions in <strong>Traditional or Simplified Chinese, Dutch, or German</strong>:</p>
<ul>
<li><p><strong>Uploaded English PDF (</strong><code>HK_Treasury_FX_Liquidity_Policy_Q4.pdf</code><strong>):</strong></p>
<blockquote>
<p><em>"The Group Treasury division must maintain an intraday minimum unencumbered cash reserve of HKD 450,000,000..."</em></p>
</blockquote>
</li>
<li><p><strong>Employee Query in Chinese:</strong></p>
<blockquote>
<p><code>「最低港元現金儲備緩衝是多少？外匯遠期合約限額是多少？」</code></p>
</blockquote>
</li>
</ul>
<p>Standard lexical search (<code>BM25</code>) fails completely because there is zero character overlap between Chinese characters and English words.</p>
<p>BeyondRAG separates cross-lingual handling into distinct responsibilities:</p>
<ol>
<li><strong>Language Detection:</strong> Identifies script and language without mistaking accented Latin characters for foreign scripts.</li>
<li><strong>Dynamic Ingestion-Time Concept Extraction:</strong> Whenever a PDF is uploaded, a local LLM extracts key domain concepts and generates bilingual English ↔ Chinese triggers.</li>
<li><strong>Query Expansion &amp; Semantic Projection:</strong> Projects queries into a shared semantic representation, preserving original search terms while providing bilingual coverage.</li>
</ol>
<p>When a Chinese query is executed, BeyondRAG retrieves the exact English policy passage and cites the <strong>HKD 450,000,000</strong> reserve in milliseconds.</p>
<hr />
<h2>8. Embedding Configuration is Part of the Index Contract</h2>
<p>Embedding models cannot be treated as interchangeable black boxes.</p>
<p>Vector dimensions, normalization algorithms (L2 vs. cosine), distance metrics (dot product, Euclidean, cosine), and token truncation limits constitute an immutable contract with your search index.</p>
<p>If an application silently switches from a 256-dimensional model to a 1536-dimensional model, or changes normalization strategies, vector math breaks.</p>
<p>BeyondRAG introduces an <strong>Embedding Manifest</strong> stored alongside the index metadata:</p>
<pre><code class="language-json">{
  "manifest_version": "1.0",
  "provider": "ollama",
  "model_name": "qwen2.5:1.5b",
  "dimensions": 256,
  "metric": "cosine",
  "normalization": "l2",
  "created_at": "2026-10-30T16:12:12Z"
}
</code></pre>
<p>If an incoming document or query embedding does not match the index manifest, the engine <strong>fails explicitly and immediately</strong> rather than generating corrupt vector scores.</p>
<hr />
<h2>9. Ingestion Reliability: Idempotency &amp; Lifecycle States</h2>
<p>In production, documents are updated, re-indexed, and occasionally encounter corrupted pages. A dependable ingestion engine requires explicit lifecycle states:</p>
<pre><code class="language-text">[PENDING] ──&gt; [PROCESSING] ──&gt; [INDEXED]
                     │
                     └──&gt; [FAILED] (with rollback)
</code></pre>
<p>Key reliability patterns implemented in BeyondRAG:</p>
<ul>
<li><strong>Deterministic Document IDs:</strong> Document IDs are derived from a cryptographic hash of content and tenant metadata (<code>doc-sha256</code>), preventing duplicate chunk creation on retry.</li>
<li><strong>Atomic Rollback:</strong> If bulk indexing fails midway, previously indexed chunks for that document are automatically cleaned up.</li>
<li><strong>Zero Orphaned Uploads:</strong> Uploaded binary files are bound to the document lifecycle.</li>
</ul>
<hr />
<h2>10. Automated Benchmarks: Putting Architecture to the Test</h2>
<p>To verify that storage-engine pre-filtering actually solves recall starvation, we run an automated benchmark suite (<code>benchmark_rbac.py</code>) comparing naive post-filtering against BeyondRAG across 20 iterations:</p>
<table>
<thead>
<tr>
<th align="left">Strategy</th>
<th align="center">Recall Under Role Restriction</th>
<th align="center">P95 Latency</th>
<th align="left">Root Cause</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Naive kNN + Python Post-Filter</strong></td>
<td align="center"><strong>0%</strong> (<code>0 / 2</code>)</td>
<td align="center">N/A (Failed)</td>
<td align="left">Unauthorized candidates fill the HNSW queue; Python discards all hits.</td>
</tr>
<tr>
<td align="left"><strong>BeyondRAG ES 8.15 Pre-Filtered</strong></td>
<td align="center"><strong>100%</strong> (<code>2 / 2</code>)</td>
<td align="center"><strong>3.28 ms</strong></td>
<td align="left">Lucene pre-filters <code>acl_tags</code> before vector scoring and BM25 ranking.</td>
</tr>
</tbody></table>
<hr />
<h2>11. Run the Interactive Sandbox Locally</h2>
<p>You can clone and run the complete containerized BeyondRAG application with the Catppuccin Mocha interface in one command:</p>
<pre><code class="language-bash">git clone https://github.com/metemertkan/BeyondRAG.git
cd BeyondRAG
docker compose up -d --build
</code></pre>
<p>Access the dashboard at <code>http://localhost:8090</code> to:</p>
<ol>
<li><strong>Upload &amp; Tag PDFs:</strong> Upload an English Finance or Operations PDF and assign RBAC tags.</li>
<li><strong>Test Public Blocking:</strong> Run a Chinese query as <code>Public Only</code> to see the live <code>🔒 403 Access Blocked</code> card.</li>
<li><strong>Switch Roles:</strong> Toggle to <code>Finance Role</code> or <code>Operation Role</code> and watch the English document unlock and answer in Chinese with sub-10ms latency.</li>
</ol>
<hr />
<h2>Summary &amp; What’s Next</h2>
<p>Building a RAG proof of concept teaches you how to connect an LLM to a vector database.</p>
<p>Building a dependable retrieval engine teaches you how to manage architectural boundaries:</p>
<ul>
<li><strong>Decouple domain logic from infrastructure</strong> using ports and protocols.</li>
<li><strong>Push access control down into the storage engine</strong> to eliminate recall starvation.</li>
<li><strong>Separate cross-lingual responsibilities</strong> instead of relying on opaque translation.</li>
<li><strong>Treat embedding metadata as an immutable index contract.</strong></li>
</ul>
<p>In <strong>Episode #02 of Beyond a PoC</strong>, we will take this foundation further with a deep dive into <strong>Multi-Column Document Ingestion, Financial Table Parsing, and Native Multilingual Embedding Models (</strong><code>bge-m3</code><strong>)</strong>.</p>
<p>👉 <strong>Repository:</strong> <a href="https://github.com/metemertkan/BeyondRAG">https://github.com/metemertkan/BeyondRAG</a></p>
<hr />
<p><em>What has been the most frustrating architectural challenge in your production RAG deployments—authorization, cross-lingual search, or ingestion reliability? Let's discuss in the comments below.</em></p>
]]></content:encoded></item></channel></rss>