Skip to content

Commit 7ed0ac7

Browse files
committed
Improve llm architecture + improve ux in client
1 parent 786a0a4 commit 7ed0ac7

File tree

8 files changed

+1394
-154
lines changed

8 files changed

+1394
-154
lines changed

client/package-lock.json

Lines changed: 1236 additions & 77 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"lucide-react": "^0.344.0",
1818
"react": "^18.3.1",
1919
"react-dom": "^18.3.1",
20+
"react-markdown": "^10.1.0",
2021
"react-oidc-context": "^3.3.0",
2122
"react-router-dom": "^6.22.3"
2223
},

client/src/components/MessageItem.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { formatTimestamp } from "../utils/helpers";
33
import { User, Bot } from "lucide-react";
4+
import Markdown from "react-markdown";
45

56
interface MessageItemProps {
67
message: Message;
@@ -34,12 +35,10 @@ const MessageItem: React.FC<MessageItemProps> = ({ message }) => {
3435
{formatTimestamp(message.timestamp)}
3536
</span>
3637
</div>
37-
<div className="prose dark:prose-invert prose-sm sm:prose-base max-w-none">
38-
{message.content.split("\n").map((paragraph, i) => (
39-
<p key={i} className="mb-2 text-gray-800 dark:text-gray-200">
40-
{paragraph}
41-
</p>
42-
))}
38+
<div className="prose dark:prose-invert prose-sm sm:prose-base max-w-none text-gray-800 dark:text-gray-200">
39+
<Markdown>
40+
{message.content}
41+
</Markdown>
4342
</div>
4443
</div>
4544
</div>

client/src/components/UploadRecipe.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useAuth } from "../hooks/useAuth";
44

55
const UploadRecipe: React.FC = () => {
66
const [status, setStatus] = useState<
7-
"idle" | "uploading" | "success" | "error"
7+
"idle" | "uploading" | "success" | "already_uploaded" | "error"
88
>("idle");
99

1010
const { user } = useAuth();
@@ -27,7 +27,15 @@ const UploadRecipe: React.FC = () => {
2727
});
2828

2929
if (!response.ok) throw new Error("Upload failed");
30-
setStatus("success");
30+
31+
const data = await response.json();
32+
const message = data.message || "";
33+
34+
if (message.includes("already uploaded")) {
35+
setStatus("already_uploaded");
36+
} else {
37+
setStatus("success");
38+
}
3139

3240
setTimeout(() => setStatus("idle"), 3000);
3341
} catch (err) {
@@ -62,6 +70,13 @@ const UploadRecipe: React.FC = () => {
6270
</div>
6371
)}
6472

