Default Dream QA to Modal endpoints

#23
README.md CHANGED
@@ -39,7 +39,7 @@ This is not a therapy, diagnosis, or prophecy product.
39
  - `openbmb/MiniCPM-V-4.6` for image/sketch/note understanding.
40
  - `openbmb/MiniCPM5-1B` for dream summary, follow-up questions, interpretation, and Today Tip generation.
41
  - A small ASR adapter may be used only for voice transcription.
42
- - The app defaults to a stable demo backend so the local Gradio flow always works.
43
  - Optional Ollama adapters are included for local MiniCPM testing.
44
  - MiniCPM prompts are language-aware: English by default, Chinese when the user chooses `中文`.
45
 
@@ -107,7 +107,7 @@ The public Space stays lightweight and can call private Modal endpoints through
107
 
108
  Set these only as Hugging Face Space repository secrets or local shell variables. Do not store values in `.env`, docs, logs, screenshots, or git. Missing endpoints or route failures fall back to deterministic demo behavior.
109
 
110
- The Gradio UI defaults to `model` for both text and vision backends, so a configured Space calls Modal by default. The `demo` backend remains available in developer settings as the deterministic fallback path.
111
 
112
  The Hugging Face Space may run on ZeroGPU for hackathon hardware eligibility. `dream_customs.zerogpu` registers a lightweight `@spaces.GPU` startup probe so ZeroGPU accepts the app, but real MiniCPM inference still happens on the private Modal backend.
113
 
 
39
  - `openbmb/MiniCPM-V-4.6` for image/sketch/note understanding.
40
  - `openbmb/MiniCPM5-1B` for dream summary, follow-up questions, interpretation, and Today Tip generation.
41
  - A small ASR adapter may be used only for voice transcription.
42
+ - The app defaults to the Modal-hosted MiniCPM route when endpoint secrets are configured, with stable demo fallback when hosted routes are unavailable.
43
  - Optional Ollama adapters are included for local MiniCPM testing.
44
  - MiniCPM prompts are language-aware: English by default, Chinese when the user chooses `中文`.
45
 
 
107
 
108
  Set these only as Hugging Face Space repository secrets or local shell variables. Do not store values in `.env`, docs, logs, screenshots, or git. Missing endpoints or route failures fall back to deterministic demo behavior.
109
 
110
+ The Gradio UI defaults to `modal` for both text and vision backends, so a configured Space enters the private Modal MiniCPM endpoints first. The `demo` backend remains available in developer settings and as the deterministic fallback path when hosted routes are missing or fail.
111
 
112
  The Hugging Face Space may run on ZeroGPU for hackathon hardware eligibility. `dream_customs.zerogpu` registers a lightweight `@spaces.GPU` startup probe so ZeroGPU accepts the app, but real MiniCPM inference still happens on the private Modal backend.
113
 
docs/handoff.md CHANGED
@@ -181,7 +181,7 @@ Hugging Face Space Gradio UI
181
  -> MiniCPM5-1B text generation and MiniCPM-V-4.6 visual clue extraction
182
  ```
183
 
184
- The Space defaults to `model` for both text and vision backends, so a configured Space calls Modal immediately. The `demo` backend remains available in developer settings as the deterministic fallback path.
185
 
186
  The public Space can run on ZeroGPU after `dream_customs.zerogpu` registers the lightweight `@spaces.GPU` startup probe. Modal credits still pay for the hidden GPU backend; the ZeroGPU probe is only there to make the HF hardware setting valid for this Gradio frontend.
187
 
 
181
  -> MiniCPM5-1B text generation and MiniCPM-V-4.6 visual clue extraction
182
  ```
183
 
184
+ The Space defaults to `modal` for both text and vision backends, so a configured Space enters the private Modal MiniCPM endpoints first. The `model` auto route remains available for compatibility, and the `demo` backend remains available in developer settings as the deterministic fallback path.
185
 
186
  The public Space can run on ZeroGPU after `dream_customs.zerogpu` registers the lightweight `@spaces.GPU` startup probe. Modal credits still pay for the hidden GPU backend; the ZeroGPU probe is only there to make the HF hardware setting valid for this Gradio frontend.
187
 
