Sync Dream QA latency polish
#46
by ADJCJH - opened
- dream_customs/app_logic.py +10 -7
- modal_backend/contracts.py +3 -3
- modal_backend/dream_customs_modal.py +2 -1
- scripts/benchmark_hosted_latency.py +188 -0
- tests/test_app_logic.py +22 -0
- tests/test_modal_contract.py +2 -2
- tests/test_ui_actions.py +1 -1
dream_customs/app_logic.py
CHANGED
|
@@ -35,15 +35,15 @@ from dream_customs.schema import CustomsSession
|
|
| 35 |
|
| 36 |
DEFAULT_TEXT_MODEL = "hf.co/openbmb/MiniCPM5-1B-GGUF:Q8_0"
|
| 37 |
DEFAULT_VISION_MODEL = "openbmb/minicpm-v4.6"
|
| 38 |
-
DEFAULT_HOSTED_TIMEOUT_SECONDS =
|
| 39 |
-
DEFAULT_ASR_TIMEOUT_SECONDS =
|
| 40 |
DEFAULT_TEXT_TEMPERATURE = 0.0
|
| 41 |
DEFAULT_VISION_TEMPERATURE = 0.1
|
| 42 |
-
DEFAULT_TEXT_MAX_TOKENS =
|
| 43 |
-
DEFAULT_VISION_MAX_TOKENS =
|
| 44 |
-
DEFAULT_TEXT_LATENCY_BUDGET_MS =
|
| 45 |
-
DEFAULT_VISION_LATENCY_BUDGET_MS =
|
| 46 |
-
DEFAULT_ASR_LATENCY_BUDGET_MS =
|
| 47 |
|
| 48 |
|
| 49 |
def _file_path(value: Any) -> str:
|
|
@@ -154,6 +154,7 @@ def _clients(text_backend: str, vision_backend: str, **settings):
|
|
| 154 |
timeout=resolved["text_timeout_seconds"],
|
| 155 |
temperature=resolved["text_temperature"],
|
| 156 |
max_tokens=resolved["text_max_tokens"],
|
|
|
|
| 157 |
)
|
| 158 |
else:
|
| 159 |
text_client = FakeTextClient()
|
|
@@ -171,6 +172,7 @@ def _clients(text_backend: str, vision_backend: str, **settings):
|
|
| 171 |
timeout=resolved["vision_timeout_seconds"],
|
| 172 |
temperature=resolved["vision_temperature"],
|
| 173 |
max_tokens=resolved["vision_max_tokens"],
|
|
|
|
| 174 |
)
|
| 175 |
else:
|
| 176 |
vision_client = FakeVisionClient()
|
|
@@ -180,6 +182,7 @@ def _clients(text_backend: str, vision_backend: str, **settings):
|
|
| 180 |
endpoint=resolved["asr_endpoint"],
|
| 181 |
token=resolved["hosted_token"],
|
| 182 |
timeout=resolved["asr_timeout_seconds"],
|
|
|
|
| 183 |
)
|
| 184 |
else:
|
| 185 |
asr_client = FakeASRClient()
|
|
|
|
| 35 |
|
| 36 |
DEFAULT_TEXT_MODEL = "hf.co/openbmb/MiniCPM5-1B-GGUF:Q8_0"
|
| 37 |
DEFAULT_VISION_MODEL = "openbmb/minicpm-v4.6"
|
| 38 |
+
DEFAULT_HOSTED_TIMEOUT_SECONDS = 9.0
|
| 39 |
+
DEFAULT_ASR_TIMEOUT_SECONDS = 9.0
|
| 40 |
DEFAULT_TEXT_TEMPERATURE = 0.0
|
| 41 |
DEFAULT_VISION_TEMPERATURE = 0.1
|
| 42 |
+
DEFAULT_TEXT_MAX_TOKENS = 480
|
| 43 |
+
DEFAULT_VISION_MAX_TOKENS = 220
|
| 44 |
+
DEFAULT_TEXT_LATENCY_BUDGET_MS = 8000
|
| 45 |
+
DEFAULT_VISION_LATENCY_BUDGET_MS = 9000
|
| 46 |
+
DEFAULT_ASR_LATENCY_BUDGET_MS = 8000
|
| 47 |
|
| 48 |
|
| 49 |
def _file_path(value: Any) -> str:
|
|
|
|
| 154 |
timeout=resolved["text_timeout_seconds"],
|
| 155 |
temperature=resolved["text_temperature"],
|
| 156 |
max_tokens=resolved["text_max_tokens"],
|
| 157 |
+
latency_budget_ms=resolved["text_latency_budget_ms"],
|
| 158 |
)
|
| 159 |
else:
|
| 160 |
text_client = FakeTextClient()
|
|
|
|
| 172 |
timeout=resolved["vision_timeout_seconds"],
|
| 173 |
temperature=resolved["vision_temperature"],
|
| 174 |
max_tokens=resolved["vision_max_tokens"],
|
| 175 |
+
latency_budget_ms=resolved["vision_latency_budget_ms"],
|
| 176 |
)
|
| 177 |
else:
|
| 178 |
vision_client = FakeVisionClient()
|
|
|
|
| 182 |
endpoint=resolved["asr_endpoint"],
|
| 183 |
token=resolved["hosted_token"],
|
| 184 |
timeout=resolved["asr_timeout_seconds"],
|
| 185 |
+
latency_budget_ms=resolved["asr_latency_budget_ms"],
|
| 186 |
)
|
| 187 |
else:
|
| 188 |
asr_client = FakeASRClient()
|
modal_backend/contracts.py
CHANGED
|
@@ -24,12 +24,12 @@ def normalize_text_payload(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 24 |
if content:
|
| 25 |
parts.append(content)
|
| 26 |
prompt = "\n".join(parts).strip()
|
| 27 |
-
max_tokens = payload.get("max_tokens",
|
| 28 |
try:
|
| 29 |
max_tokens = int(max_tokens)
|
| 30 |
except (TypeError, ValueError):
|
| 31 |
-
max_tokens =
|
| 32 |
-
max_tokens = max(64, min(max_tokens,
|
| 33 |
temperature = payload.get("temperature", 0.0)
|
| 34 |
try:
|
| 35 |
temperature = float(temperature)
|
|
|
|
| 24 |
if content:
|
| 25 |
parts.append(content)
|
| 26 |
prompt = "\n".join(parts).strip()
|
| 27 |
+
max_tokens = payload.get("max_tokens", 480)
|
| 28 |
try:
|
| 29 |
max_tokens = int(max_tokens)
|
| 30 |
except (TypeError, ValueError):
|
| 31 |
+
max_tokens = 480
|
| 32 |
+
max_tokens = max(64, min(max_tokens, 700))
|
| 33 |
temperature = payload.get("temperature", 0.0)
|
| 34 |
try:
|
| 35 |
temperature = float(temperature)
|
modal_backend/dream_customs_modal.py
CHANGED
|
@@ -538,7 +538,8 @@ async def vision(
|
|
| 538 |
],
|
| 539 |
}
|
| 540 |
]
|
| 541 |
-
|
|
|
|
| 542 |
return response_payload(_stringify_pipeline_result(result))
|
| 543 |
|
| 544 |
|
|
|
|
| 538 |
],
|
| 539 |
}
|
| 540 |
]
|
| 541 |
+
max_tokens = max(64, min(int(payload.get("max_tokens", 220)), 400))
|
| 542 |
+
result = pipe(text=messages, max_new_tokens=max_tokens)
|
| 543 |
return response_payload(_stringify_pipeline_result(result))
|
| 544 |
|
| 545 |
|
scripts/benchmark_hosted_latency.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import tempfile
|
| 5 |
+
import time
|
| 6 |
+
import wave
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any, Callable
|
| 9 |
+
|
| 10 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 11 |
+
if str(ROOT) not in sys.path:
|
| 12 |
+
sys.path.insert(0, str(ROOT))
|
| 13 |
+
|
| 14 |
+
from dream_customs.models import HostedASRClient, HostedMiniCPMTextClient, HostedMiniCPMVisionClient
|
| 15 |
+
from dream_customs.prompts import negotiation_prompt, today_tip_prompt
|
| 16 |
+
from dream_customs.pipeline import build_intake, build_qa_state
|
| 17 |
+
from dream_customs.schema import TodayTipCard
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TextFallback:
|
| 21 |
+
def generate_negotiation(self, _prompt: str) -> dict[str, Any]:
|
| 22 |
+
return {"visitor_name": "fallback", "questions": ["fallback"], "tone_note": "fallback"}
|
| 23 |
+
|
| 24 |
+
def generate_today_tip(self, _prompt: str) -> TodayTipCard:
|
| 25 |
+
return TodayTipCard(
|
| 26 |
+
dream_summary="fallback",
|
| 27 |
+
main_question="fallback",
|
| 28 |
+
dream_anchors=["fallback"],
|
| 29 |
+
followup_questions=[],
|
| 30 |
+
user_answers=[],
|
| 31 |
+
interpretation="fallback",
|
| 32 |
+
today_tip="fallback",
|
| 33 |
+
tiny_action="fallback",
|
| 34 |
+
caring_note="fallback",
|
| 35 |
+
safety_note="",
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class VisionFallback:
|
| 40 |
+
def extract_clues(self, _image_path: str) -> list[str]:
|
| 41 |
+
return ["fallback"]
|
| 42 |
+
|
| 43 |
+
def extract_witness(self, _image_path: str):
|
| 44 |
+
raise RuntimeError("fallback witness should not be used in this benchmark")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class ASRFallback:
|
| 48 |
+
def transcribe(self, _audio_path: str) -> str:
|
| 49 |
+
return "fallback"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _measure(name: str, fn: Callable[[], Any]) -> dict[str, Any]:
|
| 53 |
+
start = time.perf_counter()
|
| 54 |
+
try:
|
| 55 |
+
value = fn()
|
| 56 |
+
ok = True
|
| 57 |
+
error = ""
|
| 58 |
+
except Exception as exc: # pragma: no cover - benchmark safety net
|
| 59 |
+
value = None
|
| 60 |
+
ok = False
|
| 61 |
+
error = exc.__class__.__name__
|
| 62 |
+
elapsed = time.perf_counter() - start
|
| 63 |
+
return {
|
| 64 |
+
"name": name,
|
| 65 |
+
"ok": ok,
|
| 66 |
+
"elapsed_seconds": round(elapsed, 3),
|
| 67 |
+
"fallback": _looks_like_fallback(value),
|
| 68 |
+
"error": error,
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _looks_like_fallback(value: Any) -> bool:
|
| 73 |
+
if isinstance(value, dict):
|
| 74 |
+
return value.get("visitor_name") == "fallback"
|
| 75 |
+
if isinstance(value, TodayTipCard):
|
| 76 |
+
return value.dream_summary == "fallback"
|
| 77 |
+
if isinstance(value, list):
|
| 78 |
+
return value == ["fallback"]
|
| 79 |
+
return value == "fallback"
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _write_probe_wav() -> str:
|
| 83 |
+
temp = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
|
| 84 |
+
temp.close()
|
| 85 |
+
with wave.open(temp.name, "wb") as wav:
|
| 86 |
+
wav.setnchannels(1)
|
| 87 |
+
wav.setsampwidth(2)
|
| 88 |
+
wav.setframerate(16000)
|
| 89 |
+
wav.writeframes(b"\x00\x00" * 1600)
|
| 90 |
+
return temp.name
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def main() -> int:
|
| 94 |
+
token = os.getenv("DREAM_CUSTOMS_HOSTED_TOKEN", "")
|
| 95 |
+
text_endpoint = os.getenv("DREAM_CUSTOMS_TEXT_ENDPOINT", "").strip()
|
| 96 |
+
vision_endpoint = os.getenv("DREAM_CUSTOMS_VISION_ENDPOINT", "").strip()
|
| 97 |
+
asr_endpoint = os.getenv("DREAM_CUSTOMS_ASR_ENDPOINT", "").strip()
|
| 98 |
+
image_path = os.getenv("DREAM_CUSTOMS_SMOKE_IMAGE", "").strip()
|
| 99 |
+
audio_path = os.getenv("DREAM_CUSTOMS_SMOKE_AUDIO", "").strip()
|
| 100 |
+
|
| 101 |
+
text_timeout = float(os.getenv("DREAM_CUSTOMS_BENCH_TEXT_TIMEOUT", "9"))
|
| 102 |
+
vision_timeout = float(os.getenv("DREAM_CUSTOMS_BENCH_VISION_TIMEOUT", "9"))
|
| 103 |
+
asr_timeout = float(os.getenv("DREAM_CUSTOMS_BENCH_ASR_TIMEOUT", "9"))
|
| 104 |
+
text_budget = int(float(os.getenv("DREAM_CUSTOMS_BENCH_TEXT_BUDGET_MS", "8000")))
|
| 105 |
+
vision_budget = int(float(os.getenv("DREAM_CUSTOMS_BENCH_VISION_BUDGET_MS", "9000")))
|
| 106 |
+
asr_budget = int(float(os.getenv("DREAM_CUSTOMS_BENCH_ASR_BUDGET_MS", "8000")))
|
| 107 |
+
|
| 108 |
+
intake = build_intake(
|
| 109 |
+
dream_text="I dreamed my phone died while I waited for an elevator.",
|
| 110 |
+
mood="Anxious",
|
| 111 |
+
)
|
| 112 |
+
answers = "The dead phone felt closest to being behind before I even start."
|
| 113 |
+
state = build_qa_state(
|
| 114 |
+
intake,
|
| 115 |
+
questions=["Which detail feels closest to your waking life right now?"],
|
| 116 |
+
answers=[answers],
|
| 117 |
+
language="en",
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
report: dict[str, Any] = {
|
| 121 |
+
"configured": {
|
| 122 |
+
"text_endpoint": bool(text_endpoint),
|
| 123 |
+
"vision_endpoint": bool(vision_endpoint),
|
| 124 |
+
"asr_endpoint": bool(asr_endpoint),
|
| 125 |
+
"token": bool(token),
|
| 126 |
+
"image_path": bool(image_path),
|
| 127 |
+
"audio_path": bool(audio_path),
|
| 128 |
+
},
|
| 129 |
+
"budgets_ms": {"text": text_budget, "vision": vision_budget, "asr": asr_budget},
|
| 130 |
+
"results": [],
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
if text_endpoint:
|
| 134 |
+
text_client = HostedMiniCPMTextClient(
|
| 135 |
+
endpoint=text_endpoint,
|
| 136 |
+
token=token,
|
| 137 |
+
timeout=text_timeout,
|
| 138 |
+
max_tokens=560,
|
| 139 |
+
latency_budget_ms=text_budget,
|
| 140 |
+
fallback=TextFallback(),
|
| 141 |
+
)
|
| 142 |
+
report["results"].append(
|
| 143 |
+
_measure(
|
| 144 |
+
"text_negotiation",
|
| 145 |
+
lambda: text_client.generate_negotiation(negotiation_prompt(intake, "en")),
|
| 146 |
+
)
|
| 147 |
+
)
|
| 148 |
+
report["results"].append(
|
| 149 |
+
_measure(
|
| 150 |
+
"text_today_tip",
|
| 151 |
+
lambda: text_client.generate_today_tip(today_tip_prompt(state, "en")),
|
| 152 |
+
)
|
| 153 |
+
)
|
| 154 |
+
if vision_endpoint and image_path and Path(image_path).exists():
|
| 155 |
+
vision_client = HostedMiniCPMVisionClient(
|
| 156 |
+
endpoint=vision_endpoint,
|
| 157 |
+
token=token,
|
| 158 |
+
timeout=vision_timeout,
|
| 159 |
+
max_tokens=220,
|
| 160 |
+
latency_budget_ms=vision_budget,
|
| 161 |
+
fallback=VisionFallback(),
|
| 162 |
+
)
|
| 163 |
+
report["results"].append(_measure("vision_clues", lambda: vision_client.extract_clues(image_path)))
|
| 164 |
+
if asr_endpoint:
|
| 165 |
+
probe_audio = ""
|
| 166 |
+
try:
|
| 167 |
+
probe_audio = audio_path if audio_path and Path(audio_path).exists() else _write_probe_wav()
|
| 168 |
+
asr_client = HostedASRClient(
|
| 169 |
+
endpoint=asr_endpoint,
|
| 170 |
+
token=token,
|
| 171 |
+
timeout=asr_timeout,
|
| 172 |
+
latency_budget_ms=asr_budget,
|
| 173 |
+
fallback=ASRFallback(),
|
| 174 |
+
)
|
| 175 |
+
report["results"].append(_measure("asr_transcribe", lambda: asr_client.transcribe(probe_audio)))
|
| 176 |
+
finally:
|
| 177 |
+
if probe_audio and probe_audio != audio_path:
|
| 178 |
+
try:
|
| 179 |
+
os.unlink(probe_audio)
|
| 180 |
+
except OSError:
|
| 181 |
+
pass
|
| 182 |
+
|
| 183 |
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
| 184 |
+
return 0
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
if __name__ == "__main__":
|
| 188 |
+
raise SystemExit(main())
|
tests/test_app_logic.py
CHANGED
|
@@ -125,6 +125,9 @@ def test_developer_settings_configure_hosted_clients():
|
|
| 125 |
vision_temperature=0.22,
|
| 126 |
text_max_tokens=256,
|
| 127 |
vision_max_tokens=128,
|
|
|
|
|
|
|
|
|
|
| 128 |
)
|
| 129 |
|
| 130 |
assert isinstance(text_client, HostedMiniCPMTextClient)
|
|
@@ -140,6 +143,25 @@ def test_developer_settings_configure_hosted_clients():
|
|
| 140 |
assert vision_client.temperature == 0.22
|
| 141 |
assert text_client.max_tokens == 256
|
| 142 |
assert vision_client.max_tokens == 128
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
|
| 145 |
def test_asr_endpoint_derives_from_modal_text_endpoint_when_secret_is_missing():
|
|
|
|
| 125 |
vision_temperature=0.22,
|
| 126 |
text_max_tokens=256,
|
| 127 |
vision_max_tokens=128,
|
| 128 |
+
text_latency_budget_ms=5000,
|
| 129 |
+
vision_latency_budget_ms=6000,
|
| 130 |
+
asr_latency_budget_ms=4000,
|
| 131 |
)
|
| 132 |
|
| 133 |
assert isinstance(text_client, HostedMiniCPMTextClient)
|
|
|
|
| 143 |
assert vision_client.temperature == 0.22
|
| 144 |
assert text_client.max_tokens == 256
|
| 145 |
assert vision_client.max_tokens == 128
|
| 146 |
+
assert text_client.latency_budget_ms == 5000
|
| 147 |
+
assert vision_client.latency_budget_ms == 6000
|
| 148 |
+
assert asr_client.latency_budget_ms == 4000
|
| 149 |
+
assert text_client.request_timeout == 5.0
|
| 150 |
+
assert vision_client.request_timeout == 6.0
|
| 151 |
+
assert asr_client.request_timeout == 4.0
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def test_default_modal_latency_settings_target_fast_feedback():
|
| 155 |
+
settings = _client_settings()
|
| 156 |
+
|
| 157 |
+
assert settings["text_timeout_seconds"] <= 10
|
| 158 |
+
assert settings["vision_timeout_seconds"] <= 10
|
| 159 |
+
assert settings["asr_timeout_seconds"] <= 10
|
| 160 |
+
assert settings["text_latency_budget_ms"] <= 10_000
|
| 161 |
+
assert settings["vision_latency_budget_ms"] <= 10_000
|
| 162 |
+
assert settings["asr_latency_budget_ms"] <= 10_000
|
| 163 |
+
assert settings["text_max_tokens"] <= 560
|
| 164 |
+
assert settings["vision_max_tokens"] <= 220
|
| 165 |
|
| 166 |
|
| 167 |
def test_asr_endpoint_derives_from_modal_text_endpoint_when_secret_is_missing():
|
tests/test_modal_contract.py
CHANGED
|
@@ -22,12 +22,12 @@ def test_normalize_text_payload_accepts_openai_style_messages():
|
|
| 22 |
{"messages": [{"role": "user", "content": "Dream of an elevator."}]}
|
| 23 |
)
|
| 24 |
assert payload["prompt"] == "Dream of an elevator."
|
| 25 |
-
assert payload["max_tokens"] ==
|
| 26 |
|
| 27 |
|
| 28 |
def test_normalize_text_payload_clamps_max_tokens():
|
| 29 |
payload = normalize_text_payload({"prompt": "x", "max_tokens": 9000})
|
| 30 |
-
assert payload["max_tokens"] ==
|
| 31 |
|
| 32 |
|
| 33 |
def test_decode_image_payload_accepts_image_key():
|
|
|
|
| 22 |
{"messages": [{"role": "user", "content": "Dream of an elevator."}]}
|
| 23 |
)
|
| 24 |
assert payload["prompt"] == "Dream of an elevator."
|
| 25 |
+
assert payload["max_tokens"] == 480
|
| 26 |
|
| 27 |
|
| 28 |
def test_normalize_text_payload_clamps_max_tokens():
|
| 29 |
payload = normalize_text_payload({"prompt": "x", "max_tokens": 9000})
|
| 30 |
+
assert payload["max_tokens"] == 700
|
| 31 |
|
| 32 |
|
| 33 |
def test_decode_image_payload_accepts_image_key():
|
tests/test_ui_actions.py
CHANGED
|
@@ -332,7 +332,7 @@ def test_mobile_reset_restores_calm_mood():
|
|
| 332 |
60,
|
| 333 |
0.2,
|
| 334 |
0.1,
|
| 335 |
-
|
| 336 |
320,
|
| 337 |
"demo",
|
| 338 |
"",
|
|
|
|
| 332 |
60,
|
| 333 |
0.2,
|
| 334 |
0.1,
|
| 335 |
+
480,
|
| 336 |
320,
|
| 337 |
"demo",
|
| 338 |
"",
|