RAGU is a modular GraphRAG engine for building, storing, and querying knowledge graphs from text. It combines:
- chunking of raw documents into stable
Chunkobjects; - LLM-based extraction of
EntityandRelationgraph artifacts; - graph construction, deduplication, optional description summarization, and Leiden community detection;
- graph, key-value, and vector storage backends;
- retrieval strategies for local graph neighborhoods, global community summaries, naive chunk-vector RAG, and mixed search.
Partially based on nano-graphrag
Our huggingface community is here
- RAGU components and methodology (EN) | RU
- NEREL ontology (EN) | RU
- RAGU-lm extraction model (EN) | RU
- Package facade
- Chunking
- Common settings, prompts, and utilities
- Prompt schemas and templates
- Graph construction and index
- Models, embedders, sparse embedders, and rerankers
- Search engines
- Storage contracts and adapters
- Graph storage adapters
- Vector DB adapters
- Triplet/entity-relation extraction
- Utilities
The recommended way is a local build:
git clone https://github.com/AsphodelRem/RAGU.git
cd RAGU
uv pip install -e .
From PyPI:
pip install graph_raguIf you want to use local models (via transformers etc.), run:
pip install graph_ragu[local]import asyncio
import os
import sys
import shutil
from ragu.common.logger import logger
logger.remove()
logger.add(sys.stdout, level="DEBUG")
from ragu import (
SimpleChunker,
KnowledgeGraph,
BuilderArguments,
Settings,
TwoStageArtifactsExtractorLLM,
)
from ragu.models.embedder import EmbedderOpenAI
from ragu.models.llm import LLMOpenAI
from ragu.models.openai import CachedAsyncOpenAI
from ragu.utils.ragu_utils import read_text_from_files
client = CachedAsyncOpenAI(
base_url=os.environ['OPENAI_BASE_URL'],
api_key=os.environ['OPENAI_API_KEY'],
rate_min_delay=2,
rate_max_simultaneous=10,
retry_times_sec=(2, 2, 2, 2, 2),
cache='./llm_cache',
debug_errors_storage='./llm_debug',
)
llm = LLMOpenAI(
client=client,
model_name="mistralai/mistral-medium-3",
)
embedder = EmbedderOpenAI(
client=client,
model_name="emb-qwen/qwen3-embedding-8b",
dim=4096,
)
# Configure working directory and language
Settings.storage_folder = "ragu_working_dir"
Settings.language = "english" # or "russian"
# Remove dir to start building graph from scratch
# shutil.rmtree(Settings.storage_folder, ignore_errors=True)
docs = read_text_from_files("path/to/your/files")
# Initialize chunker
chunker = SimpleChunker(max_chunk_size=1000)
# Set up artifact extractor
from ragu.common.prompts import ICLConfig
icl_config = ICLConfig(
enabled=True,
num_examples=2,
)
artifact_extractor = TwoStageArtifactsExtractorLLM(
llm=llm,
embedder=embedder,
icl_config=icl_config,
do_entity_validation=True,
do_relation_validation=True,
)
# Configure builder settings
builder_settings = BuilderArguments(
use_llm_summarization=True,
use_clustering=False,
build_only_vector_context=False,
make_community_summary=True,
remove_isolated_nodes=True,
)
# Build knowledge graph
knowledge_graph = KnowledgeGraph(
llm=llm,
embedder=embedder,
chunker=chunker,
artifact_extractor=artifact_extractor,
builder_settings=builder_settings,
)
asyncio.run(knowledge_graph.build_from_docs(docs))If you run the code with a storage folder that already contains a knowledge graph, RAGU will automatically load the existing graph.
RAGU ships four retrieval strategies plus a query-planning wrapper. Each engine
exposes a_search() (retrieval only) and a_query() (retrieval + LLM answer);
sync wrappers search() / query() are also available. For the conceptual
workflow of each strategy see docs/en/ragu_components.md;
for full parameters see ragu/search_engine/README.md.
Graph-neighborhood retrieval: find relevant entities, then expand to their relations, community summaries, and source chunks.
from ragu import LocalSearchEngine
local_search = LocalSearchEngine(llm=llm, knowledge_graph=knowledge_graph, embedder=embedder)
local_answer = await local_search.a_query("Who wrote Romeo and Juliet?", use_summary=True, use_chunks=True)
print(local_answer.response)Answers broad, corpus-wide questions from community summaries.
from ragu import GlobalSearchEngine
global_search = GlobalSearchEngine(llm=llm, knowledge_graph=knowledge_graph)
global_answer = await global_search.a_query("Your broad query here")
print(global_answer.response)Chunk-vector RAG without graph expansion.
from ragu import NaiveSearchEngine
naive_search = NaiveSearchEngine(llm=llm, knowledge_graph=knowledge_graph, embedder=embedder)
naive_answer = await naive_search.a_query("Your query here")
print(naive_answer.response)Runs several engines and asks the LLM to synthesize a single answer from their responses.
from ragu import MixSearchEngine
mix_search = MixSearchEngine(llm=llm, engines=[local_search, naive_search, global_search])
mixed_answer = await mix_search.a_query("Your query here")
print(mixed_answer.response)Wraps any engine, decomposes a complex question into dependent subqueries, and feeds intermediate answers forward.
from ragu import QueryPlanEngine
planned_local = QueryPlanEngine(local_search)
result = await planned_local.a_query("Who wrote the novel 'Quo Vadis' and what country was the author from?")
print(result.response)Configure the knowledge graph building pipeline using BuilderArguments:
from ragu import BuilderArguments, KnowledgeGraph
builder_arguments = BuilderArguments(
use_llm_summarization=True, # Enable LLM-based entity/relation summarization
use_clustering=False, # Apply clustering before summarization. Use it if your text contains many similar entities.
build_only_vector_context=False, # Skip graph extraction, only chunk embeddings
make_community_summary=True, # Generate community summaries
remove_isolated_nodes=True, # Remove entities without relations
cluster_only_if_more_than=10000, # Minimum entities before clustering kicks in
summarize_only_if_more_than=7, # Summarize descriptions only when there are many duplicates
max_cluster_size=128, # Maximum entities per cluster
random_seed=42,
)
knowledge_graph = KnowledgeGraph(
llm=llm,
embedder=embedder,
chunker=chunker,
artifact_extractor=artifact_extractor,
builder_settings=builder_arguments,
)
await knowledge_graph.build_from_docs(docs)Common presets (naive vector RAG only, fast graph extraction without
summarization, full GraphRAG with communities, and large-corpus clustering) with
explanations and ready-to-run scripts are in
ragu/graph/README.md.
CachedAsyncOpenAI is the network-level client shared by LLMOpenAI and EmbedderOpenAI. It controls rate limiting, retries, caching, and timeouts.
client = CachedAsyncOpenAI(
base_url="https://api.openai.com/v1",
api_key="sk-...",
rate_max_simultaneous=10,
rate_max_per_minute=100,
cache="./llm_cache",
)
llm = LLMOpenAI(client=client, model_name="gpt-4o-mini")
embedder = EmbedderOpenAI(client=client, model_name="text-embedding-3-large", dim=3072)For the full parameter tables (rate_*, retry_times_sec, embed_timeout, EmbedderOpenAI batching and tokenizer overrides), the shared vs. separate clients guidance for large corpora, and the debug store, see ragu/models/README.md.
RAGU centralises token-limit and tokenizer configuration in the Settings singleton. These defaults are used by EmbedderOpenAI (for embedding input truncation) and search engines (for LLM context truncation).
from ragu import Settings
# Embedder truncation (applied automatically inside EmbedderOpenAI)
Settings.embedder_token_limit = 8_192 # max tokens per embedding input
Settings.tokenizer_embedder_backend = "tiktoken" # "tiktoken" or "local"
Settings.tokenizer_embedder_name = "text-embedding-3-large"
# LLM context truncation (applied by search engines before answer generation)
Settings.llm_context_token_limit = 30_000 # max tokens for search-engine context
Settings.tokenizer_llm_backend = "tiktoken" # "tiktoken" or "local"
Settings.tokenizer_llm_name = "gpt-4o"Per-instance overrides on EmbedderOpenAI take precedence over Settings.
For a local BGE model with a 512-token context, set
Settings.embedder_token_limit = 512, Settings.tokenizer_embedder_backend = "local",
and Settings.tokenizer_embedder_name = "BAAI/bge-large-en-v1.5" (requires
pip install graph_ragu[local]).
Settings.save(path) writes a JSON snapshot of the configuration;
Settings.load(path) restores it. Serialization is never invoked automatically.
For the list of serialized/excluded fields and the validation behavior, see
ragu/common/README.md.
from ragu import Settings
Settings.save("./runs/exp_42/ragu_settings.json") # persist
Settings.load("./runs/exp_42/ragu_settings.json") # restore in a fresh processRAGU extractors can use few-shot examples to improve extraction quality. When enabled, the extractor selects relevant examples and includes them in the LLM prompt.
from ragu.common.prompts import ICLConfig
icl_config = ICLConfig(
enabled=True, # Enable/disable ICL
num_examples=2, # Number of examples per extraction call (1-3 recommended)
selection_strategy="semantic", # "semantic" | "bm25" | "hybrid" | "random"
)
artifact_extractor = TwoStageArtifactsExtractorLLM(
llm=llm,
embedder=embedder, # Required for "semantic" and "hybrid"; optional for "bm25" and "random"
icl_config=icl_config,
)Four selection strategies are available ("semantic", "bm25", "hybrid", "random"). Pre-built example files ship in ragu/common/prompts/icl_examples/; to generate custom ones, run python scripts/generate_icl_examples.py --config config/icl_generation.yaml. For the strategy comparison table and per-strategy embedder requirements, see ragu/common/prompts/README.md.
Each text in the corpus is processed to extract structured information. It consists of:
- Entities — textual representation, entity type, and a contextual description.
- Relations — textual description of the link between two entities (or a relation class), as well as its confidence/strength.
RAGU uses entity and relation classes from NEREL. The full type tables are available in English and Russian:
docs/en/ontology.md|docs/ru/ontology.md. Pass customentity_types/relation_typesto the extractors to override the defaults.
EntityandRelationare base graph model classes. They can be inherited to create richer domain-specific node and edge types, provided the storageNode/Edgecontract is preserved. See graph docs and storage docs.
File: ragu/triplet/llm_artifact_extractor.py.
A baseline pipeline that uses an LLM to extract entities, relations, and their descriptions in a single step. Supports optional in-context learning with few-shot examples and artifact validation.
File: ragu/triplet/two_stage_extractor.py.
Extracts entities first, then extracts relations constrained by the entity list. It can separately validate entity and relation outputs. Supports optional in-context learning with few-shot examples.
A compact model (Qwen-3-0.6B) fine-tuned on the NEREL dataset. Recognizes and normalizes entities, generates descriptions, and extracts relations. Full prompts, a worked example, and an F1 comparison live in docs/en/ragu_lm.md (RU: docs/ru/ragu_lm.md).
All RAGU components that use LLMs inherit from RaguGenerativeModule, which provides get_prompt, get_prompts, and update_prompt to view and override instructions.
from ragu import LocalSearchEngine
search_engine = LocalSearchEngine(
llm=llm,
knowledge_graph=knowledge_graph,
embedder=embedder,
)
all_prompts = search_engine.get_prompts() # {'local_search': RAGUInstruction(...)}
local_search_prompt = search_engine.get_prompt("local_search")
print(local_search_prompt.messages.to_str()) # rendered prompt text
print(local_search_prompt.pydantic_model) # response schemaTo override an instruction, build a new RAGUInstruction and call
search_engine.update_prompt("local_search", custom_instruction). Do not
edit DEFAULT_PROMPT_TEMPLATES directly — update_prompt scopes the change to a
single module instance. Full examples (including custom messages and few-shot
formatters) are in ragu/common/prompts/README.md.
- Ivan Bondarenko — idea, smart_chunker, NER model, ragu-lm
- Mikhail Komarov
- Roman Shuvalov
- Yanya Dement'yeva
- Alexandr Kuleshevskiy
- Nikita Kukuzey
- Stanislav Shtuka
- Matvey Solovyev
- Ilya Myznikov
