Spaces:
Sleeping
Sleeping
| """Thin wrapper over huggingface_hub.InferenceClient, billed to the user's own token.""" | |
| from __future__ import annotations | |
| from huggingface_hub import InferenceClient | |
| from huggingface_hub.errors import HfHubHTTPError | |
| # Reassessed 2026-07-05, revised twice same day: | |
| # | |
| # 1st pass picked GLM-5.2 on general benchmarks. 2nd pass reverted to Llama-3.3 | |
| # after checking RAG-faithfulness specifically (Vectara leaderboard: Llama-3.3 | |
| # 4.1% hallucination vs GLM-5's 10.1%). That treated the RAG context as a hard | |
| # limit the model must not go beyond. | |
| # | |
| # But the RAG context here is meant to *guide*, not limit: "Source N" citation | |
| # exists to avoid inventing titles/authors (a copyright-safety measure, see | |
| # rag.py), not to cap the plan's content at what's retrieved β most of a good | |
| # plan (budget math, channel mix, roadmap, team allocation) comes from general | |
| # marketing expertise, not the 8 retrieved excerpts (see composer.py's prompt). | |
| # Under that framing, a model that abstains/hedges more to stay strictly | |
| # faithful (DeepSeek-V3.2's lower answer rate) is a worse fit, not a better | |
| # one β the job is closer to structured planning/synthesis than strict | |
| # retrieval-QA. GLM-5.2's scale, 1M context, and long-horizon *planning* | |
| # specialization (originally built for agentic task decomposition, which is | |
| # structurally similar to "decompose a brief into a 10-section plan with a | |
| # 90-day roadmap") outweigh its higher but still-moderate hallucination rate | |
| # once citations aren't held to a strict-grounding standard. | |
| DEFAULT_MODEL = "zai-org/GLM-5.2" | |
| AVAILABLE_MODELS = [ | |
| "zai-org/GLM-5.2", # best general reasoning/planning capability, 1M context, MIT | |
| "deepseek-ai/DeepSeek-V4-Pro", # frontier reasoning, strong alternative for complex synthesis | |
| "meta-llama/Llama-3.3-70B-Instruct", # most conservative/faithful option, if you'd rather cap creative synthesis | |
| "deepseek-ai/DeepSeek-V3.2", # most faithful (6.3% hallucination) but abstains more β best if you want strict grounding back | |
| "openai/gpt-oss-120b", # Apache 2.0, cheap/fast MoE, decent all-rounder | |
| ] | |
| class LLMError(Exception): | |
| """User-facing error for LLM call failures.""" | |
| def chat( | |
| hf_token: str, | |
| model: str, | |
| messages: list[dict], | |
| max_tokens: int = 2000, | |
| temperature: float = 0.4, | |
| ) -> str: | |
| if not hf_token or not hf_token.strip(): | |
| raise LLMError("Please enter your Hugging Face access token.") | |
| client = InferenceClient(api_key=hf_token.strip()) | |
| def _call(budget: int): | |
| return client.chat_completion( | |
| model=model, | |
| messages=messages, | |
| max_tokens=budget, | |
| temperature=temperature, | |
| ) | |
| try: | |
| response = _call(max_tokens) | |
| choice = response.choices[0] | |
| # Reasoning models (GLM-5.2, DeepSeek-V3.2, ...) spend tokens thinking | |
| # before emitting the visible answer, and those tokens count against | |
| # max_tokens β a budget that fits the answer can still come back with | |
| # content=None and finish_reason="length". Retry once with headroom. | |
| if not choice.message.content and choice.finish_reason == "length": | |
| response = _call(max_tokens + 3000) | |
| choice = response.choices[0] | |
| except HfHubHTTPError as exc: | |
| status = getattr(exc.response, "status_code", None) | |
| if status == 401: | |
| raise LLMError( | |
| "Invalid Hugging Face token. Double-check the token and try again." | |
| ) from exc | |
| if status == 402: | |
| raise LLMError( | |
| "Your Hugging Face account doesn't have billing enabled for Inference " | |
| "Providers, or you're out of credit. Check https://huggingface.co/settings/billing." | |
| ) from exc | |
| if status == 429: | |
| raise LLMError( | |
| "Rate limited by Hugging Face Inference Providers. Wait a moment and try again." | |
| ) from exc | |
| raise LLMError(f"LLM request failed ({status or 'unknown error'}): {exc}") from exc | |
| except Exception as exc: # network errors, timeouts, etc. | |
| raise LLMError(f"LLM request failed: {exc}") from exc | |
| content = choice.message.content | |
| if not content: | |
| detail = ( | |
| " (it ran out of tokens while reasoning)" | |
| if choice.finish_reason == "length" | |
| else "" | |
| ) | |
| raise LLMError( | |
| f"The model ({model}) returned an empty response{detail}. " | |
| "Try again or pick a different model." | |
| ) | |
| return content | |