Skip to main content

agentic-rag-toolkit

Drop-in agent primitives for pipelines you already have -- not another framework.

agentic-rag-toolkit is a small Python library of six reusable agent building blocks distilled from production agentic-RAG work: a clinical/behavioral-health nudge engine and an LLM-powered lead-generation pipeline. Each block does one job -- classify, select a retriever, retrieve, quality-check, self-validate, or personalize tone -- and any of them can be dropped into an existing LangChain/LlamaIndex/raw-Python pipeline without adopting a whole new framework. Works with any LLM backend (Ollama, OpenAI/GPT-4o) and any vector store (Chroma, Pinecone, or your own) through one shared interface.

Why this exists

Existing tool What it actually does Why it's not the same
raki, rag-evaluation Score RAG quality after a session is over (post-hoc analysis) Never runs inside the live pipeline -- can't stop a bad answer before the user sees it
create-agentic-rag A project scaffold/boilerplate generator One-time template, not an importable library you keep using
flashrag-dev, mini-rag Full end-to-end batteries-included RAG research pipelines You adopt their entire chunk-embed-retrieve-generate flow -- can't cherry-pick one piece
LangGraph / LlamaIndex ADW Quality gates and self-correction exist as internal concepts Only usable if you buy into their full state-machine/orchestration model
pydantic-ai-rag RAG layer bolted onto one specific agent framework Locked to pydantic-ai, not portable

Three things to lead with:

  1. Runtime gating, not post-hoc scoring. QualityGateAgent blocks a bad answer before the user sees it.
  2. Composable, not all-or-nothing. Import just QualityGateAgent into an existing LangChain chain -- no rewrite required.
  3. Provider-agnostic by contract. One LLMClient interface, swap Ollama <-> OpenAI with one line. One Retriever interface, swap Chroma <-> Pinecone with one line.

Install

pip install agentic-rag-toolkit          # core only
pip install "agentic-rag-toolkit[ollama]"    # + local Ollama support
pip install "agentic-rag-toolkit[openai]"    # + OpenAI/GPT-4o support
pip install "agentic-rag-toolkit[chromadb]"  # + Chroma example support

Quickstart

from agentic_rag_toolkit import MetricClassifierAgent, QualityGateAgent

class MyLLM:
    def complete(self, prompt: str, **kwargs) -> str:
        ...  # call your model here, return raw text

llm = MyLLM()

classifier = MetricClassifierAgent(llm=llm, categories=["urgent", "routine"])
result = classifier.run("heart rate spiked to 180")
print(result.label, result.confidence)

gate = QualityGateAgent(llm=llm, min_evidence_count=1)
gate_result = gate.run(evidence=["some retrieved doc"], query="what happened?")
if not gate_result.passed:
    print("blocked:", gate_result.reason)

See examples/basic_pipeline.py for a full runnable demo (no API keys needed) chaining all six agents, and examples/ollama_chroma_pipeline.py for a production-shaped version with Ollama + Chroma.

API Reference

All LLM-facing agents ask for structured JSON output (validated with Pydantic, with automatic retry-with-error-feedback on a bad response) instead of parsing ad-hoc text formats like "label|0.92" -- see complete_json below. Every agent exposes a single .run(...) entrypoint.

LLMClient (Protocol) -- agentic_rag_toolkit.core.llm_client

Any object with a matching .complete() method satisfies this automatically (structural typing via @runtime_checkable -- no subclassing required).

class LLMClient(Protocol):
    def complete(self, prompt: str, **kwargs) -> str: ...

Two ready-made implementations ship with the toolkit:

Class Constructor args Requires
OllamaClient model: str = "llama3" pip install ollama + Ollama daemon running locally
OpenAIClient model: str = "gpt-4o" pip install openai + OPENAI_API_KEY env var set
from agentic_rag_toolkit import OllamaClient, OpenAIClient

llm = OllamaClient(model="llama3.1")   # or:
llm = OpenAIClient(model="gpt-4o")
llm.complete("Say hello in one word.")   # -> "Hello"

To use your own backend (Anthropic, a local vLLM server, etc.), just implement .complete(prompt: str, **kwargs) -> str on any class -- every agent below accepts it.


complete_json() -- agentic_rag_toolkit.core.structured

