neilA / README.md
TriggerFish212's picture
Update demo link label in README
72ab3a7
|
Raw
History Blame Contribute Delete
6.05 kB
---
title: First Contact
emoji: πŸ›Έ
colorFrom: green
colorTo: indigo
sdk: gradio
sdk_version: 6.16.0
app_file: app.py
pinned: false
license: mit
short_description: Teach an alien that knows words but has never lived a life.
tags:
- track:wood
- sponsor:modal
- achievement:offgrid
- achievement:offbrand
---
# First Contact
A small-model game for the **Build Small** hackathon β€” *An Adventure in Thousand
Token Wood* track. You teach
an alien that knows human *words* but has never experienced human life. It acts in
a tiny sandbox world, accumulates *concepts* as you teach them, and eventually
**generalizes** a learned concept to a brand-new situation on its own. That
"it finally understood me" moment is the payoff.
The model never learns in the weights sense. The alien's growing understanding
lives in a plain-Python **concept ledger** injected into the prompt every turn.
The model is a stateless function: given *(ledger + world + your words)* it returns
*(one action + an in-character reply + structured notes)*. The host code applies
the action deterministically, checks the win condition **mechanically** (never the
model judging "success"), and gates whether a new concept is learned. That loop β€”
not the model β€” is the game. See [`SPEC.md`](SPEC.md) for the full contract.
## Links
- **Demo video + post (X):** https://x.com/MrChonkyboi/status/2066654526963081589
## How to play
1. Read the current challenge at the top.
2. Type instructions to the alien in plain language.
3. It can only *do* one thing from a small, closed action set, but it can *say*
anything β€” and it tells you honestly what it could **not** understand.
4. When it proposes a new concept, confirm "it learned that" to add it to its
ledger. Later challenges test whether it can apply what it learned **without
being re-taught**.
## Architecture
```
gr.State (per session) ──► build_prompt ──► Brain.respond (@spaces.GPU)
ledger / world / challenge β”‚ β”‚ strict JSON
β–² β”‚ β–Ό
└──── learn (gated) ◄─ check_win ◄─ apply_action ◄─ parse + validate
(mechanical) (deterministic) (retry once β†’ safe wait)
```
| module | role |
|--------|------|
| `game/models.py` | dataclasses: Concept, Obj, Agent, WorldState, Action, Challenge, GameSession |
| `game/world.py` | `apply_action` (deterministic), `check_win` (mechanical), initial world |
| `game/ledger.py` | seed primitives, gated concept add, `times_applied` tracking |
| `game/challenges.py` | the 5-challenge arc + win predicates (2 generalization beats) |
| `game/prompt.py` | `build_prompt(ledger, world, challenge, utterance)` |
| `game/parsing.py` | tolerant JSON extract + validate + Β§4 retry / safe fallback |
| `game/brain.py` | `Brain` protocol + `StubBrain` \| `LocalBrain` \| `ModalBrain` |
| `game/engine.py` | the turn loop (Gradio-free, fully testable) |
| `app.py` | Gradio Blocks UI + wiring (the Space entrypoint) |
## The model is swappable (protect GPU quota)
Selected via the `BRAIN` env var:
- `stub` *(default locally)* β€” deterministic, **zero GPU**. The entire loop and
the whole challenge arc are playable and testable against it.
- `local` *(set this on the Space)* β€” a ≀32B instruct model loaded onto `cuda` at
module level; inference runs inside `@spaces.GPU`.
- `modal` β€” optional dev/serving endpoint. Never the submission path; `requests`
is imported lazily so Modal is never a hard dependency.
Pick the local model with `MODEL_ID` (default `Qwen/Qwen2.5-14B-Instruct`) and the
sampler heat with `LOCALBRAIN_TEMPERATURE` (default `0.9`; `0` = greedy). Both
defaults come from the bake-off below: the JSON envelope held 100% at *every*
temperature for every candidate, so the model pick was decided by arc completion
plus concept invention (14B was the only one strong at both), and 0.9 buys
near-peak voice at zero measured reliability cost.
## Develop / test (no GPU)
```bash
# run the full test suite (loop, parsing/fallback, world) against StubBrain
uv run --with pytest pytest -q
# run the app locally on the stub brain
uv run --with gradio python app.py
```
## Model selection (bake-off)
`bakeoff.py` picks the local model empirically β€” which ≀32B model emits clean,
schema-valid JSON *reliably* β€” without burning quota blind. It calls `respond()`
for raw text and parses **once, with no retry** (the Β§4 retry path would mask the
failures we're counting).
```bash
python bakeoff.py --self-test # prove the scorer (zero GPU)
python bakeoff.py --make-battery battery.jsonl # battery from the arc (zero GPU)
# on the Space (or via a Modal endpoint with --brain modal):
python bakeoff.py --models <id1>,<id2> --brain local --repeats 5 --arc
python bakeoff.py --models <id> --brain local --temps 0.0,0.3,0.5,0.7,1.0 --repeats 5
python bakeoff.py --models <id> --brain local --arc-transcript # eyeball the arc
```
The `--temps` sweep is the decision tool: per temperature it reports JSON
reliability **and** two voice-liveliness proxies **and** arc-win, so you can see
whether one temperature serves both jobs β€” or whether you need constrained
decoding to keep the voice warm while guaranteeing the JSON envelope.
## Deploy notes
- Set hardware to **ZeroGPU** in the Space settings and `BRAIN=local` as a Space
variable. Put the HF token in **Space secrets** (never in code).
- `sdk_version` is pinned to Gradio `6.16.0`; confirm it matches the current
ZeroGPU template when you create the Space (HF will error clearly if it's off).
In Gradio 6 `css`/`theme` moved off `Blocks()`, so `app.py` also injects the CSS
via an inline `<style>` tag β€” styling holds however Spaces launches the app.
- `@spaces.GPU(duration=20)` declares the inference budget β€” sized from the
measured ~5s p90/call (bake-off, Qwen2.5-14B) with ~4x headroom; shorter
declared durations get better queue priority. Bump it if you switch models.