MSGEncrypted commited on
Commit
dd678c1
·
1 Parent(s): b911f86

app model check

Browse files
Dockerfile CHANGED
@@ -16,8 +16,11 @@ WORKDIR /app
16
  COPY pyproject.toml uv.lock .python-version README.md models.yaml ./
17
  COPY apps/gradio-space/pyproject.toml apps/gradio-space/README.md apps/gradio-space/
18
  COPY libs/inference/pyproject.toml libs/inference/README.md libs/inference/
 
19
  COPY apps/gradio-space/src apps/gradio-space/src
20
  COPY libs/inference/src libs/inference/src
 
 
21
 
22
  RUN useradd -m -u 1000 user && \
23
  uv sync --frozen --no-dev --package gradio-space && \
@@ -25,7 +28,11 @@ RUN useradd -m -u 1000 user && \
25
 
26
  USER user
27
  ENV HOME=/home/user \
28
- PATH="/app/.venv/bin:$PATH"
 
 
 
 
29
 
30
  EXPOSE 7860
31
 
 
16
  COPY pyproject.toml uv.lock .python-version README.md models.yaml ./
17
  COPY apps/gradio-space/pyproject.toml apps/gradio-space/README.md apps/gradio-space/
18
  COPY libs/inference/pyproject.toml libs/inference/README.md libs/inference/
19
+ COPY libs/agent/pyproject.toml libs/agent/README.md libs/agent/
20
  COPY apps/gradio-space/src apps/gradio-space/src
21
  COPY libs/inference/src libs/inference/src
22
+ COPY libs/agent/src libs/agent/src
23
+ COPY skills skills
24
 
25
  RUN useradd -m -u 1000 user && \
26
  uv sync --frozen --no-dev --package gradio-space && \
 
28
 
29
  USER user
30
  ENV HOME=/home/user \
31
+ PATH="/app/.venv/bin:$PATH" \
32
+ AGENT_OUTPUTS_DIR=/tmp/agent_outputs \
33
+ AGENT_TRACES_DIR=/tmp/agent_traces
34
+
35
+ RUN mkdir -p /tmp/agent_outputs /tmp/agent_traces
36
 
37
  EXPOSE 7860
38
 
README.md CHANGED
@@ -1,7 +1,7 @@
1
  ---
2
 
3
- ## title: Small Model Hackathon
4
- emoji: 🦙
5
  colorFrom: blue
6
  colorTo: green
7
  sdk: docker
@@ -9,11 +9,13 @@ app_port: 7860
9
  pinned: false
10
  license: apache-2.0
11
 
12
- # Small Model Hackathon
13
 
