Improve Dream QA Modal feedback latency
#45
by ADJCJH - opened
- app.py +2 -2
- dream_customs/app_logic.py +1 -1
- dream_customs/models.py +35 -11
- dream_customs/prompts.py +3 -2
- dream_customs/ui/app.py +3 -3
- modal_backend/contracts.py +2 -2
- tests/test_modal_contract.py +1 -1
- tests/test_ui_actions.py +12 -2
app.py
CHANGED
|
@@ -15,7 +15,7 @@ from dream_customs.ui.app import build_demo
|
|
| 15 |
|
| 16 |
LOCAL_GRADIO_PORT = 7862
|
| 17 |
HF_SPACE_GRADIO_PORT = 7860
|
| 18 |
-
BROWSER_ASR_TIMEOUT_SECONDS =
|
| 19 |
|
| 20 |
|
| 21 |
def _default_server_port() -> int:
|
|
@@ -32,7 +32,7 @@ def _browser_asr_timeout_seconds() -> float:
|
|
| 32 |
if not value:
|
| 33 |
return BROWSER_ASR_TIMEOUT_SECONDS
|
| 34 |
try:
|
| 35 |
-
return max(
|
| 36 |
except ValueError:
|
| 37 |
return BROWSER_ASR_TIMEOUT_SECONDS
|
| 38 |
|
|
|
|
| 15 |
|
| 16 |
LOCAL_GRADIO_PORT = 7862
|
| 17 |
HF_SPACE_GRADIO_PORT = 7860
|
| 18 |
+
BROWSER_ASR_TIMEOUT_SECONDS = 20.0
|
| 19 |
|
| 20 |
|
| 21 |
def _default_server_port() -> int:
|
|
|
|
| 32 |
if not value:
|
| 33 |
return BROWSER_ASR_TIMEOUT_SECONDS
|
| 34 |
try:
|
| 35 |
+
return max(5.0, float(value))
|
| 36 |
except ValueError:
|
| 37 |
return BROWSER_ASR_TIMEOUT_SECONDS
|
| 38 |
|
dream_customs/app_logic.py
CHANGED
|
@@ -39,7 +39,7 @@ DEFAULT_HOSTED_TIMEOUT_SECONDS = 60.0
|
|
| 39 |
DEFAULT_ASR_TIMEOUT_SECONDS = 45.0
|
| 40 |
DEFAULT_TEXT_TEMPERATURE = 0.0
|
| 41 |
DEFAULT_VISION_TEMPERATURE = 0.1
|
| 42 |
-
DEFAULT_TEXT_MAX_TOKENS =
|
| 43 |
DEFAULT_VISION_MAX_TOKENS = 320
|
| 44 |
DEFAULT_TEXT_LATENCY_BUDGET_MS = 3500
|
| 45 |
DEFAULT_VISION_LATENCY_BUDGET_MS = 6500
|
|
|
|
| 39 |
DEFAULT_ASR_TIMEOUT_SECONDS = 45.0
|
| 40 |
DEFAULT_TEXT_TEMPERATURE = 0.0
|
| 41 |
DEFAULT_VISION_TEMPERATURE = 0.1
|
| 42 |
+
DEFAULT_TEXT_MAX_TOKENS = 560
|
| 43 |
DEFAULT_VISION_MAX_TOKENS = 320
|
| 44 |
DEFAULT_TEXT_LATENCY_BUDGET_MS = 3500
|
| 45 |
DEFAULT_VISION_LATENCY_BUDGET_MS = 6500
|
dream_customs/models.py
CHANGED
|
@@ -362,7 +362,7 @@ class OllamaTextClient:
|
|
| 362 |
parsed = self._generate_json(
|
| 363 |
prompt,
|
| 364 |
'{"visitor_name":"string","questions":["string","string"],"tone_note":"string"}',
|
| 365 |
-
num_predict=
|
| 366 |
)
|
| 367 |
if not parsed:
|
| 368 |
return self.fallback.generate_negotiation(prompt)
|
|
@@ -383,7 +383,7 @@ class OllamaTextClient:
|
|
| 383 |
'"risk_level":"string","alliance_reading":"string","practical_suggestion":"string",'
|
| 384 |
'"weird_task":"string","bedtime_release":"string","safety_note":"string"}'
|
| 385 |
),
|
| 386 |
-
num_predict=
|
| 387 |
)
|
| 388 |
if not parsed:
|
| 389 |
return self.fallback.generate_pact(prompt)
|
|
@@ -410,7 +410,7 @@ class OllamaTextClient:
|
|
| 410 |
'"followup_questions":["string"],"user_answers":["string"],"interpretation":"string",'
|
| 411 |
'"today_tip":"string","tiny_action":"string","caring_note":"string","safety_note":"string"}'
|
| 412 |
),
|
| 413 |
-
num_predict=
|
| 414 |
)
|
| 415 |
if not parsed:
|
| 416 |
return self.fallback.generate_today_tip(prompt)
|
|
@@ -426,7 +426,7 @@ class OllamaTextClient:
|
|
| 426 |
'"today_bridge":"string","visual_evidence":["string"],'
|
| 427 |
'"safety_flags":["string"],"language":"en"}'
|
| 428 |
),
|
| 429 |
-
num_predict=
|
| 430 |
)
|
| 431 |
if not parsed:
|
| 432 |
return self.fallback.generate_brief(prompt)
|
|
@@ -627,6 +627,7 @@ class HostedMiniCPMTextClient:
|
|
| 627 |
timeout: float = 60.0,
|
| 628 |
temperature: float = 0.0,
|
| 629 |
max_tokens: int = 780,
|
|
|
|
| 630 |
fallback: Optional[FakeTextClient] = None,
|
| 631 |
):
|
| 632 |
self.endpoint = endpoint.strip()
|
|
@@ -634,8 +635,15 @@ class HostedMiniCPMTextClient:
|
|
| 634 |
self.timeout = timeout
|
| 635 |
self.temperature = max(0.0, min(float(temperature), 0.7))
|
| 636 |
self.max_tokens = max(64, min(int(max_tokens), 1200))
|
|
|
|
| 637 |
self.fallback = fallback or FakeTextClient()
|
| 638 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 639 |
def _post_json(self, prompt: str, max_tokens: int = 700) -> Optional[Dict[str, Any]]:
|
| 640 |
if not self.endpoint:
|
| 641 |
return None
|
|
@@ -656,7 +664,7 @@ class HostedMiniCPMTextClient:
|
|
| 656 |
method="POST",
|
| 657 |
)
|
| 658 |
try:
|
| 659 |
-
with urllib.request.urlopen(request, timeout=self.
|
| 660 |
return json.loads(response.read().decode("utf-8"))
|
| 661 |
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError):
|
| 662 |
return None
|
|
@@ -676,7 +684,7 @@ class HostedMiniCPMTextClient:
|
|
| 676 |
parsed = self._generate_json(
|
| 677 |
prompt,
|
| 678 |
'{"visitor_name":"string","questions":["string"],"tone_note":"string"}',
|
| 679 |
-
max_tokens=
|
| 680 |
)
|
| 681 |
if not parsed:
|
| 682 |
return self.fallback.generate_negotiation(prompt)
|
|
@@ -697,7 +705,7 @@ class HostedMiniCPMTextClient:
|
|
| 697 |
'"risk_level":"string","alliance_reading":"string","practical_suggestion":"string",'
|
| 698 |
'"weird_task":"string","bedtime_release":"string","safety_note":"string"}'
|
| 699 |
),
|
| 700 |
-
max_tokens=
|
| 701 |
)
|
| 702 |
if not parsed:
|
| 703 |
return self.fallback.generate_pact(prompt)
|
|
@@ -724,7 +732,7 @@ class HostedMiniCPMTextClient:
|
|
| 724 |
'"followup_questions":["string"],"user_answers":["string"],"interpretation":"string",'
|
| 725 |
'"today_tip":"string","tiny_action":"string","caring_note":"string","safety_note":"string"}'
|
| 726 |
),
|
| 727 |
-
max_tokens=
|
| 728 |
)
|
| 729 |
if not parsed:
|
| 730 |
return self.fallback.generate_today_tip(prompt)
|
|
@@ -740,7 +748,7 @@ class HostedMiniCPMTextClient:
|
|
| 740 |
'"today_bridge":"string","visual_evidence":["string"],'
|
| 741 |
'"safety_flags":["string"],"language":"en"}'
|
| 742 |
),
|
| 743 |
-
max_tokens=
|
| 744 |
)
|
| 745 |
if not parsed:
|
| 746 |
return self.fallback.generate_brief(prompt)
|
|
@@ -777,6 +785,7 @@ class HostedMiniCPMVisionClient:
|
|
| 777 |
timeout: float = 60.0,
|
| 778 |
temperature: float = 0.1,
|
| 779 |
max_tokens: int = 320,
|
|
|
|
| 780 |
fallback: Optional[FakeVisionClient] = None,
|
| 781 |
):
|
| 782 |
self.endpoint = endpoint.strip()
|
|
@@ -784,8 +793,15 @@ class HostedMiniCPMVisionClient:
|
|
| 784 |
self.timeout = timeout
|
| 785 |
self.temperature = max(0.0, min(float(temperature), 0.7))
|
| 786 |
self.max_tokens = max(64, min(int(max_tokens), 800))
|
|
|
|
| 787 |
self.fallback = fallback or FakeVisionClient()
|
| 788 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
def _post_image(self, image_path: str, prompt: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
| 790 |
if not self.endpoint:
|
| 791 |
return None
|
|
@@ -811,7 +827,7 @@ class HostedMiniCPMVisionClient:
|
|
| 811 |
method="POST",
|
| 812 |
)
|
| 813 |
try:
|
| 814 |
-
with urllib.request.urlopen(request, timeout=self.
|
| 815 |
return json.loads(response.read().decode("utf-8"))
|
| 816 |
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError):
|
| 817 |
return None
|
|
@@ -876,16 +892,24 @@ class HostedASRClient:
|
|
| 876 |
endpoint: str = "",
|
| 877 |
token: str = "",
|
| 878 |
timeout: float = 45.0,
|
|
|
|
| 879 |
fallback: Optional[FakeASRClient] = None,
|
| 880 |
fallback_enabled: bool = True,
|
| 881 |
):
|
| 882 |
self.endpoint = endpoint.strip()
|
| 883 |
self.token = token.strip()
|
| 884 |
self.timeout = timeout
|
|
|
|
| 885 |
self.fallback = fallback or FakeASRClient()
|
| 886 |
self.fallback_enabled = fallback_enabled
|
| 887 |
self.last_error = ""
|
| 888 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 889 |
def _fallback_transcript(self, audio_path: Optional[str]) -> str:
|
| 890 |
if not self.fallback_enabled:
|
| 891 |
return ""
|
|
@@ -918,7 +942,7 @@ class HostedASRClient:
|
|
| 918 |
method="POST",
|
| 919 |
)
|
| 920 |
try:
|
| 921 |
-
with urllib.request.urlopen(request, timeout=self.
|
| 922 |
payload = json.loads(response.read().decode("utf-8"))
|
| 923 |
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError) as exc:
|
| 924 |
self.last_error = f"{exc.__class__.__name__}: {exc}"
|
|
|
|
| 362 |
parsed = self._generate_json(
|
| 363 |
prompt,
|
| 364 |
'{"visitor_name":"string","questions":["string","string"],"tone_note":"string"}',
|
| 365 |
+
num_predict=240,
|
| 366 |
)
|
| 367 |
if not parsed:
|
| 368 |
return self.fallback.generate_negotiation(prompt)
|
|
|
|
| 383 |
'"risk_level":"string","alliance_reading":"string","practical_suggestion":"string",'
|
| 384 |
'"weird_task":"string","bedtime_release":"string","safety_note":"string"}'
|
| 385 |
),
|
| 386 |
+
num_predict=560,
|
| 387 |
)
|
| 388 |
if not parsed:
|
| 389 |
return self.fallback.generate_pact(prompt)
|
|
|
|
| 410 |
'"followup_questions":["string"],"user_answers":["string"],"interpretation":"string",'
|
| 411 |
'"today_tip":"string","tiny_action":"string","caring_note":"string","safety_note":"string"}'
|
| 412 |
),
|
| 413 |
+
num_predict=560,
|
| 414 |
)
|
| 415 |
if not parsed:
|
| 416 |
return self.fallback.generate_today_tip(prompt)
|
|
|
|
| 426 |
'"today_bridge":"string","visual_evidence":["string"],'
|
| 427 |
'"safety_flags":["string"],"language":"en"}'
|
| 428 |
),
|
| 429 |
+
num_predict=360,
|
| 430 |
)
|
| 431 |
if not parsed:
|
| 432 |
return self.fallback.generate_brief(prompt)
|
|
|
|
| 627 |
timeout: float = 60.0,
|
| 628 |
temperature: float = 0.0,
|
| 629 |
max_tokens: int = 780,
|
| 630 |
+
latency_budget_ms: int = 0,
|
| 631 |
fallback: Optional[FakeTextClient] = None,
|
| 632 |
):
|
| 633 |
self.endpoint = endpoint.strip()
|
|
|
|
| 635 |
self.timeout = timeout
|
| 636 |
self.temperature = max(0.0, min(float(temperature), 0.7))
|
| 637 |
self.max_tokens = max(64, min(int(max_tokens), 1200))
|
| 638 |
+
self.latency_budget_ms = max(0, int(latency_budget_ms or 0))
|
| 639 |
self.fallback = fallback or FakeTextClient()
|
| 640 |
|
| 641 |
+
@property
|
| 642 |
+
def request_timeout(self) -> float:
|
| 643 |
+
if self.latency_budget_ms <= 0:
|
| 644 |
+
return self.timeout
|
| 645 |
+
return max(1.0, min(float(self.timeout), self.latency_budget_ms / 1000.0))
|
| 646 |
+
|
| 647 |
def _post_json(self, prompt: str, max_tokens: int = 700) -> Optional[Dict[str, Any]]:
|
| 648 |
if not self.endpoint:
|
| 649 |
return None
|
|
|
|
| 664 |
method="POST",
|
| 665 |
)
|
| 666 |
try:
|
| 667 |
+
with urllib.request.urlopen(request, timeout=self.request_timeout) as response:
|
| 668 |
return json.loads(response.read().decode("utf-8"))
|
| 669 |
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError):
|
| 670 |
return None
|
|
|
|
| 684 |
parsed = self._generate_json(
|
| 685 |
prompt,
|
| 686 |
'{"visitor_name":"string","questions":["string"],"tone_note":"string"}',
|
| 687 |
+
max_tokens=240,
|
| 688 |
)
|
| 689 |
if not parsed:
|
| 690 |
return self.fallback.generate_negotiation(prompt)
|
|
|
|
| 705 |
'"risk_level":"string","alliance_reading":"string","practical_suggestion":"string",'
|
| 706 |
'"weird_task":"string","bedtime_release":"string","safety_note":"string"}'
|
| 707 |
),
|
| 708 |
+
max_tokens=560,
|
| 709 |
)
|
| 710 |
if not parsed:
|
| 711 |
return self.fallback.generate_pact(prompt)
|
|
|
|
| 732 |
'"followup_questions":["string"],"user_answers":["string"],"interpretation":"string",'
|
| 733 |
'"today_tip":"string","tiny_action":"string","caring_note":"string","safety_note":"string"}'
|
| 734 |
),
|
| 735 |
+
max_tokens=560,
|
| 736 |
)
|
| 737 |
if not parsed:
|
| 738 |
return self.fallback.generate_today_tip(prompt)
|
|
|
|
| 748 |
'"today_bridge":"string","visual_evidence":["string"],'
|
| 749 |
'"safety_flags":["string"],"language":"en"}'
|
| 750 |
),
|
| 751 |
+
max_tokens=360,
|
| 752 |
)
|
| 753 |
if not parsed:
|
| 754 |
return self.fallback.generate_brief(prompt)
|
|
|
|
| 785 |
timeout: float = 60.0,
|
| 786 |
temperature: float = 0.1,
|
| 787 |
max_tokens: int = 320,
|
| 788 |
+
latency_budget_ms: int = 0,
|
| 789 |
fallback: Optional[FakeVisionClient] = None,
|
| 790 |
):
|
| 791 |
self.endpoint = endpoint.strip()
|
|
|
|
| 793 |
self.timeout = timeout
|
| 794 |
self.temperature = max(0.0, min(float(temperature), 0.7))
|
| 795 |
self.max_tokens = max(64, min(int(max_tokens), 800))
|
| 796 |
+
self.latency_budget_ms = max(0, int(latency_budget_ms or 0))
|
| 797 |
self.fallback = fallback or FakeVisionClient()
|
| 798 |
|
| 799 |
+
@property
|
| 800 |
+
def request_timeout(self) -> float:
|
| 801 |
+
if self.latency_budget_ms <= 0:
|
| 802 |
+
return self.timeout
|
| 803 |
+
return max(1.0, min(float(self.timeout), self.latency_budget_ms / 1000.0))
|
| 804 |
+
|
| 805 |
def _post_image(self, image_path: str, prompt: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
| 806 |
if not self.endpoint:
|
| 807 |
return None
|
|
|
|
| 827 |
method="POST",
|
| 828 |
)
|
| 829 |
try:
|
| 830 |
+
with urllib.request.urlopen(request, timeout=self.request_timeout) as response:
|
| 831 |
return json.loads(response.read().decode("utf-8"))
|
| 832 |
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError):
|
| 833 |
return None
|
|
|
|
| 892 |
endpoint: str = "",
|
| 893 |
token: str = "",
|
| 894 |
timeout: float = 45.0,
|
| 895 |
+
latency_budget_ms: int = 0,
|
| 896 |
fallback: Optional[FakeASRClient] = None,
|
| 897 |
fallback_enabled: bool = True,
|
| 898 |
):
|
| 899 |
self.endpoint = endpoint.strip()
|
| 900 |
self.token = token.strip()
|
| 901 |
self.timeout = timeout
|
| 902 |
+
self.latency_budget_ms = max(0, int(latency_budget_ms or 0))
|
| 903 |
self.fallback = fallback or FakeASRClient()
|
| 904 |
self.fallback_enabled = fallback_enabled
|
| 905 |
self.last_error = ""
|
| 906 |
|
| 907 |
+
@property
|
| 908 |
+
def request_timeout(self) -> float:
|
| 909 |
+
if self.latency_budget_ms <= 0:
|
| 910 |
+
return self.timeout
|
| 911 |
+
return max(1.0, min(float(self.timeout), self.latency_budget_ms / 1000.0))
|
| 912 |
+
|
| 913 |
def _fallback_transcript(self, audio_path: Optional[str]) -> str:
|
| 914 |
if not self.fallback_enabled:
|
| 915 |
return ""
|
|
|
|
| 942 |
method="POST",
|
| 943 |
)
|
| 944 |
try:
|
| 945 |
+
with urllib.request.urlopen(request, timeout=self.request_timeout) as response:
|
| 946 |
payload = json.loads(response.read().decode("utf-8"))
|
| 947 |
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError) as exc:
|
| 948 |
self.last_error = f"{exc.__class__.__name__}: {exc}"
|
dream_customs/prompts.py
CHANGED
|
@@ -58,7 +58,7 @@ Write a non-diagnostic interpretation draft, one waking-life Today Tip / 今日
|
|
| 58 |
and one weird little thing / 古怪的小事.
|
| 59 |
First answer the user's stated question directly. If the user sounds scared, sad,
|
| 60 |
overwhelmed, guilty, lonely, or asks for comfort, follow that emotion before giving any action.
|
| 61 |
-
The interpretation must be
|
| 62 |
the user's feeling, to concrete dream anchors, to the follow-up answers, to one gentle way to care for today.
|
| 63 |
Do not collapse every dream into productivity advice such as opening a task,
|
| 64 |
writing a first line, or making the first step smaller.
|
|
@@ -79,7 +79,8 @@ or a reason to seek professional help unless the user explicitly reports severe
|
|
| 79 |
self-harm, harm to others, or inability to function.
|
| 80 |
Do not infer work stress, fear, loneliness, anxiety, guilt, or self-blame unless the user actually says that feeling.
|
| 81 |
Avoid generic wellness filler such as warm water, relaxing music, positive mindset, or "tomorrow will be better".
|
| 82 |
-
Keep the whole
|
|
|
|
| 83 |
The weird little thing must be harmless, legal, low-cost, non-embarrassing, and not a command to solve the whole problem.
|
| 84 |
Avoid demanding phrases such as "immediately", "must", "fix it", or "solve it".
|
| 85 |
If the user asks for comfort, caring_note should be warm, specific, and validating.
|
|
|
|
| 58 |
and one weird little thing / 古怪的小事.
|
| 59 |
First answer the user's stated question directly. If the user sounds scared, sad,
|
| 60 |
overwhelmed, guilty, lonely, or asks for comfort, follow that emotion before giving any action.
|
| 61 |
+
The interpretation must be compact: use 1 to 2 short layers that move from
|
| 62 |
the user's feeling, to concrete dream anchors, to the follow-up answers, to one gentle way to care for today.
|
| 63 |
Do not collapse every dream into productivity advice such as opening a task,
|
| 64 |
writing a first line, or making the first step smaller.
|
|
|
|
| 79 |
self-harm, harm to others, or inability to function.
|
| 80 |
Do not infer work stress, fear, loneliness, anxiety, guilt, or self-blame unless the user actually says that feeling.
|
| 81 |
Avoid generic wellness filler such as warm water, relaxing music, positive mindset, or "tomorrow will be better".
|
| 82 |
+
Keep the whole JSON compact: each user-facing field should be one short sentence when possible.
|
| 83 |
+
Keep the whole result warm, emotionally responsive, and specific to the user's answer.
|
| 84 |
The weird little thing must be harmless, legal, low-cost, non-embarrassing, and not a command to solve the whole problem.
|
| 85 |
Avoid demanding phrases such as "immediately", "must", "fix it", or "solve it".
|
| 86 |
If the user asks for comfort, caring_note should be warm, specific, and validating.
|
dream_customs/ui/app.py
CHANGED
|
@@ -800,8 +800,8 @@ def _agent_dream_qa(dream_text: str, mood: str = "", answer: str = "", language:
|
|
| 800 |
image_value=None,
|
| 801 |
audio_value=None,
|
| 802 |
mood=mood,
|
| 803 |
-
text_backend=
|
| 804 |
-
vision_backend=
|
| 805 |
language=language,
|
| 806 |
)
|
| 807 |
view = json.loads(view_json)
|
|
@@ -919,7 +919,7 @@ def _mic_html(language: str = DEFAULT_LANGUAGE) -> str:
|
|
| 919 |
data-empty="{escape(copy['mic_empty'])}"
|
| 920 |
data-error="{escape(copy['mic_error'])}"
|
| 921 |
data-timeout="{escape(copy['mic_timeout'])}"
|
| 922 |
-
data-timeout-ms="
|
| 923 |
>
|
| 924 |
<span class="dc-mic-glyph" aria-hidden="true"></span>
|
| 925 |
</button>
|
|
|
|
| 800 |
image_value=None,
|
| 801 |
audio_value=None,
|
| 802 |
mood=mood,
|
| 803 |
+
text_backend="demo",
|
| 804 |
+
vision_backend="demo",
|
| 805 |
language=language,
|
| 806 |
)
|
| 807 |
view = json.loads(view_json)
|
|
|
|
| 919 |
data-empty="{escape(copy['mic_empty'])}"
|
| 920 |
data-error="{escape(copy['mic_error'])}"
|
| 921 |
data-timeout="{escape(copy['mic_timeout'])}"
|
| 922 |
+
data-timeout-ms="20000"
|
| 923 |
>
|
| 924 |
<span class="dc-mic-glyph" aria-hidden="true"></span>
|
| 925 |
</button>
|
modal_backend/contracts.py
CHANGED
|
@@ -24,11 +24,11 @@ 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, 1200))
|
| 33 |
temperature = payload.get("temperature", 0.0)
|
| 34 |
try:
|
|
|
|
| 24 |
if content:
|
| 25 |
parts.append(content)
|
| 26 |
prompt = "\n".join(parts).strip()
|
| 27 |
+
max_tokens = payload.get("max_tokens", 560)
|
| 28 |
try:
|
| 29 |
max_tokens = int(max_tokens)
|
| 30 |
except (TypeError, ValueError):
|
| 31 |
+
max_tokens = 560
|
| 32 |
max_tokens = max(64, min(max_tokens, 1200))
|
| 33 |
temperature = payload.get("temperature", 0.0)
|
| 34 |
try:
|
tests/test_modal_contract.py
CHANGED
|
@@ -22,7 +22,7 @@ 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():
|
|
|
|
| 22 |
{"messages": [{"role": "user", "content": "Dream of an elevator."}]}
|
| 23 |
)
|
| 24 |
assert payload["prompt"] == "Dream of an elevator."
|
| 25 |
+
assert payload["max_tokens"] == 560
|
| 26 |
|
| 27 |
|
| 28 |
def test_normalize_text_payload_clamps_max_tokens():
|
tests/test_ui_actions.py
CHANGED
|
@@ -71,10 +71,20 @@ def test_browser_voice_uses_same_origin_asr_proxy():
|
|
| 71 |
assert "_browser_asr_client()" in source
|
| 72 |
assert "fallback_enabled=False" in source
|
| 73 |
assert "asyncio.to_thread(asr_client.transcribe, temp_path)" in source
|
| 74 |
-
assert "BROWSER_ASR_TIMEOUT_SECONDS =
|
|
|
|
| 75 |
assert "DREAM_CUSTOMS_HOSTED_TOKEN" not in source
|
| 76 |
|
| 77 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
def test_image_upload_is_composer_plus_drawer():
|
| 79 |
source = inspect.getsource(ui_app.build_demo)
|
| 80 |
|
|
@@ -322,7 +332,7 @@ def test_mobile_reset_restores_calm_mood():
|
|
| 322 |
60,
|
| 323 |
0.2,
|
| 324 |
0.1,
|
| 325 |
-
|
| 326 |
320,
|
| 327 |
"demo",
|
| 328 |
"",
|
|
|
|
| 71 |
assert "_browser_asr_client()" in source
|
| 72 |
assert "fallback_enabled=False" in source
|
| 73 |
assert "asyncio.to_thread(asr_client.transcribe, temp_path)" in source
|
| 74 |
+
assert "BROWSER_ASR_TIMEOUT_SECONDS = 20.0" in source
|
| 75 |
+
assert 'data-timeout-ms="20000"' in ui_app._mic_html("en")
|
| 76 |
assert "DREAM_CUSTOMS_HOSTED_TOKEN" not in source
|
| 77 |
|
| 78 |
|
| 79 |
+
def test_agent_api_uses_fast_local_question_before_modal_tip():
|
| 80 |
+
source = inspect.getsource(ui_app._agent_dream_qa)
|
| 81 |
+
|
| 82 |
+
assert 'text_backend="demo"' in source
|
| 83 |
+
assert 'vision_backend="demo"' in source
|
| 84 |
+
assert "answer_to_card_action(" in source
|
| 85 |
+
assert "skip_to_card_action(" in source
|
| 86 |
+
|
| 87 |
+
|
| 88 |
def test_image_upload_is_composer_plus_drawer():
|
| 89 |
source = inspect.getsource(ui_app.build_demo)
|
| 90 |
|
|
|
|
| 332 |
60,
|
| 333 |
0.2,
|
| 334 |
0.1,
|
| 335 |
+
560,
|
| 336 |
320,
|
| 337 |
"demo",
|
| 338 |
"",
|