A pluggable, self-correcting agentic RAG framework with MCP-native retrieval.
Project description
ragloop
A pluggable, self-correcting agentic RAG framework. Point it at your documents, your vector store, and your LLM, and it answers questions with inline citations — checking its own work and retrying when an answer isn't grounded.
The scripted demo (examples/demo.py): a cited, grounded answer for an
in-corpus question — and an honest decline instead of an invented answer when
the corpus doesn't cover the question.
It's built so that the things companies actually differ on are swappable without touching the engine:
- Your vector store — Chroma ships as the reference backend; swap in pgvector, Pinecone, or Elasticsearch by subclassing one interface.
- Your LLM — Anthropic (Claude) and a local Ollama backend ship as reference providers; add OpenAI, Bedrock, or any model the same way.
- Your corpus, models, and retry budget — all config-driven.
How it works
plan -> retrieve -> fuse -> generate -> critique --(grounded?)--> done
^------------------------------------------(retry)----'
A LangGraph state machine decomposes the question, lets the agent choose its retrieval strategy (lexical vs. semantic vs. full-chunk read), fuses and ranks the evidence, generates a cited answer, then grades whether that answer is fully supported. If not, it feeds the critique back into retrieval and tries again — bounded by a retry budget. The single back-edge from the critic is what distinguishes this from a one-shot RAG pipeline.
Retrieval is also exposed over MCP, so any MCP-capable client can query your corpus directly with access control enforced server-side.
Install
pip install "ragloop-agentic[all]" # engine + Anthropic + Chroma + MCP
# or pick pieces: pip install "ragloop-agentic[anthropic,chroma]"
# from source: pip install -e ".[all]"
export ANTHROPIC_API_KEY=sk-ant-...
The distribution is published as
ragloop-agentic; the import name staysragloop(from ragloop import build_from_config).
Quick start
cp examples/config.example.yaml config.yaml
ragloop ingest ./your-docs --config config.yaml
ragloop ask "What is our refund window?" --config config.yaml
Or from Python:
from ragloop import build_from_config
loop = build_from_config("config.yaml")
result = loop.ask("What is our refund policy?")
print(result["answer"], result["sources"], result["grounded"])
Run the dependency-free demo to see the loop without any API keys:
python examples/quickstart.py
pytest # the same fakes power the test suite
Live demo
Clone the repo, install the full stack, set your API key, and run the scripted demo against a small fictional policy corpus (examples/corpus/):
pip install -e ".[all]"
export ANTHROPIC_API_KEY=sk-ant-...
python examples/demo.py
The script ingests five policy documents into Chroma (persisted under .chroma_demo/), then asks two questions:
- In-corpus — "What is the refund window?" — expects a cited answer grounded in the refund policy (e.g. a 30-day window) with
grounded=True. - Out-of-corpus — "Do you offer financing?" — expects the model to say the sources do not cover financing rather than inventing terms, typically with
grounded=Trueon a decline-style answer.
Each question prints the answer plus metadata:
============================================================
In-corpus: What is the refund window?
============================================================
Answer:
Customers may request a refund within 30 days of delivery [source:refunds:0].
grounded=True attempts=1 sources=['refunds:0', ...]
============================================================
Out-of-corpus: Do you offer financing?
============================================================
Answer:
The provided sources do not mention financing or payment plans.
grounded=True attempts=1 sources=[...]
Exact wording varies by model run; the important part is grounded, cited answers for in-corpus questions and an honest decline for out-of-corpus ones.
Benchmarks
ragloop ships a reproducible eval harness (evals/) that runs a naive one-shot
baseline (embed → top-k → generate) against the full self-correcting
loop over a labelled policy corpus — 14 questions, 4 of them deliberately
out-of-corpus. Scoring is deterministic and label-based (no LLM-as-judge),
so anyone can reproduce it with zero API cost on a local model:
pip install -e ".[evals,chroma]"
ollama pull llama3.2:3b # any local model works
python -m evals.runner --llm ollama --model llama3.2:3b
Representative run (local llama3.2:3b, real Chroma retrieval):
| Metric | Baseline | RagLoop | What it means |
|---|---|---|---|
| Hallucination resistance | 1.00 | 1.00 | fraction of unanswerable questions correctly declined (↑) |
| False-decline rate | 0.00 | 0.00 | answerable questions wrongly refused (↓) |
| Citation accuracy | 1.00 | 1.00 | cited IDs that are actually in the retrieved set (↑) |
| Retrieval recall@k | 1.00 | 0.90 | gold chunks surfaced (↑) |
| Answer similarity | 0.78 | 0.78 | cosine vs. ground truth, local embeddings (↑) |
| Avg latency (s) | 1.8 | 5.1 | wall-clock per question (↓) |
| Avg token cost | 415 | 1203 | approx tokens per question (↓) |
| Avg retries | 0.00 | 0.36 | extra loop iterations (↓) |
Honest read of these numbers. On a small, clean corpus both pipelines
already resist hallucination perfectly and cite accurately — so here the loop's
self-correction buys safety you can't see, at a real ~2.8× latency / ~2.9× token
cost. The loop earns that cost on noisier corpora, weaker retrieval, or
higher-stakes grounding, where a one-shot baseline would answer from a wrong
chunk and the critic catches it. Building this benchmark also surfaced a real
regression — the planner's decomposition could drop the chunk a direct search
would find — which is now fixed (recall 0.80 → 0.90) and guarded by a test. The
remaining recall gap (cross-query score comparability in fusion) is tracked as
future work. See evals/README.md for methodology and the
LLM-judged RAGAS metrics (opt-in via --judge).
Serve retrieval over MCP
ragloop serve --config config.yaml
Exposes three tools — keyword_search, semantic_search, chunk_read — to any
MCP client.
Extending it
Add a backend by implementing one interface and registering it:
- New vector store: subclass
ragloop.Retriever(add,semantic_search,keyword_search,get_chunk), then add a branch inconfig._build_retriever. - New LLM: subclass
ragloop.LLMProvider(complete), then add a branch inconfig._build_llm.
Nothing in the engine changes. See examples/quickstart.py for a complete
custom retriever and provider in ~30 lines.
Project layout
ragloop/
llm/ LLMProvider interface + Anthropic reference
retrieval/ Retriever interface + Chroma reference
engine/ state, nodes (plan/retrieve/fuse/generate/critique), graph
mcp/ MCP server exposing the retrieval tools
config.py YAML + env wiring
cli.py ingest / ask / serve
Roadmap
- Reranker hook in the fusion step (cross-encoder)
- Persistent agent memory across sessions (LangGraph checkpointer)
- Reference backends for pgvector and OpenAI
- Streaming answers
License
Apache-2.0. Contributions welcome — see issues.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ragloop_agentic-0.2.0.tar.gz.
File metadata
- Download URL: ragloop_agentic-0.2.0.tar.gz
- Upload date:
- Size: 83.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71402fb3d9575496d846720629d774ac9537e7d50f15953cbd35bbfcb722eea6
|
|
| MD5 |
477be02b38ddabd44bb6d3f92139c025
|
|
| BLAKE2b-256 |
6b84654be6dfc751f3755e80ee441e9547a0ecd7669f9abb6abb78bb86589572
|
Provenance
The following attestation bundles were made for ragloop_agentic-0.2.0.tar.gz:
Publisher:
publish.yml on jaesungl33/ragloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ragloop_agentic-0.2.0.tar.gz -
Subject digest:
71402fb3d9575496d846720629d774ac9537e7d50f15953cbd35bbfcb722eea6 - Sigstore transparency entry: 1691327586
- Sigstore integration time:
-
Permalink:
jaesungl33/ragloop@bec8816443145975bcdf0b7d7f91f6d00fc00cce -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jaesungl33
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bec8816443145975bcdf0b7d7f91f6d00fc00cce -
Trigger Event:
release
-
Statement type:
File details
Details for the file ragloop_agentic-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ragloop_agentic-0.2.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df80654185eb02106733e8ecc46aa87ba0c1cc6d1ee71172b253d58a71802112
|
|
| MD5 |
11d27acb761776939f5276b9f3338b48
|
|
| BLAKE2b-256 |
d8b4c3da283932141f7f9774d0e3de0cf195e0a45ae1373680241b1f65e47078
|
Provenance
The following attestation bundles were made for ragloop_agentic-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on jaesungl33/ragloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ragloop_agentic-0.2.0-py3-none-any.whl -
Subject digest:
df80654185eb02106733e8ecc46aa87ba0c1cc6d1ee71172b253d58a71802112 - Sigstore transparency entry: 1691327594
- Sigstore integration time:
-
Permalink:
jaesungl33/ragloop@bec8816443145975bcdf0b7d7f91f6d00fc00cce -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jaesungl33
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bec8816443145975bcdf0b7d7f91f6d00fc00cce -
Trigger Event:
release
-
Statement type: