Level Up Coding

Coding tutorials and news. The developer homepage gitconnected.com && skilled.dev && levelup.dev

Follow publication

Building a RAG-Powered Hackathon Winning App

Muhammad Luthfi Arifin
Level Up Coding
Published in
13 min readNov 21, 2024

Generated Image by Microsoft Bing
Generated Image by Microsoft Bing

TiDB Future App Hackathon 🏆

TiDB Future App Hackathon

Overview of the competition

Why we chose to focus on personalized travel itineraries

🔍 Understanding RAG (Retrieval-Augmented Generation)

Basic RAG Flow
Basic RAG Flow

How does RAG work?

Why we chose RAG over a traditional LLM pipeline

❓ Why Do We Need RAG?

Challenges of standalone LLMs:

How RAG addresses these issues with external data retrieval

🏗️ The Architecture of Hangout AI

Hangout AI RAG Architecture
Hangout AI RAG Architecture

System overview: Components and data flow

Leveraging vector databases for efficient retrieval

How the LLM and retrieval components interact

🛠️ Building the RAG system

1. Vector Database Setup

from sqlalchemy import URL, create_engine
from llama_index.vector_stores.tidbvector import TiDBVectorStore
import os

# Construct the TiDB connection URL using environment variables for security
tidb_connection_url = URL(
"mysql+pymysql",
username=os.environ['TIDB_USERNAME'], # TiDB username from environment variable
password=os.environ['TIDB_PASSWORD'], # TiDB password from environment variable
host=os.environ['TIDB_HOST'], # TiDB host from environment variable
port=4000, # TiDB port
database="test", # Database name
query={"ssl_verify_cert": False, "ssl_verify_identity": True}, # SSL settings
)

# Create a SQLAlchemy engine with the TiDB connection URL
create_engine(tidb_connection_url, pool_recycle=3600)

# Initialize the TiDBVectorStore with the connection string and other parameters
tidbvec = TiDBVectorStore(
connection_string=tidb_connection_url, # Connection string for TiDB
table_name=os.getenv("VECTOR_TABLE_NAME"), # Table name from environment variable
distance_strategy="cosine", # Distance strategy for vector similarity search
vector_dimension=768, # Dimension of the vectors
drop_existing_table=False, # Whether to drop the existing table
)

2. Preprocessing Destination Data

from llama_index.core import Document
import json

# Function to preprocess data from a JSON file
def preprocess_data(path):
documents = []
# Load data from the specified JSON file
data = json.load(open(path))
for item in data:
# Construct the text content for each document
text = f"""
Address: {item['address']}
Title: {item['title']}
Country: {item['complete_address']['country']}
Categories: {', '.join(item['categories'] if item['categories'] else [])}
Description: {item.get('description', 'No description')}
Review Count: {item['review_count']}
Review Rating: {item['review_rating']}
Open Hours: {json.dumps(item['open_hours'])}
Latitude: {item['latitude']}
Longitude: {item['longtitude']}
"""

# Create metadata for each document
metadata = {
"id": item["cid"],
"title": item["title"],
"description": item["description"],
"address": item["address"],
"complete_address": item["complete_address"],
}
# Create a Document object with the text and metadata
document = Document(text=text, metadata=metadata)
# Add the document to the list of documents
documents.append(document)
return documents

3. Data Ingestion

def init():
"""
Ingests data into the TiDB vector store for use in itinerary planning.

1. Preprocesses data from a JSON file.
2. Converts the data into Document objects with metadata.
3. Creates a vector index in the TiDB vector store.

Returns:
VectorStoreIndex: The initialized vector store index.
"""

# Load and preprocess data from the JSON file
documents = preprocess_data("./data/destinations.json")

# Create a vector index from the preprocessed documents
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
insert_batch_size=1000,
show_progress=True
)
return index

4. LLM Integration with Weather and Location Filters

from llama_index.core.vector_stores import MetadataFilters, MetadataFilter

# Function to query the vector store with specific filters and parameters
def query(date, country, startTime, endTime, address, lat, lng):
# Create metadata filters based on the country
filters = MetadataFilters(
filters=[MetadataFilter(key="complete_address.country", value=[country])]
)

# Create a query engine with the specified filters and top-k similarity search
query_engine = index.as_query_engine(filters=filters, similarity_top_k=2)

# Query the vector store using the generated prompt
response = query_engine.query(create_prompt(date, startTime, endTime, address))

# Return the response, metadata, and weather data
return {
"response": response.response, # The response from the query engine
"metadata": get_data_from_cids([node.node.metadata["id"] for node in response.source_nodes]), # Metadata from the source nodes
"weathers": weather_data, # Weather data (assumed to be defined elsewhere)
}

5. Chat-Based Itinerary with History

from llama_cloud import ChatMessage
from llama_index.core.chat_engine.types import ChatMode
from llama_index.core.vector_stores import MetadataFilters, MetadataFilter

# Function to handle chat queries for travel itinerary planning
def chat_query(messages, query, day, country, startTime, endTime, address):
# Create metadata filters based on the country
filters = MetadataFilters(filters=[MetadataFilter(key="complete_address.country", value=[country])])

# Prepare chat history messages
histories = [
ChatMessage(content=msg.content, role=msg.role, additional_kwargs={}) for msg in messages.histories
]
# Insert a system message at the beginning of the chat history
histories.insert(0, ChatMessage(
content=f"You are a travel itinerary planner. Plan a visit to {address} on {day} from {startTime} to {endTime}.",
role="system"
))
# Append the user's query to the chat history
histories.append(ChatMessage(content=query, role="user"))

# Create a chat engine with the specified filters and context mode
query_engine = index.as_chat_engine(filters=filters, chat_mode=ChatMode.CONTEXT)

# Perform the chat query with the prepared chat history
response = query_engine.chat(query, chat_history=histories)

# Return the response and metadata from the source nodes
return {
"response": response.response, # The response from the chat engine
"metadata": get_data_from_cids([node.node.metadata["id"] for node in response.source_nodes]), # Metadata from the source nodes
}

💡 Attention:

⛓️ Building RAG Without Frameworks

Why I Chose LlamaIndex

⚠️ Disclaimers: Not Production-Ready Yet

Why safeguards and improvements are needed

The next steps for making the app scalable and reliable

🎉 Conclusion

Congratulations!

🌐 Explore Code

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Responses (2)

Write a response