Skip to content

Commit 67c5f49

Browse files
committed
added promptEngr/localRAGchatbot
1 parent ef802d9 commit 67c5f49

4 files changed

Lines changed: 2790 additions & 509 deletions

File tree

promptEngr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ icon: fa-edit
9999

100100
[localRAGchatbot.html](/promptEngr/localRAGchatbot.html) contains a modified tutorial on building a chatbot (html output).
101101

102-
[localRAGchatbot.py](/promptEngr/localRAGchatbot.py) contains just the Python code for the above tutorial, suitable for running in a Python environment to check for errors.
102+
[localRAGchatbot3.py](/promptEngr/localRAGchatbot3.py) contains just the Python code for the above tutorial, suitable for running in a Python environment to check for errors.
103103

104104
[Xiao2025.pdf](/promptEngr/Xiao2025.pdf) contains the input file for the above tutorial.
105105

promptEngr/localRAGchatbot.html

Lines changed: 2645 additions & 495 deletions
Large diffs are not rendered by default.

promptEngr/localRAGchatbot.qmd

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
---
22
title: "localRAGchatbot"
33
jupyter: python3
4+
format:
5+
html:
6+
embed-resources: true
7+
toc: true
8+
monofont: JetBrainsMono Nerd Font
9+
mainfont: "TeX Gyre Schola"
410
---
511

6-
You should *not* try to render this file until you have run the code in the week 10 lecture slides. You must have Ollama and DeepSeek-R1 installed on your machine, which is described in the lecture slides. Then you must be running Ollama via `ollama serve` in a separate terminal window.
12+
You should *not* try to render this file until you have run the code in the accompanying slides. You must have Ollama and DeepSeek-R1 installed on your machine, which is described in the lecture slides. Then you must be running Ollama via `ollama serve` in a separate terminal window.
713

814
We will follow a modification of the tutorial by
915
Aashi Dutt at
@@ -31,19 +37,31 @@ You must import the following packages but be careful to just run this chunk fir
3137

3238
```{python}
3339
import ollama
34-
from langchain.text_splitter import RecursiveCharacterTextSplitter
40+
from langchain_text_splitters import RecursiveCharacterTextSplitter
3541
from langchain_community.document_loaders import PyMuPDFLoader
3642
from langchain_community.embeddings import OllamaEmbeddings
43+
from langchain_ollama import OllamaEmbeddings
3744
from chromadb.config import Settings
3845
from chromadb import Client
39-
from langchain.vectorstores import Chroma
46+
from langchain_chroma import Chroma
4047
import gradio as gr
4148
import re
4249
from concurrent.futures import ThreadPoolExecutor
4350
```
4451

