Denyol commited on
Commit
7f97427
·
1 Parent(s): 85ed8bc
app.py CHANGED
@@ -1,3 +1,8 @@
 
 
 
 
 
1
  from langchain.prompts import ChatPromptTemplate
2
  from langchain_community.document_loaders import JSONLoader
3
  from langchain_huggingface import HuggingFaceEmbeddings
@@ -5,9 +10,6 @@ from langchain_community.vectorstores import Chroma
5
  from langchain_cohere import ChatCohere
6
  from langchain_core.output_parsers import StrOutputParser
7
  from langchain_core.runnables import RunnableLambda, RunnablePassthrough
8
- import os
9
- import gradio as gr
10
- import cohere
11
 
12
 
13
  embedding_function = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
@@ -19,119 +21,248 @@ vectordb_loaded = Chroma(
19
  embedding_function=embedding_function
20
  )
21
 
22
- retriever = vectordb_loaded.as_retriever(
23
- search_type="mmr",
24
- search_kwargs={'k': 300, 'fetch_k': 500}
25
- )
26
 
27
- template = """Search for games based on the query while using the following context:
28
- {context}
29
 
30
- Query: {query}
31
- """
32
- prompt = ChatPromptTemplate.from_template(template)
33
 
34
- COHERE_API_KEY = os.getenv("COHERE_API_KEY")
 
35
 
36
- model = ChatCohere()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- chain = (
39
- {"context": retriever, "query": RunnablePassthrough()}
40
- | prompt
41
- | model
42
- | StrOutputParser()
43
- )
 
 
44
 
 
 
45
 
46
- client = cohere.ClientV2(COHERE_API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- COHERE_MODEL = "command-r-plus"
49
-
50
- def respond(
51
- message,
52
- history: list[tuple[str, str]],
53
- max_tokens,
54
- temperature,
55
- top_p,
56
- ):
57
-
58
- query = message
59
- retrieved_response = chain.invoke(query)
60
-
61
- system_message = f"""
62
- You are a friendly video game recommendation expert chatbot.
63
- Your task is to help parents and guardians to find appropriate video games for their children.
64
- Extract the child's age, preferred genre and multiplayer preference.
65
-
66
- After you extracted the information you need, you should:
67
- - Suggest 5 video games that fit the given criteria.
68
- - If no games exactly match the genre, suggest similar alternatives.
69
-
70
- Use the following information to generate your suggestions:
71
- {retrieved_response}
72
- If you don't find enough games in the info you are given, then use your own knowledge.
73
- Only suggest video games that exist. Do NOT make up game titles.
74
-
75
- ### Response Format:
76
- Game 1:
77
- - Name: [Game Title from the information you are given]
78
- - Genres: [List of genres from the information you are given]
79
- - Themes: [List of themes from the information you are given]
80
- - Age Rating: [Age rating by PEGI from the information you are given in a format like for example: PEGI 3]
81
- - Game Modes: [List of game modes from the information you are given]
82
- - PLatforms: [List of platforms from the information you are given]
83
- - Summary: [Summary of the game from the information you are given]
84
- - The reasons why you recommend the game
85
-
86
- and so on.
87
-
88
- Format the response as a clear, structured and easily understandable list.
89
-
90
- After you gave your recommendations ask the user for feedback. Are they satisfied with the results or not? Do they have any questions about the given games?
91
- If they are not satisfied, then give the user the options of receiving more recommendations or changing their preferences.
92
- If they have questions about a game/games then provide the user with real information about the game/games.
93
- If they are satisifed and have no questions, then tell them that you were very happy to help and end the conversation.
94
  """
95
-
96
- messages = [{"role": "system", "content": system_message}]
97
- for val in history:
98
- if val[0]:
99
- messages.append({"role": "user", "content": val[0]})
100
- if val[1]:
101
- messages.append({"role": "assistant", "content": val[1]})
102
-
103
- messages.append({"role": "user", "content": message})
104
-
105
- response = ""
106
-
107
- response = client.chat(
108
- messages=messages,
109
- model=COHERE_MODEL,
110
- temperature=temperature,
111
- max_tokens=max_tokens,
112
  )
113
 
114
- yield response.message.content[0].text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
 
117
- """
118
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
119
- """
120
  demo = gr.ChatInterface(
121
  respond,
122
- additional_inputs=[
123
- gr.Slider(minimum=1, maximum=2048, value=2048, step=1, label="Max new tokens"),
124
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
125
- gr.Slider(
126
- minimum=0.1,
127
- maximum=1.0,
128
- value=0.95,
129
- step=0.05,
130
- label="Top-p (nucleus sampling)",
131
- ),
132
- ],
133
- )
134
 
 
 
 
 
 
135
 
136
  if __name__ == "__main__":
137
- demo.launch()
 
1
+ import gradio as gr
2
+ import cohere
3
+ import os
4
+ import re
5
+ import json
6
  from langchain.prompts import ChatPromptTemplate
7
  from langchain_community.document_loaders import JSONLoader
8
  from langchain_huggingface import HuggingFaceEmbeddings
 
10
  from langchain_cohere import ChatCohere
11
  from langchain_core.output_parsers import StrOutputParser
12
  from langchain_core.runnables import RunnableLambda, RunnablePassthrough
 
 
 
13
 
14
 
15
  embedding_function = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
 
21
  embedding_function=embedding_function
22
  )
