neilA / README.md
TriggerFish212's picture
Update demo link label in README
72ab3a7
|
Raw
History Blame Contribute Delete
6.05 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade
metadata
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 for the full contract.

Links

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)

# 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).

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.