Spaces:
Sleeping
Sleeping
Download core/api_client.py from build-small-hackathon/Emberglade: direct link, hf CLI and curl.
- Browser
- Download file 3.02 kB
-
https://huggingface.co/spaces/build-small-hackathon/Emberglade/resolve/main/core/api_client.py
- Command line
-
hf download hf://spaces/build-small-hackathon/Emberglade/core/api_client.py
-
curl -L -o api_client.py https://huggingface.co/spaces/build-small-hackathon/Emberglade/resolve/main/core/api_client.py
3.02 kB
| """ | |
| core/api_client.py — MiniCPM-V API wrapper. | |
| Handles: | |
| • OpenAI-compatible streaming via the `openai` SDK | |
| • API key resolution (env var → UI field → public fallback) | |
| • Friendly error messages for 401 / 429 / network failures | |
| """ | |
| import os | |
| from collections.abc import Generator | |
| from openai import OpenAI, APIStatusError, APIConnectionError | |
| from PIL import Image | |
| from config import API_BASE_URL, PUBLIC_API_KEY, MODELS | |
| from core.image_utils import pil_to_data_url | |
| def _resolve_key(ui_key: str) -> str: | |
| """ | |
| Priority: env var MINICPM_API_KEY → UI field → hardcoded public key. | |
| """ | |
| return ( | |
| os.environ.get("MINICPM_API_KEY", "").strip() | |
| or (ui_key or "").strip() | |
| or PUBLIC_API_KEY | |
| ) | |
| def stream_description( | |
| image: Image.Image, | |
| prompt: str, | |
| model_label: str, | |
| max_tokens: int, | |
| temperature: float, | |
| api_key: str, | |
| ) -> Generator[str, None, None]: | |
| """ | |
| Stream the model's response token-by-token. | |
| Yields: | |
| Cumulative response string — each yield is the full text so far, | |
| so Gradio can update the textbox incrementally. | |
| """ | |
| if image is None: | |
| yield "⚠️ Please upload an image first." | |
| return | |
| key = _resolve_key(api_key) | |
| model_id = MODELS[model_label] | |
| data_url = pil_to_data_url(image) | |
| client = OpenAI(api_key=key, base_url=API_BASE_URL) | |
| try: | |
| stream = client.chat.completions.create( | |
| model=model_id, | |
| messages=[{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "image_url", "image_url": {"url": data_url}}, | |
| {"type": "text", "text": prompt}, | |
| ], | |
| }], | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| stream=True, | |
| ) | |
| result = "" | |
| for chunk in stream: | |
| delta = chunk.choices[0].delta.content or "" | |
| if delta: | |
| result += delta | |
| yield result | |
| except APIStatusError as e: | |
| if e.status_code == 401: | |
| yield ( | |
| "🔐 **API key rejected (401).**\n\n" | |
| "The public key may have hit its rate limit. \n" | |
| "Get a free personal key at https://modelbest.cn and paste it " | |
| "in the **API Key** field." | |
| ) | |
| elif e.status_code == 429: | |
| yield ( | |
| "⏳ **Rate limit reached (429).**\n\n" | |
| "The free public key is shared — wait a moment and retry, " | |
| "or use your own key from https://modelbest.cn" | |
| ) | |
| else: | |
| yield f"❌ API error {e.status_code}:\n\n```\n{e.message}\n```" | |
| except APIConnectionError: | |
| yield ( | |
| "❌ **Cannot reach the API** (`api.modelbest.cn`). \n" | |
| "Check your internet connection and try again." | |
| ) | |
| except Exception as e: | |
| yield f"❌ Unexpected error:\n\n```\n{e}\n```" | |