23
 
24
+ def query_rag(query_text):
25
+ results = vectordb_loaded.max_marginal_relevance_search(query=query_text, k=300, fetch_k=1500)
26
+ context_text = "\n".join([doc.page_content for doc in results])
 
27
 
28
+ return context_text
 
29
 
 
 
 
30
 
31
+ def collect_preferences(message, preferences):
32
+ print(f"Current preferences before extraction: {preferences}")
33
 
34
+ genre_names = [
35
+ "Point-and-click",
36
+ "Fighting",
37
+ "Shooter",
38
+ "Music",
39
+ "Platform",
40
+ "Puzzle",
41
+ "Racing",
42
+ "Real Time Strategy (RTS)",
43
+ "Role-playing (RPG)",
44
+ "Simulator",
45
+ "Sport",
46
+ "Strategy",
47
+ "Turn-based strategy (TBS)",
48
+ "Tactical",
49
+ "Hack and slash/Beat 'em up",
50
+ "Quiz/Trivia",
51
+ "Pinball",
52
+ "Adventure",
53
+ "Indie",
54
+ "Arcade",
55
+ "Visual Novel",
56
+ "Card & Board Game",
57
+ "MOBA"
58
+ ]
59
 
60
+ game_modes = [
61
+ "Single player",
62
+ "Multiplayer",
63
+ "Co-operative",
64
+ "Split screen",
65
+ "Massively Multiplayer Online (MMO)"
66
+ "Battle Royale"
67
+ ]
68
 
69
+ extraction_prompt = f"""
70
+ You are an expert assistant who manages user preferences about children's video games.
71
 
72
+ Task:
73
+ - You are given the CURRENT user preferences as a JSON dictionary.
74
+ - You are also given a NEW user message.
75
+ - Your job is to intelligently UPDATE the preferences based on the new message with the following lists: {genre_names}, {game_modes}
76
+ - Update existing child entries if the information is about the same child.
77
+ - Add new child entries if the user mentions a new child.
78
+ - Merge new genres, game modes, and platforms with existing ones without duplication.
79
+ - Respect any content to avoid preferences.
80
+ - If something is no longer valid (e.g., user says "forget about racing games"), REMOVE that data.
81
+ - If the user wants all preferences to be deleted or start over, then DELETE the CURRENT user preferences and CREATE a NEW empty one.
82
+ - If the user input is vague (e.g., "fun", "educational"), only update the genre field with up to five related genres from the list.
83
+ Do not guess game modes or platforms based on vague terms. Avoid inventing new children or unrelated preferences.
84
+ - If the CURRENT user preferences is empty, then create the JSON dictionary like this:
85
+
86
+ {{
87
+ "children": [
88
+ {{
89
+ "age": null,
90
+ "genres": [],
91
+ "game_modes": [],
92
+ "platforms": [],
93
+ "content_to_avoid": []
94
+ }},
95
+ }}
96
+
97
+ AND then edit it based on the NEW user message.
98
+ - Ignore the user message if that is not related to the topic.
99
+
100
+ RULES:
101
+ - Always respond with the FULL updated JSON, and ONLY the JSON. No extra text.
102
+ - Preserve all data not mentioned in the new message unless explicitly removed.
103
+ - Merge arrays without duplicates (e.g., genres, platforms).
104
+ - If no changes are needed, simply output the original JSON.
105
+
106
+ CURRENT USER PREFERENCES:
107
+ {json.dumps(preferences, indent=2)}
108
 
109
+ NEW USER MESSAGE:
110
+ "{message}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  """
112
+
113
+ extraction_response = client.chat(
114
+ messages=[
115
+ {"role": "system", "content": extraction_prompt},
116
+ {"role": "user", "content": message}
117
+ ],
118
+ model=COHERE_MODEL,
 
 
 
 
 
 
 
 
 
 
119
  )
