Spaces:
Running
Running
adds a lot of improvements to launch.sh and demo spaces and secures read token at demo launch
Browse files- launch.sh +11 -2
- scripts/deploy_demo_space.py +195 -4
- templates/spaces/demo_gpt/app.py +200 -70
- templates/spaces/demo_smol/app.py +72 -2
launch.sh
CHANGED
@@ -1454,14 +1454,23 @@ export HF_USERNAME="$HF_USERNAME"
|
|
1454 |
|
1455 |
print_info "Deploying demo space for model: $DEMO_MODEL_ID"
|
1456 |
print_info "Using subfolder: $DEMO_SUBFOLDER"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1457 |
|
1458 |
python scripts/deploy_demo_space.py \
|
1459 |
-
--hf-token "$
|
|
|
1460 |
--hf-username "$HF_USERNAME" \
|
1461 |
--model-id "$DEMO_MODEL_ID" \
|
1462 |
--subfolder "$DEMO_SUBFOLDER" \
|
1463 |
--space-name "${REPO_SHORT}-demo" \
|
1464 |
-
--config-file "$CONFIG_FILE"
|
|
|
1465 |
|
1466 |
if [ $? -eq 0 ]; then
|
1467 |
DEMO_SPACE_URL="https://huggingface.co/spaces/$HF_USERNAME/${REPO_SHORT}-demo"
|
|
|
1454 |
|
1455 |
print_info "Deploying demo space for model: $DEMO_MODEL_ID"
|
1456 |
print_info "Using subfolder: $DEMO_SUBFOLDER"
|
1457 |
+
|
1458 |
+
# Additional demo parameters
|
1459 |
+
DEMO_EXTRA_ARGS=""
|
1460 |
+
if [ "$MODEL_FAMILY" = "GPT-OSS" ]; then
|
1461 |
+
# Prebuilt medical example for GPT-OSS demo
|
1462 |
+
DEMO_EXTRA_ARGS="--examples-type medical"
|
1463 |
+
fi
|
1464 |
|
1465 |
python scripts/deploy_demo_space.py \
|
1466 |
+
--hf-token "$HF_WRITE_TOKEN" \
|
1467 |
+
--space-secret-token "$HF_READ_TOKEN" \
|
1468 |
--hf-username "$HF_USERNAME" \
|
1469 |
--model-id "$DEMO_MODEL_ID" \
|
1470 |
--subfolder "$DEMO_SUBFOLDER" \
|
1471 |
--space-name "${REPO_SHORT}-demo" \
|
1472 |
+
--config-file "$CONFIG_FILE" \
|
1473 |
+
$DEMO_EXTRA_ARGS
|
1474 |
|
1475 |
if [ $? -eq 0 ]; then
|
1476 |
DEMO_SPACE_URL="https://huggingface.co/spaces/$HF_USERNAME/${REPO_SHORT}-demo"
|
scripts/deploy_demo_space.py
CHANGED
@@ -37,10 +37,38 @@ logger = logging.getLogger(__name__)
|
|
37 |
class DemoSpaceDeployer:
|
38 |
"""Deploy demo space to Hugging Face Spaces"""
|
39 |
|
40 |
-
def __init__(
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
self.hf_token = hf_token
|
|
|
|
|
44 |
self.hf_username = hf_username
|
45 |
# Allow passing just a repo name without username and auto-prefix
|
46 |
self.model_id = model_id if "/" in model_id else f"{hf_username}/{model_id}"
|
@@ -55,6 +83,10 @@ class DemoSpaceDeployer:
|
|
55 |
self.developer_message: Optional[str] = None
|
56 |
self.model_identity: Optional[str] = None
|
57 |
self.reasoning_effort: Optional[str] = None
|
|
|
|
|
|
|
|
|
58 |
|
59 |
# Determine demo type from model_id if not provided
|
60 |
if demo_type is None:
|
@@ -78,6 +110,24 @@ class DemoSpaceDeployer:
|
|
78 |
except Exception as e:
|
79 |
logger.warning(f"Could not load config messages: {e}")
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
def _load_config_messages(self) -> None:
|
82 |
"""Load system/developer/model_identity from a training config file if provided."""
|
83 |
if not self.config_file:
|
@@ -148,6 +198,23 @@ os.environ['MODEL_IDENTITY'] = {_json.dumps(self.model_identity or "")}
|
|
148 |
os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
|
149 |
os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
|
150 |
os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
|
152 |
"""
|
153 |
else:
|
@@ -163,6 +230,23 @@ os.environ['MODEL_IDENTITY'] = {_json.dumps(self.model_identity or "")}
|
|
163 |
os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
|
164 |
os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
|
165 |
os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
|
167 |
"""
|
168 |
return env_setup
|
@@ -251,6 +335,56 @@ os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"
|
|
251 |
description="Default reasoning effort (low|medium|high)"
|
252 |
)
|
253 |
logger.info("✅ Set REASONING_EFFORT variable")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
|
255 |
except Exception as e:
|
256 |
logger.error(f"❌ Failed to set model variables: {e}")
|
@@ -542,7 +676,7 @@ os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"
|
|
542 |
self.api.add_space_secret(
|
543 |
repo_id=self.space_id,
|
544 |
key="HF_TOKEN",
|
545 |
-
value=self.
|
546 |
description="Hugging Face token for model access"
|
547 |
)
|
548 |
logger.info("✅ Successfully set HF_TOKEN secret via API")
|
@@ -583,6 +717,27 @@ os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"
|
|
583 |
logger.info(f" SYSTEM_MESSAGE={self.system_message}")
|
584 |
if self.developer_message:
|
585 |
logger.info(f" DEVELOPER_MESSAGE={self.developer_message}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
586 |
|
587 |
logger.info(f"\n🔧 To set secrets in your Space:")
|
588 |
logger.info(f"1. Go to your Space settings: {self.space_url}/settings")
|
@@ -687,23 +842,59 @@ def main():
|
|
687 |
|
688 |
parser = argparse.ArgumentParser(description="Deploy demo space to Hugging Face Spaces")
|
689 |
parser.add_argument("--hf-token", required=True, help="Hugging Face token")
|
|
|
|
|
|
|
|
|
|
|
690 |
parser.add_argument("--hf-username", required=True, help="Hugging Face username")
|
691 |
parser.add_argument("--model-id", required=True, help="Model ID to deploy demo for")
|
692 |
parser.add_argument("--subfolder", default="int4", help="Model subfolder (default: int4)")
|
693 |
parser.add_argument("--space-name", help="Custom space name (optional)")
|
694 |
parser.add_argument("--demo-type", choices=["smol", "gpt"], help="Demo type: 'smol' for SmolLM, 'gpt' for GPT-OSS (auto-detected if not specified)")
|
695 |
parser.add_argument("--config-file", help="Path to the training config file to import context (system/developer/model_identity)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
696 |
|
697 |
args = parser.parse_args()
|
698 |
|
699 |
deployer = DemoSpaceDeployer(
|
700 |
hf_token=args.hf_token,
|
|
|
701 |
hf_username=args.hf_username,
|
702 |
model_id=args.model_id,
|
703 |
subfolder=args.subfolder,
|
704 |
space_name=args.space_name,
|
705 |
demo_type=args.demo_type,
|
706 |
config_file=args.config_file,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
707 |
)
|
708 |
|
709 |
success = deployer.deploy()
|
|
|
37 |
class DemoSpaceDeployer:
|
38 |
"""Deploy demo space to Hugging Face Spaces"""
|
39 |
|
40 |
+
def __init__(
|
41 |
+
self,
|
42 |
+
hf_token: str,
|
43 |
+
# Token used for API actions that create/update the Space (write perms)
|
44 |
+
hf_username: str,
|
45 |
+
model_id: str,
|
46 |
+
subfolder: str = "int4",
|
47 |
+
space_name: Optional[str] = None,
|
48 |
+
demo_type: Optional[str] = None,
|
49 |
+
config_file: Optional[str] = None,
|
50 |
+
# Optional token used as the Space's HF_TOKEN secret (read-only recommended)
|
51 |
+
space_secret_token: Optional[str] = None,
|
52 |
+
# Examples configuration
|
53 |
+
examples_type: Optional[str] = None,
|
54 |
+
disable_examples: Optional[bool] = None,
|
55 |
+
examples_json: Optional[str] = None,
|
56 |
+
# Branding overrides
|
57 |
+
brand_owner_name: Optional[str] = None,
|
58 |
+
brand_team_name: Optional[str] = None,
|
59 |
+
brand_discord_url: Optional[str] = None,
|
60 |
+
brand_hf_org: Optional[str] = None,
|
61 |
+
brand_hf_label: Optional[str] = None,
|
62 |
+
brand_hf_url: Optional[str] = None,
|
63 |
+
brand_gh_org: Optional[str] = None,
|
64 |
+
brand_gh_label: Optional[str] = None,
|
65 |
+
brand_gh_url: Optional[str] = None,
|
66 |
+
brand_project_name: Optional[str] = None,
|
67 |
+
brand_project_url: Optional[str] = None,
|
68 |
+
):
|
69 |
self.hf_token = hf_token
|
70 |
+
# The token we will store in the Space secrets. Defaults to hf_token if not provided
|
71 |
+
self.space_secret_token = space_secret_token or hf_token
|
72 |
self.hf_username = hf_username
|
73 |
# Allow passing just a repo name without username and auto-prefix
|
74 |
self.model_id = model_id if "/" in model_id else f"{hf_username}/{model_id}"
|
|
|
83 |
self.developer_message: Optional[str] = None
|
84 |
self.model_identity: Optional[str] = None
|
85 |
self.reasoning_effort: Optional[str] = None
|
86 |
+
# Examples context
|
87 |
+
self.examples_type: Optional[str] = (examples_type or None)
|
88 |
+
self.disable_examples: Optional[bool] = (disable_examples if disable_examples is not None else None)
|
89 |
+
self.examples_json: Optional[str] = (examples_json or None)
|
90 |
|
91 |
# Determine demo type from model_id if not provided
|
92 |
if demo_type is None:
|
|
|
110 |
except Exception as e:
|
111 |
logger.warning(f"Could not load config messages: {e}")
|
112 |
|
113 |
+
# Branding defaults (can be overridden via CLI)
|
114 |
+
self.brand_owner_name = brand_owner_name or self.hf_username or "Tonic"
|
115 |
+
self.brand_team_name = brand_team_name or f"Team{self.brand_owner_name}"
|
116 |
+
self.brand_discord_url = brand_discord_url or "https://discord.gg/qdfnvSPcqP"
|
117 |
+
# HF org/link
|
118 |
+
_default_hf_org = brand_hf_org or self.hf_username or "MultiTransformer"
|
119 |
+
self.brand_hf_org = _default_hf_org
|
120 |
+
self.brand_hf_label = brand_hf_label or self.brand_hf_org
|
121 |
+
self.brand_hf_url = brand_hf_url or f"https://huggingface.co/{self.brand_hf_org}"
|
122 |
+
# GitHub org/link
|
123 |
+
_default_gh_org = brand_gh_org or self.hf_username or "tonic-ai"
|
124 |
+
self.brand_gh_org = _default_gh_org
|
125 |
+
self.brand_gh_label = brand_gh_label or self.brand_gh_org
|
126 |
+
self.brand_gh_url = brand_gh_url or f"https://github.com/{self.brand_gh_org}"
|
127 |
+
# Project link
|
128 |
+
self.brand_project_name = brand_project_name or "MultiTonic"
|
129 |
+
self.brand_project_url = brand_project_url or "https://github.com/MultiTonic"
|
130 |
+
|
131 |
def _load_config_messages(self) -> None:
|
132 |
"""Load system/developer/model_identity from a training config file if provided."""
|
133 |
if not self.config_file:
|
|
|
198 |
os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
|
199 |
os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
|
200 |
os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
|
201 |
+
{"os.environ['EXAMPLES_TYPE'] = " + _json.dumps(self.examples_type) + "\n" if self.examples_type else ''}
|
202 |
+
{"os.environ['DISABLE_EXAMPLES'] = 'true'\n" if self.disable_examples else ("os.environ['DISABLE_EXAMPLES'] = 'false'\n" if self.disable_examples is not None else '')}
|
203 |
+
{"os.environ['EXAMPLES_JSON'] = " + _json.dumps(self.examples_json) + "\n" if self.examples_json else ''}
|
204 |
+
|
205 |
+
# Branding/owner variables
|
206 |
+
os.environ['HF_USERNAME'] = {_json.dumps(self.hf_username)}
|
207 |
+
os.environ['BRAND_OWNER_NAME'] = {_json.dumps(self.brand_owner_name)}
|
208 |
+
os.environ['BRAND_TEAM_NAME'] = {_json.dumps(self.brand_team_name)}
|
209 |
+
os.environ['BRAND_DISCORD_URL'] = {_json.dumps(self.brand_discord_url)}
|
210 |
+
os.environ['BRAND_HF_ORG'] = {_json.dumps(self.brand_hf_org)}
|
211 |
+
os.environ['BRAND_HF_LABEL'] = {_json.dumps(self.brand_hf_label)}
|
212 |
+
os.environ['BRAND_HF_URL'] = {_json.dumps(self.brand_hf_url)}
|
213 |
+
os.environ['BRAND_GH_ORG'] = {_json.dumps(self.brand_gh_org)}
|
214 |
+
os.environ['BRAND_GH_LABEL'] = {_json.dumps(self.brand_gh_label)}
|
215 |
+
os.environ['BRAND_GH_URL'] = {_json.dumps(self.brand_gh_url)}
|
216 |
+
os.environ['BRAND_PROJECT_NAME'] = {_json.dumps(self.brand_project_name)}
|
217 |
+
os.environ['BRAND_PROJECT_URL'] = {_json.dumps(self.brand_project_url)}
|
218 |
|
219 |
"""
|
220 |
else:
|
|
|
230 |
os.environ['SYSTEM_MESSAGE'] = {_json.dumps(self.system_message or (self.model_identity or ""))}
|
231 |
os.environ['DEVELOPER_MESSAGE'] = {_json.dumps(self.developer_message or "")}
|
232 |
os.environ['REASONING_EFFORT'] = {_json.dumps((self.reasoning_effort or "medium"))}
|
233 |
+
{"os.environ['EXAMPLES_TYPE'] = " + _json.dumps(self.examples_type) + "\n" if self.examples_type else ''}
|
234 |
+
{"os.environ['DISABLE_EXAMPLES'] = 'true'\n" if self.disable_examples else ("os.environ['DISABLE_EXAMPLES'] = 'false'\n" if self.disable_examples is not None else '')}
|
235 |
+
{"os.environ['EXAMPLES_JSON'] = " + _json.dumps(self.examples_json) + "\n" if self.examples_json else ''}
|
236 |
+
|
237 |
+
# Branding/owner variables
|
238 |
+
os.environ['HF_USERNAME'] = {_json.dumps(self.hf_username)}
|
239 |
+
os.environ['BRAND_OWNER_NAME'] = {_json.dumps(self.brand_owner_name)}
|
240 |
+
os.environ['BRAND_TEAM_NAME'] = {_json.dumps(self.brand_team_name)}
|
241 |
+
os.environ['BRAND_DISCORD_URL'] = {_json.dumps(self.brand_discord_url)}
|
242 |
+
os.environ['BRAND_HF_ORG'] = {_json.dumps(self.brand_hf_org)}
|
243 |
+
os.environ['BRAND_HF_LABEL'] = {_json.dumps(self.brand_hf_label)}
|
244 |
+
os.environ['BRAND_HF_URL'] = {_json.dumps(self.brand_hf_url)}
|
245 |
+
os.environ['BRAND_GH_ORG'] = {_json.dumps(self.brand_gh_org)}
|
246 |
+
os.environ['BRAND_GH_LABEL'] = {_json.dumps(self.brand_gh_label)}
|
247 |
+
os.environ['BRAND_GH_URL'] = {_json.dumps(self.brand_gh_url)}
|
248 |
+
os.environ['BRAND_PROJECT_NAME'] = {_json.dumps(self.brand_project_name)}
|
249 |
+
os.environ['BRAND_PROJECT_URL'] = {_json.dumps(self.brand_project_url)}
|
250 |
|
251 |
"""
|
252 |
return env_setup
|
|
|
335 |
description="Default reasoning effort (low|medium|high)"
|
336 |
)
|
337 |
logger.info("✅ Set REASONING_EFFORT variable")
|
338 |
+
|
339 |
+
# Branding variables
|
340 |
+
branding_vars = {
|
341 |
+
"HF_USERNAME": self.hf_username,
|
342 |
+
"BRAND_OWNER_NAME": self.brand_owner_name,
|
343 |
+
"BRAND_TEAM_NAME": self.brand_team_name,
|
344 |
+
"BRAND_DISCORD_URL": self.brand_discord_url,
|
345 |
+
"BRAND_HF_ORG": self.brand_hf_org,
|
346 |
+
"BRAND_HF_LABEL": self.brand_hf_label,
|
347 |
+
"BRAND_HF_URL": self.brand_hf_url,
|
348 |
+
"BRAND_GH_ORG": self.brand_gh_org,
|
349 |
+
"BRAND_GH_LABEL": self.brand_gh_label,
|
350 |
+
"BRAND_GH_URL": self.brand_gh_url,
|
351 |
+
"BRAND_PROJECT_NAME": self.brand_project_name,
|
352 |
+
"BRAND_PROJECT_URL": self.brand_project_url,
|
353 |
+
}
|
354 |
+
for key, value in branding_vars.items():
|
355 |
+
self.api.add_space_variable(
|
356 |
+
repo_id=self.space_id,
|
357 |
+
key=key,
|
358 |
+
value=value,
|
359 |
+
description=f"Branding: {key}"
|
360 |
+
)
|
361 |
+
logger.info("✅ Set branding variables")
|
362 |
+
|
363 |
+
# Examples variables
|
364 |
+
if self.examples_type:
|
365 |
+
self.api.add_space_variable(
|
366 |
+
repo_id=self.space_id,
|
367 |
+
key="EXAMPLES_TYPE",
|
368 |
+
value=self.examples_type,
|
369 |
+
description="Examples pack type (e.g., general|medical)"
|
370 |
+
)
|
371 |
+
logger.info(f"✅ Set EXAMPLES_TYPE={self.examples_type}")
|
372 |
+
if self.disable_examples is not None:
|
373 |
+
self.api.add_space_variable(
|
374 |
+
repo_id=self.space_id,
|
375 |
+
key="DISABLE_EXAMPLES",
|
376 |
+
value=("true" if self.disable_examples else "false"),
|
377 |
+
description="Disable built-in examples"
|
378 |
+
)
|
379 |
+
logger.info(f"✅ Set DISABLE_EXAMPLES={self.disable_examples}")
|
380 |
+
if self.examples_json:
|
381 |
+
self.api.add_space_variable(
|
382 |
+
repo_id=self.space_id,
|
383 |
+
key="EXAMPLES_JSON",
|
384 |
+
value=self.examples_json,
|
385 |
+
description="Custom examples JSON override"
|
386 |
+
)
|
387 |
+
logger.info("✅ Set EXAMPLES_JSON override")
|
388 |
|
389 |
except Exception as e:
|
390 |
logger.error(f"❌ Failed to set model variables: {e}")
|
|
|
676 |
self.api.add_space_secret(
|
677 |
repo_id=self.space_id,
|
678 |
key="HF_TOKEN",
|
679 |
+
value=self.space_secret_token,
|
680 |
description="Hugging Face token for model access"
|
681 |
)
|
682 |
logger.info("✅ Successfully set HF_TOKEN secret via API")
|
|
|
717 |
logger.info(f" SYSTEM_MESSAGE={self.system_message}")
|
718 |
if self.developer_message:
|
719 |
logger.info(f" DEVELOPER_MESSAGE={self.developer_message}")
|
720 |
+
# Branding variables
|
721 |
+
logger.info(f" HF_USERNAME={self.hf_username}")
|
722 |
+
logger.info(f" BRAND_OWNER_NAME={self.brand_owner_name}")
|
723 |
+
logger.info(f" BRAND_TEAM_NAME={self.brand_team_name}")
|
724 |
+
logger.info(f" BRAND_DISCORD_URL={self.brand_discord_url}")
|
725 |
+
logger.info(f" BRAND_HF_ORG={self.brand_hf_org}")
|
726 |
+
logger.info(f" BRAND_HF_LABEL={self.brand_hf_label}")
|
727 |
+
logger.info(f" BRAND_HF_URL={self.brand_hf_url}")
|
728 |
+
logger.info(f" BRAND_GH_ORG={self.brand_gh_org}")
|
729 |
+
logger.info(f" BRAND_GH_LABEL={self.brand_gh_label}")
|
730 |
+
logger.info(f" BRAND_GH_URL={self.brand_gh_url}")
|
731 |
+
logger.info(f" BRAND_PROJECT_NAME={self.brand_project_name}")
|
732 |
+
logger.info(f" BRAND_PROJECT_URL={self.brand_project_url}")
|
733 |
+
|
734 |
+
# Examples variables
|
735 |
+
if self.examples_type:
|
736 |
+
logger.info(f" EXAMPLES_TYPE={self.examples_type}")
|
737 |
+
if self.disable_examples is not None:
|
738 |
+
logger.info(f" DISABLE_EXAMPLES={'true' if self.disable_examples else 'false'}")
|
739 |
+
if self.examples_json:
|
740 |
+
logger.info(f" EXAMPLES_JSON={self.examples_json}")
|
741 |
|
742 |
logger.info(f"\n🔧 To set secrets in your Space:")
|
743 |
logger.info(f"1. Go to your Space settings: {self.space_url}/settings")
|
|
|
842 |
|
843 |
parser = argparse.ArgumentParser(description="Deploy demo space to Hugging Face Spaces")
|
844 |
parser.add_argument("--hf-token", required=True, help="Hugging Face token")
|
845 |
+
parser.add_argument(
|
846 |
+
"--space-secret-token",
|
847 |
+
required=False,
|
848 |
+
help="Token to store as Space secret HF_TOKEN (defaults to --hf-token). Use a READ token here for least privilege.",
|
849 |
+
)
|
850 |
parser.add_argument("--hf-username", required=True, help="Hugging Face username")
|
851 |
parser.add_argument("--model-id", required=True, help="Model ID to deploy demo for")
|
852 |
parser.add_argument("--subfolder", default="int4", help="Model subfolder (default: int4)")
|
853 |
parser.add_argument("--space-name", help="Custom space name (optional)")
|
854 |
parser.add_argument("--demo-type", choices=["smol", "gpt"], help="Demo type: 'smol' for SmolLM, 'gpt' for GPT-OSS (auto-detected if not specified)")
|
855 |
parser.add_argument("--config-file", help="Path to the training config file to import context (system/developer/model_identity)")
|
856 |
+
# Examples configuration
|
857 |
+
parser.add_argument("--examples-type", choices=["general", "medical"], help="Examples pack to enable in the demo UI")
|
858 |
+
parser.add_argument("--disable-examples", action="store_true", help="Disable rendering of example prompts in the UI")
|
859 |
+
parser.add_argument("--examples-json", help="Custom examples JSON (list[str]) to override built-in examples")
|
860 |
+
# Branding customization
|
861 |
+
parser.add_argument("--brand-owner-name", help="Owner name shown in the UI title (defaults to HF username)")
|
862 |
+
parser.add_argument("--brand-team-name", help="Team name shown in Join Us (defaults to Team<owner>)")
|
863 |
+
parser.add_argument("--brand-discord-url", help="Discord invite URL for Join Us section")
|
864 |
+
parser.add_argument("--brand-hf-org", help="Hugging Face org/username to link in Join Us")
|
865 |
+
parser.add_argument("--brand-hf-label", help="Label for the HF link (defaults to org)")
|
866 |
+
parser.add_argument("--brand-hf-url", help="Custom HF link URL (defaults to https://huggingface.co/<org>)")
|
867 |
+
parser.add_argument("--brand-gh-org", help="GitHub org/username to link in Join Us")
|
868 |
+
parser.add_argument("--brand-gh-label", help="Label for the GitHub link (defaults to org)")
|
869 |
+
parser.add_argument("--brand-gh-url", help="Custom GitHub link URL (defaults to https://github.com/<org>)")
|
870 |
+
parser.add_argument("--brand-project-name", help="Project name to link in Join Us")
|
871 |
+
parser.add_argument("--brand-project-url", help="Project URL to link in Join Us")
|
872 |
|
873 |
args = parser.parse_args()
|
874 |
|
875 |
deployer = DemoSpaceDeployer(
|
876 |
hf_token=args.hf_token,
|
877 |
+
space_secret_token=(args.space_secret_token or None),
|
878 |
hf_username=args.hf_username,
|
879 |
model_id=args.model_id,
|
880 |
subfolder=args.subfolder,
|
881 |
space_name=args.space_name,
|
882 |
demo_type=args.demo_type,
|
883 |
config_file=args.config_file,
|
884 |
+
examples_type=args.examples_type,
|
885 |
+
disable_examples=(True if getattr(args, 'disable_examples', False) else None),
|
886 |
+
examples_json=args.examples_json,
|
887 |
+
brand_owner_name=args.brand_owner_name,
|
888 |
+
brand_team_name=args.brand_team_name,
|
889 |
+
brand_discord_url=args.brand_discord_url,
|
890 |
+
brand_hf_org=args.brand_hf_org,
|
891 |
+
brand_hf_label=args.brand_hf_label,
|
892 |
+
brand_hf_url=args.brand_hf_url,
|
893 |
+
brand_gh_org=args.brand_gh_org,
|
894 |
+
brand_gh_label=args.brand_gh_label,
|
895 |
+
brand_gh_url=args.brand_gh_url,
|
896 |
+
brand_project_name=args.brand_project_name,
|
897 |
+
brand_project_url=args.brand_project_url,
|
898 |
)
|
899 |
|
900 |
success = deployer.deploy()
|
templates/spaces/demo_gpt/app.py
CHANGED
@@ -6,7 +6,9 @@ import spaces
|
|
6 |
import re
|
7 |
import logging
|
8 |
import os
|
|
|
9 |
from peft import PeftModel
|
|
|
10 |
|
11 |
# ----------------------------------------------------------------------
|
12 |
# Environment Variables Configuration
|
@@ -34,6 +36,115 @@ print(f" Model Name: {MODEL_NAME}")
|
|
34 |
print(f" Model Subfolder: {MODEL_SUBFOLDER}")
|
35 |
print(f" Use LoRA: {USE_LORA}")
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
# ----------------------------------------------------------------------
|
38 |
# KaTeX delimiter config for Gradio
|
39 |
# ----------------------------------------------------------------------
|
@@ -94,8 +205,9 @@ except Exception as e:
|
|
94 |
print(f"❌ Error loading model: {e}")
|
95 |
raise e
|
96 |
|
97 |
-
def format_conversation_history(chat_history):
|
98 |
-
|
|
|
99 |
for item in chat_history:
|
100 |
role = item["role"]
|
101 |
content = item["content"]
|
@@ -104,7 +216,7 @@ def format_conversation_history(chat_history):
|
|
104 |
messages.append({"role": role, "content": content})
|
105 |
return messages
|
106 |
|
107 |
-
def format_analysis_response(text):
|
108 |
"""Enhanced response formatting with better structure and LaTeX support."""
|
109 |
# Look for analysis section followed by final response
|
110 |
m = re.search(r"analysis(.*?)assistantfinal", text, re.DOTALL | re.IGNORECASE)
|
@@ -136,7 +248,20 @@ def format_analysis_response(text):
|
|
136 |
return cleaned
|
137 |
|
138 |
@spaces.GPU(duration=60)
|
139 |
-
def generate_response(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
if not input_data.strip():
|
141 |
yield "Please enter a prompt."
|
142 |
return
|
@@ -236,74 +361,79 @@ def generate_response(input_data, chat_history, max_new_tokens, model_identity,
|
|
236 |
logging.exception("Generation streaming failed")
|
237 |
yield f"❌ Error during generation: {e}"
|
238 |
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
gr.Slider(label="Max new tokens", minimum=64, maximum=4096, step=1, value=2048),
|
243 |
-
gr.Textbox(
|
244 |
-
label="Model Identity",
|
245 |
-
value=MODEL_IDENTITY,
|
246 |
-
lines=3,
|
247 |
-
placeholder="Optional identity/persona for the model"
|
248 |
-
),
|
249 |
-
gr.Textbox(
|
250 |
-
label="System Prompt",
|
251 |
-
value=DEFAULT_SYSTEM_PROMPT,
|
252 |
-
lines=4,
|
253 |
-
placeholder="Change system prompt"
|
254 |
-
),
|
255 |
-
gr.Textbox(
|
256 |
-
label="Developer Prompt",
|
257 |
-
value=DEFAULT_DEVELOPER_PROMPT,
|
258 |
-
lines=4,
|
259 |
-
placeholder="Optional developer instructions"
|
260 |
-
),
|
261 |
-
gr.Dropdown(
|
262 |
-
label="Reasoning Effort",
|
263 |
-
choices=["low", "medium", "high"],
|
264 |
-
value=DEFAULT_REASONING_EFFORT,
|
265 |
-
interactive=True,
|
266 |
-
),
|
267 |
-
gr.Slider(label="Temperature", minimum=0.1, maximum=2.0, step=0.1, value=0.7),
|
268 |
-
gr.Slider(label="Top-p", minimum=0.05, maximum=1.0, step=0.05, value=0.9),
|
269 |
-
gr.Slider(label="Top-k", minimum=1, maximum=100, step=1, value=50),
|
270 |
-
gr.Slider(label="Repetition Penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.0)
|
271 |
-
],
|
272 |
-
examples=[
|
273 |
-
[{"text": "Explain Newton's laws clearly and concisely with mathematical formulas"}],
|
274 |
-
[{"text": "Write a Python function to calculate the Fibonacci sequence"}],
|
275 |
-
[{"text": "What are the benefits of open weight AI models? Include analysis."}],
|
276 |
-
[{"text": "Solve this equation: $x^2 + 5x + 6 = 0$"}],
|
277 |
-
],
|
278 |
-
cache_examples=False,
|
279 |
-
type="messages",
|
280 |
-
description=f"""
|
281 |
-
|
282 |
-
# 🙋🏻♂️Welcome to 🌟{MODEL_NAME} Demo !
|
283 |
|
284 |
-
|
285 |
-
|
|
|
|
|
286 |
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
|
|
|
|
|
|
|
|
|
|
292 |
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
307 |
|
308 |
if __name__ == "__main__":
|
309 |
-
demo.launch(share=True)
|
|
|
6 |
import re
|
7 |
import logging
|
8 |
import os
|
9 |
+
import json
|
10 |
from peft import PeftModel
|
11 |
+
from typing import Any, Dict, List, Generator
|
12 |
|
13 |
# ----------------------------------------------------------------------
|
14 |
# Environment Variables Configuration
|
|
|
36 |
print(f" Model Subfolder: {MODEL_SUBFOLDER}")
|
37 |
print(f" Use LoRA: {USE_LORA}")
|
38 |
|
39 |
+
# ----------------------------------------------------------------------
|
40 |
+
# Branding/Owner customization (override via Space variables)
|
41 |
+
# ----------------------------------------------------------------------
|
42 |
+
|
43 |
+
HF_USERNAME = os.getenv("HF_USERNAME", "")
|
44 |
+
BRAND_OWNER_NAME = os.getenv("BRAND_OWNER_NAME", HF_USERNAME or "Tonic")
|
45 |
+
BRAND_TEAM_NAME = os.getenv("BRAND_TEAM_NAME", f"Team{BRAND_OWNER_NAME}" if BRAND_OWNER_NAME else "TeamTonic")
|
46 |
+
|
47 |
+
BRAND_DISCORD_URL = os.getenv("BRAND_DISCORD_URL", "https://discord.gg/qdfnvSPcqP")
|
48 |
+
|
49 |
+
# Hugging Face org/links
|
50 |
+
_default_hf_org = os.getenv("HF_ORG", HF_USERNAME) or "MultiTransformer"
|
51 |
+
BRAND_HF_ORG = os.getenv("BRAND_HF_ORG", _default_hf_org)
|
52 |
+
BRAND_HF_LABEL = os.getenv("BRAND_HF_LABEL", BRAND_HF_ORG)
|
53 |
+
BRAND_HF_URL = os.getenv("BRAND_HF_URL", f"https://huggingface.co/{BRAND_HF_ORG}")
|
54 |
+
|
55 |
+
# GitHub org/links
|
56 |
+
_default_gh_org = os.getenv("GITHUB_ORG", "tonic-ai")
|
57 |
+
BRAND_GH_ORG = os.getenv("BRAND_GH_ORG", _default_gh_org)
|
58 |
+
BRAND_GH_LABEL = os.getenv("BRAND_GH_LABEL", BRAND_GH_ORG)
|
59 |
+
BRAND_GH_URL = os.getenv("BRAND_GH_URL", f"https://github.com/{BRAND_GH_ORG}")
|
60 |
+
|
61 |
+
# Project link (optional)
|
62 |
+
BRAND_PROJECT_NAME = os.getenv("BRAND_PROJECT_NAME", "MultiTonic")
|
63 |
+
BRAND_PROJECT_URL = os.getenv("BRAND_PROJECT_URL", "https://github.com/MultiTonic")
|
64 |
+
|
65 |
+
# ----------------------------------------------------------------------
|
66 |
+
# Title/Description content (Markdown/HTML)
|
67 |
+
# ----------------------------------------------------------------------
|
68 |
+
|
69 |
+
TITLE_MD = f"# 🙋🏻♂️ Welcome to 🌟{BRAND_OWNER_NAME}'s ⚕️{MODEL_NAME} Demo !"
|
70 |
+
|
71 |
+
DESCRIPTION_MD = f"""
|
72 |
+
**Model**: `{LORA_MODEL_ID}`
|
73 |
+
**Base**: `{BASE_MODEL_ID}`
|
74 |
+
|
75 |
+
✨ **Enhanced Features:**
|
76 |
+
- 🧠 **Advanced Reasoning**: Detailed analysis and step-by-step thinking
|
77 |
+
- 📊 **LaTeX Support**: Mathematical formulas rendered beautifully (use `$` or `$$`)
|
78 |
+
- 🎯 **Improved Formatting**: Clear separation of reasoning and final responses
|
79 |
+
- 📝 **Smart Logging**: Better error handling and request tracking
|
80 |
+
|
81 |
+
💡 **Usage Tips:**
|
82 |
+
- Adjust reasoning level in system prompt (e.g., "Reasoning: high")
|
83 |
+
- Use LaTeX for math: `$E = mc^2$` or `$$\\int x^2 dx$$`
|
84 |
+
- Wait a couple of seconds initially for model loading
|
85 |
+
"""
|
86 |
+
|
87 |
+
# ----------------------------------------------------------------------
|
88 |
+
# Examples configuration with robust fallbacks
|
89 |
+
# ----------------------------------------------------------------------
|
90 |
+
|
91 |
+
def _normalize_examples(string_items: List[str]) -> List[List[Dict[str, str]]]:
|
92 |
+
"""Convert a list of strings to Gradio ChatInterface examples format."""
|
93 |
+
return [[{"text": s}] for s in string_items]
|
94 |
+
|
95 |
+
DEFAULT_EXAMPLES_GENERAL: List[str] = [
|
96 |
+
"Explain Newton's laws clearly and concisely with mathematical formulas",
|
97 |
+
"Write a Python function to calculate the Fibonacci sequence",
|
98 |
+
"What are the benefits of open weight AI models? Include analysis.",
|
99 |
+
"Solve this equation: $x^2 + 5x + 6 = 0$",
|
100 |
+
]
|
101 |
+
|
102 |
+
DEFAULT_EXAMPLES_MEDICAL: List[str] = [
|
103 |
+
"A 68-year-old man complains of several blisters arising over the back and trunk for the preceding 2 weeks. He takes no medications and has not noted systemic symptoms such as fever, sore throat, weight loss, or fatigue. The general physical examination is normal. The oral mucosa and the lips are normal. Several 2- to 3-cm bullae are present over the trunk and back. A few excoriations where the blisters have ruptured are present. The remainder of the skin is normal, without erythema or scale. What is the best diagnostic approach at this time?",
|
104 |
+
"A 28-year-old woman, gravida 2, para 1, at 40 weeks of gestation is admitted to the hospital in active labor. The patient has attended many prenatal appointments and followed her physician's advice about screening for diseases, laboratory testing, diet, and exercise. Her pregnancy has been uncomplicated. She has no history of a serious illness. Her first child was delivered via normal vaginal delivery. Her vital signs are within normal limits. Cervical examination shows 100% effacement and 10 cm dilation. A cardiotocograph is shown. Which of the following is the most appropriate initial step in management?",
|
105 |
+
"An 18-year-old woman has eaten homemade preserves. Eighteen hours later, she develops diplopia, dysarthria, and dysphagia. She presents to the emergency room for assessment and on examination her blood pressure is 112/74 mmHg, heart rate 110/min, and respirations 20/min. The pertinent findings are abnormal extraocular movements due to cranial nerve palsies, difficulty swallowing and a change in her voice. The strength in her arms is 4/5 and 5/5 in her legs, and the reflexes are normal. Which of the following is the most likely causative organism?",
|
106 |
+
"What are you & who made you?",
|
107 |
+
]
|
108 |
+
|
109 |
+
_examples_type_env = os.getenv("EXAMPLES_TYPE", "medical").strip().lower()
|
110 |
+
_disable_examples = os.getenv("DISABLE_EXAMPLES", "false").strip().lower() in {"1", "true", "yes"}
|
111 |
+
_examples_json_raw = os.getenv("EXAMPLES_JSON") or os.getenv("CUSTOM_EXAMPLES_JSON")
|
112 |
+
|
113 |
+
EXAMPLES_FINAL: List[List[Dict[str, str]]]
|
114 |
+
if _disable_examples:
|
115 |
+
EXAMPLES_FINAL = []
|
116 |
+
else:
|
117 |
+
custom_items: List[str] = []
|
118 |
+
if _examples_json_raw:
|
119 |
+
try:
|
120 |
+
parsed = json.loads(_examples_json_raw)
|
121 |
+
if isinstance(parsed, list) and parsed:
|
122 |
+
# Accept list[str]
|
123 |
+
if all(isinstance(x, str) for x in parsed):
|
124 |
+
custom_items = parsed # type: ignore[assignment]
|
125 |
+
# Accept list[dict{"text": str}]
|
126 |
+
elif all(isinstance(x, dict) and "text" in x and isinstance(x["text"], str) for x in parsed):
|
127 |
+
custom_items = [x["text"] for x in parsed]
|
128 |
+
except Exception:
|
129 |
+
custom_items = []
|
130 |
+
|
131 |
+
if custom_items:
|
132 |
+
EXAMPLES_FINAL = _normalize_examples(custom_items)
|
133 |
+
else:
|
134 |
+
if _examples_type_env in {"med", "medical", "health"}:
|
135 |
+
EXAMPLES_FINAL = _normalize_examples(DEFAULT_EXAMPLES_MEDICAL)
|
136 |
+
else:
|
137 |
+
EXAMPLES_FINAL = _normalize_examples(DEFAULT_EXAMPLES_GENERAL)
|
138 |
+
|
139 |
+
JOIN_US_MD = f"""
|
140 |
+
## Join us :
|
141 |
+
🌟{BRAND_TEAM_NAME}🌟 is always making cool demos! Join our active builder's 🛠️community 👻
|
142 |
+
[Join us on Discord]({BRAND_DISCORD_URL})
|
143 |
+
On 🤗Hugging Face: [{BRAND_HF_LABEL}]({BRAND_HF_URL})
|
144 |
+
On 🌐GitHub: [{BRAND_GH_LABEL}]({BRAND_GH_URL}) & contribute to 🌟 [{BRAND_PROJECT_NAME}]({BRAND_PROJECT_URL})
|
145 |
+
🤗 Big thanks to the Hugging Face team for the community support 🤗
|
146 |
+
"""
|
147 |
+
|
148 |
# ----------------------------------------------------------------------
|
149 |
# KaTeX delimiter config for Gradio
|
150 |
# ----------------------------------------------------------------------
|
|
|
205 |
print(f"❌ Error loading model: {e}")
|
206 |
raise e
|
207 |
|
208 |
+
def format_conversation_history(chat_history: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
209 |
+
"""Normalize Gradio chat history items into a list of role/content messages."""
|
210 |
+
messages: List[Dict[str, str]] = []
|
211 |
for item in chat_history:
|
212 |
role = item["role"]
|
213 |
content = item["content"]
|
|
|
216 |
messages.append({"role": role, "content": content})
|
217 |
return messages
|
218 |
|
219 |
+
def format_analysis_response(text: str) -> str:
|
220 |
"""Enhanced response formatting with better structure and LaTeX support."""
|
221 |
# Look for analysis section followed by final response
|
222 |
m = re.search(r"analysis(.*?)assistantfinal", text, re.DOTALL | re.IGNORECASE)
|
|
|
248 |
return cleaned
|
249 |
|
250 |
@spaces.GPU(duration=60)
|
251 |
+
def generate_response(
|
252 |
+
input_data: str,
|
253 |
+
chat_history: List[Dict[str, Any]],
|
254 |
+
max_new_tokens: int,
|
255 |
+
model_identity: str,
|
256 |
+
system_prompt: str,
|
257 |
+
developer_prompt: str,
|
258 |
+
reasoning_effort: str,
|
259 |
+
temperature: float,
|
260 |
+
top_p: float,
|
261 |
+
top_k: int,
|
262 |
+
repetition_penalty: float,
|
263 |
+
) -> Generator[str, None, None]:
|
264 |
+
"""Stream tokens as they are generated, yielding formatted partial/final outputs."""
|
265 |
if not input_data.strip():
|
266 |
yield "Please enter a prompt."
|
267 |
return
|
|
|
361 |
logging.exception("Generation streaming failed")
|
362 |
yield f"❌ Error during generation: {e}"
|
363 |
|
364 |
+
# ----------------------------------------------------------------------
|
365 |
+
# UI/Styling: CSS + custom Chatbot + two-column description
|
366 |
+
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
|
368 |
+
APP_CSS = """
|
369 |
+
#main_chatbot {height: calc(100vh - 120px);} /* Increase chatbot viewport height */
|
370 |
+
.gradio-container {min-height: 100vh;}
|
371 |
+
"""
|
372 |
|
373 |
+
description_html = f"""
|
374 |
+
<div style=\"display:flex; gap: 16px; align-items:flex-start; flex-wrap: wrap\">
|
375 |
+
<div style=\"flex: 1 1 60%; min-width: 300px;\">
|
376 |
+
{DESCRIPTION_MD}
|
377 |
+
</div>
|
378 |
+
<div style=\"flex: 1 1 35%; min-width: 260px;\">
|
379 |
+
{JOIN_US_MD}
|
380 |
+
</div>
|
381 |
+
</div>
|
382 |
+
"""
|
383 |
|
384 |
+
custom_chatbot = gr.Chatbot(label="Chatbot", elem_id="main_chatbot", latex_delimiters=LATEX_DELIMS)
|
385 |
+
|
386 |
+
demo = gr.ChatInterface(
|
387 |
+
fn=generate_response,
|
388 |
+
chatbot=custom_chatbot,
|
389 |
+
title=f"🙋🏻♂️ Welcome to 🌟{BRAND_OWNER_NAME}'s ⚕️{MODEL_NAME} Demo !",
|
390 |
+
description=description_html,
|
391 |
+
additional_inputs=[
|
392 |
+
gr.Slider(label="Max new tokens", minimum=64, maximum=4096, step=1, value=2048),
|
393 |
+
gr.Textbox(
|
394 |
+
label="🪪Model Identity",
|
395 |
+
value=MODEL_IDENTITY,
|
396 |
+
lines=1,
|
397 |
+
placeholder="Optional identity/persona for the model"
|
398 |
+
),
|
399 |
+
gr.Textbox(
|
400 |
+
label="🤖System Prompt",
|
401 |
+
value=DEFAULT_SYSTEM_PROMPT,
|
402 |
+
lines=1,
|
403 |
+
placeholder="Change system prompt"
|
404 |
+
),
|
405 |
+
gr.Textbox(
|
406 |
+
label="👨🏻💻Developer Prompt",
|
407 |
+
value=DEFAULT_DEVELOPER_PROMPT,
|
408 |
+
lines=1,
|
409 |
+
placeholder="Optional developer instructions"
|
410 |
+
),
|
411 |
+
gr.Dropdown(
|
412 |
+
label="🧠Reasoning Effort",
|
413 |
+
choices=["low", "medium", "high"],
|
414 |
+
value=DEFAULT_REASONING_EFFORT,
|
415 |
+
interactive=True,
|
416 |
+
),
|
417 |
+
gr.Slider(label="🌡️Temperature", minimum=0.1, maximum=2.0, step=0.1, value=0.7),
|
418 |
+
gr.Slider(label="↗️Top-p", minimum=0.05, maximum=1.0, step=0.05, value=0.9),
|
419 |
+
gr.Slider(label="🔝Top-k", minimum=1, maximum=100, step=1, value=50),
|
420 |
+
gr.Slider(label="🦜Repetition Penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.0),
|
421 |
+
],
|
422 |
+
additional_inputs_accordion=gr.Accordion(label="🔧Advanced Inputs", open=False),
|
423 |
+
examples=EXAMPLES_FINAL,
|
424 |
+
cache_examples=False,
|
425 |
+
type="messages",
|
426 |
+
fill_height=True,
|
427 |
+
fill_width=True,
|
428 |
+
textbox=gr.Textbox(
|
429 |
+
label="Query Input",
|
430 |
+
placeholder="Type your prompt (supports LaTeX: $x^2 + y^2 = z^2$)"
|
431 |
+
),
|
432 |
+
stop_btn="Stop Generation",
|
433 |
+
multimodal=False,
|
434 |
+
theme=gr.themes.Soft(),
|
435 |
+
css=APP_CSS,
|
436 |
+
)
|
437 |
|
438 |
if __name__ == "__main__":
|
439 |
+
demo.launch(mcp_server=True, share=True)
|
templates/spaces/demo_smol/app.py
CHANGED
@@ -15,16 +15,44 @@ torch.set_default_dtype(torch.float32)
|
|
15 |
logging.basicConfig(level=logging.INFO)
|
16 |
logger = logging.getLogger(__name__)
|
17 |
|
|
|
|
|
|
|
|
|
18 |
# Get model ID from environment variable or use default
|
19 |
MAIN_MODEL_ID = os.getenv("HF_MODEL_ID", "Tonic/petite-elle-L-aime-3-sft")
|
20 |
MODEL_SUBFOLDER = os.getenv("MODEL_SUBFOLDER", "int4") # Default to int4 for CPU deployment
|
21 |
MODEL_NAME = os.getenv("MODEL_NAME", "SmolLM3 Fine-tuned Model")
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
24 |
model = None
|
25 |
tokenizer = None
|
26 |
-
DEFAULT_SYSTEM_PROMPT =
|
27 |
-
|
|
|
|
|
|
|
28 |
description = f"A fine-tuned version of SmolLM3-3B optimized for conversations. This is the {MODEL_SUBFOLDER} quantized version for efficient deployment."
|
29 |
presentation1 = """
|
30 |
### 🎯 Features
|
@@ -68,6 +96,46 @@ def download_chat_template():
|
|
68 |
return None
|
69 |
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
def load_model():
|
72 |
"""Load the model and tokenizer"""
|
73 |
global model, tokenizer
|
@@ -225,6 +293,8 @@ with gr.Blocks() as demo:
|
|
225 |
placeholder="Bonjour je m'appel Tonic!",
|
226 |
lines=2
|
227 |
)
|
|
|
|
|
228 |
advanced_checkbox = gr.Checkbox(label="🧪 Advanced Settings", value=False)
|
229 |
with gr.Column(visible=False) as advanced_settings:
|
230 |
max_length = gr.Slider(
|
|
|
15 |
logging.basicConfig(level=logging.INFO)
|
16 |
logger = logging.getLogger(__name__)
|
17 |
|
18 |
+
# ----------------------------------------------------------------------
|
19 |
+
# Model & UI configuration (env-driven)
|
20 |
+
# ----------------------------------------------------------------------
|
21 |
+
|
22 |
# Get model ID from environment variable or use default
|
23 |
MAIN_MODEL_ID = os.getenv("HF_MODEL_ID", "Tonic/petite-elle-L-aime-3-sft")
|
24 |
MODEL_SUBFOLDER = os.getenv("MODEL_SUBFOLDER", "int4") # Default to int4 for CPU deployment
|
25 |
MODEL_NAME = os.getenv("MODEL_NAME", "SmolLM3 Fine-tuned Model")
|
26 |
|
27 |
+
# Branding/Owner customization
|
28 |
+
HF_USERNAME = os.getenv("HF_USERNAME", "")
|
29 |
+
BRAND_OWNER_NAME = os.getenv("BRAND_OWNER_NAME", HF_USERNAME or "Tonic")
|
30 |
+
BRAND_TEAM_NAME = os.getenv("BRAND_TEAM_NAME", f"Team{BRAND_OWNER_NAME}" if BRAND_OWNER_NAME else "TeamTonic")
|
31 |
+
|
32 |
+
BRAND_DISCORD_URL = os.getenv("BRAND_DISCORD_URL", "https://discord.gg/qdfnvSPcqP")
|
33 |
+
|
34 |
+
_default_hf_org = os.getenv("BRAND_HF_ORG") or HF_USERNAME or "MultiTransformer"
|
35 |
+
BRAND_HF_ORG = _default_hf_org
|
36 |
+
BRAND_HF_LABEL = os.getenv("BRAND_HF_LABEL", BRAND_HF_ORG)
|
37 |
+
BRAND_HF_URL = os.getenv("BRAND_HF_URL", f"https://huggingface.co/{BRAND_HF_ORG}")
|
38 |
+
|
39 |
+
_default_gh_org = os.getenv("BRAND_GH_ORG") or HF_USERNAME or "tonic-ai"
|
40 |
+
BRAND_GH_ORG = _default_gh_org
|
41 |
+
BRAND_GH_LABEL = os.getenv("BRAND_GH_LABEL", BRAND_GH_ORG)
|
42 |
+
BRAND_GH_URL = os.getenv("BRAND_GH_URL", f"https://github.com/{BRAND_GH_ORG}")
|
43 |
+
|
44 |
+
BRAND_PROJECT_NAME = os.getenv("BRAND_PROJECT_NAME", "MultiTonic")
|
45 |
+
BRAND_PROJECT_URL = os.getenv("BRAND_PROJECT_URL", "https://github.com/MultiTonic")
|
46 |
+
|
47 |
+
|
48 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
49 |
model = None
|
50 |
tokenizer = None
|
51 |
+
DEFAULT_SYSTEM_PROMPT = os.getenv(
|
52 |
+
"SYSTEM_MESSAGE",
|
53 |
+
"Tu es un assistant francophone rigoureux et bienveillant."
|
54 |
+
)
|
55 |
+
title = f"# 🙋🏻♂️ Bienvenue chez 🌟{BRAND_OWNER_NAME} — {MODEL_NAME}"
|
56 |
description = f"A fine-tuned version of SmolLM3-3B optimized for conversations. This is the {MODEL_SUBFOLDER} quantized version for efficient deployment."
|
57 |
presentation1 = """
|
58 |
### 🎯 Features
|
|
|
96 |
return None
|
97 |
|
98 |
|
99 |
+
# ----------------------------------------------------------------------
|
100 |
+
# Examples configuration with robust fallbacks
|
101 |
+
# ----------------------------------------------------------------------
|
102 |
+
|
103 |
+
def _normalize_examples_flat(string_items: List[str]) -> List[str]:
|
104 |
+
"""Convert list[str] to a flat list usable by gr.Examples targeting a single input."""
|
105 |
+
return list(string_items)
|
106 |
+
|
107 |
+
DEFAULT_EXAMPLES_GENERAL: List[str] = [
|
108 |
+
"Explique les lois de Newton avec des formules mathématiques",
|
109 |
+
"Écris une fonction Python pour calculer la suite de Fibonacci",
|
110 |
+
"Quels sont les avantages des modèles IA open-weight ?",
|
111 |
+
"Résous cette équation : $x^2 + 5x + 6 = 0$",
|
112 |
+
]
|
113 |
+
|
114 |
+
DEFAULT_EXAMPLES_MEDICAL: List[str] = [
|
115 |
+
"Un homme de 68 ans présente plusieurs bulles sur le dos et le tronc depuis 2 semaines. Quel est le meilleur diagnostic à ce stade ?",
|
116 |
+
"Une femme de 28 ans en travail à 40 semaines de gestation : quelle est la première étape de prise en charge ?",
|
117 |
+
"Une femme de 18 ans a consommé des conserves maison et présente diplopie et dysphagie. Quel est l'agent causal le plus probable ?",
|
118 |
+
]
|
119 |
+
|
120 |
+
_examples_type_env = (os.getenv("EXAMPLES_TYPE", "general") or "general").strip().lower()
|
121 |
+
_disable_examples = os.getenv("DISABLE_EXAMPLES", "false").strip().lower() in {"1", "true", "yes"}
|
122 |
+
_examples_json_raw = os.getenv("EXAMPLES_JSON") or os.getenv("CUSTOM_EXAMPLES_JSON")
|
123 |
+
|
124 |
+
if not _disable_examples and _examples_json_raw:
|
125 |
+
try:
|
126 |
+
parsed = json.loads(_examples_json_raw)
|
127 |
+
if isinstance(parsed, list) and parsed and all(isinstance(x, str) for x in parsed):
|
128 |
+
EXAMPLES_FLAT: List[str] = _normalize_examples_flat(parsed)
|
129 |
+
else:
|
130 |
+
EXAMPLES_FLAT = []
|
131 |
+
except Exception:
|
132 |
+
EXAMPLES_FLAT = []
|
133 |
+
else:
|
134 |
+
if _examples_type_env in {"med", "medical", "santé", "sante"}:
|
135 |
+
EXAMPLES_FLAT = _normalize_examples_flat(DEFAULT_EXAMPLES_MEDICAL)
|
136 |
+
else:
|
137 |
+
EXAMPLES_FLAT = _normalize_examples_flat(DEFAULT_EXAMPLES_GENERAL)
|
138 |
+
|
139 |
def load_model():
|
140 |
"""Load the model and tokenizer"""
|
141 |
global model, tokenizer
|
|
|
293 |
placeholder="Bonjour je m'appel Tonic!",
|
294 |
lines=2
|
295 |
)
|
296 |
+
if EXAMPLES_FLAT:
|
297 |
+
gr.Examples(label="Exemples", examples=EXAMPLES_FLAT, inputs=user_input)
|
298 |
advanced_checkbox = gr.Checkbox(label="🧪 Advanced Settings", value=False)
|
299 |
with gr.Column(visible=False) as advanced_settings:
|
300 |
max_length = gr.Slider(
|