4552
# The document
46-
The whole point of this tutorial is create a chatbot that can answer questions about the book *Foundations of LLMs* by Xiao, et al. The following chunk loads the document. It assumes you have downloaded the document and saved it in the same directory as this file. You then split it into smaller chunks, use DeepSeek-R1 to generate embeddings, and store the embeddings in a vector store.
53+
The whole point of this tutorial is create a chatbot that can answer questions about the book *Foundations of LLMs* by Xiao and Zhu. The following chunk loads the document. It assumes you have downloaded the document and saved it in the same directory as this file. You then split it into smaller chunks, use DeepSeek-R1 to generate embeddings, and store the embeddings in a vector store.
54+
55+
# Packages
56+
You will use a number of packages to accomplish this. First is `PyMuPDFLoader`, which allows you to load a PDF document. You can read more about it at [https://pymupdf.readthedocs.io/en/latest/rag.html](https://pymupdf.readthedocs.io/en/latest/rag.html). It has many more capabilities we won't use and handles many other filetypes. We're just going to load a well-behaved PDF.
57+
58+
Second, we split the texts into chunks. The `RecursiveCharacterTextSplitter` is a very popular tool provided by `LangChain` for, as you might guess, splitting large amounts of text, such as our 231 page book, into small chunks. `LangChain` is a popular framework for building LLM applications. It *orchestrates* the various tools you use. You can read more about it at [https://python.langchain.com/docs/](https://python.langchain.com/docs/).
59+
60+
Next we generate embeddings for each chunk, using DeepSeek R1. The actual generation process is quite time-consuming on a typical laptop. You can see in the comments that it takes seven minutes on my machine and that you can tell when it's done by watching the window running `ollama serve`.
61+
62+
What are we going to do with these embeddings, which constitute a vector representation of the text? We will use them to create a vector database, which we will use to answer questions about the book. For this, we'll use ChromaDB, a popular vector database. You can read more about it at [https://docs.trychroma.com/](https://docs.trychroma.com/).
63+
64+
# The setup code
4765

4866
```{python}
4967
#. Step 1: Load the document using PyMuPDFLoader
@@ -55,7 +73,7 @@ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=20
5573
chunks = text_splitter.split_documents(documents)
5674
5775
#. Step 3: Initialize Ollama embeddings
58-
embedding_function = OllamaEmbeddings(model="deepseek-r1")
76+
embedding_function = OllamaEmbeddings(model="nomic-embed-text")
5977
6078
6179
#. Step 4: Parallelize embedding generation
@@ -72,7 +90,7 @@ client = Client(Settings())
7290
#. client.delete_collection(
7391
#. name="foundations_of_llms"
7492
#. ) # Delete any existing collection if needed
75-
collection = client.create_collection(name="foundations_of_llms")
93+
collection = client.get_or_create_collection(name="foundations_of_llms")
7694
7795
#. Step 6: Add documents and embeddings to Chroma
7896
for idx, chunk in enumerate(chunks):
@@ -86,6 +104,7 @@ for idx, chunk in enumerate(chunks):
86104
print("Embeddings stored successfully!")
87105
```
88106

107+
# Retrieve the context to answer the question
89108
Next you have to initialize a retriever that will retrieve the context from the vector store.
90109

91110
```{python}
@@ -100,23 +119,24 @@ retriever = Chroma(
100119
101120
102121
def retrieve_context(question):
103-
results = retriever.get_relevant_documents(question)
122+
results = retriever.invoke(question)
104123
context = "\n\n".join([doc.page_content for doc in results])
105124
return context
106125
107126
```
108127

128+
# Create the prompt
109129
Next, create the prompt, send it to DeepSeek-R1 using Ollama, and obtain a response. If this doesn't work, it may mean that you are not running Ollama via `ollama serve` in a separate terminal window.
110130

111131
```{python}
112-
def query_deepseek(question, context):
132+
def query_qwen(question, context):
113133
114134
# Format the input as a structured prompt
115135
formatted_promt = f"Question: {question}\n\nContext: {context}"
116136
117137
# Send the prompt to DeepSeek-R1 using Ollama
118138
response = ollama.chat(
119-
model="deepseek-r1", messages=[{"role": "user", "content": formatted_promt}]
139+
model="qwen3.5:9b", messages=[{"role": "user", "content": formatted_promt}]
120140
)
121141
122142
# Extract and clean the response
@@ -127,6 +147,7 @@ def query_deepseek(question, context):
127147
return final_answer
128148
```
129149

150+
# Define the RAG pipeline
130151
Next, define the RAG (retrieval augmented generation) pipeline.
131152

132153
```{python}
@@ -136,7 +157,7 @@ def rag_pipeline(question):
136157
context = retrieve_context(question)
137158
138159
# Generate an answer using DeepSeek-R1
139-
answer = query_deepseek(question, context)
160+
answer = query_qwen(question, context)
140161
return answer
141162
```
142163

@@ -148,7 +169,8 @@ def ask_question(question):
148169
return rag_pipeline(question)
149170
```
150171

151-
Finally, create a Gradio interface for the chatbot. This will appear as a web page in your browser. The user can enter as many prompts as they like.
172+
# Gradio interface
173+
Finally, create a Gradio interface for the chatbot. This will appear as a web page in your browser. The user can enter as many prompts as they like. Gradio is a popular library for creating web interfaces for machine learning models. You can read more about it at [https://gradio.app/](https://gradio.app/).
152174

153175
```{python}
154176
#. Create a Gradio interface
@@ -157,11 +179,11 @@ interface = gr.Interface(
157179
inputs="text",
158180
outputs="text",
159181
title="RAG Chatbot: Foundations of LLMs",
160-
description="Ask any question about the Foundations of LLMs book. Powered by DeepSeek-R1.",
182+
description="Ask any question about the Foundations of LLMs book. Powered by Qwen 3.5.",
161183
)
162184
```
163185

164-
Note that I have commented out the following line that actually launches the Gradio interface. This is only for rendering purposes. When you want to actually run the code, you should uncomment it. The interface will then appear in your browser if you point to `http://localhost:7860`.
186+
Note that I have commented out the following line that actually launches the Gradio interface. This is only for rendering purposes. It should be commented out when you render the document. When you want to actually run the code, you should uncomment it. The interface will then appear in your browser if you point to `http://localhost:7860`.
165187

166188
```{python}
167189
#. Launch the Gradio app

promptEngr/localRAGchatbot3.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import ollama
2+
from langchain_text_splitters import RecursiveCharacterTextSplitter
3+
from langchain_community.document_loaders import PyMuPDFLoader
4+
from langchain_ollama import OllamaEmbeddings
5+
from chromadb.config import Settings
6+
from chromadb import Client
7+
from langchain_chroma import Chroma
8+
import gradio as gr
9+
import re
10+
from concurrent.futures import ThreadPoolExecutor
11+
12+
13+
# Step 1: Load the document using PyMuPDFLoader
14+
loader = PyMuPDFLoader("Xiao2025.pdf")
15+
documents = loader.load()
16+
17+
# Step 2: Split text into smaller chunks
18+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
19+
chunks = text_splitter.split_documents(documents)
20+
21+
# Step 3: Initialize Ollama embeddings
22+
embedding_function = OllamaEmbeddings(model="nomic-embed-text")
23+
24+
25+
# Step 4: Parallelize embedding generation
26+
def generate_embedding(chunk):
27+
return embedding_function.embed_query(chunk.page_content)
28+
29+
30+
with ThreadPoolExecutor() as executor:
31+
embeddings = list(executor.map(generate_embedding, chunks))
32+
33+
# Step 5: Recreate the collection
34+
client = Client(Settings())
35+
# . client.delete_collection(
36+
# . name="foundations_of_llms"
37+
# . ) # Delete any existing collection if needed
38+
collection = client.create_collection(name="foundations_of_llms")
39+
40+
# Step 6: Add documents and embeddings to Chroma
41+
for idx, chunk in enumerate(chunks):
42+
collection.add(
43+
documents=[chunk.page_content],
44+
metadatas=[{"id": idx}],
45+
embeddings=[embeddings[idx]],
46+
ids=[str(idx)], # Ensure IDs are strings
47+
)
48+
49+
print("Embeddings stored successfully!")
50+
51+
# initialize retriever using chroma collection
52+
53+
retriever = Chroma(
54+
collection_name="foundations_of_llms",
55+
client=client,
56+
embedding_function=embedding_function,
57+
).as_retriever()
58+
59+
60+
def retrieve_context(question):
61+
results = retriever.invoke(question)
62+
context = "\n\n".join([doc.page_content for doc in results])
63+
return context
64+
65+
66+
def query_qwen(question, context):
67+
68+
# Format the input as a structured prompt
69+
formatted_promt = f"Question: {question}\n\nContext: {context}"
70+
71+
# Send the prompt to qwen using Ollama
72+
response = ollama.chat(
73+
model="qwen3.5:9b", messages=[{"role": "user", "content": formatted_promt}]
74+
)
75+
76+
# Extract and clean the response
77+
response_content = response["message"]["content"]
78+
final_answer = re.sub(
79+
r"<think>.*?</think>", "", response_content, flags=re.DOTALL
80+
).strip()
81+
return final_answer
82+
83+
84+
def rag_pipeline(question):
85+
86+
# Retrieve context from the vector store
87+
context = retrieve_context(question)
88+
89+
# Generate an answer using Qwen 3.5:9b
90+
answer = query_qwen(question, context)
91+
return answer
92+
93+
94+
def ask_question(question):
95+
# Run the RAG pipeline
96+
return rag_pipeline(question)
97+
98+
99+
# Create a Gradio interface
100+
interface = gr.Interface(
101+
fn=ask_question,
102+
inputs="text",
103+
outputs="text",
104+
title="RAG Chatbot: Foundations of LLMs",
105+
description="Ask any question about the Foundations of LLMs book. Powered by Qwen 3.5:9b.",
106+
)
107+
108+
# Launch the Gradio app
109+
interface.launch(debug=True)

0 commit comments

Comments
 (0)