This capstone project implements a retrieval-augmented generation (RAG) system specifically designed for real-time cricket match analysis and player performance commentary. The system combines a dense vector database of historical match statistics, player profiles, and match transcripts with a large language model (LLM) to generate contextually accurate, data-backed cricket commentary and performance insights.
The project demonstrates production-grade RAG architecture across several dimensions, including semantic chunking of cricket narratives, multi-stage retrieval using both BM25 and dense embeddings, prompt engineering for sports-specific generation, and integration with live match APIs. You will build a system that retrieves relevant historical precedents from a cricket knowledge base and uses them to generate accurate, engaging match commentary that cites specific player performances, match situations, and statistical context.
As a portfolio project, this system showcases advanced RAG patterns including hybrid retrieval, re-ranking, context window optimization, and structured output generation. These skills are directly applicable to domains such as financial analysis, medical record systems, and enterprise knowledge management, where accurate source attribution is critical.
Learning Objectives
- Design and implement a hybrid retrieval system combining BM25 sparse retrieval with dense embedding-based semantic search for cricket domain-specific queries.
- Build a multi-stage ranking pipeline that re-ranks retrieved cricket match documents using cross-encoder models to improve RAG generation quality and relevance.
- Engineer prompts specifically tailored to cricket commentary generation that enforce citation of retrieved sources and maintain factual accuracy within context windows.
- Integrate vector embeddings and keyword search over real cricket datasets (player statistics, match narratives, performance transcripts) into a production-ready retrieval backend.
- Implement context window management and chunking strategies that preserve cricket narrative coherence while optimizing retrieval efficiency and token usage.
- Evaluate RAG output quality through relevance metrics, hallucination detection, and citation accuracy—metrics critical for regulated domains like finance and healthcare.
Technical Requirements
- Implement vector embedding pipeline using open-source models (e.g., sentence-transformers) to encode cricket match narratives, player bios, and statistical summaries into 384-768 dimensional vectors.
- Build FAISS or similar vector database index supporting approximate nearest neighbor search to retrieve top-K contextually similar cricket scenarios within <500ms latency.
- Integrate BM25 keyword retrieval over inverted indices to handle exact player names, team abbreviations, match dates, and statistics not captured by dense embeddings alone.
- Create fusion retriever combining BM25 and dense retrieval using reciprocal rank fusion or learned combination weights optimized for cricket query distribution.
- Implement re-ranking stage using cross-encoder models to score relevance of retrieved cricket documents relative to the user query before passing to generation.
- Engineer prompt templates with instruction finetuning to enforce RAG outputs cite retrieved match statistics, include player names with context, and avoid hallucinating cricket facts.
- Design chunking strategy splitting cricket match transcripts and player records into 256-512 token chunks with rolling overlap to preserve narrative coherence and cross-document relevance.
- Build evaluation framework measuring BLEU/ROUGE similarity to ground-truth cricket commentary, measuring hallucination rate, and tracking citation precision/recall against retrievals.
Architecture & Design
The RAG architecture consists of five core components orchestrated in a data flow pipeline. The first is the Indexing Pipeline, which processes cricket match transcripts, player statistics, and historical narratives, applies semantic chunking with overlap, embeds chunks using a domain-optimized encoder, and stores both embeddings in FAISS and raw text with metadata in a document store.
The second component is the Retrieval Engine, which accepts user queries and performs hybrid search by executing BM25 keyword search and dense vector search in parallel. It merges results using reciprocal rank fusion and returns the top-10 candidate documents. The third component is the Re-ranking Module, which takes those candidate documents and the original query, applies a cross-encoder model to score relevance, reorders results by confidence, and selects the top three documents for LLM context.
The fourth component is the Prompt Engineering Layer, which constructs a system prompt enforcing citation requirements, inserts the retrieved cricket documents as context, frames the user query as a cricket analysis request, and specifies the required output structure of commentary paired with cited sources. The fifth and final component is the Generation and Post-processing stage, which calls the LLM with the formatted prompt, parses the output to extract commentary and source citations, validates those citations against retrieval records, and surfaces hallucination warnings when generated cricket facts lack supporting evidence.
This modular design allows independent optimization of retrieval quality, re-ranking accuracy, and generation fidelity — a critical property in production systems where retrieval latency, embedding quality, and LLM output calibration directly impact user experience. Data flows from raw cricket documents through embedding to a vector index, user queries route through dual retrieval pathways, results aggregate and re-rank, context feeds into the prompt, and LLM output routes through citation validation before final delivery.
# Cricket RAG System - Architecture Skeleton & Project Structure
# End-to-End Document Q&A System for Cricket Match Analysis
import os
import json
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass, field
from datetime import datetime
from collections import defaultdict
import hashlib
# ============================================================================
# DATA MODELS - Cricket Domain Entities
# ============================================================================
@dataclass
class CricketMatch:
"""Represents a cricket match with metadata."""
match_id: str
team_a: str
team_b: str
match_date: str
venue: str
format_type: str # Test, ODI, T20
match_summary: str # Raw document/commentary text
def __hash__(self):
return hash(self.match_id)
@dataclass
class CricketPlayer:
"""Represents a cricket player with performance statistics."""
player_name: str
team: str
role: str # Batsman, Bowler, All-rounder
career_stats: Dict[str, int] = field(default_factory=dict)
def __repr__(self):
return f"{self.player_name} ({self.team}, {self.role})"
@dataclass
class RetrievedContext:
"""Retrieved document chunks relevant to a query."""
chunk_id: str
chunk_text: str
source_match_id: str
relevance_score: float
player_references: List[str] = field(default_factory=list)
@dataclass
class QueryResponse:
"""Final response generated from RAG pipeline."""
query: str
answer: str
retrieved_chunks: List[RetrievedContext]
generation_timestamp: str
# ============================================================================
# INDEXING PIPELINE - Knowledge Base Preparation (Like Match Footage Review)
# ============================================================================
class IndexingPipeline:
"""Mirrors the cricket analyst's preparation phase."""
def __init__(self):
self.document_store: Dict[str, CricketMatch] = {}
self.index_chunks: Dict[str, str] = {} # chunk_id -> text
self.player_index: Dict[str, List[str]] = defaultdict(list) # player -> chunk_ids
self.keyword_index: Dict[str, List[str]] = defaultdict(list) # keyword -> chunk_ids
def ingest_match_document(self, match: CricketMatch) -> None:
"""
Ingest a complete match document.
Like: Analyst watches full match footage and records statistics.
"""
self.document_store[match.match_id] = match
self._chunk_and_index(match)
def _chunk_and_index(self, match: CricketMatch) -> None:
"""
Break match summary into searchable chunks.
Like: Analyst creates indexed notes on key moments, player performances.
"""
sentences = match.match_summary.split('.')
for idx, sentence in enumerate(sentences):
if sentence.strip():
chunk_id = f"{match.match_id}_chunk_{idx}"
chunk_text = sentence.strip()
self.index_chunks[chunk_id] = chunk_text
# Index by player mentions (like noting Rohit Sharma's patterns)
if "Rohit Sharma" in chunk_text:
self.player_index["Rohit Sharma"].append(chunk_id)
if "Virat Kohli" in chunk_text:
self.player_index["Virat Kohli"].append(chunk_id)
if "Jasprit Bumrah" in chunk_text:
self.player_index["Jasprit Bumrah"].append(chunk_id)
if "Babar Azam" in chunk_text:
self.player_index["Babar Azam"].append(chunk_id)
# Index by keywords
keywords = ["reverse swing", "footwork", "innings", "bowling", "batting",
"partnership", "wicket", "century", "boundary"]
for keyword in keywords:
if keyword.lower() in chunk_text.lower():
self.keyword_index[keyword].append(chunk_id)
# ============================================================================
# RETRIEVAL ENGINE - Knowledge Recall (Like Live Commentary Instinct)
# ============================================================================
class RetrievalEngine:
"""
Mirrors the cricket analyst's instinctive knowledge recall.
When player walks to crease → immediately retrieve relevant match data.
"""
def __init__(self, indexing_pipeline: IndexingPipeline):
self.pipeline = indexing_pipeline
def retrieve_context(self, query: str, top_k: int = 3) -> List[RetrievedContext]:
"""
Retrieve most relevant chunks for a query.
Like: Analyst recalls relevant statistics when commentating.
"""
retrieved = []
relevance_scores = {}
# Search by player name in query
for player_name in self.pipeline.player_index.keys():
if player_name.lower() in query.lower():
chunk_ids = self.pipeline.player_index[player_name]
for chunk_id in chunk_ids:
relevance_scores[chunk_id] = relevance_scores.get(chunk_id, 0) + 0.8
# Search by keywords in query
keywords = ["reverse swing", "footwork", "innings", "bowling", "batting",
"partnership", "wicket", "century", "boundary"]
for keyword in keywords:
if keyword.lower() in query.lower():
chunk_ids = self.pipeline.keyword_index[keyword]
for chunk_id in chunk_ids:
relevance_scores[chunk_id] = relevance_scores.get(chunk_id, 0) + 0.6
# Sort by relevance and return top-k
sorted_chunks = sorted(relevance_scores.items(),
key=lambda x: x[1],
reverse=True)[:top_k]
for chunk_id, score in sorted_chunks:
chunk_text = self.pipeline.index_chunks.get(chunk_id, "")
match_id = chunk_id.split("_chunk_")[0]
# Extract player references
player_refs = [p for p in ["Rohit Sharma", "Virat Kohli", "Jasprit Bumrah", "Babar Azam"]
if p in chunk_text]
retrieved.append(RetrievedContext(
chunk_id=chunk_id,
chunk_text=chunk_text,
source_match_id=match_id,
relevance_score=score,
player_references=player_refs
))
return retrieved
# ============================================================================
# GENERATION STAGE - Commentary Generation
# ============================================================================
class CommentaryGenerator:
"""Generates natural language responses using retrieved context."""
def generate_response(self, query: str, retrieved_contexts: List[RetrievedContext]) -> str:
"""
Generate commentary-style response.
Like: Analyst synthesizes retrieved facts into coherent live commentary.
"""
if not retrieved_contexts:
return "I don't have relevant match data to answer this question."
response = f"Based on match analysis: "
for context in retrieved_contexts:
response += f"\n• {context.chunk_text}"
if context.player_references:
response += f" [Involving: {', '.join(context.player_references)}]"
return response
# ============================================================================
# END-TO-END RAG SYSTEM
# ============================================================================
class CricketRAGSystem:
"""Complete RAG system for cricket match Q&A."""
def __init__(self):
self.indexing_pipeline = IndexingPipeline()
self.retrieval_engine = RetrievalEngine(self.indexing_pipeline)
self.generator = CommentaryGenerator()
def add_match_document(self, match: CricketMatch) -> None:
"""Add a match document to the knowledge base."""
self.indexing_pipeline.ingest_match_document(match)
print(f"✓ Indexed match: {match.team_a} vs {match.team_b} ({match.match_date})")
def answer_query(self, query: str) -> QueryResponse:
"""
Complete RAG pipeline: Retrieve → Generate → Return.
"""
print(f"\n🏏 Processing query: '{query}'")
# RETRIEVAL: Get relevant context
retrieved_contexts = self.retrieval_engine.retrieve_context(query, top_k=3)
print(f" Retrieved {len(retrieved_contexts)} relevant chunks")
# GENERATION: Create response
answer = self.generator.generate_response(query, retrieved_contexts)
print(f" Generated response")
# Package response
response = QueryResponse(
query=query,
answer=answer,
retrieved_chunks=retrieved_contexts,
generation_timestamp=datetime.now().isoformat()
)
return response
# ============================================================================
# DEMO: Complete End-to-End RAG System
# ============================================================================
if __name__ == "__main__":
print("=" * 70)
print("CRICKET RAG SYSTEM - Document Q&A Demo")
print("=" * 70)
# Initialize RAG system
rag_system = CricketRAGSystem()
# Create sample match documents
india_vs_pakistan_match = CricketMatch(
match_id="IND_PAK_TEST_2024_001",
team_a="India",
team_b="Pakistan",
match_date="2024-01-15",
venue="Lahore",
format_type="Test",
match_summary="""
India vs Pakistan Test Match. Rohit Sharma opened the innings with
exceptional footwork against Pakistan's reverse swing bowling attack.
Virat Kohli came to the crease and quickly settled in. Jasprit Bumrah's
bowling was outstanding, taking key wickets. Babar Azam showed brilliant
footwork in the second innings. The partnership between Rohit Sharma and
Virat Kohli produced a century stand. Jasprit Bumrah took another
important wicket with a reverse swing delivery. India's batting
partnership eventually led to victory.
"""
)
# Add match to knowledge base (INDEXING PHASE)
rag_system.add_match_document(india_vs_pakistan_match)
# Process example queries (RETRIEVAL + GENERATION)
queries = [
"How did Rohit Sharma perform against reverse swing?",
"What was Jasprit Bumrah's key contribution?",
"Tell me about the partnership involving Virat Kohli.",
"How did Babar Azam's footwork look in the second innings?"
]
print("\n" + "=" * 70)
print("QUERY PROCESSING")
print("=" * 70)
for query in queries:
response = rag_system.answer_query(query)
print(f"\n📋 Query: {response.query}")
print(f"📝 Answer:\n{response.answer}")
print("-" * 70)
print("\n✓ RAG System demonstration complete!")Phase 1 — Core Implementation
Phase 1 implements the essential indexing and retrieval pipeline. In this phase, you will build the document indexing layer that chunks cricket match transcripts and player statistics using semantic overlap, generates embeddings using a pretrained sentence-transformer model, creates a FAISS vector index for approximate nearest neighbor search, and implements BM25 keyword retrieval over match metadata.
The retrieval engine built in this phase performs parallel hybrid search by combining dense and sparse retrieval. It merges results using reciprocal rank fusion to balance precision and recall across different query types, and returns ranked candidates with relevance scores.
This phase demonstrates core RAG competencies that are foundational to production system design. These include understanding chunking trade-offs — where larger chunks preserve context but reduce retrieval specificity — as well as embedding model selection and evaluation, vector index construction and approximate search algorithms, and fusion strategies for combining multiple ranking signals.