73+
{status === "already_uploaded" && (
74+
<div className="flex items-center justify-center gap-2 text-sm text-yellow-600">
75+
<CheckCircle className="w-5 h-5" />
76+
File Already Uploaded!
77+
</div>
78+
)}
79+
6580
{status === "error" && (
6681
<div className="flex items-center justify-center gap-2 text-sm text-red-600">
6782
<XCircle className="w-5 h-5" />

genai/routes/routes.py

Lines changed: 16 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
from fastapi.responses import JSONResponse
1010
import os
1111

12-
# from config import Config
1312
from logger import logger
1413
from time import perf_counter
1514

16-
from vector_database.qdrant_vdb import QdrantVDB
17-
from rag.ingestion_pipeline import IngestionPipeline
18-
from rag.llm.chat_model import ChatModel
19-
# from rag.llm.cloud_chat_model import CloudLLM
15+
from service.llm_service import generate_response
16+
17+
from service.qdrant_service import (
18+
file_already_uploaded,
19+
collection_already_exists,
20+
ingest_file
21+
)
22+
2023
from service.rag_service import (
2124
retrieve_similar_docs,
2225
prepare_prompt,
@@ -37,43 +40,6 @@
3740

3841
router = APIRouter()
3942

40-
# Set vector database
41-
qdrant = QdrantVDB()
42-
43-
# Set chat model for local llm models
44-
# Make calls to local models in openwebui hosted by the university
45-
llm = ChatModel(model_name="llama3.3:latest")
46-
47-
# Alternatively, we can switch to a chat model based on cloud models as well
48-
# If you want to use other cloud models, please adjust model_name,
49-
# model_provider, and api key
50-
# accordingly
51-
52-
# Examples:
53-
# llm_cloud_anthropic = CloudLLM(
54-
# model_name="claude-3-sonnet-20240229",
55-
# model_provider="anthropic",
56-
# api_key=Config.api_key_anthropic,
57-
# )
58-
# llm_cloud_openai = CloudLLM(
59-
# model_name="gpt-4-1106-preview",
60-
# model_provider="openai",
61-
# api_key=Config.api_key_openai,
62-
# )
63-
#
64-
# llm_cloud_mistral = CloudLLM(
65-
# model_name="mistral-medium",
66-
# model_provider="mistral",
67-
# api_key=Config.api_key_mistral,
68-
# )
69-
70-
# If no parameters are provided, the default cloud model will be openai.
71-
# If a cloud model is wanted, please remove the comment
72-
# for package import "CloudLLM"
73-
74-
# Example:
75-
# llm = CloudLLM() # same as llm_cloud_openai
76-
7743

7844
@router.post("/upload")
7945
async def upload_file(
@@ -102,26 +68,15 @@ async def upload_file(
10268
buffer.write(await file.read())
10369

10470
collection_name = f"recipes_{current_user.user_id}"
105-
if (
106-
qdrant.client.collection_exists(collection_name)
107-
and qdrant.collection_contains_file(
108-
qdrant.client,
109-
collection_name,
110-
filename
111-
)
112-
):
71+
if file_already_uploaded(collection_name, filename):
11372
logger.info(
11473
"File already exists in qdrant for user %s",
11574
current_user.username
11675
)
11776
return {"message": f"File '{filename}' already uploaded."}
11877

119-
vector_store = qdrant.create_and_get_vector_storage(collection_name)
120-
ingestion_pipeline = IngestionPipeline(vector_store=vector_store)
121-
ingestion_pipeline.ingest(file_path, filename)
122-
78+
ingest_file(collection_name, file_path, filename)
12379
file_upload_successfully_counter.inc()
124-
12580
return {"message": "File processed successfully."}
12681

12782
except Exception as e:
@@ -163,35 +118,32 @@ async def generate(request: Request):
163118

164119
try:
165120
retrieved_docs = ""
166-
if qdrant.client.collection_exists(collection_name):
167-
vector_store = qdrant.create_and_get_vector_storage(
168-
collection_name
169-
)
121+
if collection_already_exists(collection_name):
170122
logger.info(
171-
"Vector store is created for the collection %s for user_id %s",
123+
"Collection %s already exists for user_id %s",
172124
collection_name,
173125
user_id
174126
)
175-
retrieved_docs = retrieve_similar_docs(vector_store, query)
127+
128+
retrieved_docs = retrieve_similar_docs(collection_name, query)
176129
logger.info("Similar docs retrieved from the vector store")
177130

178131
messages = process_raw_messages(messages_raw)
179132
logger.info("Raw messages are processed for prompt preparation")
180133

181134
prompt = prepare_prompt(
182-
llm.get_system_prompt(),
183135
query,
184136
retrieved_docs,
185137
messages
186138
)
187139
logger.info("Prompt is prepared")
188140

189-
response = llm.invoke(prompt)
141+
response = generate_response(prompt)
190142
logger.info("Response is generated")
191143

192144
generation_successfully_counter.inc()
193145

194-
return JSONResponse(content={"response": response.content})
146+
return JSONResponse(content={"response": response})
195147

196148
except Exception as e:
197149
logger.error("Generation is failed. Error: %s", str(e), exc_info=True)

genai/service/llm_service.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# from config import Config
2+
# from rag.llm.cloud_chat_model import CloudLLM
3+
from rag.llm.chat_model import ChatModel
4+
5+
from langchain_core.prompt_values import PromptValue
6+
7+
8+
# Set chat model for local llm models
9+
# Make calls to local models in openwebui hosted by the university
10+
llm = ChatModel(model_name="llama3.3:latest")
11+
12+
# Alternatively, we can switch to a chat model based on cloud models as well
13+
# If you want to use other cloud models, please adjust model_name,
14+
# model_provider, and api key
15+
# accordingly
16+
17+
# Examples:
18+
# llm_cloud_anthropic = CloudLLM(
19+
# model_name="claude-3-sonnet-20240229",
20+
# model_provider="anthropic",
21+
# api_key=Config.api_key_anthropic,
22+
# )
23+
# llm_cloud_openai = CloudLLM(
24+
# model_name="gpt-4-1106-preview",
25+
# model_provider="openai",
26+
# api_key=Config.api_key_openai,
27+
# )
28+
#
29+
# llm_cloud_mistral = CloudLLM(
30+
# model_name="mistral-medium",
31+
# model_provider="mistral",
32+
# api_key=Config.api_key_mistral,
33+
# )
34+
35+
# If no parameters are provided, the default cloud model will be openai.
36+
# If a cloud model is wanted, please remove the comment
37+
# for package import "CloudLLM"
38+
39+
# Example:
40+
# llm = CloudLLM() # same as llm_cloud_openai
41+
42+
43+
def get_system_prompt() -> str:
44+
"""
45+
Returns the system prompt for the LLM.
46+
This function provides the initial context and instructions for the LLM.
47+
"""
48+
return llm.get_system_prompt()
49+
50+
51+
def generate_response(prompt: PromptValue) -> str:
52+
response = llm.invoke(prompt)
53+
return response.content

genai/service/qdrant_service.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from rag.ingestion_pipeline import IngestionPipeline
2+
from vector_database.qdrant_vdb import QdrantVDB
3+
from logger import logger
4+
5+
6+
# Set vector database
7+
qdrant = QdrantVDB()
8+
9+
10+
def file_already_uploaded(collection_name: str, filename: str) -> bool:
11+
"""
12+
Checks if a file has already been uploaded to the vector database.
13+
This function is used to avoid duplicate uploads of the same file.
14+
"""
15+
if (
16+
qdrant.client.collection_exists(collection_name)
17+
and qdrant.collection_contains_file(
18+
qdrant.client,
19+
collection_name,
20+
filename
21+
)
22+
):
23+
return True
24+
return False
25+
26+
27+
def get_vector_store(collection_name: str):
28+
"""
29+
Returns the vector store for the specified collection name.
30+
This function is used to retrieve the vector store instance for a specific
31+
collection in the vector database.
32+
"""
33+
return qdrant.create_and_get_vector_storage(collection_name)
34+
35+
36+
def collection_already_exists(collection_name: str) -> bool:
37+
"""
38+
Checks if a collection already exists in the vector database.
39+
This function is used to avoid creating duplicate collections.
40+
"""
41+
if qdrant.client.collection_exists(collection_name):
42+
logger.info(
43+
"Collection already exists in qdrant: %s",
44+
collection_name,
45+
)
46+
return True
47+
return False
48+
49+
50+
def ingest_file(collection_name: str, file_path: str, filename: str):
51+
"""
52+
Ingests a file into the vector database.
53+
This function is used to process and store the file content in the vector
54+
database for later retrieval.
55+
"""
56+
pipeline = IngestionPipeline(
57+
vector_store=get_vector_store(collection_name)
58+
)
59+
pipeline.ingest(file_path, filename)

genai/service/rag_service.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
from typing import List, Dict
22

3-
from langchain_qdrant import QdrantVectorStore
43
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
54
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
65

6+
from service.qdrant_service import get_vector_store
7+
from service.llm_service import get_system_prompt
78

8-
def retrieve_similar_docs(vector_store: QdrantVectorStore, user_query: str):
9+
10+
def retrieve_similar_docs(collection_name: str, user_query: str):
911
"""Retrieve similar documents based on the user query"""
12+
vector_store = get_vector_store(collection_name)
1013
retriever = vector_store.as_retriever(search_kwargs={"k": 5})
1114
retrieved_docs = retriever.invoke(user_query)
1215
docs_content = "\n\n".join(doc.page_content for doc in retrieved_docs)
1316
return docs_content
1417

1518

16-
def prepare_prompt(system_prompt: str,
17-
user_query: str,
19+
def prepare_prompt(user_query: str,
1820
docs_content: str,
1921
messages: List[BaseMessage]):
2022
"""Prepare the prompt with prompt templates to give to LLM"""
2123
prompt_template = ChatPromptTemplate([
22-
"system", system_prompt,
24+
"system", get_system_prompt(),
2325
MessagesPlaceholder("msgs")
2426
])
2527

0 commit comments

Comments
 (0)