What You'll Build
In this exercise, you will build a cricket match analysis system using Retrieval-Augmented Generation (RAG). The system combines a vector database of historical cricket scorecard data with a language model to answer complex questions about player performance, match statistics, and tactical patterns.
The core architecture retrieves relevant historical matches from a vector-indexed knowledge base — indexed by batting averages, bowling figures, match conditions, and team compositions — and then uses a large language model to synthesize that retrieved data into coherent analytical responses.
This hands-on project teaches you how to structure domain-specific document embeddings, implement semantic search over structured sports data, and design prompts that leverage retrieved context to generate accurate, contextually aware insights about cricket performance metrics.
Throughout the project, you will work with real player names, match venues, and performance data to understand how RAG systems solve the fundamental problem of grounding LLM responses in factual, retrievable information rather than relying on parametric knowledge alone.
Prerequisites
- Solid understanding of embeddings and vector similarity (cosine distance, Euclidean distance) from RAG fundamentals.
- Working knowledge of Python with libraries like numpy, pandas for data manipulation and numeric operations.
- Familiarity with JSON data structures and document-based indexing for storing and querying structured records.
- Basic understanding of LLM prompt engineering and how context windows are filled with retrieved documents.
- Access to OpenAI API or compatible LLM service; faiss, langchain, or similar retrieval libraries installed locally.
Setup & Project Structure
Begin by initializing a Python project with a cricket-themed folder structure that organizes your knowledge base, retrieval logic, and generation pipeline into distinct modules. The project separates concerns across three layers: a data layer for match scorecards, a retrieval layer for the vector index and search logic, and a generation layer for prompt construction and LLM calls.
For the underlying tooling, you will use a vector database such as FAISS to store embeddings of cricket match records, a document loader to parse scorecard JSON data, and a query engine that chains retrieval with LLM-based synthesis. Install dependencies covering embedding generation via sentence-transformers, vector indexing via faiss-cpu, language model interaction via openai or langchain, and data handling via pandas and json.
This modular approach mirrors production RAG systems where retrieval and generation are deliberately decoupled, allowing each pipeline stage to be independently optimized without affecting the others.
#!/bin/bash
# Cricket RAG System - Project Setup
# Create project structure
mkdir -p cricket-rag-system/{data,retrievers,generators,indexes,outputs}
cd cricket-rag-system
# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install required dependencies
pip install --upgrade pip
pip install openai==1.3.0
pip install sentence-transformers==2.2.2
pip install faiss-cpu==1.7.4
pip install pandas==2.0.3
pip install numpy==1.24.3
pip install python-dotenv==1.0.0
pip install langchain==0.0.300
pip install pydantic==2.0.0
# Create .env file for API keys
echo 'OPENAI_API_KEY="your-api-key-here"' > .env
# Create directory structure with init files
touch retrievers/__init__.py
touch generators/__init__.py
touch data/cricket_scorecards.json
touch main.py
echo "✓ Cricket RAG System initialized successfully"
echo "✓ Project structure created at: $(pwd)"
echo "✓ Virtual environment activated"
echo "✓ Dependencies installed"
echo "\nNext: Configure OPENAI_API_KEY in .env file"
Step 1 — Foundation
Build the foundational data layer by creating cricket match records as structured documents, each containing scorecard information, player statistics, match conditions, and venue details. These documents form your knowledge base — the source material that the retrieval system will search through when answering queries.
Each document must be serializable to JSON and include both metadata fields — such as match_id, teams, venue, date, and format — and performance data such as individual player stats, innings summaries, and bowling figures. This structured representation ensures the retrieval system has sufficient context to match queries accurately.
Next, use the sentence-transformers library to generate dense vector embeddings from these documents, converting textual descriptions of match performances into high-dimensional vectors. The embedding model learns to place semantically similar matches close together in vector space — for example, two Test matches at the MCG with similar batting conditions would cluster near one another.
This foundation is critical because the quality of your embeddings directly determines the retrieval system's ability to surface relevant historical matches when answering new queries about cricket performance. Poor embeddings at this stage will degrade every subsequent component in the pipeline.
# data/cricket_scorecards.json - Sample cricket match knowledge base
# This file should be placed in the data/ directory
import json
from typing import List, Dict
from dataclasses import dataclass, asdict
from datetime import datetime
@dataclass
class PlayerStatistic:
"""Individual player performance in a match"""
player_name: str
role: str # batsman, bowler, all-rounder
runs_scored: int = 0
balls_faced: int = 0
fours: int = 0
sixes: int = 0
wickets_taken: int = 0
balls_bowled: int = 0
runs_conceded: int = 0
def get_batting_average(self) -> float:
"""Calculate batting average"""
return self.runs_scored / max(1, self.balls_faced)
def get_economy_rate(self) -> float:
"""Calculate bowling economy (runs per over)"""
overs = self.balls_bowled / 6 if self.balls_bowled > 0 else 1
return self.runs_conceded / overs
@dataclass
class InningsRecord:
"""Single innings performance"""
team_name: str
total_runs: int
total_wickets: int
overs_batted: float
batting_lineup: List[str]
bowling_attack: List[str]
@dataclass
class CricketMatchRecord:
"""Complete match information for vector indexing"""
match_id: str
match_date: str
venue: str
country: str
format: str # Test, ODI, T20
team_1: str
team_2: str
team_1_innings: InningsRecord
team_2_innings: InningsRecord
result: str
match_winner: str
player_performances: List[PlayerStatistic]
toss_winner: str
toss_decision: str # bat, bowl
weather: str
pitch_condition: str
def to_document(self) -> str:
"""Convert match record to searchable text document"""
doc = f"""Match ID: {self.match_id}
Date: {self.match_date}
Venue: {self.venue}, {self.country}
Format: {self.format}
Teams: {self.team_1} vs {self.team_2}
{self.team_1} Innings: {self.team_1_innings.total_runs}/{self.team_1_innings.total_wickets} ({self.team_1_innings.overs_batted} overs)
Key Batsmen: {', '.join(self.team_1_innings.batting_lineup)}
Key Bowlers: {', '.join(self.team_1_innings.bowling_attack)}
{self.team_2} Innings: {self.team_2_innings.total_runs}/{self.team_2_innings.total_wickets} ({self.team_2_innings.overs_batted} overs)
Key Batsmen: {', '.join(self.team_2_innings.batting_lineup)}
Key Bowlers: {', '.join(self.team_2_innings.bowling_attack)}
Result: {self.match_winner} won
Toss: {self.toss_winner} won toss, chose to {self.toss_decision}
Weather: {self.weather}
Pitch: {self.pitch_condition}
Notable Performances:
"""
for perf in self.player_performances:
if perf.role == 'batsman':
doc += f"- {perf.player_name}: {perf.runs_scored} runs off {perf.balls_faced} balls ({perf.fours}x4, {perf.sixes}x6)\n"
elif perf.role == 'bowler':
doc += f"- {perf.player_name}: {perf.wickets_taken}/{perf.runs_conceded} from {perf.balls_bowled//6} overs (economy: {perf.get_economy_rate():.2f})\n"
return doc
# Sample cricket match records
sample_matches = [
CricketMatchRecord(
match_id="IND_AUS_MCG_2023_01",
match_date="2023-12-26",
venue="Melbourne Cricket Ground (MCG)",
country="Australia",
format="Test",
team_1="India",
team_2="Australia",
team_1_innings=InningsRecord(
team_name="India",
total_runs=191,
total_wickets=6,
overs_batted=58.3,
batting_lineup=["Rohit Sharma", "Shubman Gill", "Virat Kohli"],
bowling_attack=["Jasprit Bumrah", "Mohammed Shami"]
),
team_2_innings=InningsRecord(
team_name="Australia",
total_runs=247,
total_wickets=8,
overs_batted=72.0,
batting_lineup=["Steve Smith", "Travis Head", "Marnus Labuschagne"],
bowling_attack=["Scott Boland", "Mitchell Starc"]
),
result="Australia won by 2 wickets",
match_winner="Australia",
player_performances=[
PlayerStatistic(player_name="Rohit Sharma", role="batsman", runs_scored=47, balls_faced=89, fours=4, sixes=0),
PlayerStatistic(player_name="Virat Kohli", role="batsman", runs_scored=52, balls_faced=97, fours=6, sixes=1),
PlayerStatistic(player_name="Jasprit Bumrah", role="bowler", wickets_taken=2, balls_bowled=132, runs_conceded=38),
PlayerStatistic(player_name="Steve Smith", role="batsman", runs_scored=68, balls_faced=141, fours=7, sixes=0),
PlayerStatistic(player_name="Scott Boland", role="bowler", wickets_taken=3, balls_bowled=144, runs_conceded=41),
],
toss_winner="India",
toss_decision="bat",
weather="Overcast, cool conditions",
pitch_condition="Green top, favorable to fast bowling"
),
CricketMatchRecord(
match_id="IND_AUS_WANKHEDE_2023_02",
match_date="2023-02-09",
venue="Wankhede Stadium",
country="India",
format="Test",
team_1="India",
team_2="Australia",
team_1_innings=InningsRecord(
team_name="India",
total_runs=326,
total_wickets=7,
overs_batted=95.0,
batting_lineup=["Rohit Sharma", "Shubman Gill", "Virat Kohli"],
bowling_attack=["Jasprit Bumrah", "Ravindra Jadeja"]
),
team_2_innings=InningsRecord(
team_name="Australia",
total_runs=197,
total_wickets=10,
overs_batted=64.0,
batting_lineup=["David Warner", "Steve Smith"],
bowling_attack=["Pat Cummins", "Cameron Green"]
),
result="India won by 129 runs",
match_winner="India",
player_performances=[
PlayerStatistic(player_name="Rohit Sharma", role="batsman", runs_scored=94, balls_faced=176, fours=11, sixes=2),
PlayerStatistic(player_name="Virat Kohli", role="batsman", runs_scored=78, balls_faced=153, fours=9, sixes=0),
PlayerStatistic(player_name="Ravindra Jadeja", role="bowler", wickets_taken=4, balls_bowled=156, runs_conceded=37),
PlayerStatistic(player_name="Steve Smith", role="batsman", runs_scored=42, balls_faced=98, fours=4, sixes=0),
PlayerStatistic(player_name="Pat Cummins", role="bowler", wickets_taken=2, balls_bowled=120, runs_conceded=51),
],
toss_winner="Australia",
toss_decision="bowl",
weather="Clear, warm conditions",
pitch_condition="Turning pitch, favorable to spinners"
),
]
def save_cricket_scorecards(filename: str = "data/cricket_scorecards.json"):
"""Save cricket match records to JSON file"""
records = [asdict(match) for match in sample_matches]
with open(filename, 'w') as f:
json.dump(records, f, indent=2)
print(f"✓ Saved {len(records)} cricket matches to {filename}")
def load_cricket_scorecards(filename: str = "data/cricket_scorecards.json") -> List[CricketMatchRecord]:
"""Load cricket match records from JSON file"""
with open(filename, 'r') as f:
records = json.load(f)
print(f"✓ Loaded {len(records)} cricket matches from {filename}")
return records
if __name__ == "__main__":
# Initialize sample data
save_cricket_scorecards()
print("\n--- Sample Match Document ---")
print(sample_matches[0].to_document())
Step 2 — Core Logic
Implement the retrieval layer by building a vector index of your cricket match documents and creating a semantic search function that ranks matches by relevance to a given user query. Load your match records, generate embeddings for each document's text representation using a pre-trained sentence transformer model, and store those embeddings in a FAISS index for efficient similarity search.
The retrieval function works by taking a user query — for example, 'How has Rohit Sharma performed at the MCG against fast bowling?' — embedding it using the same model used to index the documents, and then retrieving the top-k most similar match documents from the FAISS index using cosine similarity.
This approach enables semantic matching that goes beyond simple keyword overlap. The system understands, for instance, that a query referencing 'bowling speed above 140 km/h' is semantically similar to documents describing 'fast bowling' or 'pace attacks,' even when exact keywords do not appear in both.
The matches returned by this retrieval step become the context passed to the language model in the next stage, grounding its response in actual historical data rather than relying on general knowledge.
# retrievers/cricket_retriever.py
# Semantic retrieval engine for cricket match database
import json
import numpy as np
from typing import List, Tuple, Dict
from sentence_transformers import SentenceTransformer
import faiss
from dataclasses import dataclass
@dataclass
class RetrievedMatch:
"""Match record with relevance score"""
match_id: str
venue: str
teams: str
document_text: str
relevance_score: float
class CricketRetriever:
"""Semantic search engine for cricket match knowledge base"""
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
"""Initialize retriever with embedding model"""
# Load pre-trained embedding model (lightweight, ~80MB)
self.embedding_model = SentenceTransformer(model_name)
self.embedding_dimension = self.embedding_model.get_sentence_embedding_dimension()
# Initialize FAISS index (L2 distance)
self.index = faiss.IndexFlatL2(self.embedding_dimension)
# Storage for match metadata
self.match_metadata = []
self.match_documents = []
self.embeddings = None
print(f"✓ CricketRetriever initialized with {model_name}")
print(f"✓ Embedding dimension: {self.embedding_dimension}")
def ingest_cricket_records(self, filepath: str = "data/cricket_scorecards.json"):
"""Load cricket match records and create embeddings"""
with open(filepath, 'r') as f:
records = json.load(f)
print(f"\n📚 Ingesting {len(records)} cricket matches...")
# Convert records to documents and generate embeddings
documents = []
for record in records:
# Create searchable document from match record
doc_text = self._create_document_text(record)
documents.append(doc_text)
# Store metadata
self.match_metadata.append({
'match_id': record['match_id'],
'venue': record['venue'],
'date': record['match_date'],
'teams': f"{record['team_1']} vs {record['team_2']}",
'format': record['format'],
'winner': record['match_winner'],
'country': record['country']
})
self.match_documents = documents
# Generate embeddings for all documents
print(f"🔄 Generating embeddings for {len(documents)} matches...")
self.embeddings = self.embedding_model.encode(
documents,
normalize_embeddings=True,
show_progress_bar=False
)
# Add embeddings to FAISS index
self.index.add(self.embeddings.astype(np.float32))
print(f"✓ Created FAISS index with {len(self.embeddings)} vectors")
print(f"✓ Index size in memory: {self.index.ntotal} match records")
def retrieve_matches(
self,
query: str,
top_k: int = 3,
similarity_threshold: float = 0.0
) -> List[RetrievedMatch]:
"""Retrieve most relevant cricket matches for a query"""
# Embed the query
query_embedding = self.embedding_model.encode(
[query],
normalize_embeddings=True
)
# Search FAISS index (returns distances, not scores)
distances, indices = self.index.search(
query_embedding.astype(np.float32),
min(top_k, len(self.match_documents))
)
# Convert distances to similarity scores
# For L2 distance: similarity ≈ 1 / (1 + distance)
results = []
for distance, idx in zip(distances[0], indices[0]):
if idx == -1: # Invalid result from FAISS
continue
# Convert L2 distance to similarity score (0-1)
similarity_score = 1 / (1 + distance)
if similarity_score >= similarity_threshold:
results.append(RetrievedMatch(
match_id=self.match_metadata[idx]['match_id'],
venue=self.match_metadata[idx]['venue'],
teams=self.match_metadata[idx]['teams'],
document_text=self.match_documents[idx],
relevance_score=float(similarity_score)
))
return results
def _create_document_text(self, record: Dict) -> str:
"""Convert match record to searchable document"""
doc = f"""
Match: {record['team_1']} vs {record['team_2']}
Date: {record['match_date']}
Venue: {record['venue']}, {record['country']}
Format: {record['format']}
{record['team_1']} Innings: {record['team_1_innings']['total_runs']}/{record['team_1_innings']['total_wickets']} ({record['team_1_innings']['overs_batted']} overs)
Batsmen: {', '.join(record['team_1_innings']['batting_lineup'])}
Bowlers: {', '.join(record['team_1_innings']['bowling_attack'])}
{record['team_2']} Innings: {record['team_2_innings']['total_runs']}/{record['team_2_innings']['total_wickets']} ({record['team_2_innings']['overs_batted']} overs)
Batsmen: {', '.join(record['team_2_innings']['batting_lineup'])}
Bowlers: {', '.join(record['team_2_innings']['bowling_attack'])}
Result: {record['match_winner']} won. Toss: {record['toss_winner']} won, chose to {record['toss_decision']}
Weather: {record['weather']}
Pitch: {record['pitch_condition']}
"""
return " ".join(doc.split()) # Normalize whitespace
def get_retrieval_stats(self) -> Dict:
"""Return retriever statistics"""
return {
'total_matches_indexed': len(self.match_documents),
'embedding_model': self.embedding_model.get_sentence_embedding_dimension(),
'index_size': self.index.ntotal,
'vector_dimension': self.embedding_dimension
}
if __name__ == "__main__":
# Test retriever
retriever = CricketRetriever()
retriever.ingest_cricket_records()
# Example queries
test_queries = [
"How did Rohit Sharma perform at the MCG?",
"What was the result when India played Australia on a turning pitch?",
"Bowling performance by Jasprit Bumrah in Test cricket"
]
print("\n🏏 Testing Cricket Retriever\n" + "="*50)
for query in test_queries:
print(f"\n📋 Query: {query}")
print("-" * 50)
matches = retriever.retrieve_matches(query, top_k=2)
for i, match in enumerate(matches, 1):
print(f"\nResult {i} (Score: {match.relevance_score:.3f})")
print(f"Match: {match.teams}")
print(f"Venue: {match.venue}")
print(f"Document excerpt: {match.document_text[:150]}...")
Step 3 — Integration & Enhancement
Complete the RAG pipeline by implementing a generation layer that combines retrieved cricket match context with an LLM to produce informed, grounded responses. Design a prompt template that explicitly incorporates the retrieved match documents as context, ensuring the LLM bases its analysis on historical data rather than generating generic answers.
The integration step chains the retriever and generator into a single flow. When a user asks a cricket question, the system first retrieves relevant matches from the vector index, then constructs a prompt that includes both the user's question and the retrieved context formatted as a numbered list of relevant historical matches with key statistics, and finally passes this augmented prompt to an LLM.
The LLM uses the provided context to synthesize a detailed response that references specific players, venues, performance metrics, and outcomes. This approach directly addresses the hallucination problem common in standard LLMs — by constraining the model to answer based only on retrieved factual data, responses become verifiable and the risk of fabricated statistics is substantially reduced.
# generators/cricket_analyzer.py
# LLM-based analysis engine using retrieved cricket context
import os
from typing import List, Dict
from dotenv import load_dotenv
from openai import OpenAI
from retrievers.cricket_retriever import CricketRetriever, RetrievedMatch
load_dotenv()
class CricketAnalyzer:
"""RAG generator: Synthesizes retrieved cricket context into analytical responses"""
def __init__(self, api_key: str = None):
"""Initialize analyzer with OpenAI client"""
self.api_key = api_key or os.getenv('OPENAI_API_KEY')
if not self.api_key:
raise ValueError("OPENAI_API_KEY not found in environment")
self.client = OpenAI(api_key=self.api_key)
self.model = "gpt-3.5-turbo"
self.temperature = 0.7 # Moderate creativity for analysis
print(f"✓ CricketAnalyzer initialized with {self.model}")
def analyze_cricket_query(
self,
query: str,
retrieved_matches: List[RetrievedMatch],
include_stats: bool = True
) -> Dict:
"""Generate cricket analysis based on retrieved context"""
# Format retrieved matches into context string
context = self._format_context(retrieved_matches, include_stats)
# Build the augmented prompt
system_prompt = """
You are an expert cricket analyst with deep knowledge of match statistics, player performances, and tactical patterns.
Your task is to answer cricket questions based ONLY on the provided historical match data.
Instructions:
1. Ground all responses in the provided context—cite specific matches, dates, venues, and statistics
2. If the context doesn't contain information to answer the question, explicitly state that
3. Provide quantitative insights (averages, economy rates, strike rates) when relevant
4. Explain tactical patterns or performance trends observed across matches
5. Keep responses concise but detailed (2-4 paragraphs)
"""
user_message = f"""
HISTORICAL CONTEXT (Retrieved Matches):
{context}
USER QUESTION:
{query}
Please analyze this question based on the historical match data provided above.
"""
# Call LLM with augmented context
response = self.client.chat.completions.create(
model=self.model,
temperature=self.temperature,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)
analysis = response.choices[0].message.content
return {
'query': query,
'analysis': analysis,
'context_matches_used': len(retrieved_matches),
'model': self.model,
'retrieved_match_ids': [m.match_id for m in retrieved_matches]
}
def _format_context(self, matches: List[RetrievedMatch], include_stats: bool) -> str:
"""Format retrieved matches into context for LLM"""
if not matches:
return "No relevant historical matches found."
context_lines = []
for i, match in enumerate(matches, 1):
relevance = match.relevance_score
match_info = f"""
Match {i} (Relevance: {relevance:.1%})
ID: {match.match_id}
Teams: {match.teams}
Venue: {match.venue}
Details:
{match.document_text}
"""
context_lines.append(" ".join(match_info.split()))
return "\n" + "\n".join(context_lines)
def generate_match_summary(
self,
match_id: str,
match_text: str
) -> str:
"""Generate a narrative summary of a cricket match"""
prompt = f"""
Based on the following cricket match data, write a 2-3 sentence narrative summary highlighting key moments and performances:
{match_text}
"""
response = self.client.chat.completions.create(
model=self.model,
temperature=0.6,
messages=[
{"role": "user", "content": prompt}
]
)
return response.choices[0].message.content
class RAGPipeline:
"""Complete RAG system: Retrieval + Augmented + Generation"""
def __init__(self):
"""Initialize both retriever and analyzer"""
self.retriever = CricketRetriever()
self.analyzer = CricketAnalyzer()
self.retriever.ingest_cricket_records()
def query(self, question: str, top_k: int = 3) -> Dict:
"""Execute full RAG pipeline: retrieve → augment → generate"""
print(f"\n🏏 Processing Query: {question}")
print("="*60)
# Step 1: Retrieve relevant matches
print(f"\n📚 Step 1: Retrieving top {top_k} relevant matches...")
retrieved = self.retriever.retrieve_matches(question, top_k=top_k)
if not retrieved:
return {
'question': question,
'analysis': 'No relevant cricket matches found in knowledge base.',
'retrieved_matches': []
}
print(f"✓ Retrieved {len(retrieved)} matches")
for i, match in enumerate(retrieved, 1):
print(f" {i}. {match.match_id} ({match.relevance_score:.1%})")
# Step 2: Generate analysis using retrieved context
print(f"\n🤖 Step 2: Generating analysis with LLM...")
result = self.analyzer.analyze_cricket_query(question, retrieved)
print(f"✓ Analysis generated using {self.analyzer.model}")
return result
if __name__ == "__main__":
# Initialize RAG pipeline
rag_pipeline = RAGPipeline()
# Test questions
test_questions = [
"How has Rohit Sharma performed when playing against Australia at different venues?",
"What tactical patterns emerge from India's recent Test matches?",
"Analyze Jasprit Bumrah's bowling effectiveness across different match conditions"
]
print("\n🏏 CRICKET ANALYSIS RAG SYSTEM\n" + "="*60)
for question in test_questions:
result = rag_pipeline.query(question, top_k=2)
print(f"\n📊 ANALYSIS RESULT:")
print("-" * 60)
print(f"Question: {result['query']}")
print(f"\nAnalysis:\n{result['analysis']}")
print(f"\n✓ Used {result['context_matches_used']} retrieved matches")
print(f"✓ Matches: {', '.join(result['retrieved_match_ids'])}")
print("="*60)
Step 4 — Testing & Verification
Run the complete RAG pipeline end-to-end to verify that retrieval, augmentation, and generation work together correctly. Execute test queries against your cricket knowledge base and validate that the system retrieves relevant historical matches and generates coherent, factually grounded analysis.
During validation, confirm that retrieved matches carry high relevance scores and that the LLM's output explicitly references the retrieved data, including specific statistics and match details. Also monitor pipeline latency — measuring how long retrieval and generation each take — to ensure the system remains responsive under realistic query loads.
Finally, verify that the system handles edge cases gracefully, such as queries that return no relevant matches or references to ambiguous player and venue names. Robust handling of these scenarios is essential before considering the pipeline production-ready.
#!/bin/bash
# test_rag_pipeline.sh - End-to-end testing script
set -e # Exit on error
echo "🏏 CRICKET RAG SYSTEM - TESTING SUITE"
echo "====================================="
echo ""
# Activate virtual environment
source venv/bin/activate
# Test 1: Data initialization
echo "Test 1: Loading cricket scorecard data..."
python3 << 'EOF'
from data.cricket_scorecards import save_cricket_scorecards, load_cricket_scorecards
save_cricket_scorecards()
records = load_cricket_scorecards()
print(f"✓ Data initialized: {len(records)} matches loaded")
for record in records:
print(f" - Match ID: {record['match_id']}")
print(f" Teams: {record['team_1']} vs {record['team_2']}")
print(f" Venue: {record['venue']}")
print(f" Winner: {record['match_winner']}")
EOF
echo ""
echo "Test 2: Testing retriever on sample queries..."
python3 << 'EOF'
from retrievers.cricket_retriever import CricketRetriever
import time
retriever = CricketRetriever()
retriever.ingest_cricket_records()
test_queries = [
"Rohit Sharma at the MCG",
"India vs Australia Test match",
"Fast bowling performance by Jasprit Bumrah"
]
for query in test_queries:
print(f"\nQuery: '{query}'")
start = time.time()
matches = retriever.retrieve_matches(query, top_k=2)
elapsed = time.time() - start
print(f"✓ Retrieved {len(matches)} matches in {elapsed*1000:.1f}ms")
for match in matches:
print(f" - {match.match_id} (score: {match.relevance_score:.3f})")
EOF
echo ""
echo "Test 3: Full RAG pipeline test..."
python3 << 'EOF'
from generators.cricket_analyzer import RAGPipeline
print("Initializing RAG pipeline...")
rag = RAGPipeline()
test_question = "How has Rohit Sharma performed at the MCG?"
print(f"\nTest Question: {test_question}")
print("-" * 50)
result = rag.query(test_question, top_k=2)
print(f"\n✓ RAG Pipeline Execution Successful")
print(f"✓ Analysis generated with {result['context_matches_used']} context matches")
print(f"\nGenerated Analysis:")
print(result['analysis'])
EOF
echo ""
echo "Test 4: Retrieval metrics..."
python3 << 'EOF'
from retrievers.cricket_retriever import CricketRetriever
retriever = CricketRetriever()
retriever.ingest_cricket_records()
stats = retriever.get_retrieval_stats()
print("\nRetriever Statistics:")
for key, value in stats.items():
print(f" {key}: {value}")
EOF
echo ""
echo "====================================="
echo "✓ ALL TESTS PASSED"
echo "====================================="
echo ""
echo "Expected Output Summary:"
echo " ✓ 2 cricket matches loaded from data/cricket_scorecards.json"
echo " ✓ Retrieval latency < 100ms for semantic search"
echo " ✓ Retrieved matches have relevance scores 0.6-0.95"
echo " ✓ LLM generates coherent analysis with match references"
echo " ✓ No hallucinated statistics (all references to retrieved data)"
echo ""
echo "Next Steps:"
echo " 1. Expand cricket_scorecards.json with more historical matches"
echo " 2. Tune embedding model for cricket domain"
echo " 3. Add player-specific retrieval patterns"
echo " 4. Implement caching for frequently asked questions"
Warning: The most common error is mismatched embedding dimensions between your stored vectors and query embeddings. This occurs when you switch embedding models or reload a FAISS index that was built with a different model. Always ensure you use the SAME sentence-transformer model when building the index and when encoding new queries. If you change models, you MUST rebuild the entire FAISS index. Additionally, if your OPENAI_API_KEY is not set correctly in the .env file, the LLM integration will fail silently. Always verify your API key is valid by running a quick test call before running the full pipeline. If you get 'index.ntotal = 0', it means embeddings weren't added to FAISS—check that ingest_cricket_records() was called and completed without errors.
Extension Challenge: Enhance your cricket RAG system with multi-document retrieval and cross-match analysis. Instead of returning individual matches, implement a "match similarity clustering" feature that groups retrieved matches by tactical pattern (e.g., "matches where India was chasing on turning pitches"). Then, have the LLM generate comparative analysis across these clusters: "In 3 similar chasing scenarios on turning pitches, India's win rate was X%, with Virat Kohli averaging Y runs." This requires (1) post-processing retrieved matches to identify common tactical features, (2) prompting the LLM to synthesize patterns across multiple matches, and (3) generating insights about win probabilities and player performance trends. This transforms your RAG system from fact retrieval into tactical intelligence generation—a much higher-value use case in professional cricket analytics.
- Retrieval-Augmented Generation grounds LLM responses in factual, retrievable context, reducing hallucinations by constraining output to provided documents.
- Vector embeddings enable semantic search by converting textual match records into numerical representations where similar cricket scenarios cluster together geometrically.
- FAISS indexing provides millisecond-latency retrieval across millions of documents using efficient nearest-neighbor algorithms, critical for production RAG systems.
- Augmented prompts explicitly include retrieved context as formatted context blocks, ensuring the LLM synthesizes analysis directly from historical data rather than parameter space.
- Domain-specific embeddings (trained on cricket terminology and statistics) outperform general embeddings, improving retrieval precision for specialized queries like player performance patterns.
- RAG pipeline reduces API costs and hallucinations compared to fine-tuning large LLMs, making it the preferred approach for knowledge-intensive applications with rapidly updating source data.