File size: 11,651 Bytes
7f97427
 
 
 
 
a3cc1c6
 
 
 
 
 
 
 
 
 
 
b31445d
a3cc1c6
b31445d
 
 
 
 
7f97427
 
 
a3cc1c6
7f97427
a3cc1c6
 
7f97427
 
a3cc1c6
7f97427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3cc1c6
7f97427
 
 
 
 
 
 
 
a3cc1c6
7f97427
 
a3cc1c6
7f97427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b32b8c0
7f97427
 
a3cc1c6
7f97427
 
 
 
 
 
 
6068273
607aecd
7f97427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6cab2b3
7f97427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7a92c2
7f97427
 
a7a92c2
 
7f97427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1a2b183
6cfe4c9
 
 
7f97427
 
 
 
 
 
 
 
 
6cfe4c9
a7a92c2
 
 
 
 
 
7f97427
 
b7d6f65
 
 
 
 
 
 
 
 
 
 
 
 
 
f94aa15
 
d48a35f
eda7b52
7f97427
6cfe4c9
 
4dc945b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import gradio as gr
import cohere
import os
import re
import json
from langchain.prompts import ChatPromptTemplate
from langchain_community.document_loaders import JSONLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_cohere import ChatCohere
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough


embedding_function = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

persist_directory = "./chroma_langchain_db"

vectordb_loaded = Chroma(
    persist_directory=persist_directory,
    embedding_function=embedding_function
)

def query_rag(query_text):
  results = vectordb_loaded.max_marginal_relevance_search(query=query_text, k=300, fetch_k=1500)
  context_text = "\n".join([doc.page_content for doc in results])

  return context_text


def collect_preferences(message, preferences):
    print(f"Current preferences before extraction: {preferences}")

    genre_names = [
      "Point-and-click",
      "Fighting",
      "Shooter",
      "Music",
      "Platform",
      "Puzzle",
      "Racing",
      "Real Time Strategy (RTS)",
      "Role-playing (RPG)",
      "Simulator",
      "Sport",
      "Strategy",
      "Turn-based strategy (TBS)",
      "Tactical",
      "Hack and slash/Beat 'em up",
      "Quiz/Trivia",
      "Pinball",
      "Adventure",
      "Indie",
      "Arcade",
      "Visual Novel",
      "Card & Board Game",
      "MOBA"
    ]

    game_modes = [
        "Single player",
        "Multiplayer",
        "Co-operative",
        "Split screen",
        "Massively Multiplayer Online (MMO)"
        "Battle Royale"
    ]

    extraction_prompt = f"""
    You are an expert assistant who manages user preferences about children's video games.

    Task:
    - You are given the CURRENT user preferences as a JSON dictionary.
    - You are also given a NEW user message.
    - Your job is to intelligently UPDATE the preferences based on the new message with the following lists: {genre_names}, {game_modes}
    - Update existing child entries if the information is about the same child.
    - Add new child entries if the user mentions a new child.
    - Merge new genres, game modes, and platforms with existing ones without duplication.
    - Respect any content to avoid preferences.
    - If something is no longer valid (e.g., user says "forget about racing games"), REMOVE that data.
    - If the user wants all preferences to be deleted or start over, then DELETE the CURRENT user preferences and CREATE a NEW empty one.
    - If the user input is vague (e.g., "fun", "educational"), only update the genre field with up to five related genres from the list.
      Do not guess game modes or platforms based on vague terms. Avoid inventing new children or unrelated preferences.
    - If the CURRENT user preferences is empty, then create the JSON dictionary like this:

      {{
        "children": [
          {{
            "age": null,
            "genres": [],
            "game_modes": [],
            "platforms": [],
            "content_to_avoid": []
          }},
      }}

      AND then edit it based on the NEW user message.
    - Ignore the user message if that is not related to the topic.

    RULES:
    - Always respond with the FULL updated JSON, and ONLY the JSON. No extra text.
    - Preserve all data not mentioned in the new message unless explicitly removed.
    - Merge arrays without duplicates (e.g., genres, platforms).
    - If no changes are needed, simply output the original JSON.

    CURRENT USER PREFERENCES:
    {json.dumps(preferences, indent=2)}

    NEW USER MESSAGE:
    "{message}"
    """

    extraction_response = client.chat(
        messages=[
            {"role": "system", "content": extraction_prompt},
            {"role": "user", "content": message}
        ],
        model=COHERE_MODEL,
    )

    raw_output = extraction_response.message.content[0].text.strip()
    cleaned_output = re.sub(r"^```json\s*|\s*```$", "", raw_output).strip()

    try:
        updated_preferences = json.loads(cleaned_output)
    except json.JSONDecodeError:
        print(f"Error parsing extracted JSON: {raw_output}")
        updated_preferences = preferences

    print(f"Updated preferences after extraction: {updated_preferences}")
    return updated_preferences
    
    
def missing_info(preferences):
  for child in preferences['children']:
      if child['age'] is None or not child['genres'] or not child['game_modes'] or not child['platforms']:
        return True

  return False

COHERE_API_KEY = os.getenv("COHERE_API_KEY")
client = cohere.ClientV2(COHERE_API_KEY)
COHERE_MODEL = "command-a-03-2025"

user_preferences = {}

