Spaces:
Paused
Paused
| """ | |
| EBS LLM Playground - AI4PH workshop, Exercise 2 | |
| ------------------------------------------------- | |
| Two hands-on tasks: | |
| Task 1 Event information extraction (measles article; NER / EE / QA prompt styles) | |
| Task 2 Report generation: LLM only vs LLM + RAG (norovirus reports + reference shelf) | |
| Runs on ZeroGPU or dedicated GPU hardware without changes: `import spaces` and | |
| @spaces.GPU are active on ZeroGPU and harmless no-ops elsewhere. | |
| Models | |
| * PandemIQ-Llama - Llama-3.1-8B continually pre-trained on pandemic text (the model | |
| behind BEACON). A *base* model, so the code falls back to plain-prompt generation. | |
| * Qwen3-4B-Instruct-2507 - non-thinking instruct model (no <think> blocks). [ungated] | |
| * Gemma 4 E4B (google/gemma-4-E4B-it) - loaded via AutoProcessor. [GATED] | |
| * Llama-3.2-3B-Instruct. [GATED] | |
| GATED models need a one-time licence acceptance on the Space owner's Hugging Face | |
| account AND an HF_TOKEN secret set on the Space. Ungated models need neither. | |
| The three Task 1 prompt styles follow the benchmark tasks in BAND (Fu et al., AAAI 2024): | |
| Named Entity Recognition, Event Extraction, Question Answering. | |
| All news text is FICTIONAL and for training only. The reference shelf holds real, sourced facts. | |
| """ | |
| import math | |
| import os | |
| import re | |
| from collections import Counter | |
| import gradio as gr | |
| import spaces # required for ZeroGPU | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoProcessor, AutoTokenizer | |
| # --- model menu ------------------------------------------------------------- | |
| MODELS = { | |
| "PandemIQ-Llama (BEACON, 8B)": "Paschalidis-NOC-Lab/PandemIQ-Llama", | |
| "Qwen3 4B Instruct": "Qwen/Qwen3-4B-Instruct-2507", | |
| "Gemma 4 E4B": "google/gemma-4-E4B-it", | |
| "Llama-3.2 3B Instruct": "meta-llama/Llama-3.2-3B-Instruct", | |
| } | |
| MODEL_NAMES = list(MODELS.keys()) | |
| HF_TOKEN = os.environ.get("HF_TOKEN") # required for the gated models (Gemma, Llama) | |
| # --- lazy model cache ------------------------------------------------------- | |
| # Cache entry: (model, kind, tp) where kind is "processor" or "tokenizer" and | |
| # tp is the AutoProcessor / AutoTokenizer used for templating and decoding. | |
| _CACHE = {} | |
| def _load(model_id): | |
| if model_id in _CACHE: | |
| return _CACHE[model_id] | |
| load_kwargs = dict(torch_dtype=torch.bfloat16, low_cpu_mem_usage=True, token=HF_TOKEN) | |
| if "gemma-4" in model_id.lower(): | |
| tp, kind = AutoProcessor.from_pretrained(model_id, token=HF_TOKEN), "processor" | |
| else: | |
| tp, kind = AutoTokenizer.from_pretrained(model_id, token=HF_TOKEN), "tokenizer" | |
| model = AutoModelForCausalLM.from_pretrained(model_id, **load_kwargs) | |
| _CACHE[model_id] = (model, kind, tp) | |
| return _CACHE[model_id] | |
| def _apply_template(tp, content): | |
| """Render the prompt string. Chat models use their template (thinking disabled | |
| where supported); base models with no template get the raw prompt.""" | |
| messages = [{"role": "user", "content": content}] | |
| try: | |
| try: | |
| return tp.apply_chat_template(messages, tokenize=False, | |
| add_generation_prompt=True, enable_thinking=False) | |
| except TypeError: | |
| return tp.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) | |
| except Exception: | |
| return content # base / completion model (e.g. PandemIQ) | |
| def _generate(model_id, content, temperature, max_tokens): | |
| model, kind, tp = _load(model_id) | |
| model.to("cuda") | |
| tok = tp if kind == "tokenizer" else tp.tokenizer | |
| text = _apply_template(tp, content) | |
| inputs = (tp(text=text, return_tensors="pt") if kind == "processor" | |
| else tp(text, return_tensors="pt")).to("cuda") | |
| gen_kwargs = dict(max_new_tokens=int(max_tokens), pad_token_id=tok.eos_token_id) | |
| if temperature and float(temperature) > 0: | |
| gen_kwargs.update(do_sample=True, temperature=float(temperature)) | |
| else: | |
| gen_kwargs.update(do_sample=False) | |
| with torch.no_grad(): | |
| output = model.generate(**inputs, **gen_kwargs) | |
| out = tok.decode(output[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True).strip() | |
| if "</think>" in out: # insurance if a thinking model slips in | |
| out = out.split("</think>")[-1].strip() | |
| return out | |
| # ============================================================================ | |
| # SOURCE MATERIAL (FICTIONAL news - training only) | |
| # ============================================================================ | |
| # --- Task 1: single article, deliberately hard -------------------------------- | |
| # Traps: ambiguous city (Victoria - country never stated); three rash illnesses | |
| # named (measles / rubella / chickenpox); several different numbers, one of which | |
| # (9 chickenpox) belongs to a DIFFERENT disease; the one confirmed measles case is | |
| # never written as "1"; the investigation is still open (onward transmission | |
| # unknown); and several facts are explicitly unresolved ("could not be confirmed", | |
| # "results expected") and should come back as "not reported". | |
| ARTICLE = """[FICTIONAL - training example, not a real alert] | |
| Health unit investigates possible measles exposure at World Cup viewing events | |
| VICTORIA - Public health officials are investigating a possible measles exposure after | |
| an adult traveller who attended two large World Cup viewing events last week tested | |
| positive for the disease, the regional health authority said Tuesday. | |
| The traveller arrived from overseas on June 18 and developed a fever, cough and red eyes | |
| several days later. Clinicians first assessed the patient for rubella, sometimes called | |
| German measles, before laboratory testing confirmed measles on July 6. | |
| "We are still early in this investigation, and we do not yet know whether the virus has | |
| spread to anyone else in the community," said Dr. Alan Mercier, a medical health officer | |
| with the authority. | |
| Two further suspected cases are being investigated, with laboratory results expected | |
| later this week. Neither has been confirmed, officials said. | |
| The authority estimates that up to 150 people may have been exposed at a downtown arena | |
| on June 27, where about 4,000 people gathered to watch a televised match, and at a hotel | |
| lobby the next morning. Anyone who was there is asked to check their immunisation records. | |
| In a separate notice the same day, the authority said it was monitoring a cluster of | |
| 9 chickenpox cases at a local primary school, and stressed that the two situations are | |
| unrelated. Measles, rubella and chickenpox can all cause a rash but are caused by | |
| different viruses. | |
| The traveller's vaccination history could not be confirmed. A pop-up MMR vaccination | |
| clinic opens July 9, and contact tracing is under way. | |
| """ | |
| # --- Task 2: several reports on ONE evolving event --------------------------- | |
| # Source 1 is a genuine EARLY SIGNAL: symptoms and disruption, no investigation, | |
| # no laboratory result, no named pathogen. Later sources add and revise. Loosely | |
| # modelled on real reporting of norovirus at mass-gathering sporting events. | |
| CORPUS = """[FICTIONAL - training examples, not real alerts. Four reports on the SAME event.] | |
| [Source 1 - 22 June, City Herald (local news)] | |
| A national team cancelled its open training session on Sunday after several players and | |
| support staff reported feeling unwell overnight, the team said in a brief statement. The | |
| statement did not say how many people were affected or give a cause, only that those | |
| involved were "resting and being monitored by team medical staff." Two supporters' groups | |
| posted online that some fans who attended the same downtown fan festival on Saturday had | |
| also reported stomach upset. Local health authorities had not commented as of Sunday | |
| evening, and no investigation had been announced. | |
| [Source 2 - 24 June, Host City Health Department (official statement)] | |
| Laboratory testing has confirmed norovirus in 11 of 18 samples collected from people who | |
| became unwell after attending the fan festival or staying at two team hotels. Forty-three | |
| people have so far reported symptoms, including 16 accredited team personnel. Investigators | |
| are examining food service at the festival site; the venue's water supply was tested and | |
| cleared. A department spokesperson described the situation as "a small number of linked | |
| cases, not an outbreak," and said no additional public health measures were needed at this | |
| time. Most people reported mild illness lasting one to two days. | |
| [Source 3 - 25 June, National Wire Service] | |
| The number of people reporting gastrointestinal illness linked to fan festival venues has | |
| risen to 78, including 5 admitted to hospital for dehydration, health officials said | |
| Wednesday. A second national team has withdrawn players from training. Organisers said a | |
| match scheduled for Thursday would go ahead, and that enhanced cleaning and hand-hygiene | |
| measures had been introduced across accredited venues. Officials repeated that the risk to | |
| the general public was low. Norovirus has disrupted previous major sporting events, | |
| including the 2018 Winter Olympics. | |
| [Source 4 - 25 June, social media posts (unverified)] | |
| Posts shared thousands of times claimed that "over 1,000 fans" had been hospitalised and | |
| that the city's tap water was contaminated, urging visitors to avoid all downtown | |
| restaurants and drink only bottled water. The health department said the claims were false, | |
| that the water supply had been tested and cleared, and that the number of people admitted | |
| to hospital remained in the single digits. | |
| """ | |
| # ============================================================================ | |
| # REFERENCE SHELF (real, sourced background for RAG). Includes measles AND | |
| # norovirus AND general mass-gathering material so participants can watch the | |
| # retriever select the relevant subset. | |
| # ============================================================================ | |
| REFERENCES = [ | |
| {"topic": "norovirus", "tag": "CDC norovirus", | |
| "title": "CDC - About norovirus / Yellow Book", | |
| "url": "https://www.cdc.gov/norovirus/about/index.html", | |
| "text": ("Symptoms usually begin 12 to 48 hours after exposure to norovirus, and most " | |
| "people recover in 1 to 3 days. Common symptoms are diarrhoea, vomiting, nausea " | |
| "and stomach pain. Dehydration is the main complication and can require medical " | |
| "attention, especially in young children and older adults.")}, | |
| {"topic": "norovirus", "tag": "CDC norovirus", | |
| "title": "CDC - Norovirus (Yellow Book)", | |
| "url": "https://www.cdc.gov/yellow-book/hcp/travel-associated-infections-diseases/norovirus.html", | |
| "text": ("Norovirus is the leading cause of vomiting, diarrhoea and foodborne illness in " | |
| "the United States, causing about half of all foodborne disease outbreaks. It is " | |
| "highly contagious and spreads through contaminated food or water, contaminated " | |
| "surfaces, and close contact with an infected person.")}, | |
| {"topic": "norovirus", "tag": "Outbreak definition", | |
| "title": "Public health definition of a norovirus outbreak", | |
| "url": "https://www.cdph.ca.gov/Programs/CID/DCDC/Pages/Norovirus.aspx", | |
| "text": ("An outbreak of norovirus is defined as two or more people becoming ill from a " | |
| "common source or common exposure. Outbreaks commonly occur in restaurants, " | |
| "schools, cruise ships, healthcare facilities and other settings where large " | |
| "groups share food or common spaces.")}, | |
| {"topic": "norovirus", "tag": "Norovirus control", | |
| "title": "Norovirus control measures", | |
| "url": "https://www.cdph.ca.gov/Programs/CID/DCDC/Pages/Norovirus.aspx", | |
| "text": ("A person is most infectious while symptomatic and for at least 2 days after " | |
| "symptoms stop. People who are ill should not prepare food or care for others " | |
| "during illness and for at least 48 hours afterwards. Handwashing with soap and " | |
| "water is more effective than alcohol-based hand sanitiser against norovirus.")}, | |
| {"topic": "general", "tag": "CDC mass gatherings", | |
| "title": "CDC Yellow Book - Mass gatherings", | |
| "url": "https://www.cdc.gov/yellow-book/hcp/travel-for-work-other/mass-gatherings.html", | |
| "text": ("Mass gatherings bring large numbers of people to one location for a shared " | |
| "purpose, in numbers large enough to strain local resources. The 2026 FIFA World " | |
| "Cup, hosted by Canada, Mexico and the United States, is expected to draw around " | |
| "3 million attendees. Crowd density, not only total attendance, drives spread.")}, | |
| {"topic": "general", "tag": "CDC mass gatherings", | |
| "title": "CDC Yellow Book - Mass gatherings", | |
| "url": "https://www.cdc.gov/yellow-book/hcp/travel-for-work-other/mass-gatherings.html", | |
| "text": ("Travellers can import an infection to a mass-gathering host site and infect other " | |
| "attendees and the local population, and can export diseases internationally after " | |
| "leaving. Host locations differ in their capacity to detect and respond to public " | |
| "health emergencies, which affects surveillance during an event.")}, | |
| {"topic": "measles", "tag": "CDC measles", | |
| "title": "CDC - Measles data and research", | |
| "url": "https://www.cdc.gov/measles/data-research/index.html", | |
| "text": ("Measles is highly contagious and spreads through the air when an infected person " | |
| "coughs or sneezes. Symptoms include high fever, cough, runny nose, red eyes and a " | |
| "rash appearing several days after the first symptoms. A substantial share of " | |
| "reported US cases are hospitalised, highest among children under 5; complications " | |
| "include pneumonia and encephalitis.")}, | |
| {"topic": "measles", "tag": "PHAC measles surveillance", | |
| "title": "Public Health Agency of Canada - Measles and rubella weekly monitoring", | |
| "url": "https://health-infobase.canada.ca/measles-rubella/", | |
| "text": ("Measles is a nationally notifiable disease in Canada, reported weekly through the " | |
| "Canadian Measles and Rubella Surveillance System. All laboratory-confirmed cases, " | |
| "and cases epidemiologically linked to a confirmed case, must be notified " | |
| "nationally; reporting of probable cases is at provincial discretion. Measles was " | |
| "eliminated in Canada in 1998, but importations continue to cause cases.")}, | |
| ] | |
| # ============================================================================ | |
| # VISIBLE RETRIEVER (keyword TF-IDF with topic filtering; no extra deps) | |
| # ============================================================================ | |
| _STOP = set("""a an the and or of to in for on at by with from as is are was were be been being | |
| this that these those it its their there here has have had not no we you they he she said says | |
| about into over under more most other some such than then so if but which who whom whose what | |
| when where why how all any each own same only per may can could will would should""".split()) | |
| def _tokens(text): | |
| return [w for w in re.findall(r"[a-z0-9]+", text.lower()) if w not in _STOP and len(w) > 2] | |
| _DOC_TOKENS = [set(_tokens(r["text"] + " " + r["title"])) for r in REFERENCES] | |
| _N = len(REFERENCES) | |
| _IDF = {} | |
| for _d in _DOC_TOKENS: | |
| for _w in _d: | |
| _IDF[_w] = _IDF.get(_w, 0) + 1 | |
| _IDF = {w: math.log(1 + _N / c) for w, c in _IDF.items()} | |
| TOPIC_KEYWORDS = { | |
| "norovirus": ["norovirus", "gastrointestinal", "gastroenteritis", "vomiting", | |
| "diarrhoea", "diarrhea", "stomach", "foodborne"], | |
| "measles": ["measles", "rubella", "mmr", "immunisation", "immunization"], | |
| } | |
| def _query_topics(query): | |
| low = query.lower() | |
| return {t for t, kws in TOPIC_KEYWORDS.items() if any(kw in low for kw in kws)} | |
| def retrieve(query, k=4): | |
| """Filter the shelf by topic, then rank the rest with query-frequency-weighted | |
| TF-IDF normalised by chunk length (so long passages do not win on generic words).""" | |
| topics = _query_topics(query) | |
| q_tf = Counter(_tokens(query)) | |
| scored = [] | |
| for i, doc in enumerate(_DOC_TOKENS): | |
| topic = REFERENCES[i].get("topic", "general") | |
| if topics and topic != "general" and topic not in topics: | |
| continue | |
| overlap = set(q_tf) & doc | |
| raw = sum(q_tf[w] * (_IDF.get(w, 0.0) ** 2) for w in overlap) | |
| norm = math.sqrt(sum(_IDF.get(w, 0.0) ** 2 for w in doc)) or 1.0 | |
| scored.append((raw / norm, i)) | |
| scored.sort(reverse=True) | |
| return [(REFERENCES[i], s) for s, i in scored[:k] if s > 0] | |
| def format_refs_for_prompt(hits): | |
| return "\n\n".join(f"[{i+1}] ({r['tag']}) {r['text']}" for i, (r, _s) in enumerate(hits)) | |
| def format_refs_for_display(hits): | |
| if not hits: | |
| return "No reference passages matched." | |
| return "\n\n".join( | |
| f"[{i+1}] {r['title']} (relevance {s:.1f})\n{r['text']}\nSource: {r['url']}" | |
| for i, (r, s) in enumerate(hits) | |
| ) | |
| # ============================================================================ | |
| # PROMPTS | |
| # ============================================================================ | |
| # --- Task 1: three framings from BAND (Fu et al., AAAI 2024) ----------------- | |
| NER_PROMPT = """You are helping build a surveillance database. Read the news article and | |
| list every mention of each entity type below, exactly as it appears in the text. List a | |
| mention once. If a type has no mention, write "none". Do not interpret or choose between | |
| mentions - list them all. | |
| DISEASE: | |
| PATHOGEN: | |
| LOCATION: | |
| DATE: | |
| CASE_COUNT: | |
| SYMPTOM: | |
| CONTROL_MEASURE: | |
| ARTICLE: | |
| {SOURCE} | |
| """ | |
| EE_PROMPT = """You are extracting ONE structured outbreak record for a surveillance database. | |
| The article may mention more than one health event; identify the SINGLE main outbreak the | |
| article is about, and fill the template for THAT event only. Do not include figures or details | |
| that belong to a different disease or a different event. | |
| Use ONLY this article. If a field is not stated, write "not reported". Do not guess. | |
| Main event (one phrase): | |
| Disease: | |
| Pathogen: | |
| Location (city; country only if stated): | |
| Date of report: | |
| Exposure date(s) and place(s): | |
| Confirmed cases (number): | |
| Suspected cases (number): | |
| People potentially exposed (number): | |
| Vaccination status of the case(s): | |
| Control measures: | |
| Onward transmission established? (yes / no / unknown): | |
| ARTICLE: | |
| {SOURCE} | |
| """ | |
| QA_PROMPT = """Answer each question using ONLY the article below. If the article does not | |
| state the answer, reply exactly "not reported". Keep each answer to one short line. | |
| 1. Which single disease is the subject of the investigation? | |
| 2. What pathogen causes that disease? | |
| 3. In which city is the event, and in which country? | |
| 4. How many cases of the investigated disease are laboratory-confirmed? | |
| 5. How many cases are suspected but not yet confirmed? | |
| 6. How many people are estimated to have been exposed? | |
| 7. On what date and at what place did the exposure happen? | |
| 8. What is the vaccination status of the confirmed case? | |
| 9. What control measures have been announced? | |
| 10. Has spread to other people been confirmed? | |
| ARTICLE: | |
| {SOURCE} | |
| """ | |
| TASK1_PROMPTS = { | |
| "Named entity recognition (NER)": NER_PROMPT, | |
| "Event extraction (EE)": EE_PROMPT, | |
| "Question answering (QA)": QA_PROMPT, | |
| } | |
| TASK1_STYLES = list(TASK1_PROMPTS.keys()) | |
| # --- Task 2: one report prompt, run with and without retrieved references ---- | |
| REPORT_PROMPT = """You are drafting a short situation report for a public health team during a | |
| mass-gathering event, based on the news reports below. | |
| Write these sections: | |
| - Headline (one line) | |
| - Situation: what is happening, where, and since when | |
| - Case counts: give confirmed vs reported/suspected numbers, and note how the numbers change | |
| across the reports and any figures that conflict | |
| - Likely cause (say if it is disputed) | |
| - Is this an outbreak? Apply the standard public health definition and justify your answer. | |
| - Is the timeline consistent with the usual incubation period for this pathogen? | |
| - Recommended control measures | |
| - Source reliability: which reports are official, and which are unverified | |
| - Concern level (low / moderate / high) with one sentence of justification | |
| Use only what the reports and any reference material support. Flag disagreements and | |
| anything you cannot verify. Do not invent numbers. | |
| NEWS REPORTS: | |
| {SOURCE} | |
| """ | |
| # ============================================================================ | |
| # INFERENCE WRAPPERS | |
| # ============================================================================ | |
| def _compose(prompt: str, source: str) -> str: | |
| return prompt.replace("{SOURCE}", source) if "{SOURCE}" in prompt \ | |
| else prompt.strip() + "\n\n--- SOURCE ---\n" + source | |
| def _err(model_name, e): | |
| return ( | |
| f"⚠️ Could not get a response from '{model_name}'.\n\n{e!r}\n\n" | |
| "First runs download the model weights and can take a few minutes. If this persists: " | |
| "gated models (Gemma 4, Llama-3.2) need their licence accepted on the Space owner's " | |
| "Hugging Face account and an HF_TOKEN secret set on the Space; the model may also be " | |
| "too large for the available GPU memory." | |
| ) | |
| def run_one(model_name, full_prompt, temperature, max_tokens): | |
| try: | |
| return _generate(MODELS[model_name], full_prompt, temperature, max_tokens) \ | |
| or "(empty response)" | |
| except Exception as e: # noqa: BLE001 | |
| return _err(model_name, e) | |
| def run_task1(model_a, model_b, prompt, temperature, max_tokens): | |
| content = _compose(prompt, ARTICLE) | |
| return (run_one(model_a, content, temperature, max_tokens), | |
| run_one(model_b, content, temperature, max_tokens)) | |
| def run_task2(model_name, prompt, temperature, max_tokens): | |
| """Same model and prompt, run twice: without references, then with retrieved references.""" | |
| plain = _compose(prompt, CORPUS) | |
| hits = retrieve(CORPUS, k=4) | |
| grounded = ( | |
| plain | |
| + "\n\nREFERENCE MATERIAL (authoritative public health sources):\n" | |
| + format_refs_for_prompt(hits) | |
| + "\n\nUse the reference material for background and technical claims (the outbreak " | |
| "definition, incubation period, and control measures). Put the reference number in " | |
| "square brackets, e.g. [1], after any claim drawn from it. Do not add facts that are " | |
| "in neither the news reports nor the reference material." | |
| ) | |
| return (run_one(model_name, plain, temperature, max_tokens), | |
| run_one(model_name, grounded, temperature, max_tokens), | |
| format_refs_for_display(hits)) | |
| # ============================================================================ | |
| # UI | |
| # ============================================================================ | |
| INTRO = """ | |
| # 🦠 EBS LLM Playground | |
| ### AI4PH workshop · *From online news to early outbreak detection* · Exercise 2 | |
| Two jobs an event-based-surveillance (EBS) system does with a language model: | |
| **turn a news story into structured data**, and **write a situation report from several sources**. | |
| Edit the prompts, run the models, and check every output against the source text beside it. | |
| > Models run on this Space's GPU; the **first run of each downloads its weights** (a few minutes). | |
| > **Gemma 4** and **Llama-3.2** are gated: accept their licence on Hugging Face and set an HF_TOKEN secret. | |
| > News text is **fictional**, for training only. The reference shelf holds real, sourced material. | |
| """ | |
| TASK1_INTRO = """ | |
| **Task 1 · Event extraction — turning a news story into structured data.** Extract the key | |
| facts from one article, asked for in **three different ways**, and see how the framing changes | |
| the result. Pick a **prompt style**, run it on **two models**, then read the article and check | |
| every field. | |
| - **NER** lists every mention of each entity type (high recall, no interpretation). | |
| - **Event extraction (EE)** builds one structured record and forces the model to choose the main event. | |
| - **QA** asks targeted questions, one answer each (natural, but answers need not be consistent). | |
| This article is deliberately hard: it names **three rash illnesses**, gives several different | |
| numbers, never states the **country**, and is written while the investigation is still open. | |
| Watch whether each model picks the **right disease**, keeps **confirmed / suspected / exposed** | |
| apart, and says *"not reported"* rather than guessing. | |
| """ | |
| TASK2_INTRO = """ | |
| **Task 2 · Report generation: LLM only vs LLM + RAG.** The same model and prompt run **twice** - | |
| once on the news alone, once with relevant passages **retrieved from a reference shelf** of real | |
| public health sources. The retrieval step is shown, so you can see exactly what was added. | |
| The four reports track one event over four days, starting with an **early signal**: people unwell | |
| and a training session cancelled, with no lab result and no investigation yet. Later reports revise | |
| the numbers and one is an unverified rumour. | |
| Compare the two reports. Look hardest at the background the news never supplies - whether this | |
| meets the **definition of an outbreak**, whether the timeline fits **norovirus's incubation | |
| period**, and which **control measures** apply. That is where the ungrounded model hedges or | |
| invents, and where retrieval earns its keep. | |
| """ | |
| def build_task1(): | |
| with gr.Tab("Task 1 · Event extraction"): | |
| gr.Markdown(TASK1_INTRO) | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=1): | |
| gr.Markdown("**News article** - read this to check the output") | |
| gr.Textbox(value=ARTICLE, lines=24, interactive=False, | |
| buttons=["copy"], container=False) | |
| with gr.Column(scale=1): | |
| style = gr.Radio(TASK1_STYLES, value=TASK1_STYLES[1], | |
| label="Prompt style (from the BAND benchmark)") | |
| prompt = gr.Textbox(value=TASK1_PROMPTS[TASK1_STYLES[1]], lines=16, | |
| label="Prompt (edit me!)", buttons=["copy"]) | |
| with gr.Row(): | |
| model_a = gr.Dropdown(MODEL_NAMES, value=MODEL_NAMES[0], label="Model A") | |
| model_b = gr.Dropdown(MODEL_NAMES, value=MODEL_NAMES[1], label="Model B") | |
| with gr.Accordion("Advanced settings", open=False): | |
| temperature = gr.Slider(0.0, 1.0, value=0.2, step=0.1, label="Temperature") | |
| max_tokens = gr.Slider(128, 2048, value=800, step=64, label="Max tokens") | |
| with gr.Row(): | |
| run_btn = gr.Button("Run ▶", variant="primary") | |
| reset_btn = gr.Button("Reset prompt") | |
| with gr.Row(equal_height=True): | |
| out_a = gr.Textbox(label="Model A output", lines=18, buttons=["copy"]) | |
| out_b = gr.Textbox(label="Model B output", lines=18, buttons=["copy"]) | |
| style.change(lambda s: TASK1_PROMPTS[s], inputs=style, outputs=prompt) | |
| reset_btn.click(lambda s: TASK1_PROMPTS[s], inputs=style, outputs=prompt) | |
| run_btn.click(run_task1, inputs=[model_a, model_b, prompt, temperature, max_tokens], | |
| outputs=[out_a, out_b]) | |
| def build_task2(): | |
| with gr.Tab("Task 2 · Report: LLM vs LLM + RAG"): | |
| gr.Markdown(TASK2_INTRO) | |
| with gr.Row(equal_height=True): | |
| with gr.Column(scale=1): | |
| gr.Markdown("**News reports (4 sources)** - read these to check the output") | |
| gr.Textbox(value=CORPUS, lines=26, interactive=False, | |
| buttons=["copy"], container=False) | |
| with gr.Column(scale=1): | |
| prompt = gr.Textbox(value=REPORT_PROMPT, lines=18, | |
| label="Prompt (edit me!)", buttons=["copy"]) | |
| model = gr.Dropdown(MODEL_NAMES, value=MODEL_NAMES[1], | |
| label="Model (the same one is used for both reports)") | |
| with gr.Accordion("Advanced settings", open=False): | |
| temperature = gr.Slider(0.0, 1.0, value=0.2, step=0.1, label="Temperature") | |
| max_tokens = gr.Slider(128, 2048, value=900, step=64, label="Max tokens") | |
| with gr.Row(): | |
| run_btn = gr.Button("Run both ▶", variant="primary") | |
| reset_btn = gr.Button("Reset prompt") | |
| with gr.Accordion("📚 The reference shelf (what RAG can draw on)", open=False): | |
| gr.Textbox( | |
| value="\n\n".join(f"({r['tag']}) {r['text']}\nSource: {r['url']}" | |
| for r in REFERENCES), | |
| lines=14, interactive=False, container=False, buttons=["copy"]) | |
| with gr.Row(equal_height=True): | |
| out_plain = gr.Textbox(label="① LLM only (news reports alone)", lines=20, | |
| buttons=["copy"]) | |
| out_rag = gr.Textbox(label="② LLM + RAG (news + retrieved references)", lines=20, | |
| buttons=["copy"]) | |
| gr.Markdown("**What the retriever pulled from the shelf** (the RAG step, made visible)") | |
| retrieved = gr.Textbox(label="Retrieved passages", lines=10, buttons=["copy"]) | |
| reset_btn.click(lambda: REPORT_PROMPT, outputs=prompt) | |
| run_btn.click(run_task2, inputs=[model, prompt, temperature, max_tokens], | |
| outputs=[out_plain, out_rag, retrieved]) | |
| with gr.Blocks(title="EBS LLM Playground") as demo: | |
| gr.Markdown(INTRO) | |
| build_task1() | |
| build_task2() | |
| gr.Markdown( | |
| "<sub>Models run on this Space's GPU. PandemIQ-Llama (c) its authors " | |
| "(MIT / Llama-3.1 licence). Prompt styles follow BAND (Fu et al., AAAI 2024). " | |
| "News text is fictional and for training only.</sub>" | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Soft()) |