Spaces:
Build error
Build error
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! | |
If the chatbot's response time is very long it means it currently gathers game information. | |
Try to tell the chatbot to delete all stored information about user preferences if you are starting fresh. | |
After that the response time goes down to around 3-4 seconds until it has every information it needs to make suggestions. | |
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) |