def respond(message, history):
      global user_preferences
      user_preferences = collect_preferences(message, user_preferences)

      if not missing_info(user_preferences):
        filtered = {
          'genres': [],
          'game_modes': [],
          'platforms': []
        }

        for child in user_preferences['children']:
          filtered['genres'].extend(child.get('genres', []))
          filtered['game_modes'].extend(child.get('game_modes', []))
          filtered['platforms'].extend(child.get('platforms', []))

        filtered = {k: list(set(v)) for k, v in filtered.items()}

        games = query_rag(str(filtered))

      else: games = {}

      #print(games)
      #print(user_preferences)
      system_message = f"""
      You are a friendly and expert video game recommendation assistant helping parents find appropriate games for their children.

      Your job involves the following:

      1. You are given the user's extracted preferences in JSON format:
      {json.dumps(user_preferences, indent=2)}

      2. Check the data for completeness:
        - For each child, the following fields MUST be filled:
          - "age" must not be null.
          - "genres" must have at least one value.
          - "game_modes" must have at least one value.
          - "platforms" must have at least one value.
          - "content_to_avoid" can be empty, but should be present and also ask the user about this as well.

      3. If ANY required data is missing for any child:
        - DO NOT suggest games yet.
        - ONLY ask for the missing details, not the ones already filled in.
        - Be polite and encouraging, acknowledging what is already known.
        - Be specific and list *only* what is missing per child.
        - Wait for the user's response before continuing.
        - If the user input was vague (see here:{message}), then make sure to tell them how it was interpreted relating to the genres.

      4. Only AFTER all required data is complete for all children:
        - Use the context below to find matching games.
        - DO NOT invent games or use outside knowledge.
        - Only recommend games from this context:
        {games}

      5. For each child, recommend exactly five games that:
        - Match their genre, platform, and game mode preferences.
        - Are appropriate for their age.
        - Do NOT contain any content the parent wants to avoid.

      6. If a requested genre is unsuitable for the child's age, offer safer alternatives with an explanation.

      7. NEVER recommend the same game more than once in a session.

      8. When ready, use the following format for each game:

      Game 1:
      - Name: [Title of the game from the context]
      - Genres: [List of all genres from the context separated by commas word for word]
      - Themes: [List of all themes from the context separated by commas word for word, if any]
      - Age Ratings: [List of all age ratings from the context separated by commas word for word]
      - Game Modes: [List of all game modes from the context separated by commas word for word]
      - Platforms: [List of all platforms from the context on which the game is available separated by commas word for word]
      - Summary: [The summary of the game word for word from the context]
      - Reasons for recommendation

      9. After recommending, ask the user:
        - If they're satisfied.
        - If they'd like to update preferences or receive more recommendations.
        - If they have any questions about the recommended games.

      Only proceed to recommendations when you are 100% certain all required data is present and valid.

      Stay on the topic of videogame recommendation, remind the user about this as well if their inputs are not related to the topic.
      """

      messages = [{"role": "system", "content": system_message}]

      for val in history:
          if val[0]:
              messages.append({"role": "user", "content": val[0]})
          if val[1]:
              messages.append({"role": "assistant", "content": val[1]})

      messages.append({"role": "user", "content": message})

      response = client.chat(
          messages=messages,
          model=COHERE_MODEL,
      )

      text_output = response.message.content[0].text

      return text_output


demo = gr.ChatInterface(
    respond,
    chatbot=gr.Chatbot(value=[[None,
        """
        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:

        1. Age of the child: This will help ensure the games are age-appropriate.
        2. Preferred genres: What types of games do they enjoy? (e.g., adventure, sports, puzzle)
        3. Game mode preferences: Do they prefer single-player, multiplayer, or both?
        4. Platforms: Which devices do you have? (e.g., PC, PlayStation, Xbox, Nintendo Switch)
        5. Content to avoid: Are there any themes or contents you'd like to avoid? (e.g., violence, horror)

        If there are multiple children, please provide details for each.
        Once I have all the information, I make sure to store it for future use and I’ll suggest five suitable games for each child!
        
        You can change your preferences at any time, I'll make sure to keep up with you!
        You can even request to start over the whole information gathering process!
        If you want me to delete all the stored information that I have about your preferences, then I'll happily comply!
        """
        ]]),
    title="Videogame Recommender Chatbot",
    description="""
    Welcome dear Tester!
    This chatbot is designed to help parents and guardians find suitable and age-appropriate video games for their children. 
    It considers the following factors: age, preferred genres, game mode preferences, available platforms and contents to avoid.
    
    Your Role as a Tester:
    Please try using the chatbot as if you're a parent or guardian searching for a new game for your child.
    I advise you to do the testing in multiple recommendation sessions for more results!
    While testing check out the survey below and test the chatbot based on the questions found there!
    To check if the chatbot gave accurate info, use https://www.igdb.com/ or search inside the "games.json" file found in the "Files" menu.
    After interacting with the chatbot, fill out the short survey to help me evaluate its usefulness, clarity, and ease of use. Your feedback is very important!
    
    The survey is available here: https://docs.google.com/forms/d/e/1FAIpQLSdyQ3LPr3UCf4JA6sbs4tcqsA5Bxdark5hS4QvIawFthChqBA/viewform?usp=sharing

    IF YOU DON'T SEE THE FULL MESSAGE ABOVE THEN PRESS THE "F11" KEY TWICE!
    """,
    autoscroll=True,
)

if __name__ == "__main__":
    demo.launch(ssr_mode=False)