Complete reference for running the RAG Evaluation Framework, the DocGPT RAG system, the test suites, and configuring logging across the entire project.
- 1. Prerequisites and Environment Setup
- 2. Running the Evaluator
- 3. Running the RAG System (DocGPT)
- 4. Running the Tests
- 5. Logging
| Component | Requirement |
|---|---|
| Python | 3.10+ (3.11+ for DocGPT) |
| Docker & Docker Compose | Required for DocGPT infrastructure (PostgreSQL + MongoDB) |
uv |
Required for DocGPT dependency management (install guide) |
pip |
Required for the evaluation framework |
| Pandoc | Required by DocGPT for document conversion (pypandoc will attempt auto-install) |
| Git | For cloning the repository |
# Clone the repository
git clone https://github.com/RESHAPELab/RAG-evaluation.git
cd RAG-evaluation
# Install core dependencies (includes ragas for LLM-based evaluation)
pip install -r requirements.txt
# Install the package itself in editable mode (recommended for development)
pip install -e .
# Install optional extras
pip install -e ".[dev]" # pytest, ruff, mypy for development
pip install -e ".[excel]" # openpyxl for Excel file support
pip install -e ".[bibtex]" # bibtexparser for advanced BibTeX parsing
# Or install everything at once
pip install -e ".[dev,excel,bibtex]"Verify the installation:
python -c "from rag_evaluation import RAGEvaluator; print('Evaluation framework OK')"# Navigate to the DocGPT directory
cd systems/docgpt
# Install uv if you don't have it
# Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install all dependencies (reads pyproject.toml and uv.lock)
uv sync
# Install dev dependencies too
uv sync --dev
# Copy the environment template and fill in your keys
cp .env.example .env
# Edit .env with your actual values (see Section 1.4)| Variable | Required For | Description |
|---|---|---|
OPENAI_API_KEY |
RagasEvaluator only |
OpenAI API key for LLM-based metrics. Get one at https://platform.openai.com/api-keys |
Note: The basic
RAGEvaluator(rule-based) requires no API keys or external services.
Set these in systems/docgpt/.env:
| Variable | Required | Default | Description |
|---|---|---|---|
AI_GEMINI_APIKEY |
Yes | — | Google Gemini API key |
APP_DISCORD_TOKEN |
Yes (for bot) | — | Discord bot token |
STORAGE_VECTOR_URL |
No | postgresql+psycopg://root:example@localhost:5432/postgres |
PostgreSQL connection string for vector storage |
STORAGE_MEMORY_URL |
No | mongodb://root:example@localhost:27017 |
MongoDB connection string for chat memory |
LOG_LEVEL |
No | DEBUG |
Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
AI_GEMINI_MODEL |
No | gemini-1.5-flash |
Gemini model name to use |
ASSISTANT_K |
No | 100 |
Number of retrieval results |
ASSISTANT_TOKENS_LIMIT |
No | 2000 |
Maximum tokens in retrieval context |
ASSISTANT_SCORE_THRESHOLD |
No | null |
Minimum similarity score filter |
DISTANCE_THRESHOLD |
No | null |
Maximum distance threshold for retrieval |
API_PORT |
No | 8000 |
Port for the FastAPI server |
INTERACTION_LOG_DIR |
No | logs |
Directory for automatic interaction log files (CSV + JSONL) |
The framework provides three main evaluation tools:
| Tool | Type | API Key Required | Use Case |
|---|---|---|---|
RAGEvaluator |
Rule-based metrics | No | Fast, deterministic, cost-free evaluation |
RagasEvaluator |
LLM-based metrics (via ragas) | Yes (OPENAI_API_KEY) |
Sophisticated semantic evaluation |
QualitativeLogger |
Structured logging | No | Side-by-side RAG vs LLM comparison logging |
The RAGEvaluator uses keyword-overlap heuristics to compute three metrics:
- Faithfulness — Is the answer grounded in the provided context?
- Context Precision — Does the answer come from the ground truth? (requires
ground_truth) - Relevance — Does the answer address the query and use the context?
from rag_evaluation import RAGEvaluator
evaluator = RAGEvaluator()
results = evaluator.evaluate(
query="What is machine learning?",
context="Machine learning is a subset of AI that enables systems to learn and improve from experience without being explicitly programmed.",
answer="Machine learning is a subset of artificial intelligence. It allows systems to learn from experience without explicit programming.",
ground_truth="Machine learning is a subset of AI that enables systems to learn and improve from experience without being explicitly programmed."
)
# Print each metric
for metric_name, metric_result in results.items():
print(f"{metric_name}: {metric_result['score']:.3f}")
print(f" Details: {metric_result['details']['reasoning']}")Example output:
faithfulness: 1.000
Details: 2 out of 2 sentences are grounded in context
context_precision: 0.750
Details: 6 out of 8 answer terms found in ground truth
relevance: 0.629
Details: Answer addresses 2/3 query terms and uses 7/17 context terms
evaluator = RAGEvaluator()
results = evaluator.evaluate_batch(
queries=["What is ML?", "What is deep learning?"],
contexts=["ML is a subset of AI that learns from data.", "Deep learning uses neural networks with many layers."],
answers=["ML is a subset of AI.", "Deep learning uses multi-layered neural networks."],
ground_truths=["ML is a subset of AI that learns from data.", "Deep learning uses multi-layered neural networks."]
)
# Get aggregated average scores
avg_scores = evaluator.get_average_scores(results)
print("Average scores:", avg_scores)# Only compute faithfulness and relevance (skip context_precision)
evaluator = RAGEvaluator(metrics=['faithfulness', 'relevance'])
results = evaluator.evaluate(query=query, context=context, answer=answer)
# results will only contain 'faithfulness' and 'relevance' keysAvailable metric names: faithfulness, context_precision, relevance
from rag_evaluation import RAGEvaluator
from rag_evaluation.data_ingestion import DataTableLoader
loader = DataTableLoader()
# Load from CSV — the file must have columns: query, context, answer, ground_truth
data = loader.load_for_evaluation('examples/sample_data.csv')
evaluator = RAGEvaluator()
results = evaluator.evaluate_batch(**data)
avg_scores = evaluator.get_average_scores(results)
print("Results per sample:", results)
print("Average scores:", avg_scores)The RagasEvaluator wraps the ragas library and uses an LLM (OpenAI by default) for semantic evaluation. It provides four metrics:
- Faithfulness — Factual consistency via LLM claim verification
- Answer Relevancy — Semantic relevance of the answer to the query
- Context Precision — How relevant the retrieved context is
- Context Recall — Whether all needed information was retrieved (requires
ground_truth)
# Set your OpenAI API key
# Windows (PowerShell):
$env:OPENAI_API_KEY = "sk-your-key-here"
# macOS/Linux:
export OPENAI_API_KEY="sk-your-key-here"from rag_evaluation import RagasEvaluator
evaluator = RagasEvaluator()
results = evaluator.evaluate(
query="What is machine learning?",
context="Machine learning is a subset of AI that enables systems to learn...",
answer="Machine learning allows computers to learn from data...",
ground_truth="Machine learning is a subset of AI..."
)
for metric_name, metric_result in results.items():
print(f"{metric_name}: {metric_result['score']}")evaluator = RagasEvaluator()
results = evaluator.evaluate_batch(
queries=["What is ML?", "What is DL?"],
contexts=["ML is...", "DL is..."],
answers=["ML allows...", "DL uses..."],
ground_truths=["ML is...", "DL is..."]
)
avg_scores = evaluator.get_average_scores(results)
print("Average ragas scores:", avg_scores)# Only use faithfulness and answer_relevancy
evaluator = RagasEvaluator(metrics=['faithfulness', 'answer_relevancy'])Available metric names: faithfulness, answer_relevancy, context_precision, context_recall
from rag_evaluation import RAGEvaluator, RagasEvaluator
query = "What is machine learning?"
context = "Machine learning is a subset of AI that enables systems to learn..."
answer = "ML is a type of AI that learns from data."
# Rule-based
basic = RAGEvaluator()
basic_results = basic.evaluate(query, context, answer)
print("Basic:", {k: v['score'] for k, v in basic_results.items()})
# LLM-based
ragas = RagasEvaluator()
ragas_results = ragas.evaluate(query, context, answer)
print("Ragas:", {k: v['score'] for k, v in ragas_results.items()})The QualitativeLogger records RAG-augmented answers alongside direct LLM answers for human review. It outputs to CSV (for Excel/Google Sheets) and JSON (for scripts).
from rag_evaluation import QualitativeLogger, LogEntry, RAGEvaluator
logger = QualitativeLogger()
# Log a single entry
logger.log(LogEntry(
category="factual",
model_name="gemini-1.5-flash",
question="What is data.table?",
rag_context="data.table is an R package that provides an enhanced version of data.frame...",
rag_answer="data.table is an R package that extends data.frame with fast aggregation...",
llm_answer="data.table is a popular R package used for data manipulation...",
))
# Optionally attach evaluation scores
evaluator = RAGEvaluator()
scores = evaluator.evaluate(
query="What is data.table?",
context="data.table is an R package...",
answer="data.table is an R package that extends...",
)
logger.log(LogEntry(
category="code",
model_name="gemini-1.5-flash",
question="How to read CSV with data.table?",
rag_context="Use fread() to read files...",
rag_answer="Use fread() for fast CSV reading...",
llm_answer="Use read.csv() to read CSV files...",
evaluation_scores=scores,
))
# Save to disk (creates both CSV and JSON)
written_files = logger.save(output_dir="output/logs")
print(written_files)
# {'csv': 'output/logs/qualitative_log_2026-02-11_143022.csv',
# 'json': 'output/logs/qualitative_log_2026-02-11_143022.json'}CSV output — flat table, one row per entry. Evaluation scores are flattened into individual columns:
| timestamp | category | model_name | question | rag_context | rag_answer | llm_answer | faithfulness | context_precision | relevance |
|---|---|---|---|---|---|---|---|---|---|
| 2026-02-11T14:30:22 | factual | gemini-1.5-flash | What is data.table? | data.table is... | data.table extends... | data.table is a library... | |||
| 2026-02-11T14:30:23 | code | gemini-1.5-flash | How to read CSV? | Use fread()... | Use fread()... | Use read.csv()... | 0.85 | 0.72 | 0.64 |
JSON output — structured array of objects with nested evaluation_scores.
# Save only CSV
logger.save(output_dir="logs", formats=["csv"])
# Save only JSON
logger.save(output_dir="logs", formats=["json"])
# Custom filename prefix
logger.save(output_dir="logs", filename_prefix="my_experiment")
# Creates: logs/my_experiment_2026-02-11_143022.csvRun evaluation on a data file directly from the command line:
# Basic usage (auto-detects file format)
python examples/evaluate.py examples/sample_data.csv
# Verbose output (prints per-sample details)
python examples/evaluate.py examples/sample_data.csv --verbose
# Save results to a JSON file
python examples/evaluate.py examples/sample_data.csv --output results.json
# Specify file format explicitly
python examples/evaluate.py examples/sample_data.bib --type bibtex
# Select specific metrics
python examples/evaluate.py examples/sample_data.csv --metrics faithfulness relevance
# Combine options
python examples/evaluate.py data.json --verbose --output results.json --metrics faithfulness context_precision relevanceFull CLI reference:
usage: evaluate.py [-h] [--type {csv,json,bibtex,auto}] [--output OUTPUT]
[--metrics {faithfulness,context_precision,relevance} ...]
[--verbose]
data_file
positional arguments:
data_file Path to the evaluation data file
optional arguments:
--type Input format (default: auto)
--output Save results to this JSON file
--metrics Metrics to compute (default: all)
--verbose, -v Print detailed per-sample output
Log RAG vs LLM answers from a data file:
# Basic usage — logs to logs/ directory as CSV + JSON
python examples/qualitative_eval.py examples/sample_qualitative_data.csv
# Custom output directory with verbose console output
python examples/qualitative_eval.py data.csv --output-dir results/logs --verbose
# Include evaluation metric scores in the log
python examples/qualitative_eval.py data.csv --with-scores --verbose
# Override model name and select specific scoring metrics
python examples/qualitative_eval.py data.csv --model-name gemini-2.0-flash --with-scores --metrics faithfulness relevanceFull CLI reference:
usage: qualitative_eval.py [-h] [--type {csv,json,excel,auto}]
[--output-dir OUTPUT_DIR] [--model-name MODEL_NAME]
[--with-scores]
[--metrics {faithfulness,context_precision,relevance} ...]
[--verbose]
data_file
positional arguments:
data_file Path to input data file (CSV, JSON, or Excel)
optional arguments:
--type Input format (default: auto)
--output-dir Directory for log files (default: logs/)
--model-name Override the model name for all entries
--with-scores Compute evaluation metrics and attach to entries
--metrics Which metrics to compute (default: all)
--verbose, -v Print detailed per-entry output
Required CSV columns for qualitative logging:
category,model_name,question,rag_context,rag_answer,llm_answer
factual,gemini-1.5-flash,"What is data.table?","data.table is...","data.table is a package...","data.table is a library..."from rag_evaluation.data_ingestion import DataTableLoader
loader = DataTableLoader()
# Load raw records
records = loader.load('data.csv') # Returns List[Dict]
records = loader.load('data.json') # Auto-detects format
records = loader.load('data.xlsx') # Requires openpyxl
# Load ready for evaluation (returns dict with queries, contexts, answers, ground_truths)
data = loader.load_for_evaluation('data.csv')
# Load ready for qualitative logging
data = loader.load_for_qualitative_logging('qualitative_data.csv')
# Custom column mapping
data = loader.load_for_evaluation(
'data.csv',
query_column='question', # default: 'query'
context_column='retrieved', # default: 'context'
answer_column='response', # default: 'answer'
ground_truth_column='expected' # default: 'ground_truth'
)CSV format for evaluation:
query,context,answer,ground_truth
"What is ML?","ML is a subset of AI...","ML allows computers to learn...","ML is a subset of AI..."JSON format for evaluation:
[
{
"query": "What is ML?",
"context": "ML is a subset of AI...",
"answer": "ML allows computers to learn...",
"ground_truth": "ML is a subset of AI..."
}
]from rag_evaluation.data_ingestion import JabrefLoader
loader = JabrefLoader()
# Load raw entries
entries = loader.load('references.bib')
# Load ready for evaluation
data = loader.load_for_evaluation('references.bib')BibTeX field mapping:
| BibTeX Field | Maps To |
|---|---|
title |
query |
abstract (or note) |
context |
abstract |
ground_truth |
All metrics return a dictionary with score (float 0.0–1.0) and details (dict with reasoning):
{
'faithfulness': {
'score': 0.857,
'details': {
'total_sentences': 7,
'supported_sentences': 6,
'unsupported_sentences': ['One hallucinated sentence here.'],
'reasoning': '6 out of 7 sentences are grounded in context'
}
},
'context_precision': {
'score': 0.75,
'details': {
'answer_terms_count': 8,
'ground_truth_overlap': 6,
'context_overlap': 7,
'precision_percentage': '75.0%',
'reasoning': '6 out of 8 answer terms found in ground truth'
}
},
'relevance': {
'score': 0.63,
'details': {
'query_relevance': 0.67,
'context_relevance': 0.53,
'query_terms_in_answer': 2,
'total_query_terms': 3,
'context_terms_in_answer': 7,
'total_context_terms': 13,
'reasoning': 'Answer addresses 2/3 query terms and uses 7/13 context terms'
}
}
}Score interpretation guide:
| Score Range | Rating | Recommended Action |
|---|---|---|
| 0.8–1.0 | Excellent | Model is performing well |
| 0.6–0.8 | Good | Minor improvements possible |
| 0.4–0.6 | Fair | Review model configuration and data |
| 0.0–0.4 | Poor | Significant issues — needs attention |
Metric-specific troubleshooting:
| Low Score In | Likely Problem | Solutions |
|---|---|---|
| Faithfulness | Model is hallucinating | Improve context quality, lower temperature, add grounding instructions |
| Context Precision | Model not using correct sources | Improve retrieval ranking, verify ground truth quality |
| Relevance | Answer misses the question | Better prompt engineering, ensure context is relevant to query |
DocGPT is a Retrieval-Augmented Generation system that answers questions about the R data.table package. It retrieves relevant documentation from a vector store and uses Google Gemini to generate answers. It runs as either a Discord bot or a FastAPI HTTP server.
┌─────────────────────────────────────────────────────────────────┐
│ DocGPT │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Discord │ │ Assistant │ │ Google Gemini │ │
│ │ Bot / API │───▶│ (LangChain) │───▶│ LLM │ │
│ └──────────┘ └──────┬───────┘ └────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Vector Store │ │
│ │ (PostgreSQL + │ │
│ │ pgvector) │ │
│ └──────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ Memory Store │ │
│ │ (MongoDB) │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Key components:
| Component | Technology | Purpose |
|---|---|---|
| LLM | Google Gemini (via langchain-google-genai) |
Generates answers |
| Vector Store | PostgreSQL + pgvector (via langchain-postgres) |
Stores and retrieves document embeddings |
| Memory Store | MongoDB (via pymongo) |
Persists chat history per session |
| Interface | Discord bot (py-cord) or FastAPI |
User-facing interface |
| Content ingestion | Git clone + text splitting | Ingests data.table repo and wiki |
| Config | config.yml with env var interpolation |
All settings in one place |
| DI Container | dependency-injector |
Wires all components together |
DocGPT requires PostgreSQL (with pgvector) and MongoDB. Both are provided via Docker Compose.
cd systems/docgpt
# Start infrastructure services in the background
docker compose up -d
# Verify services are running
docker compose psExpected output:
NAME IMAGE STATUS PORTS
vector_storage ankane/pgvector Up 0.0.0.0:5432->5432/tcp
memory_storage mongo Up 0.0.0.0:27017->27017/tcp
Ports used:
| Service | Port | Protocol |
|---|---|---|
| PostgreSQL (pgvector) | 5432 | TCP |
| MongoDB | 27017 | TCP |
Conflict warning: If another PostgreSQL instance is already running on port 5432, you will get a "password authentication failed" error. Either stop the other service or update
STORAGE_VECTOR_URLin your.envwith the correct credentials.
Stopping infrastructure:
docker compose down # Stop containers
docker compose down -v # Stop and remove volumes (deletes all data)Before the bot can answer questions, you must ingest the data.table documentation into the vector store. This is a one-time operation (or whenever you want to refresh the data).
cd systems/docgpt
# Make sure Docker services are running
docker compose up -d
# Run the ingestion
uv run python main.py --ingestWhat happens during ingestion:
- Clones the
Rdatatable/data.tablerepository (code + wiki) intoassets/ - Converts documents (Markdown, code files) into text using pypandoc
- Splits documents into chunks using LangChain text splitters
- Generates embeddings and stores them in PostgreSQL via pgvector
Ingestion logs will appear on stderr (see Section 5):
[2026-02-11 14:30:22] [INFO] [__main__]: Fetching wiki documents...
[2026-02-11 14:30:25] [INFO] [__main__]: Fetching code documents...
[2026-02-11 14:30:45] [WARNING] [__main__]: Total of 3 documents failed to ingest
[2026-02-11 14:30:45] [INFO] [__main__]: Failed files summary: [...]
Note: Some files may fail to ingest (e.g., binary files, malformed documents). The system logs each failure with the file name, file type, file path, exception type, and reason. These failures are non-fatal — the rest of the documents are still ingested successfully.
cd systems/docgpt
# Make sure Docker services are running
docker compose up -d
# Start the Discord bot
uv run python main.pyThe bot will log in to Discord and listen for messages. You can interact with it by asking questions about the R data.table package in your Discord server.
Console output on startup:
[2026-02-11 14:31:00] [DEBUG] [src.app.discord]: Logged in as DocGPT#1234 (ID: 123456789)
Instead of the Discord bot, you can run DocGPT as an HTTP API:
cd systems/docgpt
# Make sure Docker services are running
docker compose up -d
# Start the FastAPI server
uv run python main.py --apiThe server starts on port 8000 by default (configurable via API_PORT env var).
API documentation is available at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
The API response now includes the retrieved context alongside the answer:
{
"question": "What is data.table?",
"answer": "data.table is an R package that extends data.frame...",
"retrieved_context": "data.table is an R package that provides...",
"source_count": 5,
"session_id": "test-session"
}Every question asked to DocGPT — whether through the Discord bot, the FastAPI server, or the terminal CLI — is automatically logged to structured files on disk. Each interaction records:
- Timestamp — when the question was asked
- Session ID — the Discord thread ID, API session, or
"cli" - Question — the user's original question
- Retrieved Context — the full text of all document chunks retrieved from the vector store
- Answer — the LLM-generated response
- Source Count — how many document chunks were retrieved
- Source Metadata — file names, paths, and projects of retrieved documents (JSONL only)
By default, logs are written to systems/docgpt/logs/. A pair of files is created each time the bot starts:
logs/
├── interactions_2026-02-11_143022.csv ← open in Excel / Google Sheets
└── interactions_2026-02-11_143022.jsonl ← parse with Python or any JSON tool
Each interaction is appended immediately after it occurs, so nothing is lost if the process crashes.
To change the log directory, set the INTERACTION_LOG_DIR environment variable:
# Windows (PowerShell)
$env:INTERACTION_LOG_DIR = "C:\my_logs"
uv run python main.py
# macOS/Linux
INTERACTION_LOG_DIR=/var/log/docgpt uv run python main.pyAt startup you will see a confirmation message:
[2026-02-11 14:30:22] [INFO] [__main__]: Interaction logs: CSV=logs/interactions_2026-02-11_143022.csv, JSONL=logs/interactions_2026-02-11_143022.jsonl
Open the CSV in Excel or Google Sheets. Each row is one interaction:
| timestamp | session_id | question | retrieved_context | answer | source_count |
|---|---|---|---|---|---|
| 2026-02-11T14:30:22 | 1234567890 | What is data.table? | data.table is an R package that... | data.table extends data.frame... | 5 |
Each line in the JSONL file is a self-contained JSON object with all fields plus the full source metadata:
{
"timestamp": "2026-02-11T14:30:22",
"session_id": "1234567890",
"question": "What is data.table?",
"retrieved_context": "data.table is an R package that...",
"answer": "data.table extends data.frame...",
"source_count": 5,
"source_metadata": [
{"file_name": "README.md", "project": "data.table", "source": "wiki"},
{"file_name": "data.table.Rmd", "project": "data.table", "source": "code"}
]
}To parse the JSONL file in Python:
import json
with open("logs/interactions_2026-02-11_143022.jsonl") as f:
for line in f:
entry = json.loads(line)
print(f"Q: {entry['question']}")
print(f"A: {entry['answer'][:80]}...")
print(f" Sources: {entry['source_count']} chunks retrieved")
print()The fastest way to verify logging is via the FastAPI server:
cd systems/docgpt
docker compose up -d
uv run python main.py --ingest # one-time
uv run python main.py --apiIn another terminal, send a test request:
curl -X POST http://localhost:8000/api/v1/assistant/prompt -H "Content-Type: application/json" -d "{\"message\": \"What is data.table?\", \"session_id\": \"test\"}"Then check systems/docgpt/logs/ — you will find the CSV and JSONL files with your interaction logged.
The logged interactions can be used as input for the evaluation framework to score the RAG system:
import json
from rag_evaluation import RAGEvaluator
evaluator = RAGEvaluator()
with open("systems/docgpt/logs/interactions_2026-02-11_143022.jsonl") as f:
for line in f:
entry = json.loads(line)
scores = evaluator.evaluate(
query=entry["question"],
context=entry["retrieved_context"],
answer=entry["answer"],
)
print(f"Q: {entry['question']}")
for metric, result in scores.items():
print(f" {metric}: {result['score']:.3f}")All DocGPT configuration lives in systems/docgpt/config.yml. Values use ${ENV_VAR:default} syntax for environment variable interpolation.
Full configuration structure:
core:
logging: # Python logging dict-config
version: 1
formatters:
formatter:
format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s"
handlers:
console:
class: "logging.StreamHandler"
level: ${LOG_LEVEL:DEBUG} # Controlled by LOG_LEVEL env var
formatter: "formatter"
stream: "ext://sys.stderr" # Output to stderr
root:
level: ${LOG_LEVEL:DEBUG}
handlers: ["console"]
ai:
gemini:
model_name: ${AI_GEMINI_MODEL:gemini-1.5-flash}
api_key: ${AI_GEMINI_APIKEY} # Required — no default
assistant:
k: ${ASSISTANT_K:100} # Top-K retrieval results
tokens_limit: ${ASSISTANT_TOKENS_LIMIT:2000}
score_threshold: ${ASSISTANT_SCORE_THRESHOLD:null}
distance_threshold: ${DISTANCE_THRESHOLD:null}
storage:
vector:
backend: ${STORAGE_VECTOR_BACKEND:chroma}
url: ${STORAGE_VECTOR_URL:postgresql+psycopg://root:example@localhost:5432/postgres}
memory:
url: ${STORAGE_MEMORY_URL:mongodb://root:example@localhost:27017}
app:
discord:
token: ${APP_DISCORD_TOKEN} # Required — no default
api:
port: ${API_PORT:8000}| Problem | Cause | Solution |
|---|---|---|
password authentication failed |
Another PostgreSQL on port 5432 | Stop the other service or change STORAGE_VECTOR_URL |
AI_GEMINI_APIKEY error |
Missing API key | Set AI_GEMINI_APIKEY in .env |
APP_DISCORD_TOKEN error |
Missing Discord token | Set APP_DISCORD_TOKEN in .env |
pypandoc errors |
Pandoc not installed | Run pypandoc.ensure_pandoc_installed() or install Pandoc manually |
| Ingestion failures | Binary/malformed files | Non-fatal — check logs for details, other documents still work |
| Empty answers from bot | Data not ingested | Run uv run python main.py --ingest first |
uv: command not found |
uv not installed | Install uv: see Section 1.3 |
The evaluation framework has a comprehensive test suite under tests/:
| Test File | What It Tests |
|---|---|
tests/test_evaluator.py |
RAGEvaluator — initialization, single eval, batch eval, average scores |
tests/test_metrics.py |
Individual metrics — FaithfulnessMetric, ContextPrecisionMetric, RelevanceMetric |
tests/test_data_ingestion.py |
DataTableLoader — CSV loading, JSON loading, evaluation format, error handling |
tests/conftest.py |
Shared pytest fixtures (evaluator, sample_data, batch_data) |
# From the repository root
cd RAG-evaluation
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests with verbose output
pytest tests/ -v
# Run with short traceback format
pytest tests/ -v --tb=short# Run tests with coverage report
pytest tests/ -v --cov=rag_evaluation --cov-report=term-missing
# Generate HTML coverage report
pytest tests/ -v --cov=rag_evaluation --cov-report=html
# Open htmlcov/index.html in your browser
# Generate XML coverage report (for CI)
pytest tests/ -v --cov=rag_evaluation --cov-report=xml# Run only evaluator tests
pytest tests/test_evaluator.py -v
# Run only metric tests
pytest tests/test_metrics.py -v
# Run only data ingestion tests
pytest tests/test_data_ingestion.py -v
# Run a specific test class
pytest tests/test_evaluator.py::TestRAGEvaluatorEvaluate -v
# Run a specific test method
pytest tests/test_evaluator.py::TestRAGEvaluatorEvaluate::test_evaluate_returns_all_metrics -v
# Run tests matching a keyword
pytest tests/ -v -k "faithfulness"The test suite provides reusable fixtures in tests/conftest.py:
evaluator— A defaultRAGEvaluator()with all three metricsevaluator_faithfulness_only— ARAGEvaluator(metrics=["faithfulness"])sample_data— A dict withquery,context,answer,ground_truth(for single evaluation)batch_data— A dict withqueries,contexts,answers,ground_truths(for batch evaluation)
DocGPT tests live in systems/docgpt/tests/. The test infrastructure is set up (with conftest.py and fixtures/), but test implementations are still being added.
cd systems/docgpt
# Install dev dependencies
uv sync --dev
# Run DocGPT tests
uv run pytest tests/ -v --tb=shortNote: Since DocGPT tests are still being developed, the CI pipeline uses
|| echo "No tests found yet"to avoid failing the build.
The GitHub Actions CI pipeline (.github/workflows/ci.yml) runs automatically on pushes and pull requests to main, master, and develop branches.
CI Jobs:
| Job | What It Does | Matrix |
|---|---|---|
lint |
Runs Ruff linter + formatter check | Python 3.11 |
type-check |
Runs mypy type checking on rag_evaluation/ |
Python 3.11 |
test |
Runs pytest with coverage on the evaluation framework | Python 3.10, 3.11, 3.12 |
test-docgpt |
Runs pytest on DocGPT tests | Python 3.11 |
To replicate CI locally:
# Lint check
ruff check . --output-format=github
# Format check
ruff format --check .
# Type check
mypy rag_evaluation/ --ignore-missing-imports
# Tests with coverage (replicates the CI test job)
pytest tests/ -v --tb=short --cov=rag_evaluation --cov-report=term-missing --cov-report=xml# Check for lint errors
ruff check .
# Auto-fix lint errors where possible
ruff check . --fix
# Check formatting
ruff format --check .
# Auto-format code
ruff format .The Ruff configuration is in pyproject.toml:
- Line length: 100
- Target: Python 3.10
- Selected rules:
E(pycodestyle errors),F(pyflakes),I(isort),N(pep8-naming),UP(pyupgrade) - Ignored:
E501(line too long — handled by formatter)
# Type check the evaluation framework
mypy rag_evaluation/ --ignore-missing-importsDocGPT uses Python's standard logging module configured via config.yml. The logging dictionary config is applied at startup via dependency-injector resource initialization.
Default log format:
[2026-02-11 14:30:22,123] [INFO] [__main__]: Your log message here
Format breakdown: [timestamp] [level] [logger_name]: message
Default output: All logs go to stderr (not stdout), which means they won't interfere with program output.
Startup initialization in main.py:
application = containers.Settings()
application.config.from_yaml("config.yml", envs_required=True, required=True)
application.core.init_resources() # <-- This applies logging.config.dictConfig()Set the LOG_LEVEL environment variable before starting DocGPT:
# Windows (PowerShell)
$env:LOG_LEVEL = "INFO"
uv run python main.py
# macOS/Linux
LOG_LEVEL=INFO uv run python main.pyAvailable levels (from most to least verbose):
| Level | Value | Shows |
|---|---|---|
DEBUG |
10 | Everything — includes LangChain internals, retrieval details |
INFO |
20 | Operational messages — ingestion progress, startup info |
WARNING |
30 | Only warnings and errors — e.g., failed document ingestion counts |
ERROR |
40 | Only errors — e.g., individual document ingestion failures |
CRITICAL |
50 | Only fatal errors |
Recommended settings:
| Scenario | Log Level |
|---|---|
| Development / debugging | DEBUG |
| Normal operation | INFO |
| Production / quiet mode | WARNING |
DocGPT automatically logs every RAG interaction to disk. This is separate from Python's logging module — it produces structured data files you can open in Excel or parse with scripts.
See Section 3.6 — Automatic Interaction Logging for full details on:
- Where the files are written (
systems/docgpt/logs/by default) - CSV and JSONL format descriptions
- How to change the output directory (
INTERACTION_LOG_DIR) - How to test the logging
- How to feed logs into the evaluation framework for scoring
The evaluation framework (rag_evaluation/) does not configure logging by itself. To enable logging in your evaluation scripts, add standard Python logging configuration:
import logging
# Basic configuration — logs to console
logging.basicConfig(
level=logging.DEBUG,
format="[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s"
)
# Now use the evaluator as normal
from rag_evaluation import RAGEvaluator
evaluator = RAGEvaluator()
results = evaluator.evaluate(query=query, context=context, answer=answer)For file-based logging:
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
handlers=[
logging.StreamHandler(), # Console
logging.FileHandler("evaluation.log"), # File
]
)For the ragas evaluator, additional debug output can be enabled:
import logging
# Enable ragas library debug logs
logging.getLogger("ragas").setLevel(logging.DEBUG)
# Enable httpx/openai request logs (shows API calls)
logging.getLogger("httpx").setLevel(logging.DEBUG)
logging.getLogger("openai").setLevel(logging.DEBUG)The QualitativeLogger produces structured log files (distinct from Python's logging module). These are evaluation artifacts, not debug logs.
Where files are written:
logger.save(output_dir="logs")
# Creates:
# logs/qualitative_log_2026-02-11_143022.csv
# logs/qualitative_log_2026-02-11_143022.jsonCSV file — Open in Excel or Google Sheets for human review. Columns include all LogEntry fields plus any evaluation score columns (flattened).
JSON file — Load programmatically for downstream analysis:
import json
with open("logs/qualitative_log_2026-02-11_143022.json") as f:
entries = json.load(f)
for entry in entries:
print(f"Q: {entry['question']}")
print(f" RAG: {entry['rag_answer'][:80]}...")
print(f" LLM: {entry['llm_answer'][:80]}...")
if entry.get('evaluation_scores'):
print(f" Scores: {entry['evaluation_scores']}")From the CLI:
# Generate qualitative logs with verbose console output
python examples/qualitative_eval.py data.csv --output-dir my_logs --verbose
# The --verbose flag prints each entry to the console as it's processed
# The output files are always written to --output-dir regardless of --verboseDocGPT enables LangChain debug and verbose modes by default in main.py:
from langchain_core.globals import set_debug, set_verbose
set_debug(True)
set_verbose(True)This produces very detailed output showing:
- Every LLM prompt sent to Gemini
- Raw LLM responses
- Retrieval queries and results
- Chain execution steps
To reduce noise in production, disable these before starting:
set_debug(False)
set_verbose(False)Or set LOG_LEVEL=WARNING to suppress most output while keeping LangChain debug available in the code.
# ── Evaluation Framework ─────────────────────────────────────────
# Install
pip install -r requirements.txt && pip install -e ".[dev]"
# Run all tests
pytest tests/ -v --cov=rag_evaluation --cov-report=term-missing
# Run evaluator from CLI
python examples/evaluate.py data.csv --verbose
python examples/evaluate.py data.json --output results.json --metrics faithfulness relevance
# Run qualitative logger from CLI
python examples/qualitative_eval.py data.csv --with-scores --verbose --output-dir logs/
# Lint and format
ruff check . --fix && ruff format .
# Type check
mypy rag_evaluation/ --ignore-missing-imports
# ── DocGPT RAG System ────────────────────────────────────────────
cd systems/docgpt
# Setup
cp .env.example .env # Then edit .env with your keys
uv sync # Install dependencies
docker compose up -d # Start PostgreSQL + MongoDB
# Ingest data (one-time)
uv run python main.py --ingest
# Run Discord bot (interaction logs go to logs/ automatically)
uv run python main.py
# Run API server (interaction logs go to logs/ automatically)
uv run python main.py --api
# Custom log directory
$env:INTERACTION_LOG_DIR = "my_logs" # PowerShell
uv run python main.py --api
# View interaction logs
# Open logs/interactions_*.csv in Excel
# Or parse logs/interactions_*.jsonl with Python
# Run DocGPT tests
uv run pytest tests/ -v --tb=short
# Tear down
docker compose down