The shared helper every LLM-facing agent uses internally. Public and importable if you want the same schema-validated-JSON-with-retry behavior in your own custom agents.

def complete_json(
    llm: LLMClient,
    prompt: str,
    schema: type[BaseModel],
    max_retries: int = 2,
) -> BaseModel
Argument Type Description
llm LLMClient Any object with .complete()
prompt str Your instruction text (the JSON schema is appended automatically)
schema type[BaseModel] A Pydantic model class describing the expected response shape
max_retries int, default 2 Extra attempts if the response fails to parse/validate. Total attempts = max_retries + 1

Returns: a validated instance of schema. Raises: ValueError if every attempt fails (message includes the last raw response and validation error).

from pydantic import BaseModel
from agentic_rag_toolkit import complete_json

class Sentiment(BaseModel):
    positive: bool
    reason: str

result = complete_json(llm, "Is this review positive? 'Loved it!'", Sentiment)
print(result.positive, result.reason)

MetricClassifierAgent -- agentic_rag_toolkit.classifiers.metric_classifier

Labels incoming text into one of a fixed set of categories -- the first decision point in a pipeline.

Constructor

MetricClassifierAgent(llm: LLMClient, categories: list[str], max_retries: int = 2)
Argument Type Default Notes
llm LLMClient required
categories list[str] required Must be non-empty -- raises ValueError otherwise
max_retries int 2 Passed through to complete_json

Method

.run(input_text: str) -> ClassificationResult

ClassificationResult is {label: str, confidence: float} (confidence constrained to 0.0-1.0).

Raises ValueError if input_text is empty/blank, if the LLM returns a label outside categories, or if the response can't be parsed after retries.

from agentic_rag_toolkit import MetricClassifierAgent

classifier = MetricClassifierAgent(llm=llm, categories=["urgent", "routine"])
result = classifier.run("heart rate spiked to 180")
# result.label == "urgent", result.confidence == 0.91

RetrieverSelectorAgent + Retriever -- agentic_rag_toolkit.retrieval.retriever_selector

Routes a classified category to the correct retriever. Pure lookup -- does not call an LLM, so it takes no llm argument (unlike every other agent here).

Retriever is a Protocol: any object with .retrieve(query: str, top_k: int = 5) -> list[str] qualifies -- wrap your Chroma collection, Pinecone index, or anything else this way.

Constructor

RetrieverSelectorAgent(retriever_map: dict[str, Retriever])

retriever_map must be non-empty -- raises ValueError otherwise.

Method

.run(category: str) -> Retriever

Raises KeyError (with the list of known categories in the message) if category isn't in retriever_map.

from agentic_rag_toolkit import RetrieverSelectorAgent

selector = RetrieverSelectorAgent(retriever_map={
    "urgent": urgent_chroma_wrapper,
    "routine": routine_chroma_wrapper,
})
retriever = selector.run("urgent")
docs = retriever.retrieve("blood sugar spike guidance")

SubQuestionRetrieverAgent -- agentic_rag_toolkit.retrieval.sub_question_retriever

Breaks one complex question into smaller sub-questions and retrieves evidence for each in parallel, rather than one broad fuzzy search.

Constructor

SubQuestionRetrieverAgent(
    llm: LLMClient,
    retriever: Retriever,
    max_sub_questions: int = 4,
    max_retries: int = 2,
)
Argument Type Default Notes
llm LLMClient required Used only to decompose the question
retriever Retriever required Any object with .retrieve()
max_sub_questions int 4 Caps how many sub-questions are generated (and therefore worker threads spawned)
max_retries int 2 Retries for the decomposition call

Method

.run(question: str) -> dict[str, list[str]]

Returns a dict mapping each generated sub-question to its retrieved evidence list. Raises ValueError if question is empty. If decomposition fails validation even after retries, it silently falls back to treating the original question as the only sub-question -- decomposition is a nice-to-have, not a hard requirement.

from agentic_rag_toolkit import SubQuestionRetrieverAgent

agent = SubQuestionRetrieverAgent(llm=llm, retriever=my_retriever, max_sub_questions=3)
results = agent.run("What should happen when a patient's blood sugar spikes?")
# {"What is a safe blood sugar range?": [...], "What should someone do right now?": [...]}

QualityGateAgent + GateResult -- agentic_rag_toolkit.quality.quality_gate

The core differentiator of this toolkit. Checks retrieved evidence is sufficient before generation happens, so the pipeline can stop instead of letting the LLM hallucinate.

Constructor

QualityGateAgent(llm: LLMClient, min_evidence_count: int = 1, max_retries: int = 2)
Argument Type Default Notes
llm LLMClient required
min_evidence_count int 1 If fewer evidence items are passed in, the gate fails immediately -- no LLM call needed
max_retries int 2

Method

.run(evidence: list[str], query: str) -> GateResult

GateResult is {passed: bool, reason: str}. Raises ValueError if the LLM's sufficiency judgment can't be parsed after retries.

from agentic_rag_toolkit import QualityGateAgent

gate = QualityGateAgent(llm=llm, min_evidence_count=2)
result = gate.run(evidence=["doc A", "doc B"], query="what is a safe blood sugar range?")
if not result.passed:
    print("blocked:", result.reason)

SelfValidatorAgent + ValidationResult -- agentic_rag_toolkit.quality.self_validator

Final compliance/safety check on a generated answer -- runs after your own generation step, against rules you define.

Constructor

SelfValidatorAgent(llm: LLMClient, rules: list[str], max_retries: int = 2)

rules must be non-empty -- raises ValueError otherwise.

Method

.run(generated_answer: str) -> ValidationResult

ValidationResult is {valid: bool, violated_rules: list[str]}. Raises ValueError if generated_answer is empty.

from agentic_rag_toolkit import SelfValidatorAgent

validator = SelfValidatorAgent(llm=llm, rules=[
    "never state a specific medication dosage",
    "always suggest consulting a doctor if severe",
])
result = validator.run("Take 500mg twice a day.")
# result.valid == False, "never state a specific medication dosage" in result.violated_rules

TonePersonalizerAgent -- agentic_rag_toolkit.personalization.tone_personalizer

Rewrites an already-approved answer into a target voice, changing delivery only -- never facts. Meant to run last, after QualityGateAgent and SelfValidatorAgent have both passed, so a nicer tone can never mask a bad or non-compliant answer. Returns plain text, not structured JSON -- there's no schema for "does this sound gentle enough."

Constructor

TonePersonalizerAgent(llm: LLMClient, tone: str)

tone must be non-empty -- raises ValueError otherwise.

Method

.run(approved_answer: str) -> str

Raises ValueError if approved_answer is empty.

from agentic_rag_toolkit import TonePersonalizerAgent

personalizer = TonePersonalizerAgent(llm=llm, tone="gentle and encouraging, like a supportive friend")
message = personalizer.run("Your blood sugar reading is high.")

Full end-to-end wiring of all six agents: examples/basic_pipeline.py (no API keys needed) and examples/ollama_chroma_pipeline.py (production-shaped, Ollama + Chroma).

Development

python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest --cov=agentic_rag_toolkit tests/ -v

Release process for maintainers lives in PUBLISHING.md, not here -- this README is for people using the package, not publishing it.

License

MIT -- see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agentic_rag_toolkit-0.1.0.tar.gz (16.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentic_rag_toolkit-0.1.0-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

Details for the file agentic_rag_toolkit-0.1.0.tar.gz.

File metadata

  • Download URL: agentic_rag_toolkit-0.1.0.tar.gz
  • Upload date:
  • Size: 16.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for agentic_rag_toolkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1d87edd8ba4991d36feea1a7d3e9da7390b1308421430cead4698889c5817c28
MD5 5d1c87c54d55dbfba0d3713895b79b56
BLAKE2b-256 c3c6fcfb1e5a174e2cb95568939f70c87be163eedb7e1c9339e2717e4d5d4a44

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentic_rag_toolkit-0.1.0.tar.gz:

Publisher: publish.yml on Mohanapriya-sk/agentic-rag-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agentic_rag_toolkit-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for agentic_rag_toolkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3d71a9c6d8aa2b73c9d61ec2acaeb095c6fc089b10a1934ff7722896a9913071
MD5 d5ee6ba9da94db3871c90fe2ac4e7e65
BLAKE2b-256 64e8bc9ff99e25c417e42a87d9fbfd34b104853f05282bfd6e6ace0f7ab3dbf6

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentic_rag_toolkit-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Mohanapriya-sk/agentic-rag-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page