Skip to content

Framework Integrations

Medha can be plugged into LangChain, LlamaIndex, and Haystack using each framework's native extension point. The cache is transparent to the rest of the pipeline: a hit short-circuits before the LLM is called; a miss falls through normally and the result is stored for next time.

Framework Integration point Medha role
LangChain langchain_core.caches.BaseCache Intercepts LLM calls via set_llm_cache()
LlamaIndex Workflow @step Pre-filter before the query engine
Haystack @component in a Pipeline First stage, short-circuits on cache hit

All examples use InMemoryBackend — no Qdrant, no PostgreSQL, no API keys required.

Requirements:

pip install "medha-archai[fastembed]"
pip install langchain-core langchain-community
pip install llama-index-core
pip install haystack-ai

Shared Setup

A single Medha instance and a mock SQL generator used by all three integrations.

import asyncio
from medha import Medha, Settings, SearchStrategy
from medha.embeddings.fastembed_adapter import FastEmbedAdapter

embedder = FastEmbedAdapter(model_name="BAAI/bge-small-en-v1.5")
settings = Settings(backend_type="memory", score_threshold_semantic=0.82)
medha = Medha("framework_demo", embedder=embedder, settings=settings)
await medha.start()

# Mock LLM — simulates a Text-to-SQL model (no API key needed)
_SQL_MAP = {
    "users":    "SELECT COUNT(*) FROM users",
    "revenue":  "SELECT SUM(amount) FROM invoices WHERE YEAR(created_at) = YEAR(NOW())",
    "products": "SELECT * FROM products ORDER BY price DESC LIMIT 10",
    "orders":   "SELECT COUNT(*) FROM orders WHERE DATE(created_at) = CURDATE()",
    "salary":   "SELECT AVG(salary) FROM employees",
}

def mock_llm(question: str) -> str:
    q = question.lower()
    for keyword, sql in _SQL_MAP.items():
        if keyword in q:
            return sql
    return f"SELECT * FROM unknown  -- question: {question}"

# Warm the cache with known pairs
seed_pairs = [
    ("How many users are registered?",          "SELECT COUNT(*) FROM users"),
    ("What is the total revenue this year?",    "SELECT SUM(amount) FROM invoices WHERE YEAR(created_at) = YEAR(NOW())"),
    ("Show the top 10 most expensive products", "SELECT * FROM products ORDER BY price DESC LIMIT 10"),
    ("Count orders placed today",               "SELECT COUNT(*) FROM orders WHERE DATE(created_at) = CURDATE()"),
    ("What is the average employee salary?",    "SELECT AVG(salary) FROM employees"),
]
for q, sql in seed_pairs:
    await medha.store(q, sql)

LangChain

LangChain exposes BaseCache as the standard cache interface. Any object implementing lookup() / update() / clear() can be registered globally with set_llm_cache() — every subsequent LLM call checks the cache first.

chain.invoke(question)
    └─ LLM.__call__(prompt)
           └─ cache.lookup(prompt, llm_string)   ← Medha.search()
                  hit  → return cached Generation
                  miss → call LLM → cache.update() → return
import concurrent.futures
from langchain_core.caches import BaseCache
from langchain_core.outputs import Generation
from langchain_core.globals import set_llm_cache
from typing import Optional


def _run_async(coro):
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
        return pool.submit(asyncio.run, coro).result()


class MedhaLangChainCache(BaseCache):
    def __init__(self, medha_instance: Medha) -> None:
        self._medha = medha_instance

    def lookup(self, prompt: str, llm_string: str) -> Optional[list[Generation]]:
        hit = _run_async(self._medha.search(prompt))
        if hit.strategy == SearchStrategy.NO_MATCH:
            return None
        return [Generation(text=hit.generated_query)]

    def update(self, prompt: str, llm_string: str, return_val: list[Generation]) -> None:
        sql = return_val[0].text if return_val else ""
        _run_async(self._medha.store(prompt, sql))

    def clear(self, **kwargs) -> None:
        pass


set_llm_cache(MedhaLangChainCache(medha))

LlamaIndex

LlamaIndex 0.10+ Workflows use typed Event objects and @step decorated async methods. The cache check is a first step that short-circuits to StopEvent on a hit, or emits CacheMissEvent to trigger the LLM step.

