Architecture
RAG Doctor is organized as a TypeScript monorepo. Each package has a single, well-defined responsibility. Packages import only from @rag-doctor/types — never from each other — which keeps dependencies acyclic and makes individual packages independently embeddable.
Data flow
The analysis pipeline has six stages:
Raw Trace (JSON) │ ▼@rag-doctor/ingestion → validate, normalize │ ▼@rag-doctor/rules → run rule engine, produce findings │ ▼@rag-doctor/diagnostics → map findings to root cause │ ▼@rag-doctor/reporters → format output (JSON or text) │ ▼Output (terminal / file / programmatic)Packages
@rag-doctor/typesFoundationShared TypeScript interfaces and types used across all packages. Defines the shape of traces, findings, configurations, diagnosis results, and reporter output. All packages import from here — never from each other.
RagTraceRagChunkFindingDiagnosisResultRuleConfigAnalysisResult@rag-doctor/ingestionInput layerAccepts raw trace input (JSON file, object, or Buffer), validates the structure, and emits a normalized internal representation. Handles field aliasing for common trace formats from LangChain, LlamaIndex, and custom instrumentation.
ingestTracevalidateTracenormalizeChunks@rag-doctor/rulesRule libraryContains all built-in rules as pure functions. Each rule takes a normalized trace and returns zero or more findings. Rules are grouped into named packs. Custom rules can be registered at runtime.
runRulesregisterRulegetRulePackBUILT_IN_RULES@rag-doctor/coreOrchestrationThe public embedding API. Wires ingestion → rules → reporters together into a single analyze() function. This is what you import when using RAG Doctor as a library inside your application.
analyzediagnosecreateAnalyzer@rag-doctor/diagnosticsRoot cause engineTakes a set of findings and applies the heuristic graph to identify the primary root cause, contributing factors, and produce structured recommendations. Stateless, deterministic.
diagnoseFindingsDIAGNOSTIC_GRAPH@rag-doctor/reportersOutput formattersFormats analysis output. The JSON reporter produces machine-readable structured output. The text reporter produces human-readable terminal output with color and severity indicators.
JsonReporterTextReportercreateReporterrag-doctor (CLI)Command-line interfaceThin command-line wrapper around @rag-doctor/core. Handles argument parsing, file I/O, configuration loading, and terminal rendering. All analysis logic lives in the core package.
analyze commanddiagnose commandEmbedding the analysis engine
Import @rag-doctor/core directly to use RAG Doctor as an embedded library:
1import { analyze } from "@rag-doctor/core";2import type { RagTrace } from "@rag-doctor/types";3 4const trace: RagTrace = {5 query: "What is the recommended dose of ibuprofen?",6 chunks: [7 { id: "c1", content: "...", score: 0.88, tokens: 210, source: "db/ibuprofen.txt" },8 { id: "c2", content: "...", score: 0.43, tokens: 380, source: "db/dosage.txt" },9 ],10 model: "gpt-4o",11 totalTokens: 590,12};13 14const result = await analyze(trace, { pack: "recommended" });15 16if (!result.ok) {17 console.log(`Found ${result.findings.length} issues`);18 result.findings.forEach((f) => console.log(`[${f.severity}] ${f.rule}: ${f.message}`));19}Writing a custom rule
1import { registerRule } from "@rag-doctor/rules";2import type { RuleContext, Finding } from "@rag-doctor/types";3 4registerRule({5 id: "empty-chunk",6 name: "Empty Chunk",7 description: "Detects chunks with no usable content",8 defaultSeverity: "error",9 evaluate(ctx: RuleContext): Finding[] {10 return ctx.chunks11 .filter((chunk) => chunk.content.trim().length < 10)12 .map((chunk) => ({13 rule: "empty-chunk",14 severity: "error",15 message: `Chunk ${chunk.id} contains insufficient content`,16 evidence: { chunkId: chunk.id, contentLength: chunk.content.length },17 }));18 },19});Package scope
@rag-doctor/rules can be used directly without the CLI or reporters.