docs/smoke/2026-06-10-modal-default-entrypoint-smoke.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modal Default Entrypoint Smoke - 2026-06-10
2
+
3
+ ## Scope
4
+
5
+ Verify that Dream QA now enters the Modal-hosted MiniCPM route by default for text and vision while preserving deterministic fallback behavior when hosted endpoint secrets are not available in the local shell.
6
+
7
+ No endpoint URLs, bearer tokens, Hugging Face tokens, Modal tokens, or secret values were printed or recorded.
8
+
9
+ ## Local Default Action Smoke
10
+
11
+ Command:
12
+
13
+ ```bash
14
+ .venv/bin/python - <<'PY'
15
+ import json
16
+ from dream_customs.ui.actions import submit_dream_action, skip_to_card_action
17
+
18
+ state, view_json = submit_dream_action(
19
+ dream_text='I dreamed of a late elevator with melted buttons.',
20
+ mood='Uneasy',
21
+ )
22
+ view = json.loads(view_json)
23
+ print({
24
+ 'submit_status': view['status'],
25
+ 'submit_phase': view['phase'],
26
+ 'text_backend': view['debug']['text_backend'],
27
+ 'vision_backend': view['debug']['vision_backend'],
28
+ 'question_present': bool(view['question']),
29
+ })
30
+
31
+ state, view_json = skip_to_card_action(state)
32
+ view = json.loads(view_json)
33
+ print({
34
+ 'card_status': view['status'],
35
+ 'card_phase': view['phase'],
36
+ 'card_title': view['card_title'],
37
+ 'tip_mentions_elevator': 'elevator' in view['card_text'].lower(),
38
+ })
39
+ PY
40
+ ```
41
+
42
+ Observed:
43
+
44
+ ```text
45
+ {'submit_status': 'ask', 'submit_phase': 'ask', 'text_backend': 'modal', 'vision_backend': 'modal', 'question_present': True}
46
+ {'card_status': 'tip', 'card_phase': 'tip', 'card_title': 'Today Tip', 'tip_mentions_elevator': True}
47
+ ```
48
+
49
+ Result: pass. The default backend values are `modal` and the local no-secret path still falls back to a usable dream Q&A flow.
50
+
51
+ ## Test Evidence
52
+
53
+ - Focused regression: `.venv/bin/python -m pytest tests/test_ui_actions.py::test_mobile_defaults_to_modal_backends tests/test_app_logic.py::test_defaults_use_modal_model_entrypoint -q`
54
+ - Full suite: `.venv/bin/python -m pytest -q` -> 98 passed, 2 warnings.
55
+ - Today Tip eval: `.venv/bin/python scripts/evaluate_today_tip_quality.py` -> 11 cases passed.
56
+ - Whitespace check: `git diff --check` -> passed.
57
+ - Local Gradio config: `Text generation` default value is `modal`; `Image understanding` default value is `modal`.
58
+ - Public Space check: pending.
docs/superpowers/plans/2026-06-10-modal-default-entrypoint.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modal Default Entrypoint Plan - 2026-06-10
2
+
3
+ ## Goal
4
+
5
+ Make the public Dream QA default backend route explicit: text and image understanding should enter through the Modal-hosted MiniCPM path by default, while deterministic demo behavior remains available as a user-selectable and failure fallback route.
6
+
7
+ ## Guardrails
8
+
9
+ - Do not store or print endpoint URLs, tokens, HF tokens, Modal tokens, or secret values.
10
+ - Keep text-only and demo fallback behavior working when hosted endpoints are absent, cold, slow, or failing.
11
+ - Do not replace the Hugging Face Space Gradio frontend with Modal.
12
+ - Do not add non-MiniCPM default models.
13
+
14
+ ## Tasks
15
+
16
+ - [x] Add a regression test that expects default text and vision backends to be `modal`.
17
+ - [x] Change the default text and vision backend constants to `modal`.
18
+ - [x] Make the Advanced backend labels show Modal as the intentional default entrypoint.
19
+ - [x] Update docs to describe Modal as the default configured route and demo as fallback.
20
+ - [x] Run focused tests, full pytest, eval, local smoke, and whitespace checks.
21
+ - [ ] Commit, push, sync to Hugging Face Space, merge, and re-check the public app.
22
+
23
+ ## Progress
24
+
25
+ - Focused regression tests pass after changing defaults to `modal`.
26
+ - Local default action smoke passes without configured endpoint secrets; the app records `modal` in debug and falls back to a usable Q&A flow.
27
+ - Full pytest passes with 98 tests; Today Tip eval passes 11 cases; `git diff --check` passes.
dream_customs/defaults.py CHANGED
@@ -1,3 +1,2 @@
1
- DEFAULT_TEXT_BACKEND = "model"
2
- DEFAULT_VISION_BACKEND = "model"
3
-
 
