"""
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 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)
@spaces.GPU(duration=180)
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 "" in out: # insurance if a thinking model slips in
out = out.split("")[-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(
"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."
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft())