| """ |
| World Cup Storyteller β Hugging Face Spaces app.py |
| NLP Homework 4 β ARI 525 |
| |
| Three approaches, equal treatment: |
| - Zero-Shot : prompt only β LLM knowledge |
| - Few-Shot : prompt + 2 style examples β LLM knowledge |
| - RAG : prompt β retrieve from WC dataset β grounded generation |
| """ |
|
|
| import os |
| import time |
| import warnings |
| warnings.filterwarnings("ignore") |
|
|
| import pandas as pd |
| import numpy as np |
| import gradio as gr |
| from groq import Groq |
| from sentence_transformers import SentenceTransformer |
| import faiss |
|
|
| |
| |
| |
|
|
| GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "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") |
| MODEL = "llama-3.1-8b-instant" |
| client = Groq(api_key=GROQ_API_KEY) |
|
|
| |
| |
| |
|
|
| try: |
| cups = pd.read_csv("WorldCups.csv") |
| matches = pd.read_csv("WorldCupMatches.csv") |
| print("β
Dataset loaded.") |
| except FileNotFoundError as e: |
| raise RuntimeError( |
| "CSV files not found. Upload WorldCups.csv and WorldCupMatches.csv " |
| "to the Space root folder." |
| ) from e |
|
|
|
|
| |
| |
| |
|
|
| def clean_data(cups, matches): |
| matches_clean = matches.dropna( |
| subset=["Home Team Name", "Away Team Name", |
| "Home Team Goals", "Away Team Goals"] |
| ).copy() |
| matches_clean["Home Team Name"] = matches_clean["Home Team Name"].str.strip() |
| matches_clean["Away Team Name"] = matches_clean["Away Team Name"].str.strip() |
| matches_clean["Home Team Goals"] = matches_clean["Home Team Goals"].astype(int) |
| matches_clean["Away Team Goals"] = matches_clean["Away Team Goals"].astype(int) |
| cups_clean = cups.dropna(subset=["Year", "Winner"]).copy() |
| cups_clean["Year"] = cups_clean["Year"].astype(int) |
| return cups_clean, matches_clean |
|
|
|
|
| def build_team_context(team, year, cups, matches): |
| cup_info = cups[cups["Year"] == year] |
| if cup_info.empty: |
| return None |
| cup = cup_info.iloc[0] |
| team_matches = matches[ |
| (matches["Year"] == year) & |
| ((matches["Home Team Name"] == team) | (matches["Away Team Name"] == team)) |
| ].sort_values("Stage") |
| if team_matches.empty: |
| return None |
|
|
| match_lines = [] |
| for _, row in team_matches.iterrows(): |
| home, away = row["Home Team Name"], row["Away Team Name"] |
| hg, ag = int(row["Home Team Goals"]), int(row["Away Team Goals"]) |
| if home == team: |
| result = "WIN" if hg > ag else ("DRAW" if hg == ag else "LOSS") |
| line = f"[{row['Stage']}] {team} vs {away}: {hg}-{ag} ({result})" |
| else: |
| result = "WIN" if ag > hg else ("DRAW" if hg == ag else "LOSS") |
| line = f"[{row['Stage']}] {team} vs {home}: {ag}-{hg} ({result})" |
| match_lines.append(line) |
|
|
| final_note = "" |
| if cup.get("Winner") == team: |
| final_note = f"{team} WON the {year} World Cup! π" |
| elif cup.get("Runners-Up") == team: |
| final_note = f"{team} were runners-up in {year}." |
| elif cup.get("Third") == team: |
| final_note = f"{team} finished third in {year}." |
|
|
| return ( |
| f"{team}'s Journey β World Cup {year}\n" |
| f"Host: {cup.get('Country', 'Unknown')}\n" |
| f"{final_note}\n\nMatch-by-match results:\n" + |
| "\n".join(match_lines) |
| ) |
|
|
|
|
| def build_edition_context(year, cups, matches): |
| cup_info = cups[cups["Year"] == year] |
| if cup_info.empty: |
| return None |
| cup = cup_info.iloc[0] |
| edition_matches = matches[matches["Year"] == year].sort_values("Stage") |
| match_lines = [ |
| f"[{row['Stage']}] {row['Home Team Name']} {int(row['Home Team Goals'])} " |
| f"- {int(row['Away Team Goals'])} {row['Away Team Name']}" |
| for _, row in edition_matches.iterrows() |
| ] |
| return ( |
| f"World Cup {year} β Host: {cup.get('Country', 'Unknown')}\n" |
| f"Winner: {cup.get('Winner', 'Unknown')}\n" |
| f"Runners-up: {cup.get('Runners-Up', 'Unknown')}\n" |
| f"Third Place: {cup.get('Third', 'Unknown')}\n" |
| f"Goals Scored: {cup.get('GoalsScored', 'Unknown')}\n" |
| f"Attendance: {cup.get('Attendance', 'Unknown')}\n\nAll Matches:\n" + |
| "\n".join(match_lines) |
| ) |
|
|
|
|
| cups_clean, matches_clean = clean_data(cups, matches) |
|
|
|
|
| |
| |
| |
|
|
| def build_knowledge_base(cups, matches): |
| chunks, metadata = [], [] |
| for _, cup_row in cups.iterrows(): |
| year = int(cup_row["Year"]) |
| year_matches = matches[matches["Year"] == year] |
| teams = set( |
| year_matches["Home Team Name"].tolist() + |
| year_matches["Away Team Name"].tolist() |
| ) |
| for team in teams: |
| ctx = build_team_context(team, year, cups, matches) |
| if ctx: |
| chunks.append(ctx) |
| metadata.append({"team": team, "year": year}) |
| ctx = build_edition_context(year, cups, matches) |
| if ctx: |
| chunks.append(ctx) |
| metadata.append({"team": "ALL", "year": year}) |
| return chunks, metadata |
|
|
|
|
| print("β³ Loading embedding model...") |
| embedder = SentenceTransformer("all-MiniLM-L6-v2") |
|
|
| print("β³ Building & embedding knowledge base...") |
| kb_chunks, kb_metadata = build_knowledge_base(cups_clean, matches_clean) |
| kb_embeddings = embedder.encode(kb_chunks, show_progress_bar=False, convert_to_numpy=True) |
|
|
| dim = kb_embeddings.shape[1] |
| faiss_index = faiss.IndexFlatL2(dim) |
| faiss_index.add(kb_embeddings) |
| print(f"β
RAG index ready β {faiss_index.ntotal} vectors") |
|
|
|
|
| def retrieve_context(query, top_k=3): |
| query_vec = embedder.encode([query], convert_to_numpy=True) |
| distances, indices = faiss_index.search(query_vec, top_k) |
| retrieved = [] |
| for idx in indices[0]: |
| if idx < len(kb_chunks): |
| meta = kb_metadata[idx] |
| retrieved.append(f"[Retrieved: {meta['team']} β {meta['year']}]\n{kb_chunks[idx]}") |
| return "\n\n---\n\n".join(retrieved) |
|
|
|
|
| |
| |
| |
|
|
| BASE_SYSTEM_PROMPT = """ |
| You are a passionate, knowledgeable sports journalist specializing in FIFA World Cup history. |
| Generate vivid, engaging, narrative-driven stories about World Cup tournaments and team journeys. |
| |
| Guidelines: |
| - Write in flowing narrative prose β no bullet points |
| - Build tension and highlight dramatic moments |
| - Be factually accurate based on your knowledge |
| - Adapt tone to the user's request (documentary, dramatic, casual, etc.) |
| - Keep stories between 250-400 words unless asked otherwise |
| """.strip() |
|
|
| RAG_SYSTEM_PROMPT = """ |
| You are a passionate sports journalist specializing in FIFA World Cup history. |
| You will be given retrieved historical match data. Use it to write an accurate, |
| vivid, narrative-driven story. Only use facts present in the retrieved data. |
| |
| Guidelines: |
| - Write in flowing narrative prose β no bullet points |
| - Build tension and highlight dramatic moments |
| - Stick strictly to the facts in the retrieved data β do not invent scores |
| - Adapt tone to the user's request |
| - Keep stories between 250-400 words unless asked otherwise |
| """.strip() |
|
|
| FEW_SHOT_EXAMPLES = [ |
| { |
| "prompt": "Tell the story of France's 1998 World Cup victory.", |
| "story": ( |
| "It was the summer that France found its destiny on home soil. Les Bleus entered " |
| "the 1998 World Cup as hosts carrying immense pressure, but from the very first " |
| "whistle they played with a quiet, relentless authority.\n\n" |
| "The group stage was a statement β South Africa swept aside 3-0, Saudi Arabia " |
| "dismantled 4-0, Denmark edged out 2-1. By the knockout rounds, France carried " |
| "the weight of a nation's expectations into every match.\n\n" |
| "Paraguay made them suffer β a lone golden goal was all that separated the sides. " |
| "Italy pushed them to penalties, a nerve-shredding duel France survived with " |
| "ice-cold nerves. Then came Croatia. France trailed before turning the game on " |
| "its head to win 2-1.\n\n" |
| "The final against defending champions Brazil became a coronation. Three goals, " |
| "zero reply. The Stade de France erupted, and a generation of French children " |
| "discovered what it felt like to be champions of the world." |
| ) |
| }, |
| { |
| "prompt": "Tell the story of West Germany's 1954 World Cup win.", |
| "story": ( |
| "They called it the Miracle of Bern, and for good reason. No one believed West " |
| "Germany could win the 1954 World Cup β least of all after Hungary handed them " |
| "an 8-3 humiliation in the group stage.\n\n" |
| "But West Germany, crafty and resilient, had rested key players for that match " |
| "and quietly plotted their path forward. They dispatched Turkey twice, squeezed " |
| "past Yugoslavia, then demolished Austria 6-1 in a dazzling semi-final.\n\n" |
| "The final was a rematch nobody expected. Hungary β the Mighty Magyars, unbeaten " |
| "for four years β led 2-0 within eight minutes. The world assumed it was over.\n\n" |
| "It was not. West Germany clawed back to 2-2, and then six minutes from the end " |
| "Helmut Rahn struck. 3-2. A country still rebuilding from the rubble of war had " |
| "become world champions." |
| ) |
| } |
| ] |
|
|
|
|
| |
| |
| |
|
|
| def generate_zeroshot(user_prompt): |
| start = time.time() |
| response = client.chat.completions.create( |
| model=MODEL, |
| messages=[ |
| {"role": "system", "content": BASE_SYSTEM_PROMPT}, |
| {"role": "user", "content": user_prompt} |
| ], |
| temperature=0.8, |
| max_tokens=600 |
| ) |
| elapsed = round(time.time() - start, 2) |
| return response.choices[0].message.content.strip(), elapsed |
|
|
|
|
| def generate_fewshot(user_prompt): |
| messages = [{"role": "system", "content": BASE_SYSTEM_PROMPT}] |
| for ex in FEW_SHOT_EXAMPLES: |
| messages.append({"role": "user", "content": ex["prompt"]}) |
| messages.append({"role": "assistant", "content": ex["story"]}) |
| messages.append({"role": "user", "content": user_prompt}) |
| start = time.time() |
| response = client.chat.completions.create( |
| model=MODEL, messages=messages, temperature=0.8, max_tokens=600 |
| ) |
| elapsed = round(time.time() - start, 2) |
| return response.choices[0].message.content.strip(), elapsed |
|
|
|
|
| def generate_rag(user_prompt, top_k=3): |
| retrieved = retrieve_context(user_prompt, top_k=top_k) |
| full_message = ( |
| f"Retrieved World Cup data:\n---\n{retrieved}\n---\n\n" |
| f"User request: {user_prompt}" |
| ) |
| start = time.time() |
| response = client.chat.completions.create( |
| model=MODEL, |
| messages=[ |
| {"role": "system", "content": RAG_SYSTEM_PROMPT}, |
| {"role": "user", "content": full_message} |
| ], |
| temperature=0.8, |
| max_tokens=600 |
| ) |
| elapsed = round(time.time() - start, 2) |
| return response.choices[0].message.content.strip(), elapsed |
|
|
|
|
| |
| |
| |
|
|
| def generate_story_ui(user_prompt, approach): |
| if not user_prompt.strip(): |
| return "β οΈ Please enter a prompt.", "" |
| try: |
| if approach == "Zero-Shot": |
| story, elapsed = generate_zeroshot(user_prompt) |
| info = f"β‘ Zero-Shot (LLM knowledge only) | β±οΈ {elapsed}s | π {len(story.split())} words" |
| elif approach == "Few-Shot": |
| story, elapsed = generate_fewshot(user_prompt) |
| info = f"π Few-Shot (LLM knowledge + 2 style examples) | β±οΈ {elapsed}s | π {len(story.split())} words" |
| else: |
| story, elapsed = generate_rag(user_prompt) |
| info = f"π RAG (retrieved from WC dataset) | β±οΈ {elapsed}s | π {len(story.split())} words" |
| return story, info |
| except Exception as e: |
| return f"β Error: {str(e)}", "" |
|
|
|
|
| EXAMPLES = [ |
| "Tell me the story of Brazil's 2002 World Cup campaign like a sports documentary.", |
| "What happened in the 1966 World Cup? Who won and how?", |
| "Describe Argentina's 1986 World Cup triumph β focus on the drama.", |
| "Tell Italy's 2006 World Cup story.", |
| "What was the most dramatic World Cup final ever?", |
| ] |
|
|
| with gr.Blocks( |
| title="β½ World Cup Storyteller", |
| theme=gr.themes.Base(), |
| css=""" |
| #header { text-align: center; padding: 1.5em 0 0.5em; } |
| #header h1 { font-size: 2.2em; } |
| #header p { color: #777; font-size: 1.05em; } |
| #story-box textarea { font-size: 1.05em; line-height: 1.8; } |
| .note { font-size: 0.85em; color: #777; margin-top: 0.4em; line-height: 1.6; } |
| """ |
| ) as demo: |
|
|
| with gr.Column(elem_id="header"): |
| gr.Markdown("# β½ World Cup Storyteller") |
| gr.Markdown( |
| "Ask anything about World Cup history β get a vivid narrative story. " |
| "Compare how different NLP approaches handle the same prompt." |
| ) |
|
|
| with gr.Row(): |
|
|
| with gr.Column(scale=1, min_width=260): |
| gr.Markdown("### βοΈ NLP Approach") |
| approach = gr.Radio( |
| choices=["Zero-Shot", "Few-Shot", "RAG"], |
| value="Few-Shot", |
| label="" |
| ) |
| gr.Markdown( |
| "**Zero-Shot** β Prompt only. No examples, no data. " |
| "The LLM relies entirely on its own knowledge.\n\n" |
| "**Few-Shot** β 2 hand-crafted story examples guide the writing style. " |
| "Still uses LLM knowledge, no dataset.\n\n" |
| "**RAG** β Retrieves real match data from the WC dataset (1930β2014) " |
| "before generating. Most factually grounded.", |
| elem_classes="note" |
| ) |
|
|
| with gr.Column(scale=2): |
| gr.Markdown("### βοΈ Your Prompt") |
| user_prompt = gr.Textbox( |
| placeholder="e.g. Tell me the story of Brazil's 2002 World Cup campaign...", |
| label="Ask anything about World Cup history", |
| lines=3 |
| ) |
| gr.Examples( |
| examples=[[p] for p in EXAMPLES], |
| inputs=user_prompt, |
| label="Try an example" |
| ) |
| btn = gr.Button("ποΈ Generate Story", variant="primary", size="lg") |
|
|
| gr.Markdown("### π Story") |
| story_out = gr.Textbox( |
| label="", lines=16, interactive=False, elem_id="story-box" |
| ) |
| info_out = gr.Markdown("") |
|
|
| btn.click( |
| fn=generate_story_ui, |
| inputs=[user_prompt, approach], |
| outputs=[story_out, info_out] |
| ) |
|
|
| gr.Markdown( |
| "---\n" |
| "*Data: FIFA World Cup dataset (1930β2014) Β· " |
| "LLM: Llama 3 8B via Groq Β· " |
| "Embeddings: all-MiniLM-L6-v2 Β· " |
| "Built for NLP HW4 β ARI 525*" |
| ) |
|
|
| demo.launch() |
|
|