Spaces:
Sleeping
Sleeping
Switch to SmolLM2-360M (prebuilt wheels, no C++ compile)
Browse files- README.md +12 -5
- app.py +2 -2
- health_coach.py +40 -54
- requirements.txt +2 -1
README.md
CHANGED
|
@@ -14,8 +14,9 @@ tags:
|
|
| 14 |
- tiny-titan
|
| 15 |
- best-agent
|
| 16 |
- off-brand
|
|
|
|
| 17 |
models:
|
| 18 |
-
-
|
| 19 |
---
|
| 20 |
|
| 21 |
# 🫀 Body Debt
|
|
@@ -33,16 +34,16 @@ Body Debt calculates the precise recovery cost of last night's choices — alcoh
|
|
| 33 |
|
| 34 |
## The model
|
| 35 |
|
| 36 |
-
**
|
| 37 |
|
| 38 |
The face scan stress classifier is a custom 7→16→8→1 MLP (~2KB ONNX) that converts facial geometry features into a fatigue score.
|
| 39 |
|
| 40 |
## Tech
|
| 41 |
|
| 42 |
-
- **LLM**:
|
| 43 |
- **Face analysis**: MediaPipe FaceMesh → 7 stress features → ONNX MLP
|
| 44 |
- **Scoring**: Deterministic 5-system engine with physiological weights, drink-type modifiers, training CNS load, circadian alignment penalties
|
| 45 |
-
- **UI**: Gradio
|
| 46 |
|
| 47 |
## Privacy
|
| 48 |
|
|
@@ -66,9 +67,15 @@ python generate_model.py # creates the ONNX stress model
|
|
| 66 |
python app.py
|
| 67 |
```
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
## Full product
|
| 70 |
|
| 71 |
-
The complete Body Debt application (Next.js, ZK proofs on SKALE, real-time animated dashboard) is at: [github.com/
|
| 72 |
|
| 73 |
---
|
| 74 |
|
|
|
|
| 14 |
- tiny-titan
|
| 15 |
- best-agent
|
| 16 |
- off-brand
|
| 17 |
+
- openai-codex
|
| 18 |
models:
|
| 19 |
+
- HuggingFaceTB/SmolLM2-360M-Instruct
|
| 20 |
---
|
| 21 |
|
| 22 |
# 🫀 Body Debt
|
|
|
|
| 34 |
|
| 35 |
## The model
|
| 36 |
|
| 37 |
+
**SmolLM2-360M-Instruct** (360M parameters) — runs entirely on CPU via HuggingFace Transformers. No external API calls, no cloud inference. Your health data stays on-device.
|
| 38 |
|
| 39 |
The face scan stress classifier is a custom 7→16→8→1 MLP (~2KB ONNX) that converts facial geometry features into a fatigue score.
|
| 40 |
|
| 41 |
## Tech
|
| 42 |
|
| 43 |
+
- **LLM**: SmolLM2-360M-Instruct (360M params) via HuggingFace Transformers
|
| 44 |
- **Face analysis**: MediaPipe FaceMesh → 7 stress features → ONNX MLP
|
| 45 |
- **Scoring**: Deterministic 5-system engine with physiological weights, drink-type modifiers, training CNS load, circadian alignment penalties
|
| 46 |
+
- **UI**: Gradio 6 with custom dark theme
|
| 47 |
|
| 48 |
## Privacy
|
| 49 |
|
|
|
|
| 67 |
python app.py
|
| 68 |
```
|
| 69 |
|
| 70 |
+
## OpenAI Codex Track
|
| 71 |
+
|
| 72 |
+
This Space was built with OpenAI Codex as the coding agent. The public source repository, including Codex-attributed commits, is here:
|
| 73 |
+
|
| 74 |
+
**Repository:** [github.com/udirobert/bodydebt](https://github.com/udirobert/bodydebt)
|
| 75 |
+
|
| 76 |
## Full product
|
| 77 |
|
| 78 |
+
The complete Body Debt application (Next.js, ZK proofs on SKALE, real-time animated dashboard) is at: [github.com/udirobert/bodydebt](https://github.com/udirobert/bodydebt)
|
| 79 |
|
| 80 |
---
|
| 81 |
|
app.py
CHANGED
|
@@ -198,7 +198,7 @@ def run_analysis(
|
|
| 198 |
progress(1.0, desc="Done!")
|
| 199 |
|
| 200 |
advice_md = "### 🤖 Recovery Prescription\n\n"
|
| 201 |
-
advice_md += f"*Generated by
|
| 202 |
|
| 203 |
return score_md, face_text, advice_md
|
| 204 |
|
|
@@ -323,7 +323,7 @@ with gr.Blocks(title="Body Debt") as demo:
|
|
| 323 |
gr.Markdown(
|
| 324 |
"""
|
| 325 |
---
|
| 326 |
-
*Body Debt uses
|
| 327 |
Face analysis uses MediaPipe FaceMesh — no biometric data leaves your device.
|
| 328 |
Built for the [Build Small Hackathon](https://huggingface.co/spaces/huggingface/build-small-hackathon).*
|
| 329 |
"""
|
|
|
|
| 198 |
progress(1.0, desc="Done!")
|
| 199 |
|
| 200 |
advice_md = "### 🤖 Recovery Prescription\n\n"
|
| 201 |
+
advice_md += f"*Generated by SmolLM2-360M running locally*\n\n{advice}"
|
| 202 |
|
| 203 |
return score_md, face_text, advice_md
|
| 204 |
|
|
|
|
| 323 |
gr.Markdown(
|
| 324 |
"""
|
| 325 |
---
|
| 326 |
+
*Body Debt uses SmolLM2-360M-Instruct (360M parameters) running locally via HuggingFace Transformers.
|
| 327 |
Face analysis uses MediaPipe FaceMesh — no biometric data leaves your device.
|
| 328 |
Built for the [Build Small Hackathon](https://huggingface.co/spaces/huggingface/build-small-hackathon).*
|
| 329 |
"""
|
health_coach.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
-
Local LLM health coach using
|
| 3 |
Generates personalized recovery advice from stressor + face scan data.
|
| 4 |
Falls back to a template-based response if model unavailable.
|
| 5 |
"""
|
|
@@ -7,27 +7,9 @@ Falls back to a template-based response if model unavailable.
|
|
| 7 |
from __future__ import annotations
|
| 8 |
|
| 9 |
import os
|
| 10 |
-
from pathlib import Path
|
| 11 |
from typing import Optional
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
MODEL_REPO = "hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF"
|
| 16 |
-
MODEL_FILE = "llama-3.2-1b-instruct-q4_k_m.gguf"
|
| 17 |
-
CACHE_DIR = Path.home() / ".cache" / "body-debt-models"
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
def get_model_path() -> Path:
|
| 21 |
-
local = CACHE_DIR / MODEL_FILE
|
| 22 |
-
if local.exists():
|
| 23 |
-
return local
|
| 24 |
-
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
| 25 |
-
path = hf_hub_download(
|
| 26 |
-
repo_id=MODEL_REPO,
|
| 27 |
-
filename=MODEL_FILE,
|
| 28 |
-
local_dir=str(CACHE_DIR),
|
| 29 |
-
)
|
| 30 |
-
return Path(path)
|
| 31 |
|
| 32 |
|
| 33 |
def generate_advice(
|
|
@@ -37,66 +19,70 @@ def generate_advice(
|
|
| 37 |
face_stress: Optional[float] = None,
|
| 38 |
progress_callback=None,
|
| 39 |
) -> str:
|
| 40 |
-
"""Generate personalized recovery advice using local
|
| 41 |
try:
|
| 42 |
if progress_callback:
|
| 43 |
progress_callback(0.1, "Loading model...")
|
| 44 |
-
|
| 45 |
-
if progress_callback:
|
| 46 |
-
progress_callback(0.5, "Model loaded, generating advice...")
|
| 47 |
-
return _llm_generate(model_path, debt_score, system_scores, stressor_summary, face_stress)
|
| 48 |
except Exception as e:
|
|
|
|
| 49 |
return _fallback_advice(debt_score, system_scores, stressor_summary)
|
| 50 |
|
| 51 |
|
| 52 |
-
def
|
| 53 |
debt_score: int,
|
| 54 |
system_scores: list[dict],
|
| 55 |
stressor_summary: str,
|
| 56 |
face_stress: Optional[float],
|
| 57 |
-
) ->
|
| 58 |
systems_text = "\n".join(
|
| 59 |
f"- {s['label']}: {s['score']}/100 (clears {s['cleared_at']})"
|
| 60 |
for s in system_scores
|
| 61 |
)
|
| 62 |
face_text = f"\nFacial stress indicator: {face_stress:.0f}/100" if face_stress else ""
|
| 63 |
|
| 64 |
-
return
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
|
| 75 |
|
| 76 |
-
def
|
| 77 |
-
model_path: Path,
|
| 78 |
debt_score: int,
|
| 79 |
system_scores: list[dict],
|
| 80 |
stressor_summary: str,
|
| 81 |
face_stress: Optional[float],
|
|
|
|
| 82 |
) -> str:
|
| 83 |
-
from
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
prompt,
|
| 94 |
-
max_tokens=512,
|
| 95 |
-
temperature=0.7,
|
| 96 |
-
top_p=0.9,
|
| 97 |
-
stop=["<|eot_id|>"],
|
| 98 |
)
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
|
| 102 |
def _fallback_advice(debt_score: int, system_scores: list[dict], stressor_summary: str) -> str:
|
|
|
|
| 1 |
"""
|
| 2 |
+
Local LLM health coach using HuggingFace Transformers.
|
| 3 |
Generates personalized recovery advice from stressor + face scan data.
|
| 4 |
Falls back to a template-based response if model unavailable.
|
| 5 |
"""
|
|
|
|
| 7 |
from __future__ import annotations
|
| 8 |
|
| 9 |
import os
|
|
|
|
| 10 |
from typing import Optional
|
| 11 |
|
| 12 |
+
MODEL_ID = "HuggingFaceTB/SmolLM2-360M-Instruct"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
def generate_advice(
|
|
|
|
| 19 |
face_stress: Optional[float] = None,
|
| 20 |
progress_callback=None,
|
| 21 |
) -> str:
|
| 22 |
+
"""Generate personalized recovery advice using a small local LLM."""
|
| 23 |
try:
|
| 24 |
if progress_callback:
|
| 25 |
progress_callback(0.1, "Loading model...")
|
| 26 |
+
return _transformers_generate(debt_score, system_scores, stressor_summary, face_stress, progress_callback)
|
|
|
|
|
|
|
|
|
|
| 27 |
except Exception as e:
|
| 28 |
+
print(f"LLM generation failed: {e}")
|
| 29 |
return _fallback_advice(debt_score, system_scores, stressor_summary)
|
| 30 |
|
| 31 |
|
| 32 |
+
def _build_messages(
|
| 33 |
debt_score: int,
|
| 34 |
system_scores: list[dict],
|
| 35 |
stressor_summary: str,
|
| 36 |
face_stress: Optional[float],
|
| 37 |
+
) -> list[dict]:
|
| 38 |
systems_text = "\n".join(
|
| 39 |
f"- {s['label']}: {s['score']}/100 (clears {s['cleared_at']})"
|
| 40 |
for s in system_scores
|
| 41 |
)
|
| 42 |
face_text = f"\nFacial stress indicator: {face_stress:.0f}/100" if face_stress else ""
|
| 43 |
|
| 44 |
+
return [
|
| 45 |
+
{
|
| 46 |
+
"role": "system",
|
| 47 |
+
"content": "You are a concise recovery coach. Given physiological debt data, provide specific, actionable recovery advice in 4 categories: Right Now, This Morning, Today, Avoid. Be direct, no fluff. Use the system scores to prioritize which body systems need attention most urgently.",
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"role": "user",
|
| 51 |
+
"content": f"My body debt score: {debt_score}/100\nStressors: {stressor_summary}{face_text}\n\nSystem breakdown:\n{systems_text}\n\nGive me my recovery prescription.",
|
| 52 |
+
},
|
| 53 |
+
]
|
| 54 |
|
| 55 |
|
| 56 |
+
def _transformers_generate(
|
|
|
|
| 57 |
debt_score: int,
|
| 58 |
system_scores: list[dict],
|
| 59 |
stressor_summary: str,
|
| 60 |
face_stress: Optional[float],
|
| 61 |
+
progress_callback=None,
|
| 62 |
) -> str:
|
| 63 |
+
from transformers import pipeline
|
| 64 |
|
| 65 |
+
if progress_callback:
|
| 66 |
+
progress_callback(0.3, "Loading SmolLM2-360M...")
|
| 67 |
+
|
| 68 |
+
pipe = pipeline(
|
| 69 |
+
"text-generation",
|
| 70 |
+
model=MODEL_ID,
|
| 71 |
+
device_map="auto",
|
| 72 |
+
torch_dtype="auto",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
)
|
| 74 |
+
|
| 75 |
+
if progress_callback:
|
| 76 |
+
progress_callback(0.6, "Generating advice...")
|
| 77 |
+
|
| 78 |
+
messages = _build_messages(debt_score, system_scores, stressor_summary, face_stress)
|
| 79 |
+
output = pipe(messages, max_new_tokens=300, temperature=0.7, do_sample=True)
|
| 80 |
+
result = output[0]["generated_text"][-1]["content"]
|
| 81 |
+
|
| 82 |
+
if progress_callback:
|
| 83 |
+
progress_callback(1.0, "Done!")
|
| 84 |
+
|
| 85 |
+
return result
|
| 86 |
|
| 87 |
|
| 88 |
def _fallback_advice(debt_score: int, system_scores: list[dict], stressor_summary: str) -> str:
|
requirements.txt
CHANGED
|
@@ -2,5 +2,6 @@ gradio>=5.0.0,<7.0.0
|
|
| 2 |
mediapipe>=0.10.14
|
| 3 |
numpy>=1.26.0
|
| 4 |
onnxruntime>=1.18.0
|
| 5 |
-
llama-cpp-python>=0.3.0
|
| 6 |
huggingface_hub>=0.25.0
|
|
|
|
|
|
|
|
|
| 2 |
mediapipe>=0.10.14
|
| 3 |
numpy>=1.26.0
|
| 4 |
onnxruntime>=1.18.0
|
|
|
|
| 5 |
huggingface_hub>=0.25.0
|
| 6 |
+
transformers>=4.45.0
|
| 7 |
+
accelerate>=1.0.0
|