120
 
121
+ raw_output = extraction_response.message.content[0].text.strip()
122
+ cleaned_output = re.sub(r"^```json\s*|\s*```$", "", raw_output).strip()
123
+
124
+ try:
125
+ updated_preferences = json.loads(cleaned_output)
126
+ except json.JSONDecodeError:
127
+ print(f"Error parsing extracted JSON: {raw_output}")
128
+ updated_preferences = preferences
129
+
130
+ print(f"Updated preferences after extraction: {updated_preferences}")
131
+ return updated_preferences
132
+
133
+
134
+ def missing_info(preferences):
135
+ for child in preferences['children']:
136
+ if child['age'] is None or not child['genres'] or not child['game_modes'] or not child['platforms']:
137
+ return True
138
+
139
+ return False
140
+
141
+
142
+ client = cohere.ClientV2(COHERE_API_KEY)
143
+ COHERE_MODEL = "command-a-03-2025"
144
+
145
+ user_preferences = {}
146
+
147
+ def respond(message, history):
148
+ global user_preferences
149
+ user_preferences = collect_preferences(message, user_preferences)
150
+
151
+ if not missing_info(user_preferences):
152
+ filtered = {
153
+ 'genres': [],
154
+ 'game_modes': [],
155
+ 'platforms': []
156
+ }
157
+
158
+ for child in user_preferences['children']:
159
+ filtered['genres'].extend(child.get('genres', []))
160
+ filtered['game_modes'].extend(child.get('game_modes', []))
161
+ filtered['platforms'].extend(child.get('platforms', []))
162
+
163
+ filtered = {k: list(set(v)) for k, v in filtered.items()}
164
+
165
+ games = query_rag(str(filtered))
166
+
167
+ else: games = {}
168
+
169
+ #print(games)
170
+ #print(user_preferences)
171
+ system_message = f"""
172
+ You are a friendly and expert video game recommendation assistant helping parents find appropriate games for their children.
173
+
174
+ Your job involves the following:
175
+
176
+ 1. You are given the user's extracted preferences in JSON format:
177
+ {json.dumps(user_preferences, indent=2)}
178
+
179
+ 2. Check the data for completeness:
180
+ - For each child, the following fields MUST be filled:
181
+ - "age" must not be null.
182
+ - "genres" must have at least one value.
183
+ - "game_modes" must have at least one value.
184
+ - "platforms" must have at least one value.
185
+ - "content_to_avoid" can be empty, but should be present and also ask the user about this as well.
186
+
187
+ 3. If ANY required data is missing for any child:
188
+ - DO NOT suggest games yet.
189
+ - ONLY ask for the missing details, not the ones already filled in.
190
+ - Be polite and encouraging, acknowledging what is already known.
191
+ - Be specific and list *only* what is missing per child.
192
+ - Wait for the user's response before continuing.
193
+ - If the user input was vague (see here:{message}), then make sure to tell them how it was interpreted relating to the genres.
194
+
195
+ 4. Only AFTER all required data is complete for all children:
196
+ - Use the context below to find matching games.
197
+ - DO NOT invent games or use outside knowledge.
198
+ - Only recommend games from this context:
199
+ {games}
200
+
201
+ 5. For each child, recommend exactly five games that:
202
+ - Match their genre, platform, and game mode preferences.
203
+ - Are appropriate for their age.
204
+ - Do NOT contain any content the parent wants to avoid.
205
+
206
+ 6. If a requested genre is unsuitable for the child's age, offer safer alternatives with an explanation.
207
+
208
+ 7. NEVER recommend the same game more than once in a session.
209
+
210
+ 8. When ready, use the following format for each game:
211
+
212
+ Game 1:
213
+ - Name: [Title of the game from the context]
214
+ - Genres: [List of all genres from the context separated by commas word for word]
215
+ - Themes: [List of all themes from the context separated by commas word for word, if any]
216
+ - Age Ratings: [List of all age ratings from the context separated by commas word for word]
217
+ - Game Modes: [List of all game modes from the context separated by commas word for word]
218
+ - Platforms: [List of all platforms from the context on which the game is available separated by commas word for word]
219
+ - Summary: [The summary of the game word for word from the context]
220
+ - Reasons for recommendation
221
+
222
+ 9. After recommending, ask the user:
223
+ - If they're satisfied.
224
+ - If they'd like to update preferences or receive more recommendations.
225
+
226
+ Only proceed to recommendations when you are 100% certain all required data is present and valid.
227
+ """
228
+
229
+ messages = [{"role": "system", "content": system_message}]
230
+
231
+ for val in history:
232
+ if val[0]:
233
+ messages.append({"role": "user", "content": val[0]})
234
+ if val[1]:
235
+ messages.append({"role": "assistant", "content": val[1]})
236
+
237
+ messages.append({"role": "user", "content": message})
238
+
239
+ response = client.chat(
240
+ messages=messages,
241
+ model=COHERE_MODEL,
242
+ )
243
+
244
+ text_output = response.message.content[0].text
245
+
246
+ return text_output
247
 
