Tutorial
Build a Graph RAG application for enterprise ITSM using Breadth-First Search traversal
Combine vector search and graph traversal to build a relationship-aware RAG system that answers complex enterprise ITSM questions across incidents, problems, changes, and knowledge recordsEnterprise IT operations data includes many related records. An incident record links to configuration items, problem records, change requests, and knowledge base articles. These relationships are important for understanding incidents. Standard retrieval augmented generation (RAG) systems use vector similarity search to find related text. Vector similarity search does not follow relationship links between records.
Graph RAG addresses this limitation by modeling the document store as a graph of entities. Each document contains metadata that describes its relationships to other documents. The system first performs a vector search to identify seed documents. The system then runs a Breadth-First Search (BFS) traversal over the relationship links. The traversal collects connected documents up to a configurable depth. This approach provides a relationship-aware context for large language models (LLMs).
In this tutorial, you will build a production grade Graph RAG application for enterprise IT service management (ITSM) data. You will generate synthetic ITSM data and ingest the data into DataStax Astra DB. You will create embeddings using IBM watsonx.ai. You will implement both a Normal RAG pipeline and a Graph RAG pipeline. You will also build a Carbon Design System user interface that compares both approaches side by side and visualizes entity relationships with D3.js.
Architecture of a Graph RAG application that uses BFS traversal
The application uses a three-tier architecture that is deployed in a single container. The architecture includes a data layer, a backend layer, and a front end layer.

