|
43 | 43 | sign_edt, |
44 | 44 | ) |
45 | 45 |
|
46 | | -# ── Gemma 4 via HF Inference API ───────────────────────────────────────────── |
47 | | -# Default: google/gemma-4-4b-it on hf-inference (requires accepting license at |
48 | | -# huggingface.co/google/gemma-4-4b-it with the account that owns HF_TOKEN). |
49 | | -# Override via Space env vars if needed: |
50 | | -# GEMMA_MODEL — e.g. google/gemma-4-12b-it |
51 | | -# GEMMA_PROVIDER — e.g. featherless-ai |
52 | | -_GEMMA_MODEL = os.environ.get("GEMMA_MODEL", "google/gemma-4-4b-it") |
53 | | -_GEMMA_PROVIDER = os.environ.get("GEMMA_PROVIDER", "hf-inference") |
| 46 | +# ── Gemma 4 via Google AI Studio ───────────────────────────────────────────── |
| 47 | +# Gemma 4 (E4B) is a multimodal any-to-any model — not on HF serverless. |
| 48 | +# The only public inference path is Google AI Studio (free API key at |
| 49 | +# aistudio.google.com/apikey). Add key as GOOGLE_API_KEY Space secret. |
| 50 | +# |
| 51 | +# Fallback chain (checked in order): |
| 52 | +# 1. Google AI Studio — GOOGLE_API_KEY + GEMMA_MODEL (default: gemma-4-e4b-it) |
| 53 | +# 2. HF InferenceClient — HF_TOKEN + featherless-ai + gemma-3-12b-it |
| 54 | +_GEMMA_MODEL = os.environ.get("GEMMA_MODEL", "gemma-4-e4b-it") |
| 55 | +_GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") |
54 | 56 | _HF_TOKEN = os.environ.get("HF_TOKEN") |
55 | | -_gemma_client = None |
56 | 57 | _GEMMA_AVAILABLE = False |
| 58 | +_GEMMA_BACKEND = None # "google" | "hf" |
| 59 | +_gemma_google = None |
| 60 | +_gemma_hf = None |
57 | 61 |
|
58 | 62 | try: |
59 | | - from huggingface_hub import InferenceClient |
60 | | - if _HF_TOKEN: |
61 | | - _gemma_client = InferenceClient(provider=_GEMMA_PROVIDER, api_key=_HF_TOKEN) |
| 63 | + import google.generativeai as genai |
| 64 | + if _GOOGLE_API_KEY: |
| 65 | + genai.configure(api_key=_GOOGLE_API_KEY) |
| 66 | + _gemma_google = genai.GenerativeModel(_GEMMA_MODEL) |
62 | 67 | _GEMMA_AVAILABLE = True |
| 68 | + _GEMMA_BACKEND = "google" |
63 | 69 | except Exception: |
64 | 70 | pass |
65 | 71 |
|
| 72 | +if not _GEMMA_AVAILABLE: |
| 73 | + try: |
| 74 | + from huggingface_hub import InferenceClient |
| 75 | + if _HF_TOKEN: |
| 76 | + _gemma_hf = InferenceClient(provider="featherless-ai", api_key=_HF_TOKEN) |
| 77 | + _GEMMA_AVAILABLE = True |
| 78 | + _GEMMA_BACKEND = "hf" |
| 79 | + except Exception: |
| 80 | + pass |
| 81 | + |
| 82 | +_GEMMA_DISPLAY = ( |
| 83 | + f"{_GEMMA_MODEL} via Google AI Studio" if _GEMMA_BACKEND == "google" |
| 84 | + else "google/gemma-3-12b-it via featherless-ai" if _GEMMA_BACKEND == "hf" |
| 85 | + else "unavailable" |
| 86 | +) |
| 87 | + |
66 | 88 | # ── Async helper (safe in Gradio worker threads) ───────────────────────────── |
67 | 89 | def _run_async(coro): |
68 | 90 | """Run a coroutine in a dedicated thread — avoids event-loop conflicts.""" |
@@ -171,16 +193,23 @@ def _gemma_attack_prompt(src: str, dst: str) -> str: |
171 | 193 | return base + poison |
172 | 194 |
|
173 | 195 | def _call_gemma(prompt: str, single: bool = False) -> str: |
174 | | - """Send prompt to Gemma 4; return raw text or empty string on failure.""" |
| 196 | + """Send prompt to Gemma; return raw text or empty string on failure.""" |
175 | 197 | if not _GEMMA_AVAILABLE: |
176 | 198 | return "" |
177 | 199 | try: |
178 | | - result = _gemma_client.chat.completions.create( |
179 | | - model=_GEMMA_MODEL, |
180 | | - messages=[{"role": "user", "content": prompt}], |
181 | | - max_tokens=160 if not single else 100, |
182 | | - ) |
183 | | - return result.choices[0].message.content.strip() |
| 200 | + if _GEMMA_BACKEND == "google": |
| 201 | + resp = _gemma_google.generate_content( |
| 202 | + prompt, |
| 203 | + generation_config={"max_output_tokens": 300 if not single else 120}, |
| 204 | + ) |
| 205 | + return resp.text.strip() |
| 206 | + else: |
| 207 | + result = _gemma_hf.chat.completions.create( |
| 208 | + model="google/gemma-3-12b-it", |
| 209 | + messages=[{"role": "user", "content": prompt}], |
| 210 | + max_tokens=160 if not single else 100, |
| 211 | + ) |
| 212 | + return result.choices[0].message.content.strip() |
184 | 213 | except Exception as e: |
185 | 214 | return f"[Gemma error: {e}]" |
186 | 215 |
|
@@ -624,7 +653,7 @@ def run_safe_routine(hdp_enabled: bool) -> Generator: |
624 | 653 | "approved": True, "attack": False, |
625 | 654 | "zone": src, "force_n": 0.0, "velocity_ms": 0.0, |
626 | 655 | }]) |
627 | | - gemma_status = f"[AI] **Gemma** (`{_GEMMA_MODEL}`) planning route…" |
| 656 | + gemma_status = f"[AI] **Gemma 4** (`{_GEMMA_DISPLAY}`) planning route…" |
628 | 657 | yield "", f"[AI] Gemma planning: {src} → {dst}", reset_state, "", gemma_status, "" |
629 | 658 | # Arm moves to source position while Gemma is computing |
630 | 659 | yield "", f"[AI] Arm ready at {src} — waiting for Gemma plan…", reach_state, "", gemma_status, "" |
@@ -793,11 +822,11 @@ def _edt_json() -> str: |
793 | 822 | # ── UI ──────────────────────────────────────────────────────────────────────── |
794 | 823 | HEADER = f""" |
795 | 824 | # HDP-P Physical Safety Demo |
796 | | -**Gemma** (`{_GEMMA_MODEL}` via `{_GEMMA_PROVIDER}`) plans every robot action from natural language. |
| 825 | +**Gemma** (`{_GEMMA_DISPLAY}`) plans every robot action from natural language. |
797 | 826 | **HDP-P** cryptographically verifies each Gemma action before the arm moves. |
798 | 827 |
|
799 | 828 | > Run the routine → inject an attack → toggle HDP-P to see the difference. |
800 | | -{"[OK] **Gemma connected** — live inference active." if _GEMMA_AVAILABLE else "[WARN] **Gemma unavailable** — scripted fallback mode (set `HF_TOKEN` Space secret)."} |
| 829 | +{"[OK] **Gemma connected** — live inference active." if _GEMMA_AVAILABLE else "[WARN] **Gemma unavailable** — set `GOOGLE_API_KEY` Space secret (aistudio.google.com/apikey)."} |
801 | 830 | """ |
802 | 831 |
|
803 | 832 | with gr.Blocks( |
@@ -852,7 +881,7 @@ def _edt_json() -> str: |
852 | 881 | User task description |
853 | 882 | │ |
854 | 883 | ▼ |
855 | | - Gemma ({_GEMMA_MODEL}) |
| 884 | + Gemma ({_GEMMA_DISPLAY}) |
856 | 885 | ← adversary injects text here to poison Gemma's output |
857 | 886 | │ generates: RobotAction(zone, force_n, velocity_ms) |
858 | 887 | ▼ |
|
0 commit comments