It supports two workflows:
Streamlit UI (app.py) for interactive runs and analysis
Headless runner (headless_runner.py) for scheduled and automated jobs
The goal is to run the same prompt across multiple LLMs and compare output in a normalized format with long-term storage and history.
Tech stack
- Python 3.10+
- Streamlit
- Bright Data Scraping APIs
- Supabase
Prerequisites
Python 3.10+ recommended
A Bright Data account with Web Scraper API access
A Supabase account and project
Setup
1) Create and activate a virtual environment
Linux / macOS:
python -m venv .venv
source .venv/bin/activate
Windows (PowerShell):
python -m venv .venv
..venvScriptsActivate.ps1
2) Install dependencies
pip install -r requirements.txt
3) Configure environment variables
Create a .env file in the project root:
BRIGHTDATA_API_TOKEN=your_brightdata_api_token_here
SUPABASE_URL=your_supabase_project_url
SUPABASE_API_TOKEN=your_supabase_api_key
These credentials are required for database persistence and scheduling.
4) Creating the database
Create the llm_runs table within Supabase.
create table public.llm_runs (
id bigint generated by default as identity primary key,
created_at_ts bigint not null, -- unix seconds
model_name text not null,
prompt text not null,
country text null,
target_phrase text null,
mentioned boolean not null default false,
payload jsonb not null
);
create index if not exists llm_runs_created_at_ts_idx
on public.llm_runs (created_at_ts);
create index if not exists llm_runs_model_idx
on public.llm_runs (model_name);
create index if not exists llm_runs_target_idx
on public.llm_runs (target_phrase);
Create the prompts table.
create table public.prompts (
id bigint generated by default as identity primary key,
created_at_ts bigint not null,
prompt text not null,
is_active boolean not null default true
);
create index if not exists prompts_created_at_ts_idx
on public.prompts (created_at_ts desc);
create index if not exists prompts_active_idx
on public.prompts (is_active);
Create the schedules table.
create table public.schedules (
id bigint generated by default as identity primary key,
name text not null,
is_enabled boolean not null default true,
next_run_ts bigint not null,
last_run_ts bigint null,
models jsonb not null default '[]'::jsonb,
country text null,
target_phrase text null,
only_active_prompts boolean not null default true,
locked_until_ts bigint null,
lock_owner text null,
repeat_every_seconds bigint not null default 86400
);
create index if not exists schedules_due_idx
on public.schedules (is_enabled, next_run_ts);
create index if not exists schedules_lock_idx
on public.schedules (locked_until_ts);
Starting the application
Start the headless runner
Open a terminal and run:
python headless_runner.py
This process handles scheduled jobs and database persistence.
Launch the Streamlit UI
Open a second terminal and run:
streamlit run app.py
The UI is used for interactive runs, reporting, and schedule management.
Notes
-
Uses snapshot-based polling via Bright Data
-
Results are normalized by Bright Data
-
All runs are persisted in Supabase
-
Failures are isolated per model
-
The UI and headless runner are designed to run in parallel