Data Layer: DataStax Astra DB stores 1,370 synthetic IT Service Management documents. The documents include incident records, problem records, change records, knowledge base articles, configuration items, and business services.
Each document includes a 1024-dimensional vector that is created with the IBM watsonx.ai
intfloat/multilingual-e5-largeembedding model. Each document also includes ametadata.linksarray. This array stores the IDs of related documents. These IDs represent the graph edges that are used for Breadth-First Search traversal.Backend layer: The backend is a FastAPI application. The application exposes REST endpoints for Normal RAG and Graph RAG queries.
The Normal RAG pipeline performs a single approximate nearest neighbor vector search in Astra DB and retrieves the most similar documents.
The Graph RAG pipeline starts with the same vector search to retrieve seed documents. The pipeline then runs a Breadth-First Search traversal using a
collections.dequestructure. The traversal follows document relationship links and retrieves connected documents up to depth two or until ten documents are collected.The backend builds the final context and sends the prompt to the IBM Granite-3-8b-instruct foundation model.
Front end layer: The front end is a React application that is built with Carbon Design System version 11. The user interface uses the g100 dark theme.
The interface allows side-by-side comparison of Normal RAG and Graph RAG results. The interface renders a D3.js force directed graph to visualize the Breadth First Search traversal path. The interface also displays real-time performance metrics.
The interface includes sample queries that are labeled as easy, medium, and complex. These queries help demonstrate when each retrieval approach performs better.
Deployment model: The full application runs inside a single Docker container. The container uses Red Hat Universal Base Image 9 with Python 3.12.
Supervisord manages two services. Nginx serves the React front end on port 9000 and proxies API requests under the
/apipath to the backend. Uvicorn runs the FastAPI application on internal port 8000.
Request and data flow
When a user submits a query, the front end sends the query to both the Normal RAG endpoint and the Graph RAG endpoint at the same time.
Each endpoint converts the query into an embedding using watsonx. Each endpoint retrieves documents from Astra DB. The Normal RAG pipeline retrieves the top five documents by vector similarity. The Graph RAG pipeline retrieves the top three seed documents and expands the result set using Breadth First Search traversal.
Each pipeline builds a context and sends a prompt to the IBM Granite 3 8B Instruct model. The Graph RAG response includes a traversal_path array. The front end renders this array as an interactive graph visualization.
Prerequisites
Before you start this tutorial, ensure that you meet the following requirements.
- Python version 3.12 or later is installed.
- Node.js version 20 or later installed.
- Docker Desktop version 24 or later installed.
- DataStax Astra DB account.
- Create an account at DataStax Astra DB.
- Collect the Application Token and the API Endpoint.
- IBM watsonx.ai account.
- Sign up for free trail at IBM watsonx.ai.
- Create a project and record the Project ID and API Key.
- Basic Python programming and React development skills.
- Basic knowledge of REST APIs.
- Basic experience with Docker.
No prior experience with RAG or graph traversal techniques is required.
Steps
Step 1: Clone the repository and set up credentials
Clone the project repository and move to the application directory.
git clone https://github.com/IBM/enterprise-itsm-graph-rag cd enterprise-itsm-graph-rag/codeCopy the environment file template.
cp .env.example .envOpen the
.envfile and add your credentials.# DataStax Astra DB configuration ASTRA_DB_APPLICATION_TOKEN=AstraCS:xxxxx ASTRA_DB_API_ENDPOINT=https://xxxxx-xxxxx.apps.astra.datastax.com ASTRA_DB_KEYSPACE=graphCollection ASTRA_DB_COLLECTION=itsm_documents # IBM watsonx.ai configuration WATSONX_API_KEY=your-watsonx-api-key WATSONX_PROJECT_ID=your-project-id WATSONX_URL=https://us-south.ml.cloud.ibm.com # Model configuration EMBEDDING_MODEL_ID=intfloat/multilingual-e5-large LLM_MODEL_ID=ibm/granite-3-8b-instructNote: The
.envfile is excluded from version control. Do not commit credentials to the repository.
Step 2: Understand the graph data model
Graph RAG works by storing relationships directly inside each document.
Document structure
Each document that is stored in DataStax Astra DB includes a metadata.links field. This field contains the document IDs of related records. You will ingest data into DataStax Astra DB with embeddings in the subsequent steps.
{
"_id": "INC-uuid-12345",
"page_content": "Incident: INC0000031\nDescription: High CPU usage on api-server-002...",
"$vector": [0.023, -0.041 ...1024 floats...],
"metadata": {
"type": "incident",
"number": "INC0000031",
"priority": "P1",
"state": "Resolved",
"links": [
"PRB-uuid-67890",
"CHG-uuid-11111",
"KB-uuid-22222"
]
}
}
The links array represents direct relationships to other IT Service Management records.
The ITSM relationship model follows a simple relationship structure:
- A business service links to configuration items.
- A configuration item links to incidents.
- An incident links to a problem record.
- A problem record links to a change request.
- A change request links to a knowledge base article.
- A knowledge-base article links back to incidents or problem records.
Relationship traversal: The Graph RAG pipeline reads the metadata.links array during retrieval. A Breadth-First Search traversal uses these links to discover related documents. This approach builds relationship-aware context without using a separate graph database.
Step 3: Generate synthetic IT service management data
The project includes a script that generates synthetic IT Service Management data with realistic relationships.
Run the following commands to generate the dataset.
cd backend
python data_generator.py
The script creates a file that is named combined_dataset.json in the data directory. This file contains all documents and their relationship links.
Note: The script uses Python dataclasses to define entity structures. The script assigns related document IDs at random to create realistic cross-entity connections. In the Docker deployment, data generation runs on-demand. The user interface provides a Populate Data button that triggers data creation. Pre-generated files are not required.
Data generator script: The data_generator.py script creates multiple ITSM entity types and connects them using relationship links.
Generated entities and relationships
| Entity type | Count | Relationship description |
|---|---|---|
| Business Service | 20 | Depends on other business services |
| Configuration Item | 120 | Belongs to a business service |
| Incident | 800 | Links to configuration items, problem records, change requests, and knowledge base articles |
| Change Request | 200 | Links to incidents and problem records |
| Problem Record | 80 | Links to incidents and change requests |
| Knowledge Base Article | 150 | Links to incidents and problem records |
| Total Documents | 1,370 |
Step 4: Ingest data into DataStax Astra DB with embeddings
Run the following command to start ingestion.
python ingest.py
The script ingests 1,370 documents. The script processes documents in batches of 20 and pauses between requests to stay within IBM watsonx.ai API rate limits.
The ingest.py script loads IT Service Management documents into DataStax Astra DB and prepares the data for retrieval. The script performs three tasks.
Task 1: Create the vector collection
The script creates a collection with the required vector configuration.
- Vector dimension: 1024
- Vector similarity metric: Cosine
from astrapy import DataAPIClient
from astrapy.constants import VectorMetric
from astrapy.info import CollectionDefinition
collection = db.create_collection(
"itsm_documents",
definition=CollectionDefinition.builder()
.set_vector_dimension(1024)
.set_vector_metric(VectorMetric.COSINE)
.build()
)
This configuration matches the output size of the IBM watsonx.ai embedding model.
Task 2: Normalize relationship links
Source data can store relationships as nested dictionaries. The ingestion script converts these structures into a flat list of document IDs.
# Transform {"problems": ["id1"], "changes": ["id2"]} → ["id1", "id2"]
links = item.get("links", {})
all_links = []
if isinstance(links, dict):
for link_list in links.values():
if isinstance(link_list, list):
all_links.extend(link_list)
The script stores the result in the metadata.links field. The Graph RAG traversal requires this field to contain a simple list of document IDs.
Task 3: Generate vector embeddings
The script generates embeddings using IBM watsonx.ai
from ibm_watsonx_ai.foundation_models import Embeddings
from ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames as EmbedParams
embedding = Embeddings(
model_id="intfloat/multilingual-e5-large",
params={EmbedParams.TRUNCATE_INPUT_TOKENS: 512},
credentials=credentials,
project_id=WATSONX_PROJECT_ID
)
vectors = embedding.embed_documents(texts=texts)
The script embeds the page_content field into a 1024-dimensional vector.
Step 5: Understand the normal RAG pipeline retrieval logic
The Normal RAG pipeline retrieves documents using vector similarity search only. This pipeline does not follow relationships between documents.
Document retrieval logic
In rag_pipelines.py, the NormalRAG class extends the BaseRAG class. The retrieve method performs one vector search in DataStax Astra DB.
class NormalRAG(BaseRAG):
def retrieve(self, question: str, top_k: int = 5) -> List[Dict[str, Any]]:
query_embedding = self._embed_query(question)
results = self.collection.find(
sort={"$vector": query_embedding},
limit=top_k,
include_similarity=True
)
return list(results)
The method embeds the user question and sends the embedding to Astra DB. Astra DB returns the top five documents with the closest embeddings using cosine similarity.
This approach is fast and uses a single database query. The approach works well for direct factual questions. The approach cannot retrieve related records because it does not follow relationship links.
Answer generation logic
The generate_answer method prepares a prompt and calls the language model.
def generate_answer(self, question: str, documents: List[Dict]) -> str:
context = self._format_context(documents)
prompt = f"""You are an IT operations assistant. Answer the question using ONLY the provided context.
Context:
{context}
Question: {question}
Answer:"""
response = self.llm.generate_text(prompt=prompt)
return response
The method formats retrieved documents as numbered context entries. The method then sends the prompt to the IBM Granite 3 8B Instruct model. The model generates an answer using only the retrieved documents.
Step 6: Understand the Graph RAG pipeline with Breadth-First search logic
The Graph RAG pipeline extends the Normal RAG pipeline by adding relationship traversal. The pipeline uses two phases.
Phase 1: Retrieve seed documents
The pipeline starts with a vector similarity search.
- The system embeds the user question
- The system retrieves three seed documents from DataStax Astra DB
- These seed documents represent depth zero in the graph
This step is identical to Normal RAG retrieval, but it returns fewer documents.
Phase 2: Traverse relationships with Breadth-First Search
The pipeline then expands results by following document relationships.
from collections import deque
The pipeline stores seed documents in a queue with an initial depth of zero. The pipeline tracks visited document IDs to avoid duplicate processing.
# Phase 2: BFS traversal
queue = deque([(doc["_id"], 0) for doc in seed_results])
visited = set(doc["_id"] for doc in seed_results)
all_documents = list(seed_results)
traversal_path = []
The pipeline processes documents level by level.
- The pipeline removes one document from the queue
- The pipeline checks the current depth
- The pipeline reads the
metadata.linksfield - The pipeline fetches linked documents from Astra DB
- The pipeline adds new documents to the queue with an increased depth
while queue and len(all_documents) < select_k:
current_id, current_depth = queue.popleft()
if current_depth >= max_depth:
continue
# Fetch current document
current_doc = self.collection.find_one(filter={"_id": current_id})
# Traverse links
for link_id in current_doc.get("metadata", {}).get("links", []):
if link_id not in visited and len(all_documents) < select_k:
linked_doc = self.collection.find_one(filter={"_id": link_id})
if linked_doc:
all_documents.append(linked_doc)
visited.add(link_id)
queue.append((link_id, current_depth + 1))
traversal_path.append({
"from": current_id,
"to": link_id,
"depth": current_depth + 1
})
return all_documents, traversal_path
Reason for using Breadth-First Search
Breadth-First Search prioritizes directly connected documents. For ITSM data, direct relationships such as an incident linked to a problem record or change request are more relevant than distant relationships.
The select_k limit stops traversal once enough documents are collected. This limit prevents excessive database calls when a document has many relationships.
Output of the Graph RAG pipeline
The Graph RAG pipeline returns two results.
- A list of retrieved documents used for context.
- A traversal path that records how documents are connected.
The traversal path supports visualization of entity relationships in the user interface.
Step 7. Build the FastAPI backend
The backend service uses FastAPI to expose query endpoints for Normal RAG and Graph RAG. Starting the FastAPI backend enables the core REST API that processes user queries and returns results to the frontend. Running the backend first lets you validate the RAG pipelines, confirm correct API behavior, and explore the interactive API documentation. This step ensures the core backend services work correctly before moving on to frontend development in later steps.
Install backend dependencies and start the FastAPI server.
pip install -r requirements.txt uvicorn main:app --host 0.0.0.0 --port 8000 --reloadOpen the API documentation at:
http://localhost:8000/docsThis page provides interactive access to all backend endpoints.
Additional backend endpoints
The backend also provides utility endpoints.
/metricsreturns aggregated performance statistics./data-statuschecks whether the collection contains data./populatetriggers background data generation and ingestion.
Step 8. Build the Carbon Design System front end
The front end is a React application that uses Carbon Design System version 11 for all user interface components.
Run the following commands to install dependencies and start the development server.
cd frontend
npm install
npm start
The application opens in the browser and connects to the FastAPI backend.

Sample query categories
The user interface includes predefined sample queries. These queries show when each retrieval approach works best.
| Query level | Label | Example query |
|---|---|---|
| 🟢 Easy | Normal RAG works best | How do I resolve high CPU usage on a server |
| 🟡 Medium | Both approaches work | What incidents have occurred on api-server-002? |
| 🔴 Complex | Graph RAG works best | Trace the full impact chain for the slow response time problem |
Query intent detection
The front end includes a query intent classifier.
- Simple factual queries include phrases such as how to resolve, steps to, or specific ticket numbers.
- Relationship queries include phrases such as impact chain, root cause, or linked to.
For simple queries, the interface shows a hint that Normal RAG may be sufficient. For relationship queries, the interface hides the hint and encourages Graph RAG usage.
Visualization behavior

- Seed documents at depth zero render as larger nodes.
- Document type determines node color using Carbon color tokens.
- Mouse hover displays a tooltip with document details.
This visualization helps users understand how Graph RAG follows relationships across ITSM records.
Step 9. Containerize the application with Docker
The application runs inside a single Docker container that is built with a multi stage Dockerfile. The build separates front end compilation from backend runtime.
Build and start the application with Docker Compose.
docker compose up --buildOpen the application in a browser at:
http://localhost:9000
Stage 1: Build the React front end
The first stage uses Node.js 20 on Alpine Linux to build the React front end.
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install --silent
COPY frontend/ .
ARG REACT_APP_API_URL=/api
ENV REACT_APP_API_URL=$REACT_APP_API_URL
RUN npm run build
This stage installs front end dependencies and produces a static build directory.
Stage 2: Create the runtime container
The second stage uses Red Hat Universal Base Image 9 with Python 3.12. This stage runs the backend and serves the front end.
FROM registry.access.redhat.com/ubi9/python-312:latest
USER root
The image installs required system services.
RUN dnf install -y nginx && \
dnf clean all && \
pip install --no-cache-dir supervisor
The container copies and installs the backend code.
WORKDIR /app/backend
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
The container copies the compiled front end files into the nginx web directory.
COPY --from=frontend-builder /app/frontend/build /usr/share/nginx/html
The container configures nginx to proxy API requests to the backend service.
COPY nginx-combined.conf /etc/nginx/conf.d/default.conf
The container configures supervisord to manage both services.
RUN mkdir -p /etc/supervisor/conf.d
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
The container exposes port 9000 and starts supervisord.
EXPOSE 9000
CMD ["/opt/app-root/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
Step 10. Test the application with sample queries
Use the following sample queries to compare Normal RAG and Graph RAG behavior.
Query 1: Simple Factual Lookup
Query How do I resolve high CPU usage on a server?

Normal RAG result The system retrieves a knowledge-base article about CPU troubleshooting. The response is accurate and returns in about two to three seconds.
Graph RAG result The system retrieves the same knowledge base article. The system then follows links to related incident records. The extra database calls do not improve the answer.
Result Normal RAG is the better choice for single document factual questions.
Query 2: Multi entity analysis
Query What incidents have occurred on apiserver 002 and what is their priority?

Normal RAG result The system retrieves incidents that are textually similar to the query. Some relevant incidents may be missing if their descriptions differ.
Graph RAG result The system retrieves seed incidents. The system follows links to the configuration item for apiserver 002 and related problem records. The result includes infrastructure and priority context.
Result Graph RAG provides complete information when relationships matter.
Query 3: Deep relationship traversal

Query Trace the full impact chain for the slow response time problem
Normal RAG result The system retrieves documents with similar text. The system does not retrieve the problem record, root cause, or change request.
Graph RAG result The system retrieves seed incidents that are related to slow response time. At the next level, the system retrieves the related problem record that explains the root cause. At the next level, the system retrieves the change request and knowledge base article that document the fix.
The final context includes:
- Incident records that triggered investigation
- The problem record that explains the root cause
- The change request that implemented the fix
- The knowledge base article that documents the solution
Result Graph RAG is required for questions that depend on relationship traversal across multiple records.
Optional: Step 11. Deploy the application to IBM Cloud Code Engine
This step is optional and applies only for production deployment. You need an IBM Cloud account with access to IBM Cloud Code Engine if they choose to deploy the application to IBM Cloud.
To deploy the containerized application for production use:
Push the forked or cloned repository from Step 1 into the your own GitHub account. The
.envfile is excluded from version control, so credentials remain secure.Create a Code Engine Project.
ibmcloud ce project create --name itsm-graph-ragCreate a Build from the Git Repository.
ibmcloud ce build create --name itsm-build \ --source https://github.com/your-org/enterprise-itsm-graph-rag \ --context-dir . \ --dockerfile DockerfileCreate secrets for database credentials.
ibmcloud ce secret create --name astra-creds \ --from-literal ASTRA_DB_APPLICATION_TOKEN=AstraCS:xxxxx \ --from-literal ASTRA_DB_API_ENDPOINT=https://xxxxx.apps.astra.datastax.comCreate secrets for watsonx credentials.
ibmcloud ce secret create --name watsonx-creds \ --from-literal WATSONX_API_KEY=your-key \ --from-literal WATSONX_PROJECT_ID=your-project-idDeploy the application.
ibmcloud ce app create --name itsm-graph-rag \ --build-source itsm-build \ --port 9000 \ --env-from-secret astra-creds \ --env-from-secret watsonx-creds \ --min-scale 1 \ --max-scale 3Retrieve the application URL.
ibmcloud ce app get --name itsm-graph-rag --output url
Start the application
Open the application URL retrieved from the previous command in a browser.
The URL is in the format:
https://itsm-graph-rag.xxxxxx.us-south.codeengine.appdomain.cloud
Click Populate Data to generate and ingest the dataset.

After ingestion completes, start submitting queries.

Summary
You built a production grade Graph RAG application for enterprise IT operations. The application shows how relationship aware retrieval improves answers for complex IT Service Management questions.
By completing this tutorial, you learned how to:
- Store document relationships directly in DataStax Astra DB using the
metadata.linksfield. - Use Breadth-First Search traversal to follow relationship chains across related records.
- Compare Normal RAG with Graph RAG to understand the accuracy and performance differences.
- Build and deploy a complete application with a FastAPI backend and a React front end.
- Visualize document relationships and traversal paths using D3.js
Key benefits of Graph RAG
- Relationship awareness: Graph RAG answers questions that require context from multiple connected records.
- Explainable retrieval: The traversal path shows which documents contributed to the final answer.
- Simple architecture: Document metadata stores graph edges. No separate graph database is required.
- Controlled performance: Traversal depth and document limits balance answer quality and response time.
Graph RAG is most useful when questions depend on how incidents, problems, changes, and knowledge articles connect across the IT environment.
When to use each retrieval approach
| Use Case | Recommended approach | Reason |
|---|---|---|
| Direct factual questions | Normal RAG | Fast response time and one document provides enough information |
| Root cause investigation | Graph RAG | Requires navigation from incidents to problem records and change requests |
| Impact chain analysis | Graph RAG | Requires full relationship context across multiple entity types |
| Latency sensitive applications | Normal RAG | Uses fewer database calls and provides predictable performance |
| Multi entity questions | Graph RAG | Collects related context from connected documents |
Next steps
You can extend the Graph RAG application with the following improvements:
- Add hybrid retrieval: Combine vector search and relationship traversal with metadata filters. Example filters include incident priority, incident state, or time range.
- Add weighted relationships: Assign importance scores to relationship types such as incident to problem or problem to change. Use these scores to prioritize traversal of more relevant links.
- Stream model responses: Use FastAPI streaming responses to send large language model output in real time. This approach improves perceived response speed and user experience.
- Use larger language models. Replace the current model with a larger model such as IBM Granite 13B Instruct or Llama 3 70B Instruct. Larger models can improve reasoning quality for complex queries.
- Deploy to production platforms: Use the UBI Python 3.12 container image to deploy the application on Red Hat OpenShift or IBM Cloud Code Engine. These platforms provide scaling, security, and enterprise grade reliability.
Continue learning
- watsonx.ai foundation models: Review the available large language models and embedding models.
- Agent based RAG with watsonx Orchestrate: Learn how to build AI agents that plan, reason, and call tools.
- Carbon Design System for React: Build consistent enterprise user interfaces with ready made React components.
- DataStax Astra DB vector search: Study advanced vector search features, indexing options, and query patterns.