File size: 6,490 Bytes
3ee035f
910dadd
 
 
 
3ee035f
910dadd
3ee035f
 
 
910dadd
 
d47f4b5
a2319ff
d47f4b5
910dadd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1bcb9d8
 
 
 
 
 
 
 
 
 
 
910dadd
 
 
 
 
 
 
1bcb9d8
 
 
 
 
 
 
 
910dadd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b383610
 
 
 
 
 
 
 
 
 
d6217aa
 
 
 
 
 
 
 
 
 
 
 
 
b383610
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
---
title: contimp-app
emoji: πŸ”
colorFrom: indigo
colorTo: pink
sdk: docker
app_port: 7860
pinned: false
---

# contimp-app

**Live app: https://lefft-contimp-app.hf.space** (team passcode β€” ask in Slack) Β·
hosted as a [Hugging Face Space](https://huggingface.co/spaces/lefft/contimp-app)

A small demo application that exercises an LLM on narrow, repeatable tasks and logs
every interaction (prompts, responses, tool calls, ground truth, auto-scores) to
LangFuse. It exists to generate *real* live traffic for dogfooding Oumi's
continuous model improvement ("contimp") loop: import traces as datasets, define
evals, fine-tune a small model, compare, and swap the endpoint.

The app is deliberately simple: one FastAPI service, a static vanilla-JS frontend,
no database. All state lives in committed data files and LangFuse.

## Tasks

Tasks are plugins (see *Adding a task* below). Current tasks:

| id | what the model does | ground truth |
|---|---|---|
| `pr-area` | Read a real merged oumi PR (title+body) and classify which subsystem it touches | Derived from the files the PR actually changed |
| `config-copilot` | Turn a natural-language training-run request into a valid oumi training YAML, using `get_schema` / `validate_config` tools | Requests are generated from known target field values; YAML graded on validity + per-field match |

"Deal me one" gives auto-scorable inputs; users can also type their own
(scored on validity/thumbs only).

## Run locally

```sh
cp .env.example .env  # fill in keys
uv sync
uv run uvicorn app.main:app --reload --port 7860 --env-file .env
```

Tests: `uv run pytest`

## The endpoint swap

The app talks to a **backend** β€” a wire protocol + endpoint + model + key.
The default backend is set by the `LLM_*` env vars, and supports two protocols
via `LLM_PROVIDER`:

- `openai` (default): OpenAI Chat Completions (`LLM_BASE_URL` ends in `/v1/`).
- `anthropic`: Anthropic Messages (`LLM_BASE_URL` ends in `/inference`). Needed
  for an Oumi deployment that proxies an Anthropic model β€” Oumi's
  `/chat/completions` rejects the anthropic provider (see `docs/findings.md`).

Moving the day-1 model (Haiku via Anthropic's OpenAI-compat endpoint) to an
OpenAI-compatible Oumi deployment (e.g. a tuned model) is an env-only change:

```sh
LLM_BASE_URL=https://<oumi>/inference/v1/
LLM_MODEL=projects/<project_id>/deployments/<deployment_id>
LLM_API_KEY=<oumi api key>
```

### Per-task backends

Tasks can target *different* backends β€” e.g. one demo on Anthropic-direct,
another on an Oumi proxy. A task pins itself by setting `backend="<name>"`
(see `app/tasks/base.py`); the named backend is configured with `LLM_<NAME>_*`
env vars (see `.env.example`). Tasks that don't set one use the default. Each
trace records its `backend` in metadata so traffic can be segmented per backend.

## Observability

One LangFuse trace per run; nested generations per LLM call (OpenAI wire format,
tool calls included). Every trace carries:

- tags: `[<task_id>, human|synthetic]`
- metadata: `task_id`, `app_version`, `model`, `input_id`, `ground_truth`
- scores: per-task auto-scores (`exact_match`; `yaml_valid`, `schema_valid`,
  `field_match`) plus human `thumbs` feedback
- `user_id`: self-reported handle (or `traffic-bot`), `session_id`: browser session

Filter by tag/metadata `task_id` before exporting per-task datasets. Besides the
LangFuse UI batch export, `scripts/export_traces.py` pulls traces via the API as
observation-level JSONL in the shape the oumi platform's LangFuse importer
consumes (see `docs/findings.md` for one importer gap found doing this).

## Data files

- `data/prs.jsonl` β€” mined merged PRs (title, body, changed files, derived area).
  Rebuild: `uv run python scripts/build_pr_dataset.py`
- `data/oumi_training_schema.json` β€” curated JSON Schema subset of oumi's
  TrainingConfig used by the `config-copilot` validator tool. May drift from oumi
  HEAD; rebuild: `uv run python scripts/dump_oumi_schema.py`

## Traffic generation

```sh
uv run python scripts/generate_traffic.py --target https://<deployment-url> --task pr-area --n 25
```

Synthetic traffic is tagged `synthetic` with user `traffic-bot` so it can be
segmented from human traffic.

## Adding a task

Drop one module into `app/tasks/` defining a `TASK = Task(...)` (see
`app/tasks/base.py`), plus any data file it needs. The registry auto-discovers
it; nav, sampling, running, scoring, and tracing all come for free.

## Deploy

The live app is a [Hugging Face Space](https://huggingface.co/spaces/lefft/contimp-app)
that builds from its own copy of these files (the YAML frontmatter above is its
config; it serves on port 7860). **Merging to `main` does not deploy** β€” the
Space is updated explicitly by uploading the working tree to it:

```sh
uv run --with huggingface_hub python scripts/deploy.py -m "what changed"
```

When to run it: after a change is merged to `main` and you want it live. The
upload triggers a rebuild (~1-2 min).

**`deploy.py` uploads your local working tree, not remote `main`** β€” whatever is
in the directory ships, regardless of git state. So run this checklist first:

1. `git checkout main && git pull` β€” deploy the canonical merged state.
2. `git status` β€” clean tree, no stray files (the upload skips `.git`, `.venv`,
   `__pycache__`, `.env*`, `.pytest_cache`, and `.claude/*`).
3. `uv run pytest` β€” green.
4. `hf auth whoami` β€” logged in with write access to the Space.
5. Deploy, then verify: `curl https://lefft-contimp-app.hf.space/api/health` and
   confirm the task shows at `/api/tasks` (or click the app). Traffic only reaches
   LangFuse if the Space has the `LANGFUSE_*` secrets set (see Prerequisites).

Prerequisites:

- A Hugging Face login with **write access to the Space** β€” run `hf auth login`
  once; the token is cached under `~/.cache/huggingface/` and reused across
  sessions. Check with `hf auth whoami`.
- That's the only credential `deploy.py` needs. The app's own secrets
  (`LLM_API_KEY`, `LANGFUSE_*`, `APP_PASSCODE` β€” see `.env.example` for the
  names) are **not** deployed from here: they live in the Space's
  *Settings β†’ Variables and secrets*, encrypted, and are never committed. To
  rotate one, edit it there and restart the Space; the repo doesn't change.

The app is host-agnostic (plain Docker + env vars), so it also runs on any other
container host β€” point that host at the `Dockerfile` and supply the
`.env.example` variables as environment secrets.