Metadata-Version: 2.4
Name: chATLAS_Frontend
Version: 1.1.1
Summary: Code to run the frontend app
License-Expression: Apache-2.0
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: chatlas-chains>=0.3.1
Requires-Dist: chatlas-embed>=0.1.19
Requires-Dist: flask
Requires-Dist: authlib
Requires-Dist: markdown
Requires-Dist: sqlalchemy
Requires-Dist: markupsafe
Requires-Dist: psutil
Requires-Dist: matplotlib
Requires-Dist: seaborn
Dynamic: license-file

# chATLAS Frontend

Flask frontend for chATLAS, including Search mode, Assistant mode, streaming
responses, usage logging, and intent-router enforcement.

- Production: <https://chatlas-flask-chatlas.app.cern.ch>
- Staging: <https://chatlas-staging-chatlas.app.cern.ch>

## Router Architecture

The frontend includes an intent router that classifies every authenticated
request before retrieval or answer generation. See
[Intent Router](docs/INTENT_ROUTER.md) for the full reviewer-oriented design,
function reference, rollout behavior, and test checklist.

At a high level, every routed request follows this sequence:

1. Create one row in `queries` using `INSERT ... RETURNING id`.
2. Load up to five previous query/response pairs for the thread.
3. Call the LiteLLM router without passing the selected UI mode.
4. Store one row in `router_decisions` for the query.
5. Combine the returned intent with Search or Assistant mode.
6. Run retrieval, return a terminal response, or fail open after a router error.
7. Store sources and the response against the original query ID.

The active router intents are:

| Intent | Enforcement behavior |
|---|---|
| `hep_atlas` | Continue in the selected Search or Assistant mode |
| `person_lookup` | Return authoritative ATLAS directory guidance without retrieval |
| `dangerous` | Return a safety refusal without retrieval |
| `out_of_scope` | Explain the ATLAS/HEP scope without retrieval |
| `unknown` | Ask for clarification without retrieval |

MC-request routing is intentionally disabled until the dedicated MC workflow is
available. MC production and job-option prompts remain ordinary `hep_atlas`
requests and continue through the selected Search or Assistant mode.

Router errors are logged and fail open into the selected mode. Confidence is
displayed and logged but is not used as a routing threshold. There is
deliberately no separate router-demo application; local validation uses the
real frontend so it exercises database logging, thread context, retrieval
suppression, streaming, and the visible status experience.

## Environment Variables

| Variable | Required | Purpose |
|---|---|---|
| `CHATLAS_DB_PASSWORD` | Yes | CERN DBOD vector-store and logging database password |
| `CHATLAS_EMBEDDING_MODEL_PATH` | Recommended locally | Local embedding-model directory; otherwise Hugging Face is used |
| `CHATLAS_OPENAI_KEY` | For the default Assistant model | OpenAI generation key |
| `CHATLAS_GROQ_KEY` | Only when selecting Groq | Groq generation key |
| `CHATLAS_GROQ_BASE_URL` | Only for the CERN Groq proxy | Groq-compatible endpoint |
| `CHATLAS_CHAINS_LITELLM_KEY` | Router on | CERN LiteLLM router key |
| `CHATLAS_CHAINS_LITELLM_PROXY` | Outside CERN network | SOCKS URL, normally `socks5h://localhost:1080` |
| `CHATLAS_ROUTER_MODE` | No | `off`, `shadow`, or `enforce`; default `off` |
| `CHATLAS_ROUTER_MODEL` | No | Router model; default `gpt-oss-20b` |
| `CHATLAS_ROUTER_SHOW_STATUS` | No | Show router status text; default `false` |
| `FLASK_PORT` | No | Local HTTP port; default `8080` |

Never commit `.env` files or print secret values. GitLab variables and
OpenShift secrets are separate stores; configuring one does not configure the
other.

## Local Full-Stack Test

These instructions run the actual frontend against CERN databases and
LiteLLM. Local mode bypasses OAuth and writes usage data to `log_test`, not
`log_prod`. Application startup creates or updates the logging tables,
including `queries.thread_id` and `router_decisions`.

