Denyol's picture
Update app.py
914651b verified
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)