StartEvent(question)
    └─ check_cache ──hit──→ StopEvent(sql)
                   ──miss─→ CacheMissEvent(question)
                                 └─ call_llm → store → StopEvent(sql)
from llama_index.core.workflow import Workflow, step, Event, StartEvent, StopEvent, Context
from typing import Union


class CacheMissEvent(Event):
    question: str


class MedhaWorkflow(Workflow):
    def __init__(self, medha_instance: Medha, **kwargs) -> None:
        super().__init__(**kwargs)
        self._medha = medha_instance
        self.llm_call_count: int = 0

    @step
    async def check_cache(self, ctx: Context, ev: StartEvent) -> Union[CacheMissEvent, StopEvent]:
        hit = await self._medha.search(ev.question)
        if hit.strategy != SearchStrategy.NO_MATCH:
            return StopEvent(result=hit.generated_query)
        return CacheMissEvent(question=ev.question)

    @step
    async def call_llm(self, ctx: Context, ev: CacheMissEvent) -> StopEvent:
        self.llm_call_count += 1
        sql = mock_llm(ev.question)
        await self._medha.store(ev.question, sql)
        return StopEvent(result=sql)


workflow = MedhaWorkflow(medha, timeout=30, verbose=False)
result = await workflow.run(question="Top products ranked by cost")
print(result)  # SELECT * FROM products ORDER BY price DESC LIMIT 10

Haystack

Haystack 2.x uses a declarative @component model. The MedhaCacheComponent returns the cached SQL if found, or forwards the question to the LLM component.

question → MedhaCacheComponent
                ├─ hit  → HaystackMockLLMComponent (sql passthrough) → output
                └─ miss → HaystackMockLLMComponent (calls LLM + stores) → output
from haystack import component, Pipeline
from typing import Optional


@component
class HaystackMedhaCacheComponent:
    def __init__(self, medha_instance: Medha) -> None:
        self._medha = medha_instance

    @component.output_types(sql=Optional[str], question=str)
    def run(self, question: str) -> dict:
        hit = _run_async(self._medha.search(question))
        if hit.strategy != SearchStrategy.NO_MATCH:
            return {"sql": hit.generated_query, "question": question}
        return {"sql": None, "question": question}


@component
class HaystackMockLLMComponent:
    def __init__(self, medha_instance: Medha) -> None:
        self._medha = medha_instance

    @component.output_types(sql=str)
    def run(self, question: str, sql: Optional[str] = None) -> dict:
        if sql:
            return {"sql": sql}
        result = mock_llm(question)
        _run_async(self._medha.store(question, result))
        return {"sql": result}


pipeline = Pipeline()
pipeline.add_component("cache", HaystackMedhaCacheComponent(medha))
pipeline.add_component("llm",   HaystackMockLLMComponent(medha))
pipeline.connect("cache.question", "llm.question")
pipeline.connect("cache.sql",      "llm.sql")

result = pipeline.run({"cache": {"question": "Top products ranked by cost"}})
print(result["llm"]["sql"])  # SELECT * FROM products ORDER BY price DESC LIMIT 10

Production Notes

Topic Recommendation
Backend Replace InMemoryBackend with a persistent backend for durability across restarts
Async in sync frameworks LangChain and Haystack are sync-first; wrap async Medha calls with a ThreadPoolExecutor as shown above
LlamaIndex async LlamaIndex 0.10+ supports arun() — prefer async steps in production
Shared instance One Medha instance across all frameworks is fine — all backends are thread-safe
TTL Set default_ttl_seconds in Settings to auto-expire stale queries after schema changes
Embedder FastEmbedAdapter runs locally (no API cost). For multilingual queries, switch to CohereAdapter or GeminiAdapter

Switching to a persistent backend

settings = Settings(
    backend_type="qdrant",   # or "pgvector", "elasticsearch", "lancedb", etc.
    qdrant_mode="docker",
    qdrant_host="localhost",
)
medha = Medha("production_cache", embedder=embedder, settings=settings)
await medha.start()

See Backends for the full list of available backends and their configuration options.

Full working example

The complete runnable notebook is available at demo/13_framework_integrations.ipynb.