You need a CERN account with LXPLUS access, the shared DBOD password, a CERN
LiteLLM key, and a generation-model key. Ask the chATLAS developers through
[email](mailto:atlas-comp-ml-chatlas-developers@cern.ch) or
[Mattermost](https://mattermost.web.cern.ch/ml-atlas/channels/chatlas-ai-assistant-development)
if access is missing. Do not send secret values in issues, merge requests, or
chat messages.

### 1. Install

From this directory:

```bash
uv sync --locked
```

### 2. Obtain the embedding model

On a machine with EOS access:

```bash
scp -r \
  <USERNAME>@lxplus.cern.ch:/eos/atlas/atlascerngroupdisk/phys-mlf/Chatlas/multi-qa-mpnet-base-dot-v1-ATLAS-TALK \
  /path/to/models/
```

Set `CHATLAS_EMBEDDING_MODEL_PATH` to the copied model directory.

### 3. Create a local `.env`

Create `chATLAS_Frontend/.env` locally:

```bash
CHATLAS_DB_PASSWORD=...
CHATLAS_EMBEDDING_MODEL_PATH=/absolute/path/to/multi-qa-mpnet-base-dot-v1-ATLAS-TALK
CHATLAS_OPENAI_KEY=...
CHATLAS_CHAINS_LITELLM_KEY=...

CHATLAS_ROUTER_MODE=shadow
CHATLAS_ROUTER_MODEL=gpt-oss-20b
CHATLAS_ROUTER_SHOW_STATUS=true
CHATLAS_CHAINS_LITELLM_PROXY=socks5h://localhost:1080

FLASK_PORT=8080
```

The repository `.gitignore` excludes `.env`.
`CHATLAS_GROQ_KEY` and `CHATLAS_GROQ_BASE_URL` are optional unless Groq is
selected in the UI.

### 4. Create CERN tunnels

Outside the CERN network, keep this command running:

```bash
ssh -N \
  -L 6624:dbod-chatlas.cern.ch:6624 \
  -L 6606:dbod-chatlas-cds.cern.ch:6606 \
  -D 1080 \
  <USERNAME>@lxplus.cern.ch
```

Port `6624` covers the main vector stores and `log_test`; port `6606` covers
CDS. The SOCKS proxy on `1080` carries LiteLLM traffic.

### 5. Launch

```bash
PYTHONPATH=.:../chATLAS_Chains:../chATLAS_Embed:../chATLAS_Scrape \
uv run --env-file .env python chATLAS_Frontend/launch.py \
  --local-mode \
  --db-host cern-prod \
  --port-forwarding
```

Open <http://127.0.0.1:8080>.

The `PYTHONPATH` setting makes this source checkout use its sibling chATLAS
packages. Released deployments obtain those packages from their pinned
package dependencies.

If already on the CERN network, omit `--port-forwarding`, remove
`CHATLAS_CHAINS_LITELLM_PROXY`, and connect directly.

## Local Test Plan

Start in `shadow` mode. Every request should still use the selected mode.

| Prompt | Expected router status |
|---|---|
| `How do I apply a GoodRunsList in Athena?` | ATLAS/HEP question |
| `Who is the current ATLAS spokesperson?` | Person lookup identified |
| `Draft an MC request for a ttbar sample.` | ATLAS/HEP question |
| `How do I bake sourdough bread?` | Out-of-scope request identified |
| `What about that?` | Clarification needed, unless prior context resolves it |

The grey status line summarizes the intent, selected mode when relevant,
confidence, and router availability. The response metadata and
`router_decisions` log contain the selected mode, effective workflow, status,
and rollout mode. Test the first prompt in both Search and Assistant modes.
Test context with:

1. `How do I apply a GRL in Athena?`
2. `What about Run 3?`

Then change the local `.env` to:

```bash
CHATLAS_ROUTER_MODE=enforce
```

Restart the frontend so the environment-level setting is reloaded.
Verify that `person_lookup`, `dangerous`, `out_of_scope`, and `unknown`
produce terminal responses with no citations or retrieval results.

To test fail-open behavior without modifying a secret, temporarily set
`CHATLAS_ROUTER_MODEL` to a nonexistent model and restart. The UI should
report that the router is unavailable and continue in the selected mode.
Restore `gpt-oss-20b` after the test.

## Automated Tests

Run the deterministic frontend routing tests:

```bash
PYTHONPATH=.:../chATLAS_Chains:../chATLAS_Embed:../chATLAS_Scrape \
uv run pytest tests/test_routing.py -q
```

Run the full frontend suite:

```bash
PYTHONPATH=.:../chATLAS_Chains:../chATLAS_Embed:../chATLAS_Scrape \
uv run pytest tests -q
```

The live LiteLLM smoke test is skipped unless explicitly enabled:

```bash
cd ../chATLAS_Chains
CHATLAS_RUN_LITELLM_SMOKE=1 \
uv run --env-file ../chATLAS_Frontend/.env \
  pytest tests/test_router_litellm.py -q
```

The GitLab `test_router_litellm` job enables it automatically for merge
requests that change Chains or frontend backend code.

## Reviewing Router Logs

Export routing decisions from the logging database:

```bash
uv run python chATLAS_Frontend/usage/execute_query.py \
  --query router_decisions \
  --db-host 127.0.0.1 \
  --db-port 6624 \
  --db-name log_test \
  --output router_decisions.csv
```

Run this while the DBOD SSH tunnel is active. Omit the host and port
overrides when connected directly from CERN.

The export includes query/thread IDs, intent, confidence, normalized query,
reason, metadata, selected and effective workflows, model, prompt version,
status, latency, error details, and rollout mode.

## Updating Dependencies

Edit `pyproject.toml`, then update and verify the lockfile:

```bash
uv lock
uv sync --locked
```
