Official implementation of BubbleRAG: Evidence-Driven Retrieval-Augmented Generation for Black-Box Knowledge Graphs.
- Paper: arXiv:2603.20309
If you have any questions or would like to chat, feel free to contact me at
azula_fire@163.com or dpan457@connect.hkust-gz.edu.cn. WeChat is also
fine: Azula_Fire. I am also happy to discuss anything related to retrieval
or GraphRAG.
Existing graph RAG methods often overlook a practical mismatch: during retrieval, LLMs tend to act according to their own parametric knowledge, but that behavior may not match how knowledge is actually expressed in the target KG. Without training or adaptation on a specific KG, an LLM often has insufficient knowledge of that KG's schema, entities, relations, and evidence patterns. This problem is especially common for private KGs and large-scale KGs, which are effectively black boxes to the LLM.
For example, consider the query: "find experts in machine learning". In a KG, valid answers may appear in many forms: a person who has published many machine learning papers, a senior engineer working at a well-known AI company, or a person whose students include a famous scholar. BubbleRAG focuses on this kind of evidence-driven retrieval problem over black-box KGs. For the full technical formulation and method details, please refer to the paper.
The implementation in this repository supports two usage modes:
- Reproduction mode: run the provided benchmark configs on included datasets.
- Custom-data mode: build an index from your own corpus and query it with the same BubbleRAG pipeline.
BubbleRAG/
bubblerag/
api.py # High-level BubbleRAG class API
bubble.py # BubbleRAG retrieval and query pipeline
storage/ # Milvus-backed KG/vector storage
llm/ # OpenAI-compatible LLM wrapper
embedding/ # OpenAI-compatible embedding wrapper
information_extraction/
index.py # Corpus -> chunks/entities/relations/triples
scripts/
build_index.py # Build index for any corpus
query.py # Query an existing index
evaluate.py # Run batch QA evaluation
reproduce.py # Run reproduction configs
configs/
reproduce_hotpotqa.json
reproduce_2wikimultihopqa.json
reproduce_musique.json
custom_template.json
assets/
main.png # README overview figure
main.pdf # Source figure
dataset/ # Included benchmark subsets and corpora
quickstart_corpus.json # 3-document quick start corpus
DATA_FORMAT.md # Custom corpus/QA JSON formats
Use Python 3.9+.
pip install -e .BubbleRAG uses OpenAI-compatible chat and embedding APIs. Set credentials through environment variables:
export LLM_API_KEY=your_llm_key
export EMB_API_KEY=your_embedding_keyModel names and base URLs are normal configuration values. For CLI runs, set them in the
models section of the JSON config. For Python usage, pass them directly to
BubbleRAG.from_openai(). No API keys are stored in config files or source code.
This is the recommended usage.
from bubblerag import BubbleRAG
WORKING_DIR = "runs/quickstart/kg"
CORPUS_FILE = "dataset/quickstart_corpus.json"
QUESTION = "Who leads the knowledge systems group at Arcadia AI?"
LLM_API_KEY = "your-llm-api-key"
LLM_MODEL_NAME = "your-chat-model"
LLM_BASE_URL = "https://your-llm-endpoint/v1"
EMB_API_KEY = "your-embedding-api-key"
EMB_MODEL_NAME = "your-embedding-model"
EMB_BASE_URL = "https://your-embedding-endpoint/v1"
rag = BubbleRAG.from_openai(
working_dir=WORKING_DIR,
llm_api_key=LLM_API_KEY,
embedding_api_key=EMB_API_KEY,
llm_model_name=LLM_MODEL_NAME,
llm_base_url=LLM_BASE_URL,
embedding_model_name=EMB_MODEL_NAME,
embedding_base_url=EMB_BASE_URL,
)
summary = rag.insert(CORPUS_FILE, batch_size=3, max_concurrency=6)
print(summary)
result = rag.query(QUESTION)
print(result["answer"])The same example is available as test.py. After the index has already been
built, instantiate the class with the same working_dir and call query() directly:
rag = BubbleRAG.from_openai(
working_dir=WORKING_DIR,
llm_api_key=LLM_API_KEY,
embedding_api_key=EMB_API_KEY,
llm_model_name=LLM_MODEL_NAME,
llm_base_url=LLM_BASE_URL,
embedding_model_name=EMB_MODEL_NAME,
embedding_base_url=EMB_BASE_URL,
)
result = rag.query(QUESTION)Build an index from a corpus:
python -m scripts.build_index \
--corpus-file dataset/quickstart_corpus.json \
--working-dir runs/quickstart/kgQuery an existing index:
python -m scripts.query \
--working-dir runs/quickstart/kg \
--question "Who leads the knowledge systems group at Arcadia AI?"Evaluate on a QA file:
python -m scripts.evaluate \
--working-dir runs/custom/kg \
--qa-file path/to/qa.json \
--output-file runs/custom/results.jsonRun only a small subset for a smoke test:
python -m scripts.evaluate \
--working-dir runs/custom/kg \
--qa-file path/to/qa.json \
--output-file runs/custom/results.json \
--limit 10HotpotQA:
python -m scripts.reproduce --config configs/reproduce_hotpotqa.json2WikiMultihopQA:
python -m scripts.reproduce --config configs/reproduce_2wikimultihopqa.jsonMuSiQue:
python -m scripts.reproduce --config configs/reproduce_musique.jsonIf the index already exists and you only want to rerun QA evaluation:
python -m scripts.reproduce \
--config configs/reproduce_hotpotqa.json \
--skip-indexReproduction configs store dataset paths, output paths, runtime parameters, and retrieval parameters. API keys remain environment variables.
The ablations are exposed through scripts.reproduce --ablation:
# Full BubbleRAG
python -m scripts.reproduce --config configs/reproduce_hotpotqa.json
# Without anchor specialization
python -m scripts.reproduce \
--config configs/reproduce_hotpotqa.json \
--skip-index \
--ablation no-anchor-specialization
# Without schema relaxation
python -m scripts.reproduce \
--config configs/reproduce_hotpotqa.json \
--skip-index \
--ablation no-schema-relaxation
# Without CEG ranking
python -m scripts.reproduce \
--config configs/reproduce_hotpotqa.json \
--skip-index \
--ablation no-ceg-rankingUse the matching reproduction config for 2WikiMultihopQA or MuSiQue runs. These ablations
only change query-time retrieval behavior, so the same built index can be reused with
--skip-index. By default, each run writes a timestamped result file, for example
runs/hotpotqa/results.no-schema-relaxation.20260601-153000.json, and each record includes
an ablation field. Reproduction outputs are stored as:
{
"metadata": {
"run_id": "20260601-153000",
"ablation": "no-schema-relaxation",
"runtime_config": {
"batch_size": 16,
"max_concurrency": 6,
"limit": 10
},
"retrieval_config": {
"bfs_step": 6,
"bfs_min_k": 2,
"use_schema_relaxation": false
},
"elapsed_seconds": 0.0
},
"results": []
}The ablation switches correspond to the following config fields:
use_anchor_specialization=false: use query-only keyword extraction instead of context-specialized keyword extraction.use_schema_relaxation=false: skip the initial chunk/community retrieval and early answer check before graph retrieval.use_ceg_ranking=false: use the first generated Steiner candidate instead of ranking candidate evidence graphs by the BubbleRAG scoring function.
BubbleRAG expects a corpus JSON file with title and text fields:
[
{
"title": "Example Document",
"text": "Document text used for entity and triple extraction."
}
]For evaluation, the QA file should contain question and answer fields:
[
{
"question": "Who founded Example Document?",
"answer": "Alice",
"answer_aliases": ["Alice Smith"]
}
]See DATA_FORMAT.md for details.
Use configs/custom_template.json as a starting point for custom experiments:
python -m scripts.build_index --config configs/custom_template.json
python -m scripts.evaluate --config configs/custom_template.jsonImportant fields:
models.llm_model_name: OpenAI-compatible chat model namemodels.llm_base_url: chat completion API base URLmodels.embedding_model_name: embedding model namemodels.embedding_base_url: embedding API base URLpaths.corpus_file: corpus used for indexingpaths.qa_file: QA file used for evaluationpaths.working_dir: Milvus Lite database directorypaths.output_file: evaluation result pathruntime.batch_size: embedding batch sizeruntime.max_concurrency: LLM extraction concurrencyruntime.corpus_limit: number of corpus records to index;0means allruntime.limit: number of QA records to evaluate;0means allretrieval_config.*: retrieval and expansion parametersretrieval_config.use_anchor_specialization: enable context-specialized keyword extractionretrieval_config.use_schema_relaxation: enable initial chunk retrieval and early answer checkingretrieval_config.use_ceg_ranking: enable BubbleRAG candidate evidence graph ranking
The CLI also accepts optional environment overrides named LLM_MODEL_NAME,
LLM_BASE_URL, EMB_MODEL_NAME, and EMB_BASE_URL; config files are usually clearer for
reproducible runs.
By default, experiment artifacts should be written under runs/:
runs/
hotpotqa/
kg/ # Milvus Lite database
results.20260601-153000.json
results.no-schema-relaxation.20260601-153100.json
Each evaluation record contains the question, ground-truth answer(s), generated answer,
LLM reasoning text, F1 score, runtime, and error information if the query failed.
The top-level metadata records the run id, output path, model names and base URLs,
runtime settings, full effective retrieval config, total elapsed time, and completion
progress. Passing --output-file disables automatic timestamping and writes exactly to
the requested path.
- Index construction calls both an LLM and an embedding model. Start with a small corpus when testing custom data.
- The quality of the KG depends on entity/triple extraction quality.
working_diridentifies the database. Reusing the same directory lets you query an existing index without rebuilding it.- This is research code; configs are intended to make experiments transparent and easy to modify.
@misc{pan2026bubblerag,
title={BubbleRAG: Evidence-Driven Retrieval-Augmented Generation for Black-Box Knowledge Graphs},
author={Pan, Duyi and Lou, Tianao and Li, Xin and Song, Haoze and Wu, Yiwen and Deng, Mengyi and Yang, Mingyu and Wang, Wei},
year={2026},
eprint={2603.20309},
archivePrefix={arXiv}
}