14
- Gradio chat Space for the [Build Small Hackathon](https://huggingface.co/build-small-hackathon). Runs local inference with **llama.cpp** (GGUF) by default; optional **transformers** backend via env.
15
 
16
- See **[USAGE.md](USAGE.md)** for local run, Docker smoke test, and HF Space deployment steps.
 
 
17
 
18
  ## Prerequisites
19
 
@@ -26,81 +28,75 @@ See **[USAGE.md](USAGE.md)** for local run, Docker smoke test, and HF Space depl
26
  uv sync --all-packages
27
  cp .env.example .env # optional: edit model settings
28
 
29
- # Download GGUF for offline dev (optional)
30
- uv run python scripts/download_model.py
31
-
32
  # Run Gradio locally
33
  uv run --package gradio-space python -m gradio_space.app
34
  ```
35
 
36
- Open [http://localhost:7860](http://localhost:7860). The model downloads from Hugging Face Hub on the first chat message (or set `MODEL_PATH` to a local GGUF).
37
-
38
- ## Environment variables
39
-
40
-
41
- | Variable | Default | Description |
42
- | ------------------- | --------------------------------- | ------------------------------------------ |
43
- | `INFERENCE_BACKEND` | `llama_cpp` | `llama_cpp` or `transformers` |
44
- | `MODEL_REPO` | `Qwen/Qwen2.5-3B-Instruct-GGUF` | Hub repo for GGUF |
45
- | `MODEL_FILE` | `qwen2.5-3b-instruct-q4_k_m.gguf` | GGUF filename |
46
- | `MODEL_PATH` | — | Local GGUF path (skips Hub download) |
47
- | `N_CTX` | `4096` | Context window |
48
- | `N_GPU_LAYERS` | `0` | GPU layers for llama.cpp (0 = CPU) |
49
- | `MODEL_ID` | `Qwen/Qwen2.5-3B-Instruct` | Used when `INFERENCE_BACKEND=transformers` |
50
-
51
 
52
- See `[.env.example](.env.example)` for a full template.
53
 
54
- ## Monorepo layout
 
 
 
55
 
56
  ```text
57
- apps/gradio-space/ # Gradio UI (HF Space entrypoint)
58
- libs/inference/ # Swappable inference backends
59
- scripts/ # Dev utilities
 
60
  ```
61
 
62
- ### Common commands
63
 
64
- ```bash
65
- uv add --package gradio-space <package>
66
- uv add --package inference <package>
67
- uv run --package gradio-space python -m gradio_space.app
68
- uv run python -c "from inference.factory import get_backend"
69
- ```
 
 
70
 
71
  ## Hugging Face Space deployment
72
 
73
  1. Create a Space under [build-small-hackathon](https://huggingface.co/build-small-hackathon) with **Docker** SDK.
74
  2. Link this repository (root `Dockerfile` + root `README.md` YAML above).
75
- 3. Hardware: start with **CPU basic**; upgrade to GPU if you set `N_GPU_LAYERS > 0`.
76
- 4. Add Space secrets: `MODEL_REPO`, `MODEL_FILE`, `N_CTX`, `N_GPU_LAYERS`.
77
 
78
  ```bash
79
- # Optional local Docker smoke test
80
  docker build -t hackathon-space .
81
- docker run --rm -p 7860:7860 -e MODEL_REPO=Qwen/Qwen2.5-3B-Instruct-GGUF hackathon-space
82
  ```
83
 
84
  ## Hackathon checklist
85
 
86
- - Choose a track (Backyard AI or Thousand Token Wood)
87
  - Space live under build-small-hackathon
88
- - Demo video recorded
89
  - Social post published
90
- - Submission locked in by **June 15, 2026**
91
 
92
  ### Badge targets
93
 
94
- - **Off-the-Grid** — local llama.cpp inference (default setup)
95
- - **Llama Champion** — llama.cpp + GGUF model
96
- - **Off-Brand** — custom UI via `gr.Server` (Phase 2)
97
- - **Sharing is Caring** — agent traces dataset (Phase 2)
 
 
98
 
99
- ## Transformers backend (optional)
100
 
101
  ```bash
102
- uv sync --package inference --extra transformers
103
- INFERENCE_BACKEND=transformers MODEL_ID=Qwen/Qwen2.5-3B-Instruct \
104
- uv run --package gradio-space python -m gradio_space.app
105
  ```
106
 
 
 
 
 
 
 
 
1
  ---
2
 
3
+ ## title: Lesson Agent
4
+ emoji: 📚
5
  colorFrom: blue
6
  colorTo: green
7
  sdk: docker
 
9
  pinned: false
10
  license: apache-2.0
11
 
12
+ # Lesson Agent
13
 
14
+ **Backyard AI** Gradio Space for the [Build Small Hackathon](https://huggingface.co/build-small-hackathon).
15
 
16
+ A local skill-based agent helps a teacher you know turn a **topic + grade level** into a downloadable **PowerPoint** powered by a small transformers model (`MiniCPM5-1B` by default), no cloud LLM API.
17
+
18
+ See **[USAGE.md](USAGE.md)** for local run, Docker smoke test, and HF Space deployment.
19
 
20
  ## Prerequisites
21
 
 
28
  uv sync --all-packages
29
  cp .env.example .env # optional: edit model settings
30
 
 
 
 
31
  # Run Gradio locally
32
  uv run --package gradio-space python -m gradio_space.app
33
  ```
34
 
35
+ Open [http://localhost:7860](http://localhost:7860). Use the **Lesson slides** tab: enter a topic, grade, and slide count. The model loads on first generate.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
+ ## How it works
38
 
39
+ 1. **Skill** — `skills/education-pptx/SKILL.md` (Hermes / agentskills.io format)
40
+ 2. **LLM** — local model drafts a JSON slide outline
41
+ 3. **Tool** — `create_pptx` builds the file with `python-pptx`
42
+ 4. **Trace** — JSON log saved under `outputs/traces/` for the Sharing is Caring badge
43
 
44
  ```text
45
+ apps/gradio-space/ # Gradio tabs (Lesson slides + Chat debug)
46
+ libs/agent/ # Skill agent runner, tools, trace recorder
47
+ libs/inference/ # Transformers + llama.cpp backends
48
+ skills/ # SKILL.md task definitions
49
  ```
50
 
51
+ ## Environment variables
52
 
53
+ | Variable | Default | Description |
54
+ | -------- | ------- | ----------- |
55
+ | `ACTIVE_MODEL` | `minicpm5-1b` | Preset key from `models.yaml` |
56
+ | `AGENT_OUTPUTS_DIR` | `/tmp/agent_outputs` | Generated `.pptx` files |
57
+ | `AGENT_TRACES_DIR` | `outputs/traces` | Agent trace JSON |
58
+ | `SKILLS_DIR` | `./skills` | Skill definitions root |
59
+
60
+ See [`.env.example`](.env.example) and [`models.yaml`](models.yaml) for model presets.
61
 
62
  ## Hugging Face Space deployment
63
 
64
  1. Create a Space under [build-small-hackathon](https://huggingface.co/build-small-hackathon) with **Docker** SDK.
65
  2. Link this repository (root `Dockerfile` + root `README.md` YAML above).
66
+ 3. Hardware: **GPU basic** recommended for transformers (`minicpm5-1b`).
67
+ 4. Optional secrets: `ACTIVE_MODEL`, `N_GPU_LAYERS` (if using GGUF preset).
68
 
69
  ```bash
 
70
  docker build -t hackathon-space .
71
+ docker run --rm -p 7860:7860 -e ACTIVE_MODEL=minicpm5-1b hackathon-space
72
  ```
73
 
74
  ## Hackathon checklist
75
 
76
+ - **Track:** Backyard AI lesson slide builder for a teacher you know
77
  - Space live under build-small-hackathon
78
+ - Demo video: real user enters topic → download `.pptx` → show agent trace
79
  - Social post published
80
+ - Submission by **June 15, 2026**
81
 
82
  ### Badge targets
83
 
84
+ - **Best Agent** — skill loop + `create_pptx` tool
85
+ - **Tiny Titan** — MiniCPM5 1B (≤4B)
86
+ - **OpenBMB** — `openbmb/MiniCPM5-1B`
87
+ - **Sharing is Caring** — upload traces with `scripts/upload_trace.py`
88
+ - **Off-the-Grid** — local inference only (no cloud LLM API)
89
+ - **Well-Tuned** — optional fine-tuned preset in `models.yaml` (Phase 2)
90
 
91
+ ## Agent trace upload
92
 
93
  ```bash
94
+ uv run python scripts/upload_trace.py --repo-id YOUR_USER/build-small-agent-traces
 
 
95
  ```
96
 
97
+ ## Demo video script
98
+
99
+ 1. Introduce the teacher and the problem (building a 5-slide lesson takes 30+ minutes).
100
+ 2. Open **Lesson slides**, enter topic + grade, click **Generate**.
101
+ 3. Show outline preview and download the `.pptx`.
102
+ 4. Expand the agent trace JSON — local model, no cloud API.
apps/gradio-space/pyproject.toml CHANGED
@@ -8,11 +8,13 @@ authors = [
8
  ]
9
  requires-python = ">=3.12"
10
  dependencies = [
 
11
  "gradio>=5.0.0",
12
  "inference",
13
  ]
14
 
15
  [tool.uv.sources]
 
16
  inference = { workspace = true }
17
 
18
  [build-system]
 
8
  ]
9
  requires-python = ">=3.12"
10
  dependencies = [
11
+ "agent",
12
  "gradio>=5.0.0",
13
  "inference",
14
  ]
15
 
16
  [tool.uv.sources]
17
+ agent = { workspace = true }
18
  inference = { workspace = true }
19
 
20
  [build-system]
apps/gradio-space/src/gradio_space/app.py CHANGED
@@ -2,7 +2,6 @@ import os
2
 
3
  import gradio as gr
4
 
5
- from gradio_space.model_loading import warmup
6
  from gradio_space.tabs import build_chat_tab, build_education_pptx_tab
7
  from inference.config import get_app_config
8
 
@@ -38,8 +37,6 @@ Part of the [Build Small Hackathon](https://huggingface.co/build-small-hackathon
38
  with gr.Tab("Chat (debug)"):
39
  build_chat_tab()
40
 
41
- demo.load(lambda: warmup(_app_config.active_model))
42
-
43
  return demo
44
 
45
 
 
2
 
3
  import gradio as gr
4
 
 
5
  from gradio_space.tabs import build_chat_tab, build_education_pptx_tab
6
  from inference.config import get_app_config
7
 
 
37
  with gr.Tab("Chat (debug)"):
38
  build_chat_tab()
39
 
 
 
40
  return demo
41
 
42
 
apps/gradio-space/src/gradio_space/model_loading.py CHANGED
@@ -76,3 +76,26 @@ def warmup(model_key: str | None = None) -> str:
76
  def model_status(model_key: str) -> str:
77
  model = get_model_config(model_key)
78
  return f"**{model.label}**\n\n- Backend: `{model.backend}`\n- {warmup(model_key)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def model_status(model_key: str) -> str:
77
  model = get_model_config(model_key)
78
  return f"**{model.label}**\n\n- Backend: `{model.backend}`\n- {warmup(model_key)}"
79
+
80
+
81
+ def _history_to_messages(history: list) -> list[dict[str, str]]:
82
+ messages: list[dict[str, str]] = []
83
+ for item in history:
84
+ if isinstance(item, dict):
85
+ messages.append({"role": item["role"], "content": item["content"]})
86
+ else:
87
+ user_msg, assistant_msg = item
88
+ messages.append({"role": "user", "content": user_msg})
89
+ if assistant_msg:
90
+ messages.append({"role": "assistant", "content": assistant_msg})
91
+ return messages
92
+
93
+
94
+ def chat(message: str, history: list, model_key: str) -> str:
95
+ load_error = ensure_model_loaded(model_key)
96
+ if load_error:
97
+ return load_error
98
+
99
+ messages = _history_to_messages(history)
100
+ messages.append({"role": "user", "content": message})
101
+ return get_backend(model_key).chat(messages)
apps/gradio-space/src/gradio_space/tabs/chat.py CHANGED
@@ -1,37 +1,11 @@
1
  import gradio as gr
2
 
3
- from gradio_space.model_loading import (
4
- chat as chat_fn,
5
- ensure_model_loaded,
6
- get_active_model_key,
7
- model_status,
8
- warmup,
9
- )
10
  from inference.config import get_app_config
11
 
12
  _app_config = get_app_config()
13
 
14
 
15
- def _history_to_messages(history: list) -> list[dict[str, str]]:
16
- messages: list[dict[str, str]] = []
17
- for item in history:
18
- if isinstance(item, dict):
19
- messages.append({"role": item["role"], "content": item["content"]})
20
- else:
21
- user_msg, assistant_msg = item
22
- messages.append({"role": "user", "content": user_msg})
23
- if assistant_msg:
24
- messages.append({"role": "assistant", "content": assistant_msg})
25
- return messages
26
-
27
-
28
- def chat(message: str, history: list, model_key: str) -> str:
29
- load_error = ensure_model_loaded(model_key)
30
- if load_error:
31
- return load_error
32
- return chat_fn(message, history, model_key)
33
-
34
-
35
  def build_chat_tab() -> None:
36
  gr.Markdown(
37
  """
@@ -41,7 +15,7 @@ Test the active local model with a simple chat interface.
41
  """
42
  )
43
 
44
- model_key = get_active_model_key()
45
 
46
  if _app_config.allow_model_switch and len(_app_config.models) > 1:
47
  model_dropdown = gr.Dropdown(
@@ -68,4 +42,3 @@ Test the active local model with a simple chat interface.
68
  "Explain photosynthesis in one sentence.",
69
  ],
70
  )
71
- gr.on(fn=lambda: warmup(model_key), outputs=status)
 
1
  import gradio as gr
2
 
3
+ from gradio_space.model_loading import chat, model_status, warmup
 
 
 
 
 
 
4
  from inference.config import get_app_config
5
 
6
  _app_config = get_app_config()
7
 
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  def build_chat_tab() -> None:
10
  gr.Markdown(
11
  """
 
15
  """
16
  )
17
 
18
+ model_key = _app_config.active_model
19
 
20
  if _app_config.allow_model_switch and len(_app_config.models) > 1:
21
  model_dropdown = gr.Dropdown(
 
42
  "Explain photosynthesis in one sentence.",
43
  ],
44
  )
 
libs/agent/tests/test_runner.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agent.models import SlideOutline, SlideSpec
2
+ from agent.runner import AgentRunner
3
+ from agent.tools.pptx import create_pptx
4
+
5
+
6
+ def test_extract_json_from_fenced_block():
7
+ raw = '```json\n{"title": "T", "slides": [{"title": "S", "bullets": ["a"]}]}\n```'
8
+ data = AgentRunner._extract_json(raw)
9
+ assert data["title"] == "T"
10
+
11
+
12
+ def test_create_pptx_writes_file(tmp_path, monkeypatch):
13
+ monkeypatch.setenv("AGENT_OUTPUTS_DIR", str(tmp_path))
14
+ outline = SlideOutline(
15
+ title="Photosynthesis",
16
+ slides=[
17
+ SlideSpec(title="What is it?", bullets=["Plants make food", "Uses sunlight"]),
18
+ SlideSpec(title="Why it matters", bullets=["Oxygen", "Food chain"]),
19
+ ],
20
+ )
21
+ path = create_pptx(outline, run_id="test")
22
+ assert path.exists()
23
+ assert path.suffix == ".pptx"
models.yaml CHANGED
@@ -2,7 +2,7 @@
2
  # Select active preset with ACTIVE_MODEL; override any field via .env (see .env.example).
3
 
4
  defaults:
5
- active_model: minicpm-v-4.6
6
  # Dev: set ALLOW_MODEL_SWITCH=true in .env to expose a dropdown in Gradio.
7
  # Space: keep false so visitors use one pinned model.
8
  allow_model_switch: false
 
2
  # Select active preset with ACTIVE_MODEL; override any field via .env (see .env.example).
3
 
4
  defaults:
5
+ active_model: minicpm5-1b
6
  # Dev: set ALLOW_MODEL_SWITCH=true in .env to expose a dropdown in Gradio.
7
  # Space: keep false so visitors use one pinned model.
8
  allow_model_switch: false
pyproject.toml CHANGED
@@ -5,6 +5,7 @@ description = "Build Small Hackathon — Gradio Space with local llama.cpp infer
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
  dependencies = [
 
8
  "gradio-space",
9
  "inference",
10
  ]
@@ -22,5 +23,6 @@ members = [
22
  ]
23
 
24
  [tool.uv.sources]
 
25
  gradio-space = { workspace = true }
26
  inference = { workspace = true }
 
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
  dependencies = [
8
+ "agent",
9
  "gradio-space",
10
  "inference",
11
  ]
 
23
  ]
