Spaces:
Sleeping
Sleeping
| title: Loan Collection | |
| emoji: π | |
| colorFrom: green | |
| colorTo: yellow | |
| sdk: gradio | |
| sdk_version: 6.14.0 | |
| python_version: '3.13' | |
| app_file: app.py | |
| pinned: false | |
| # π LLM Comparison Playground | |
| Compare several chat backends **side-by-side** on the same system prompt and | |
| conversation, pick the best reply per turn, and save everything for later | |
| analysis. All identifying values (endpoints, model names, labels, prompts) are | |
| **configuration** β fill them in via environment variables / Space secrets; | |
| nothing company-specific is baked into the code. | |
| ## Login | |
| The UI login is toggleable. Set `ENABLE_AUTH=false` to disable it (open access), | |
| or leave it on (the default) to require a password. Credentials are read from the | |
| environment (`APP_USERNAME` / `APP_PASSWORD`, or `APP_AUTH` for multiple accounts) | |
| and never appear in the code or the UI. If auth is on but nothing is set, a default | |
| dev login (`user` / `slm-demo`) is used and a warning is printed β set real | |
| credentials before sharing. | |
| ## Configure the backends | |
| Set the values via environment variables / Space secrets (see `.env.example`). The | |
| defaults in `config.py` are neutral placeholders. | |
| | Variable | What it is | | |
| |---|---| | |
| | `BACKEND_API_BASE_URL` | OpenAI-compatible chat-completions URL for Backend A (auth via `x-api-key`) | | |
| | `BACKEND_API_KEY` | API key for Backend A (secret) | | |
| | `BACKEND_A_LABEL` / `BACKEND_A_ENDPOINT_ID` / `BACKEND_A_MODEL` | Backend A label, endpoint id, model | | |
| | `ENABLE_BACKEND_B` | Feature flag β show the second backend for side-by-side comparison (default `false`) | | |
| | `AZURE_ENDPOINT` / `AZURE_API_KEY` / `AZURE_DEPLOYMENT` | Azure OpenAI endpoint, key (secret), deployment for Backend B | | |
| | `BACKEND_B_LABEL` | Backend B label | | |
| | `STT_API_KEY` | Optional key for voice input (falls back to `BACKEND_API_KEY`) | | |
| | `ENABLE_AUTH` / `APP_USERNAME` / `APP_PASSWORD` / `APP_AUTH` | UI login toggle + credentials | | |
| | `DATA_DIR` | Where logs are written (set to `/data` on HF persistent storage) | | |
| By default only **Backend A** (the conversational model) is shown. Set | |
| `ENABLE_BACKEND_B=true` to turn on the second backend and the side-by-side | |
| "Preferred response" picker. To compare more or fewer models, edit the `BACKENDS` | |
| list in `config.py` (anonymous labels are assigned automatically: A, B, C, β¦). | |
| ## Presets | |
| A **Preset** dropdown in the sidebar loads ready-made system-prompt + intro pairs, | |
| each with its own fixed set of fill-in **call-detail variables** (customer name, | |
| amount, dates, etc.). Selecting one fills the fields and starts a fresh session. | |
| Edit the list in `config.py` (`PRESETS`). | |
| ## Voice input | |
| Switch **Input mode** to π€ Voice to record from your mic; the recording is | |
| transcribed into the message box (via the `ringglabs` Speech-to-Text SDK) so you | |
| can edit it before sending. The STT key comes from `STT_API_KEY`, or | |
| `BACKEND_API_KEY` if that's unset. | |
| ## How it works | |
| - One shared **system prompt** + **intro message** is broadcast to every backend. | |
| - Each user message is sent to all backends; **each keeps its own thread** and | |
| continues from its own replies (threads diverge on assistant turns). | |
| - All backends **stream concurrently** into their own panel, with per-model | |
| **token counts** (input / output / cached). | |
| - Pick the **preferred** response (when comparing 2+ backends) and click | |
| **Save preferred** to record it. | |
| ## Anonymity | |
| Panels are shown **anonymously** ("Response A", "Response B", β¦) so the model | |
| identity never biases your pick. The mapping back to the real model is saved with | |
| each preferred pick (server-side only β users never see it). | |
| ## Where data is saved | |
| All under `DATA_DIR` (`./data` locally, `/data` on HF persistent storage): | |
| - `sessions/<session_id>.json` β full session, rewritten every turn. | |
| - `requests.jsonl` β one line per turn, **every request made** (appended | |
| unconditionally, including turns that errored). | |
| - `comparisons.jsonl` β one line per turn you explicitly **Save preferred**. | |
| All are gitignored by default (see `.gitignore`). | |
| ## Run locally | |
| ```bash | |
| pip install -r requirements.txt | |
| cp .env.example .env # fill in the values | |
| python app.py | |
| ``` | |
| If a backend's key is missing, only that panel shows an error β the others still work. | |
| ## Deploy to Hugging Face Spaces | |
| 1. Push these files to the Space (the `.env` and `data/` contents are gitignored | |
| and must NOT be pushed). | |
| 2. In **Settings β Variables and secrets**, add the backend/Azure keys and any | |
| login credentials as **secrets**, and the non-secret URLs/labels as **variables**. | |
| 3. To keep saved logs across restarts, enable **persistent storage** and set | |
| `DATA_DIR=/data`. (Without it, the Space filesystem is wiped on every restart.) | |
| 4. The Space builds from `requirements.txt` and runs `app.py`. | |
| ## Project structure | |
| ``` | |
| . | |
| βββ app.py # Gradio entrypoint (side-by-side comparison UI) | |
| βββ config.py # env-driven BACKENDS, defaults, DATA_DIR, presets | |
| βββ src/ | |
| β βββ clients.py # backend-agnostic streaming (custom + azure) + STT | |
| β βββ display.py # tool-call/HTML-safe rendering | |
| β βββ storage.py # session JSON + requests/comparisons logs | |
| βββ requirements.txt | |
| βββ .env.example | |
| βββ data/ # logs (gitignored) | |
| ``` | |