1
+ DEFAULT_TEXT_BACKEND = "modal"
2
+ DEFAULT_VISION_BACKEND = "modal"
 
dream_customs/ui/app.py CHANGED
@@ -474,9 +474,9 @@ def build_demo() -> gr.Blocks:
474
  text_backend = gr.Dropdown(
475
  label="Text generation",
476
  choices=[
 
477
  ("Auto: configured Space model", "model"),
478
  ("Demo: stable sample data", "demo"),
479
- ("Modal/API: private endpoint", "modal"),
480
  ("Local Ollama", "ollama"),
481
  ],
482
  value=DEFAULT_TEXT_BACKEND,
@@ -484,9 +484,9 @@ def build_demo() -> gr.Blocks:
484
  vision_backend = gr.Dropdown(
485
  label="Image understanding",
486
  choices=[
 
487
  ("Auto: configured vision model", "model"),
488
  ("Demo: skip image model", "demo"),
489
- ("Modal/API: private endpoint", "modal"),
490
  ("Local Ollama", "ollama"),
491
  ],
492
  value=DEFAULT_VISION_BACKEND,
 
474
  text_backend = gr.Dropdown(
475
  label="Text generation",
476
  choices=[
477
+ ("Modal: MiniCPM endpoint", "modal"),
478
  ("Auto: configured Space model", "model"),
479
  ("Demo: stable sample data", "demo"),
 
480
  ("Local Ollama", "ollama"),
481
  ],
482
  value=DEFAULT_TEXT_BACKEND,
 
484
  vision_backend = gr.Dropdown(
485
  label="Image understanding",
486
  choices=[
487
+ ("Modal: MiniCPM-V endpoint", "modal"),
488
  ("Auto: configured vision model", "model"),
489
  ("Demo: skip image model", "demo"),
 
490
  ("Local Ollama", "ollama"),
491
  ],
492
  value=DEFAULT_VISION_BACKEND,
tests/test_app_logic.py CHANGED
@@ -12,9 +12,15 @@ from dream_customs.app_logic import (
12
  seal_pact_action,
13
  start_declaration_action,
14
  )
 
15
  from dream_customs.models import HostedASRClient, HostedMiniCPMTextClient, HostedMiniCPMVisionClient
16
 
17
 
 
 
 
 
 
18
  def test_run_customs_once_generates_demo_outputs():
19
  negotiation, pact_text, html, debug_json = run_customs_once(
20
  dream_text="I dreamed of a late elevator.",
 
12
  seal_pact_action,
13
  start_declaration_action,
14
  )
15
+ from dream_customs.defaults import DEFAULT_TEXT_BACKEND, DEFAULT_VISION_BACKEND
16
  from dream_customs.models import HostedASRClient, HostedMiniCPMTextClient, HostedMiniCPMVisionClient
17
 
18
 
19
+ def test_defaults_use_modal_model_entrypoint():
20
+ assert DEFAULT_TEXT_BACKEND == "modal"
21
+ assert DEFAULT_VISION_BACKEND == "modal"
22
+
23
+
24
  def test_run_customs_once_generates_demo_outputs():
25
  negotiation, pact_text, html, debug_json = run_customs_once(
26
  dream_text="I dreamed of a late elevator.",
tests/test_ui_actions.py CHANGED
@@ -8,12 +8,12 @@ from dream_customs.ui.app import _reset
8
  from dream_customs.ui.copy import DEFAULT_MOOD, PROCESSING_NOTE
9
 
10
 
11
- def test_mobile_defaults_to_model_backends():
12
  _state, view_json = initial_mobile_state()
13
  view = json.loads(view_json)
14
 
15
- assert view["debug"]["text_backend"] == "model"
16
- assert view["debug"]["vision_backend"] == "model"
17
 
18
 
19
  def test_runtime_settings_are_collapsed_for_public_flow():
 
8
  from dream_customs.ui.copy import DEFAULT_MOOD, PROCESSING_NOTE
9
 
10
 
11
+ def test_mobile_defaults_to_modal_backends():
12
  _state, view_json = initial_mobile_state()
13
  view = json.loads(view_json)
14
 
15
+ assert view["debug"]["text_backend"] == "modal"
16
+ assert view["debug"]["vision_backend"] == "modal"
17
 
18
 
19
  def test_runtime_settings_are_collapsed_for_public_flow():