Build a Production-Ready RAG Pipeline with LangChain and Oracle AI Database


Creating a reliable retrieval-augmented generation (RAG) system usually involves a lot of boilerplate. With the langchain-oracledb integration, you can build a complete pipeline; document loading, chunking, vector storage, hybrid search, semantic caching, and conversation memory; in a clean and compact way.

This guide walks you through a practical, end-to-end implementation using Oracle AI Database as the single source of truth for vectors, text, cache, and history.

Why Oracle AI Database + LangChain?

Instead of juggling multiple systems (vector database + cache + message store), everything lives inside Oracle AI Database. This simplifies architecture, improves consistency, and gives you enterprise-grade features like transactions and security out of the box.

High-Level Architecture

Here’s how the pieces connect:

  • Ingestion: Load → Split → Embed → Store in OracleVS
  • Retrieval: Hybrid search combining semantic similarity and keyword search
  • Intelligence Layer: Semantic cache + persistent chat history
  • Answer Generation: Use retrieved context to generate responses

Step 1: Document Ingestion Pipeline

We’ll read runbook content from a database table, split it intelligently, and store the chunks with embeddings.

from langchain_oracledb.document_loaders import OracleDocLoader, OracleTextSplitter
from langchain_oracledb.vectorstores import OracleVS, DistanceStrategy
from langchain_community.embeddings import HuggingFaceEmbeddings

def ingest_documents(conn):
    # Load documents with metadata
    loader = OracleDocLoader(
        conn=conn,
        params={
            "owner": conn.username,
            "tablename": "RUNBOOK_SOURCE",
            "colname": "CONTENT",
            "mdata_cols": ["ID", "TITLE", "CATEGORY"]
        }
    )
    docs = loader.load()

    # Create vector store
    embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
    
    vs = OracleVS(
        conn,
        embeddings,
        table_name="DOCUMENT_VECTORS",
        distance_strategy=DistanceStrategy.COSINE
    )

    # Split and store
    splitter = OracleTextSplitter(
        conn=conn,
        params={"by": "words", "max": 250, "split": "sentence"}
    )

    vs.add_documents(docs, text_splitter=splitter)
    return vs

Step 2: Hybrid Retrieval

Combine semantic search (vector similarity) with keyword search for better results.

from langchain_oracledb.retrievers import OracleTextSearchRetriever

def hybrid_retrieve(vector_store, query, k=5):
    # Semantic search
    semantic_docs = vector_store.similarity_search(query, k=k)
    
    # Keyword search
    keyword_retriever = OracleTextSearchRetriever(
        vector_store=vector_store,
        k=k
    )
    keyword_docs = keyword_retriever.invoke(query)
    
    # Simple fusion logic (you can improve this with RRF)
    combined = semantic_docs + keyword_docs
    # Deduplicate and rank (basic version shown)
    seen = set()
    unique_docs = []
    for doc in combined:
        doc_id = doc.metadata.get("ID")
        if doc_id not in seen:
            seen.add(doc_id)
            unique_docs.append(doc)
    
    return unique_docs[:k]

Step 3: Add Semantic Caching

Reuse answers for similar questions to reduce cost and latency.

from langchain_oracledb.cache import OracleSemanticCache
from langchain.schema import Generation

def get_cached_or_generate(conn, embeddings, query, context):
    cache = OracleSemanticCache(
        conn,
        embeddings,
        table_name="SEMANTIC_CACHE",
        score_threshold=0.85
    )
    
    cached = cache.lookup(query, "answer_cache")
    
    if cached:
        return cached[0].text, True  # cache hit
    
    # Generate new answer (call your LLM here)
    answer = generate_response(query, context)
    
    cache.update(query, "answer_cache", [Generation(text=answer)])
    return answer, False

Step 4: Persist Conversation History

Keep full chat context across sessions.

from langchain_oracledb.chat_message_histories import OracleChatMessageHistory
from langchain.schema import HumanMessage, AIMessage

def save_to_history(conn, session_id, question, answer):
    history = OracleChatMessageHistory(
        session_id,
        client=conn,
        table_name="CONVERSATION_HISTORY"
    )
    
    history.add_messages([
        HumanMessage(content=question),
        AIMessage(content=answer)
    ])
    
    return len(history.messages)

Complete End-to-End Function

Here’s how everything works together in a single function:

def ask_question(conn, vector_store, question, session_id="default"):
    embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
    
    # 1. Hybrid retrieval
    relevant_docs = hybrid_retrieve(vector_store, question)
    context = "\n\n".join([doc.page_content for doc in relevant_docs])
    
    # 2. Check cache or generate
    answer, was_cached = get_cached_or_generate(conn, embeddings, question, context)
    
    # 3. Save to history
    history_length = save_to_history(conn, session_id, question, answer)
    
    return {
        "answer": answer,
        "cached": was_cached,
        "sources": len(relevant_docs),
        "history_messages": history_length
    }

How to Run This Locally

  1. Install dependencies with Poetry
  2. Run the sample script — it automatically spins up an Oracle AI Database Free container using Testcontainers
  3. Ask questions and observe hybrid retrieval, caching behavior, and growing chat history

Benefits of This Approach

  • Single database for vectors, documents, cache, and history
  • Hybrid search improves answer quality
  • Semantic caching reduces expensive LLM calls
  • Conversation state is durable and queryable
  • Very little custom code required thanks to langchain-oracledb

Final Thoughts

Building a solid RAG system doesn’t have to be complex. By leveraging Oracle AI Database through the official LangChain integration, you get a clean, maintainable pipeline that handles ingestion, retrieval, caching, and memory in one place.

This pattern scales well and keeps your architecture simple while delivering strong retrieval performance.

Start experimenting with the sample, you’ll be surprised how quickly you can get a capable system running.

Post a Comment

Previous Post Next Post