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 | |
| 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. | |
| Only proceed to recommendations when you are 100% certain all required data is present and valid. | |
| """ | |
| 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’ll suggest five suitable games for each child! | |
| """ | |
| ]]), | |
| title="Videogame Recommender Chatbot" | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |