Spaces:
Running on Zero
Running on Zero
docs: update default coach-summary model to build-small-hackathon/pozify-coach-summary1 and clarify fallback behavior in documentation
Browse files- README.md +48 -39
- docs/01-docs-index.md +3 -3
- docs/10-overview-build-small-hackathon-report.md +3 -3
- docs/30-coach-modal-training.md +10 -8
- src/pozify/slm/providers.py +73 -42
- tests/test_slm_providers.py +37 -1
README.md
CHANGED
|
@@ -46,14 +46,13 @@ The current codebase supports:
|
|
| 46 |
- verifier and conservative fallback summaries
|
| 47 |
- Modal training pipelines for both the exercise router and coach-summary model
|
| 48 |
|
| 49 |
-
The current
|
| 50 |
|
| 51 |
-
- `
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
through `POZIFY_COACH_SUMMARY_LOCAL_MODEL_DIR`.
|
| 57 |
|
| 58 |
## Product Flow
|
| 59 |
|
|
@@ -82,25 +81,25 @@ Supported router labels:
|
|
| 82 |
|
| 83 |
## Model Stack
|
| 84 |
|
| 85 |
-
| Component
|
| 86 |
-
| --- | --- | --- | --- |
|
| 87 |
-
| Pose extraction
|
| 88 |
-
| Exercise router
|
| 89 |
-
| Router baseline
|
| 90 |
-
| Rep counting
|
| 91 |
-
| Issue markers
|
| 92 |
-
| Coach summary
|
| 93 |
-
| Coach-summary fine-tune | LoRA / merged checkpoint pipeline on Modal
|
| 94 |
-
| Verifier
|
| 95 |
|
| 96 |
The trained router is intentionally tiny:
|
| 97 |
|
| 98 |
-
| Artifact
|
| 99 |
-
| --- | ---: |
|
| 100 |
-
| BiLSTM router trainable params
|
| 101 |
-
| Router input features per frame |
|
| 102 |
-
| Window length
|
| 103 |
-
| Output classes
|
| 104 |
|
| 105 |
## Run The App Locally
|
| 106 |
|
|
@@ -146,17 +145,18 @@ uv run python app.py
|
|
| 146 |
|
| 147 |
## Coach Summary Runtime Options
|
| 148 |
|
| 149 |
-
### 1.
|
| 150 |
|
| 151 |
-
|
| 152 |
|
| 153 |
```bash
|
| 154 |
-
export POZIFY_COACH_SUMMARY_MODEL=
|
| 155 |
uv run python app.py
|
| 156 |
```
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|
|
|
|
| 160 |
|
| 161 |
### 2. Use the fine-tuned merged model locally
|
| 162 |
|
|
@@ -172,7 +172,16 @@ uv run python app.py
|
|
| 172 |
This is the simplest way to use `build-small-hackathon/pozify-coach-summary1` today without adding a
|
| 173 |
dedicated inference endpoint.
|
| 174 |
|
| 175 |
-
### 3.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
Pozify can send the coach-summary prompt to a local `llama-server` that exposes the
|
| 178 |
OpenAI-compatible `/v1/chat/completions` endpoint.
|
|
@@ -200,14 +209,14 @@ uv run python app.py
|
|
| 200 |
|
| 201 |
### Useful environment variables
|
| 202 |
|
| 203 |
-
| Variable
|
| 204 |
-
| --- | --- |
|
| 205 |
-
| `POZIFY_ROUTER_DEVICE`
|
| 206 |
-
| `POZIFY_SPACES_GPU_DURATION`
|
| 207 |
-
| `POZIFY_COACH_SUMMARY_PROVIDER`
|
| 208 |
-
| `POZIFY_COACH_SUMMARY_MODEL`
|
| 209 |
| `POZIFY_COACH_SUMMARY_LOCAL_MODEL_DIR` | Prefer a local merged/model directory for coach summary. |
|
| 210 |
-
| `POZIFY_COACH_SUMMARY_BYPASS_VERIFIER` | Keep model output even when verifier fails.
|
| 211 |
|
| 212 |
## Exercise Router Training
|
| 213 |
|
|
@@ -262,10 +271,10 @@ uv run modal run scripts/coach_summary_modal.py --stage publish-merged --repo-id
|
|
| 262 |
|
| 263 |
Important runtime note:
|
| 264 |
|
| 265 |
-
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
-
|
| 269 |
|
| 270 |
## Generated Artifacts
|
| 271 |
|
|
|
|
| 46 |
- verifier and conservative fallback summaries
|
| 47 |
- Modal training pipelines for both the exercise router and coach-summary model
|
| 48 |
|
| 49 |
+
The current default coach-summary model is:
|
| 50 |
|
| 51 |
+
- `build-small-hackathon/pozify-coach-summary1`
|
| 52 |
|
| 53 |
+
Pozify first tries Hugging Face `chat_completion`, then falls back to Hugging Face
|
| 54 |
+
`text_generation` for non-chat model repos. If hosted inference still rejects the repo or returns an
|
| 55 |
+
invalid schema, the app keeps the conservative fallback summary.
|
|
|
|
| 56 |
|
| 57 |
## Product Flow
|
| 58 |
|
|
|
|
| 81 |
|
| 82 |
## Model Stack
|
| 83 |
|
| 84 |
+
| Component | Model or method | Trained here? | Runtime |
|
| 85 |
+
| ----------------------- | -------------------------------------------------------- | ----------------------- | ---------------------------------------------- |
|
| 86 |
+
| Pose extraction | MediaPipe Pose Landmarker Lite | No | CPU / MediaPipe delegate |
|
| 87 |
+
| Exercise router | PyTorch BiLSTM over 30-frame pose windows | Yes | Torch |
|
| 88 |
+
| Router baseline | scikit-learn `HistGradientBoostingClassifier` | Yes | CPU fallback/reference |
|
| 89 |
+
| Rep counting | Exercise-specific state machines | No ML | CPU |
|
| 90 |
+
| Issue markers | Transparent rules over per-rep metrics | No ML | CPU |
|
| 91 |
+
| Coach summary | `build-small-hackathon/pozify-coach-summary1` by default | Fine-tuned merged model | HF Inference, local Transformers, or llama.cpp |
|
| 92 |
+
| Coach-summary fine-tune | LoRA / merged checkpoint pipeline on Modal | Yes | Local merged-model path recommended |
|
| 93 |
+
| Verifier | Deterministic safety and grounding checks | No ML | CPU |
|
| 94 |
|
| 95 |
The trained router is intentionally tiny:
|
| 96 |
|
| 97 |
+
| Artifact | Count |
|
| 98 |
+
| ------------------------------- | --------: |
|
| 99 |
+
| BiLSTM router trainable params | 182,796 |
|
| 100 |
+
| Router input features per frame | 237 |
|
| 101 |
+
| Window length | 30 frames |
|
| 102 |
+
| Output classes | 4 |
|
| 103 |
|
| 104 |
## Run The App Locally
|
| 105 |
|
|
|
|
| 145 |
|
| 146 |
## Coach Summary Runtime Options
|
| 147 |
|
| 148 |
+
### 1. Fine-tuned coach model
|
| 149 |
|
| 150 |
+
The app defaults to the fine-tuned coach-summary model:
|
| 151 |
|
| 152 |
```bash
|
| 153 |
+
export POZIFY_COACH_SUMMARY_MODEL=build-small-hackathon/pozify-coach-summary1
|
| 154 |
uv run python app.py
|
| 155 |
```
|
| 156 |
|
| 157 |
+
Pozify tries `chat_completion` first and falls back to `text_generation` when Hugging Face reports
|
| 158 |
+
that the repo is not a chat model. The deterministic fallback summary remains enabled if hosted
|
| 159 |
+
inference is unavailable or the model output fails validation.
|
| 160 |
|
| 161 |
### 2. Use the fine-tuned merged model locally
|
| 162 |
|
|
|
|
| 172 |
This is the simplest way to use `build-small-hackathon/pozify-coach-summary1` today without adding a
|
| 173 |
dedicated inference endpoint.
|
| 174 |
|
| 175 |
+
### 3. Base cloud model override
|
| 176 |
+
|
| 177 |
+
If you need the previous base-model runtime:
|
| 178 |
+
|
| 179 |
+
```bash
|
| 180 |
+
export POZIFY_COACH_SUMMARY_MODEL=Qwen/Qwen3-14B
|
| 181 |
+
uv run python app.py
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
### 4. llama.cpp
|
| 185 |
|
| 186 |
Pozify can send the coach-summary prompt to a local `llama-server` that exposes the
|
| 187 |
OpenAI-compatible `/v1/chat/completions` endpoint.
|
|
|
|
| 209 |
|
| 210 |
### Useful environment variables
|
| 211 |
|
| 212 |
+
| Variable | Purpose |
|
| 213 |
+
| -------------------------------------- | -------------------------------------------------------- |
|
| 214 |
+
| `POZIFY_ROUTER_DEVICE` | Override router device, for example `cpu` or `cuda`. |
|
| 215 |
+
| `POZIFY_SPACES_GPU_DURATION` | `spaces.GPU` duration in seconds, default `120`. |
|
| 216 |
+
| `POZIFY_COACH_SUMMARY_PROVIDER` | `hf_inference`, `local_transformers`, or `llama_cpp`. |
|
| 217 |
+
| `POZIFY_COACH_SUMMARY_MODEL` | Coach model id or llama.cpp model alias. |
|
| 218 |
| `POZIFY_COACH_SUMMARY_LOCAL_MODEL_DIR` | Prefer a local merged/model directory for coach summary. |
|
| 219 |
+
| `POZIFY_COACH_SUMMARY_BYPASS_VERIFIER` | Keep model output even when verifier fails. |
|
| 220 |
|
| 221 |
## Exercise Router Training
|
| 222 |
|
|
|
|
| 271 |
|
| 272 |
Important runtime note:
|
| 273 |
|
| 274 |
+
- the default coach model is `build-small-hackathon/pozify-coach-summary1`
|
| 275 |
+
- Hugging Face hosted inference may still reject a repo or produce invalid JSON, so the
|
| 276 |
+
conservative fallback summary stays enabled
|
| 277 |
+
- for the most predictable fine-tuned inference path, use `POZIFY_COACH_SUMMARY_LOCAL_MODEL_DIR`
|
| 278 |
|
| 279 |
## Generated Artifacts
|
| 280 |
|
docs/01-docs-index.md
CHANGED
|
@@ -20,7 +20,7 @@ Read in this order if you want to understand and run the current project:
|
|
| 20 |
|
| 21 |
## Notes
|
| 22 |
|
| 23 |
-
- Current coach-summary
|
| 24 |
-
-
|
| 25 |
-
inference
|
| 26 |
- Current app entrypoint: `uv run python app.py`
|
|
|
|
| 20 |
|
| 21 |
## Notes
|
| 22 |
|
| 23 |
+
- Current coach-summary runtime default: `build-small-hackathon/pozify-coach-summary1`
|
| 24 |
+
- The Hugging Face provider tries `chat_completion` first, then `text_generation` for non-chat
|
| 25 |
+
model repos; local merged-model inference remains the most predictable fine-tuned path
|
| 26 |
- Current app entrypoint: `uv run python app.py`
|
docs/10-overview-build-small-hackathon-report.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
Status note:
|
| 4 |
|
| 5 |
- This report is kept as the hackathon narrative document.
|
| 6 |
-
- Current runtime defaults
|
| 7 |
- Current coach-summary training now includes a LoRA/merge/publish pipeline on Modal.
|
| 8 |
- For the current operational commands, prefer [../README.md](../README.md) and
|
| 9 |
[30-coach-modal-training.md](30-coach-modal-training.md).
|
|
@@ -57,7 +57,7 @@ pretending every video is one of the supported movements.
|
|
| 57 |
| Pose extractor | MediaPipe Pose Landmarker Lite | Fast, practical feature extractor for a Gradio Space. |
|
| 58 |
| Exercise router | Custom PyTorch BiLSTM | Tiny trainable temporal model over pose windows. |
|
| 59 |
| Baseline router | scikit-learn HistGradientBoostingClassifier | Strong baseline over engineered vectors and fallback artifact. |
|
| 60 |
-
| Coach summary |
|
| 61 |
| llama.cpp path | Qwen3-14B Instruct GGUF via `llama-server` | Local-first/off-grid coach summary path with GPU offload. |
|
| 62 |
|
| 63 |
The original hackathon build trained the exercise router first and used Qwen as a grounded
|
|
@@ -212,7 +212,7 @@ logic stay outside the GPU worker. Useful settings:
|
|
| 212 |
|
| 213 |
```bash
|
| 214 |
POZIFY_COACH_SUMMARY_PROVIDER=local_transformers
|
| 215 |
-
POZIFY_COACH_SUMMARY_MODEL=
|
| 216 |
POZIFY_SPACES_GPU_DURATION=300
|
| 217 |
```
|
| 218 |
|
|
|
|
| 3 |
Status note:
|
| 4 |
|
| 5 |
- This report is kept as the hackathon narrative document.
|
| 6 |
+
- Current runtime defaults use `build-small-hackathon/pozify-coach-summary1`.
|
| 7 |
- Current coach-summary training now includes a LoRA/merge/publish pipeline on Modal.
|
| 8 |
- For the current operational commands, prefer [../README.md](../README.md) and
|
| 9 |
[30-coach-modal-training.md](30-coach-modal-training.md).
|
|
|
|
| 57 |
| Pose extractor | MediaPipe Pose Landmarker Lite | Fast, practical feature extractor for a Gradio Space. |
|
| 58 |
| Exercise router | Custom PyTorch BiLSTM | Tiny trainable temporal model over pose windows. |
|
| 59 |
| Baseline router | scikit-learn HistGradientBoostingClassifier | Strong baseline over engineered vectors and fallback artifact. |
|
| 60 |
+
| Coach summary | build-small-hackathon/pozify-coach-summary1 | Current default fine-tuned runtime for structured JSON explanation. |
|
| 61 |
| llama.cpp path | Qwen3-14B Instruct GGUF via `llama-server` | Local-first/off-grid coach summary path with GPU offload. |
|
| 62 |
|
| 63 |
The original hackathon build trained the exercise router first and used Qwen as a grounded
|
|
|
|
| 212 |
|
| 213 |
```bash
|
| 214 |
POZIFY_COACH_SUMMARY_PROVIDER=local_transformers
|
| 215 |
+
POZIFY_COACH_SUMMARY_MODEL=build-small-hackathon/pozify-coach-summary1
|
| 216 |
POZIFY_SPACES_GPU_DURATION=300
|
| 217 |
```
|
| 218 |
|
docs/30-coach-modal-training.md
CHANGED
|
@@ -34,8 +34,9 @@ Important limitation:
|
|
| 34 |
|
| 35 |
- Publishing a merged model repo to Hugging Face does **not** currently guarantee that the repo can
|
| 36 |
be used through Hugging Face serverless `chat_completion`.
|
| 37 |
-
-
|
| 38 |
-
|
|
|
|
| 39 |
`POZIFY_COACH_SUMMARY_LOCAL_MODEL_DIR`.
|
| 40 |
|
| 41 |
## Requirements
|
|
@@ -142,10 +143,10 @@ The Modal model volume stores:
|
|
| 142 |
|
| 143 |
## Current Runtime Usage
|
| 144 |
|
| 145 |
-
###
|
| 146 |
|
| 147 |
```bash
|
| 148 |
-
export POZIFY_COACH_SUMMARY_MODEL=
|
| 149 |
uv run python app.py
|
| 150 |
```
|
| 151 |
|
|
@@ -160,15 +161,16 @@ export POZIFY_COACH_SUMMARY_ADAPTER_ID=build-small-hackathon/pozify-coach-summar
|
|
| 160 |
uv run python app.py
|
| 161 |
```
|
| 162 |
|
| 163 |
-
###
|
| 164 |
|
| 165 |
-
|
| 166 |
|
| 167 |
```bash
|
| 168 |
-
export POZIFY_COACH_SUMMARY_MODEL=
|
| 169 |
```
|
| 170 |
|
| 171 |
-
|
|
|
|
| 172 |
|
| 173 |
## Evaluation Meaning
|
| 174 |
|
|
|
|
| 34 |
|
| 35 |
- Publishing a merged model repo to Hugging Face does **not** currently guarantee that the repo can
|
| 36 |
be used through Hugging Face serverless `chat_completion`.
|
| 37 |
+
- The current codebase defaults to `build-small-hackathon/pozify-coach-summary1` and falls back
|
| 38 |
+
from `chat_completion` to `text_generation` for non-chat model repos.
|
| 39 |
+
- The most predictable fine-tuned path remains local inference through
|
| 40 |
`POZIFY_COACH_SUMMARY_LOCAL_MODEL_DIR`.
|
| 41 |
|
| 42 |
## Requirements
|
|
|
|
| 143 |
|
| 144 |
## Current Runtime Usage
|
| 145 |
|
| 146 |
+
### Default fine-tuned runtime
|
| 147 |
|
| 148 |
```bash
|
| 149 |
+
export POZIFY_COACH_SUMMARY_MODEL=build-small-hackathon/pozify-coach-summary1
|
| 150 |
uv run python app.py
|
| 151 |
```
|
| 152 |
|
|
|
|
| 161 |
uv run python app.py
|
| 162 |
```
|
| 163 |
|
| 164 |
+
### Previous base-model runtime
|
| 165 |
|
| 166 |
+
If you need to force the previous base model:
|
| 167 |
|
| 168 |
```bash
|
| 169 |
+
export POZIFY_COACH_SUMMARY_MODEL=Qwen/Qwen3-14B
|
| 170 |
```
|
| 171 |
|
| 172 |
+
The fine-tuned model may still fall back if hosted inference is unavailable or returns invalid JSON,
|
| 173 |
+
so the deterministic fallback summary remains part of the runtime path.
|
| 174 |
|
| 175 |
## Evaluation Meaning
|
| 176 |
|
src/pozify/slm/providers.py
CHANGED
|
@@ -24,7 +24,7 @@ LLAMA_CPP_BASE_URL_ENV = "POZIFY_LLAMA_CPP_BASE_URL"
|
|
| 24 |
LLAMA_CPP_TIMEOUT_ENV = "POZIFY_LLAMA_CPP_TIMEOUT"
|
| 25 |
|
| 26 |
DEFAULT_PROVIDER = "hf_inference"
|
| 27 |
-
DEFAULT_MODEL = "
|
| 28 |
DEFAULT_LLAMA_CPP_BASE_URL = "http://127.0.0.1:8080"
|
| 29 |
LOCAL_TRANSFORMERS_PROVIDER = "local_transformers"
|
| 30 |
LOCAL_TRANSFORMERS_ALIASES = {LOCAL_TRANSFORMERS_PROVIDER, "local", "transformers"}
|
|
@@ -97,52 +97,83 @@ class HFInferenceCoachSummaryModel:
|
|
| 97 |
self._client = InferenceClient(api_key=self.token)
|
| 98 |
return self._client
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
def generate_summary(self, prompt: str) -> CoachSummaryGeneration:
|
| 101 |
client = self._client_instance()
|
|
|
|
| 102 |
if hasattr(client, "chat_completion"):
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
"role": "system",
|
| 108 |
-
"content": "Return JSON only.",
|
| 109 |
-
},
|
| 110 |
-
{
|
| 111 |
-
"role": "user",
|
| 112 |
-
"content": prompt,
|
| 113 |
-
},
|
| 114 |
-
],
|
| 115 |
-
temperature=self.temperature,
|
| 116 |
-
max_tokens=self.max_tokens,
|
| 117 |
-
)
|
| 118 |
-
choices = getattr(response, "choices", None) or []
|
| 119 |
-
if not choices:
|
| 120 |
-
raise RuntimeError("Hugging Face inference returned no choices")
|
| 121 |
-
message = getattr(choices[0], "message", None)
|
| 122 |
-
text = getattr(message, "content", None)
|
| 123 |
-
if not isinstance(text, str) or not text.strip():
|
| 124 |
-
raise RuntimeError("Hugging Face inference returned an empty message")
|
| 125 |
-
return CoachSummaryGeneration(
|
| 126 |
-
text=text,
|
| 127 |
-
provider="hf_inference",
|
| 128 |
-
model=self.model,
|
| 129 |
-
)
|
| 130 |
|
| 131 |
if hasattr(client, "text_generation"):
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
)
|
| 146 |
|
| 147 |
raise RuntimeError("No supported Hugging Face inference method is available")
|
| 148 |
|
|
|
|
| 24 |
LLAMA_CPP_TIMEOUT_ENV = "POZIFY_LLAMA_CPP_TIMEOUT"
|
| 25 |
|
| 26 |
DEFAULT_PROVIDER = "hf_inference"
|
| 27 |
+
DEFAULT_MODEL = "build-small-hackathon/pozify-coach-summary1"
|
| 28 |
DEFAULT_LLAMA_CPP_BASE_URL = "http://127.0.0.1:8080"
|
| 29 |
LOCAL_TRANSFORMERS_PROVIDER = "local_transformers"
|
| 30 |
LOCAL_TRANSFORMERS_ALIASES = {LOCAL_TRANSFORMERS_PROVIDER, "local", "transformers"}
|
|
|
|
| 97 |
self._client = InferenceClient(api_key=self.token)
|
| 98 |
return self._client
|
| 99 |
|
| 100 |
+
def _generate_with_chat_completion(
|
| 101 |
+
self,
|
| 102 |
+
client: Any,
|
| 103 |
+
prompt: str,
|
| 104 |
+
) -> CoachSummaryGeneration:
|
| 105 |
+
response = client.chat_completion(
|
| 106 |
+
model=self.model,
|
| 107 |
+
messages=[
|
| 108 |
+
{
|
| 109 |
+
"role": "system",
|
| 110 |
+
"content": "Return JSON only.",
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"role": "user",
|
| 114 |
+
"content": prompt,
|
| 115 |
+
},
|
| 116 |
+
],
|
| 117 |
+
temperature=self.temperature,
|
| 118 |
+
max_tokens=self.max_tokens,
|
| 119 |
+
)
|
| 120 |
+
choices = getattr(response, "choices", None) or []
|
| 121 |
+
if not choices:
|
| 122 |
+
raise RuntimeError("Hugging Face inference returned no choices")
|
| 123 |
+
message = getattr(choices[0], "message", None)
|
| 124 |
+
text = getattr(message, "content", None)
|
| 125 |
+
if not isinstance(text, str) or not text.strip():
|
| 126 |
+
raise RuntimeError("Hugging Face inference returned an empty message")
|
| 127 |
+
return CoachSummaryGeneration(
|
| 128 |
+
text=text,
|
| 129 |
+
provider="hf_inference",
|
| 130 |
+
model=self.model,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
def _generate_with_text_generation(
|
| 134 |
+
self,
|
| 135 |
+
client: Any,
|
| 136 |
+
prompt: str,
|
| 137 |
+
) -> CoachSummaryGeneration:
|
| 138 |
+
text = client.text_generation(
|
| 139 |
+
prompt,
|
| 140 |
+
model=self.model,
|
| 141 |
+
max_new_tokens=self.max_tokens,
|
| 142 |
+
temperature=self.temperature,
|
| 143 |
+
return_full_text=False,
|
| 144 |
+
)
|
| 145 |
+
if not isinstance(text, str) or not text.strip():
|
| 146 |
+
raise RuntimeError("Hugging Face text generation returned empty output")
|
| 147 |
+
return CoachSummaryGeneration(
|
| 148 |
+
text=text,
|
| 149 |
+
provider="hf_inference",
|
| 150 |
+
model=self.model,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
def generate_summary(self, prompt: str) -> CoachSummaryGeneration:
|
| 154 |
client = self._client_instance()
|
| 155 |
+
chat_error: Exception | None = None
|
| 156 |
if hasattr(client, "chat_completion"):
|
| 157 |
+
try:
|
| 158 |
+
return self._generate_with_chat_completion(client, prompt)
|
| 159 |
+
except Exception as exc:
|
| 160 |
+
chat_error = exc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
if hasattr(client, "text_generation"):
|
| 163 |
+
try:
|
| 164 |
+
return self._generate_with_text_generation(client, prompt)
|
| 165 |
+
except Exception as exc:
|
| 166 |
+
if chat_error is None:
|
| 167 |
+
raise
|
| 168 |
+
raise RuntimeError(
|
| 169 |
+
"Hugging Face chat completion and text generation both failed: "
|
| 170 |
+
f"chat={chat_error}; text_generation={exc}"
|
| 171 |
+
) from exc
|
| 172 |
+
|
| 173 |
+
if chat_error is not None:
|
| 174 |
+
raise RuntimeError(
|
| 175 |
+
f"Hugging Face chat completion failed and text generation is unavailable: {chat_error}"
|
| 176 |
+
) from chat_error
|
| 177 |
|
| 178 |
raise RuntimeError("No supported Hugging Face inference method is available")
|
| 179 |
|
tests/test_slm_providers.py
CHANGED
|
@@ -67,7 +67,43 @@ class SlmProviderTests(unittest.TestCase):
|
|
| 67 |
model = get_coach_summary_model()
|
| 68 |
|
| 69 |
self.assertIsInstance(model, HFInferenceCoachSummaryModel)
|
| 70 |
-
self.assertEqual(model.model, "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
|
| 73 |
if __name__ == "__main__":
|
|
|
|
| 67 |
model = get_coach_summary_model()
|
| 68 |
|
| 69 |
self.assertIsInstance(model, HFInferenceCoachSummaryModel)
|
| 70 |
+
self.assertEqual(model.model, "build-small-hackathon/pozify-coach-summary1")
|
| 71 |
+
|
| 72 |
+
def test_hf_inference_falls_back_to_text_generation_for_non_chat_model(self) -> None:
|
| 73 |
+
class _TextGenerationClient:
|
| 74 |
+
def __init__(self) -> None:
|
| 75 |
+
self.text_generation_kwargs = None
|
| 76 |
+
|
| 77 |
+
def chat_completion(self, **_kwargs):
|
| 78 |
+
raise RuntimeError("not a chat model")
|
| 79 |
+
|
| 80 |
+
def text_generation(self, prompt: str, **kwargs):
|
| 81 |
+
self.text_generation_kwargs = {"prompt": prompt, **kwargs}
|
| 82 |
+
return '{"summary":"ok"}'
|
| 83 |
+
|
| 84 |
+
client = _TextGenerationClient()
|
| 85 |
+
model = HFInferenceCoachSummaryModel(
|
| 86 |
+
model="build-small-hackathon/pozify-coach-summary1",
|
| 87 |
+
max_tokens=123,
|
| 88 |
+
temperature=0.2,
|
| 89 |
+
)
|
| 90 |
+
model._client = client
|
| 91 |
+
|
| 92 |
+
generation = model.generate_summary("coach prompt")
|
| 93 |
+
|
| 94 |
+
self.assertEqual(generation.provider, "hf_inference")
|
| 95 |
+
self.assertEqual(generation.model, "build-small-hackathon/pozify-coach-summary1")
|
| 96 |
+
self.assertEqual(generation.text, '{"summary":"ok"}')
|
| 97 |
+
self.assertEqual(
|
| 98 |
+
client.text_generation_kwargs,
|
| 99 |
+
{
|
| 100 |
+
"prompt": "coach prompt",
|
| 101 |
+
"model": "build-small-hackathon/pozify-coach-summary1",
|
| 102 |
+
"max_new_tokens": 123,
|
| 103 |
+
"temperature": 0.2,
|
| 104 |
+
"return_full_text": False,
|
| 105 |
+
},
|
| 106 |
+
)
|
| 107 |
|
| 108 |
|
| 109 |
if __name__ == "__main__":
|