248
 
 
 
 
249
  demo = gr.ChatInterface(
250
  respond,
251
+ chatbot=gr.Chatbot(value=[[None,
252
+ """
253
+ Hi there! I'm here to help you find the perfect video games for your child. To get started, could you please provide the following information:
254
+
255
+ 1. Age of the child: This will help ensure the games are age-appropriate.
256
+ 2. Preferred genres: What types of games do they enjoy? (e.g., adventure, sports, puzzle)
257
+ 3. Game mode preferences: Do they prefer single-player, multiplayer, or both?
258
+ 4. Platforms: Which devices do you have? (e.g., PC, PlayStation, Xbox, Nintendo Switch)
259
+ 5. Content to avoid: Are there any themes or contents you'd like to avoid? (e.g., violence, horror)
 
 
 
260
 
261
+ If there are multiple children, please provide details for each. Once I have all the information, I’ll suggest five suitable games for each child!
262
+ """
263
+ ]]),
264
+ title="Videogame Recommender Chatbot"
265
+ )
266
 
267
  if __name__ == "__main__":
268
+ demo.launch()
chroma_langchain_db/chroma.sqlite3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:bcd9b5a3bdc7c9930ebe70ddce087ad13be0a6776b2f19d03f92cf9306cb3cdd
3
- size 9646080
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1c4d81ab7170fa4380864a409ebbeed15c7cb942bc8db36be8bbdbf0a2e6e754
3
+ size 28758016
chroma_langchain_db/{bddfa14c-8d7d-46b4-9177-cb664ba4119f → fa3e5ff8-4a5d-48c8-9f26-52a1670c41d4}/data_level0.bin RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:b8146ecc3e4c3a36ea9b3edc3778630c452f483990ec942d38e8006f4661e430
3
  size 16760000
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0758ff5cf08ffb590c5c72bad2543f82e21a8bf65f6fe43d8a7754e0b801a319
3
  size 16760000
chroma_langchain_db/{bddfa14c-8d7d-46b4-9177-cb664ba4119f → fa3e5ff8-4a5d-48c8-9f26-52a1670c41d4}/header.bin RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:18f1e924efbb5e1af5201e3fbab86a97f5c195c311abe651eeec525884e5e449
3
  size 100
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:25f71be6996ccb19e1c99790b43fde1588a52f846852bef7cab8173b91767d15
3
  size 100
chroma_langchain_db/fa3e5ff8-4a5d-48c8-9f26-52a1670c41d4/index_metadata.pickle ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1d79ddddbe0e3e3504b1dead85a82a879d2cb8b0f15a97871a4c62920d3a8a59
3
+ size 138132
chroma_langchain_db/{bddfa14c-8d7d-46b4-9177-cb664ba4119f → fa3e5ff8-4a5d-48c8-9f26-52a1670c41d4}/length.bin RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e7e2dcff542de95352682dc186432e98f0188084896773f1973276b0577d5305
3
  size 40000
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5e1e13e497a8a870685b33859c35eb62ccace2ab20962bd93735e067ab37a5cd
3
  size 40000
chroma_langchain_db/fa3e5ff8-4a5d-48c8-9f26-52a1670c41d4/link_lists.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad11dd1afa4ea0667ce84b3a667338b255027b7a32f9a8a19347affa0ecd6f52
3
+ size 13412
games.json CHANGED
The diff for this file is too large to render. See raw diff