FCC RAG Pipeline Guide¶
Retrieval-Augmented Generation (RAG) in FCC is more than vector search against a document collection. It is a persona-aware, vocabulary-grounded retrieval pipeline that respects the federation namespace of the deployed project and feeds grounded context into the SimulationEngine. GraphRAG — the graph-native variant — is being staged for v1.4.0 and already has scaffolding in the codebase.
The reference code lives at src/fcc/rag/:
chunking.py—DocumentChunkerwith 6 strategiesretriever.py—SemanticRetriever,RetrievalResultpipeline.py—RAGPipelinewith persona-aware prompt constructionintegration.py— wiring to the simulation engineunified_pipeline.py— federated retrieval across namespaces
Supporting modules:
src/fcc/search/—SearchIndex,PersonaSearchIndex,ActionSearchIndex,EmbeddingProvidersrc/fcc/knowledge/— the knowledge graph the GraphRAG variant walks
The Pipeline¶
flowchart LR
Docs[Document<br/>Corpus] --> C[DocumentChunker]
C --> E[EmbeddingProvider]
E --> I[SearchIndex]
Q[Query +<br/>Persona ID] --> R[SemanticRetriever]
I --> R
V[Vocabulary<br/>Grounding] --> R
R --> P[RAGPipeline]
P --> Grounded[Grounded<br/>Response]
classDef doc fill:#fff9c4,stroke:#f57f17;
classDef fcc fill:#c8e6c9,stroke:#2e7d32;
classDef out fill:#e3f2fd,stroke:#0d47a1;
class Docs,V,Q doc;
class C,E,I,R,P fcc;
class Grounded out;
Chunking Strategies¶
DocumentChunker supports six strategies, chosen per corpus:
| Strategy | When to use |
|---|---|
fixed_size |
Uniform text, predictable behaviour |
sentence |
Prose documents with punctuation |
paragraph |
Technical documentation |
markdown_section |
Docs-as-code (FCC's own guides are ideal candidates) |
code_block |
Source code corpora |
semantic |
Long narratives where meaning crosses paragraph boundaries |
from fcc.rag.chunking import DocumentChunker
chunker = DocumentChunker(strategy="markdown_section", chunk_size=1024, overlap=128)
chunks = chunker.chunk(text, metadata={"source": "ophiuchus/guides/patient_intake.md"})
For FCC's own documentation (Markdown), markdown_section preserves header context and produces well-scoped chunks.
Embedding Providers¶
The EmbeddingProvider protocol decouples the pipeline from any specific model:
from fcc.search.embeddings import EmbeddingProvider, MockEmbeddingProvider
# Default mock provider — 384-dim, deterministic, no network
provider = MockEmbeddingProvider()
# Or wrap sentence-transformers
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-small-en-v1.5")
class BGEProvider:
def embed(self, texts: list[str]) -> list[list[float]]:
return model.encode(texts, normalize_embeddings=True).tolist()
def dimension(self) -> int:
return 384
# Or delegate to a managed API via the AI provider plugin matrix
The mock provider is always available and gives byte-identical outputs — ideal for tests and for offline reproducibility.
Indexing a Corpus¶
from fcc.rag.chunking import DocumentChunker
from fcc.search.embeddings import MockEmbeddingProvider
from fcc.search.index import SearchIndex
chunker = DocumentChunker(strategy="markdown_section")
embeddings = MockEmbeddingProvider()
index = SearchIndex(embedding_provider=embeddings)
# Ingest one document
for chunk in chunker.chunk(text, metadata={"source": "ophiuchus/guide.md"}):
index.add(chunk.text, metadata=chunk.metadata)
# Persist
index.save("/tmp/ophiuchus.idx")
The index format is portable; load it later without rebuilding.
Persona-Aware Retrieval¶
The defining feature of the FCC RAG pipeline is persona-aware retrieval — the query prompt is annotated with the persona's R.I.S.C.E.A.R. spec, and retrieval biases toward chunks that match the persona's expected inputs and constraints.
from fcc.rag.pipeline import RAGPipeline
from fcc.rag.retriever import SemanticRetriever
from fcc.personas.registry import PersonaRegistry
from fcc.search.embeddings import MockEmbeddingProvider
registry = PersonaRegistry.from_package_data()
retriever = SemanticRetriever(index, embedding_provider=MockEmbeddingProvider())
pipeline = RAGPipeline(retriever=retriever, persona_registry=registry)
result = pipeline.query(
question="What are the prerequisites for discharge planning?",
persona_id="OPH-DC", # Ophiuchus Discharge Coordinator
)
print(result.answer)
for chunk in result.sources:
print(f" [{chunk.score:.3f}] {chunk.metadata['source']}")
Under the hood the pipeline:
- Looks up the persona spec from the registry
- Annotates the query with persona inputs / constraints / role
- Retrieves top-k chunks
- Re-ranks using persona-weight features
- Constructs a persona-style prompt and generates the answer
Vocabulary Grounding¶
Add a vocabulary grounding to filter and re-rank chunks against known concepts:
from fcc.rag.retriever import SemanticRetriever, VocabularyGrounding
grounding = VocabularyGrounding.from_namespace("ophiuchus")
retriever = SemanticRetriever(
index,
embedding_provider=MockEmbeddingProvider(),
grounding=grounding,
)
With grounding active:
- Chunks whose embedding does not intersect any vocabulary concept are down-weighted
- Concept-bearing chunks are boosted
- The answer prompt includes vocabulary-ID hints for the LLM
This is the mechanism by which a domain-specific RAG pipeline stays inside its domain rather than hallucinating cross-domain content.
Federated RAG¶
UnifiedPipeline queries across multiple namespaces in a single call:
from fcc.rag.unified_pipeline import UnifiedPipeline
unified = UnifiedPipeline.from_package_data()
# Query across ophiuchus + pyxis simultaneously (e.g. for healthcare-finance topics)
result = unified.query(
question="How does HSA account reimbursement interact with diagnosis codes?",
namespaces=["ophiuchus", "pyxis"],
persona_id="FED-COORD",
)
The UnifiedPipeline walks the federation registry, dispatches sub-queries to each namespace's index, and re-ranks globally. Results carry a namespace attribute so you can weigh or filter per-domain.
GraphRAG (Roadmap in v1.4.0)¶
FCC already ships the knowledge graph (src/fcc/knowledge/) that GraphRAG walks. The staged pattern for v1.4.0:
- Index documents as before
- Parallel to the vector index, extract entities and link them to the knowledge graph
- At query time, walk the knowledge graph from query-relevant entities to collect contextual neighbours
- Retrieve chunks that mention either the query entities or their graph neighbours
- Re-rank by combined vector similarity + graph proximity
The knowledge-graph substrate is already in place: 9 node types, 9 edge types, RDF/OWL/SKOS/JSON-LD serializers, federated graph. The v1.4.0 work is wiring the retriever to walk it at query time.
Preview the graph today:
from fcc.knowledge.graph import KnowledgeGraph
from fcc.knowledge.builders import build_full_fcc_graph
graph = build_full_fcc_graph()
print(f"Nodes: {graph.node_count()}, edges: {graph.edge_count()}")
neighbours = graph.neighbours("OPH-DC", max_depth=2)
Evaluating the Pipeline¶
Wire a RAG evaluation into the CLEAR+ benchmark suite:
fcc evaluate rag \
--index /tmp/ophiuchus.idx \
--questions tests/fixtures/rag_questions.jsonl \
--output /tmp/rag_eval.json
CLEAR+ measures seven dimensions: Cost, Latency, Efficacy, Assurance, Reliability, Coverage, Explainability. For RAG specifically, Efficacy and Explainability are the two to watch — a pipeline that retrieves fast but produces unverifiable answers is not usable.
Treat a drop in Efficacy > 5% as a regression and gate releases on the benchmark.
Integration with the Simulation Engine¶
Wire the RAG pipeline into the simulation engine so personas receive grounded context automatically:
from fcc.rag.integration import attach_rag_to_simulation
from fcc.simulation.engine import SimulationEngine
engine = SimulationEngine(registry=registry, mode="ai")
attach_rag_to_simulation(engine, pipeline)
engine.run(scenario)
Every action invocation now gets grounded context from the pipeline; the persona's prompt is prefixed with the retrieved chunks. See the integration demo notebook and the unified-pipeline demo.
Common Pitfalls¶
1. Forgetting grounding. A vanilla RAG pipeline against a domain corpus will hallucinate domain-adjacent content. Always wire a vocabulary grounding for production.
2. Mixing namespaces without federation metadata. Two chunks from ophiuchus and pyxis that mention "Account" are about different things. Use federated RAG or restrict the query to a single namespace.
3. Over-chunking. 128-token chunks lose context. 2048-token chunks dilute relevance. Start at 512-1024 and tune with the CLEAR+ benchmark.
4. Ignoring reproducibility. The mock provider and a deterministic seed give bit-identical output across runs. Use them in tests; use a real provider only at runtime.
5. Not measuring. Without a benchmark suite, you cannot tell whether a new chunk strategy helped or hurt. Wire CLEAR+ into CI.
Related Reading¶
- Vocabulary Provider Pattern — the vocabularies that ground retrieval
- Namespace Registration — federation namespaces for cross-domain RAG
- Ontology Integration — external ontologies as grounding sources
- Knowledge graph module — GraphRAG substrate
- RAG module API — implementation reference
- For Scientists: Literature Review Agents — research-facing RAG
- For Developers: Search and RAG — plumbing-level detail