24
 
25
  [tool.uv.sources]
26
+ agent = { workspace = true }
27
  gradio-space = { workspace = true }
28
  inference = { workspace = true }
scripts/upload_trace.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Upload the latest agent trace JSON to a Hugging Face dataset repo."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+
11
+ from huggingface_hub import HfApi
12
+
13
+
14
+ def _latest_trace(traces_dir: Path) -> Path:
15
+ files = sorted(traces_dir.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)
16
+ if not files:
17
+ raise FileNotFoundError(f"No trace files in {traces_dir}")
18
+ return files[0]
19
+
20
+
21
+ def main() -> None:
22
+ parser = argparse.ArgumentParser(description="Upload agent trace to HF dataset")
23
+ parser.add_argument(
24
+ "--traces-dir",
25
+ type=Path,
26
+ default=Path(os.environ.get("AGENT_TRACES_DIR", "outputs/traces")),
27
+ )
28
+ parser.add_argument(
29
+ "--repo-id",
30
+ required=True,
31
+ help="HF dataset repo, e.g. username/build-small-agent-traces",
32
+ )
33
+ parser.add_argument("--trace", type=Path, default=None, help="Specific trace file")
34
+ args = parser.parse_args()
35
+
36
+ trace_path = args.trace or _latest_trace(args.traces_dir)
37
+ data = json.loads(trace_path.read_text())
38
+
39
+ api = HfApi()
40
+ api.create_repo(args.repo_id, repo_type="dataset", exist_ok=True)
41
+ api.upload_file(
42
+ path_or_fileobj=trace_path.read_bytes(),
43
+ path_in_repo=f"traces/{trace_path.name}",
44
+ repo_id=args.repo_id,
45
+ repo_type="dataset",
46
+ commit_message=f"Add agent trace {trace_path.stem}",
47
+ )
48
+
49
+ print(f"Uploaded {trace_path} -> {args.repo_id}/traces/{trace_path.name}")
50
+ print(f"Skill: {data.get('skill')} | Model: {data.get('model')} | Run: {data.get('run_id')}")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()