Spaces:
Paused
Paused
Sync card and scripts from the monorepo
Browse files- README.md +9 -12
- app.py +20 -17
- hf_client.py +121 -0
- requirements.txt +3 -3
README.md
CHANGED
|
@@ -4,19 +4,12 @@ emoji: 💻
|
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 5.
|
| 8 |
python_version: "3.10"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
short_description: AI-powered code explanation and documentation
|
| 13 |
-
tags:
|
| 14 |
-
- code
|
| 15 |
-
- education
|
| 16 |
-
- llm
|
| 17 |
-
- developer-tools
|
| 18 |
-
models:
|
| 19 |
-
- mistralai/Mistral-7B-Instruct-v0.3
|
| 20 |
---
|
| 21 |
|
| 22 |
# Code Explainer
|
|
@@ -36,10 +29,10 @@ Choose your expertise level:
|
|
| 36 |
|
| 37 |
### Structured Output
|
| 38 |
Each explanation includes:
|
| 39 |
-
- **Overview** -
|
| 40 |
-
- **
|
| 41 |
- **Key Concepts** - Important programming concepts used
|
| 42 |
-
- **Potential
|
| 43 |
|
| 44 |
### Syntax Highlighting
|
| 45 |
Code is displayed with proper syntax highlighting for readability.
|
|
@@ -55,7 +48,7 @@ Code is displayed with proper syntax highlighting for readability.
|
|
| 55 |
|
| 56 |
| Component | Technology |
|
| 57 |
|-----------|------------|
|
| 58 |
-
| Web Framework | Gradio 5.
|
| 59 |
| AI Model | Mistral-7B via HuggingFace Inference API |
|
| 60 |
| Code Formatting | Pygments |
|
| 61 |
|
|
@@ -72,6 +65,10 @@ Code is displayed with proper syntax highlighting for readability.
|
|
| 72 |
- Complex multi-file projects may need to be explained piece by piece
|
| 73 |
- AI explanations should be verified for critical code
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
## License
|
| 76 |
|
| 77 |
MIT
|
|
|
|
| 4 |
colorFrom: green
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.31.0
|
| 8 |
python_version: "3.10"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
short_description: AI-powered code explanation and documentation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
---
|
| 14 |
|
| 15 |
# Code Explainer
|
|
|
|
| 29 |
|
| 30 |
### Structured Output
|
| 31 |
Each explanation includes:
|
| 32 |
+
- **Overview** - A brief summary of what the code does
|
| 33 |
+
- **Step-by-Step Breakdown** - Section-by-section explanation of the code
|
| 34 |
- **Key Concepts** - Important programming concepts used
|
| 35 |
+
- **Potential Improvements** - Suggested improvements, best practices, or issues to be aware of
|
| 36 |
|
| 37 |
### Syntax Highlighting
|
| 38 |
Code is displayed with proper syntax highlighting for readability.
|
|
|
|
| 48 |
|
| 49 |
| Component | Technology |
|
| 50 |
|-----------|------------|
|
| 51 |
+
| Web Framework | Gradio 5.31.0 |
|
| 52 |
| AI Model | Mistral-7B via HuggingFace Inference API |
|
| 53 |
| Code Formatting | Pygments |
|
| 54 |
|
|
|
|
| 65 |
- Complex multi-file projects may need to be explained piece by piece
|
| 66 |
- AI explanations should be verified for critical code
|
| 67 |
|
| 68 |
+
## Configuration
|
| 69 |
+
|
| 70 |
+
This Space calls the **HuggingFace Inference API** (Mistral-7B). It requires an `HF_TOKEN` secret — a token with inference access — set in **Space Settings → Secrets**. Without it, generation will fail.
|
| 71 |
+
|
| 72 |
## License
|
| 73 |
|
| 74 |
MIT
|
app.py
CHANGED
|
@@ -2,12 +2,15 @@
|
|
| 2 |
Code Explainer - AI-powered code explanation using HuggingFace Inference API.
|
| 3 |
"""
|
| 4 |
|
|
|
|
|
|
|
| 5 |
import gradio as gr
|
| 6 |
-
from huggingface_hub import InferenceClient
|
| 7 |
from pygments import highlight
|
| 8 |
from pygments.lexers import get_lexer_by_name, guess_lexer
|
| 9 |
from pygments.formatters import HtmlFormatter
|
| 10 |
|
|
|
|
|
|
|
| 11 |
# ---------------------------------------------------------------------------
|
| 12 |
# Configuration
|
| 13 |
# ---------------------------------------------------------------------------
|
|
@@ -43,7 +46,7 @@ EXPLANATION_LEVELS = {
|
|
| 43 |
# Initialize client
|
| 44 |
# ---------------------------------------------------------------------------
|
| 45 |
|
| 46 |
-
client =
|
| 47 |
|
| 48 |
# ---------------------------------------------------------------------------
|
| 49 |
# Utility functions
|
|
@@ -77,7 +80,7 @@ def format_code_html(code: str, language: str) -> str:
|
|
| 77 |
formatter = HtmlFormatter(style="monokai", noclasses=True)
|
| 78 |
return highlight(code, lexer, formatter)
|
| 79 |
except Exception:
|
| 80 |
-
return f"<pre><code>{code}</code></pre>"
|
| 81 |
|
| 82 |
|
| 83 |
# ---------------------------------------------------------------------------
|
|
@@ -96,12 +99,12 @@ def explain_code(code: str, language: str, level: str) -> tuple[str, str]:
|
|
| 96 |
|
| 97 |
# Build prompt
|
| 98 |
level_instruction = EXPLANATION_LEVELS.get(level, EXPLANATION_LEVELS["Intermediate"])
|
|
|
|
| 99 |
|
| 100 |
-
|
|
|
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
```{detected_lang.lower()}
|
| 105 |
{code}
|
| 106 |
```
|
| 107 |
|
|
@@ -122,16 +125,17 @@ Suggest any improvements, best practices, or potential issues to be aware of.
|
|
| 122 |
Keep your explanation clear, accurate, and educational."""
|
| 123 |
|
| 124 |
try:
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
| 129 |
temperature=0.7,
|
| 130 |
top_p=0.95,
|
| 131 |
-
do_sample=True,
|
| 132 |
)
|
| 133 |
-
|
| 134 |
-
explanation = response.strip()
|
| 135 |
|
| 136 |
# Add language badge
|
| 137 |
explanation = f"**Detected Language:** `{detected_lang}`\n\n---\n\n{explanation}"
|
|
@@ -141,9 +145,8 @@ Keep your explanation clear, accurate, and educational."""
|
|
| 141 |
|
| 142 |
return explanation, formatted_code
|
| 143 |
|
| 144 |
-
except
|
| 145 |
-
|
| 146 |
-
return error_msg, format_code_html(code, detected_lang)
|
| 147 |
|
| 148 |
|
| 149 |
# ---------------------------------------------------------------------------
|
|
|
|
| 2 |
Code Explainer - AI-powered code explanation using HuggingFace Inference API.
|
| 3 |
"""
|
| 4 |
|
| 5 |
+
import html
|
| 6 |
+
|
| 7 |
import gradio as gr
|
|
|
|
| 8 |
from pygments import highlight
|
| 9 |
from pygments.lexers import get_lexer_by_name, guess_lexer
|
| 10 |
from pygments.formatters import HtmlFormatter
|
| 11 |
|
| 12 |
+
from hf_client import InferenceError, make_client, with_retry
|
| 13 |
+
|
| 14 |
# ---------------------------------------------------------------------------
|
| 15 |
# Configuration
|
| 16 |
# ---------------------------------------------------------------------------
|
|
|
|
| 46 |
# Initialize client
|
| 47 |
# ---------------------------------------------------------------------------
|
| 48 |
|
| 49 |
+
client = make_client(MODEL_ID)
|
| 50 |
|
| 51 |
# ---------------------------------------------------------------------------
|
| 52 |
# Utility functions
|
|
|
|
| 80 |
formatter = HtmlFormatter(style="monokai", noclasses=True)
|
| 81 |
return highlight(code, lexer, formatter)
|
| 82 |
except Exception:
|
| 83 |
+
return f"<pre><code>{html.escape(code)}</code></pre>"
|
| 84 |
|
| 85 |
|
| 86 |
# ---------------------------------------------------------------------------
|
|
|
|
| 99 |
|
| 100 |
# Build prompt
|
| 101 |
level_instruction = EXPLANATION_LEVELS.get(level, EXPLANATION_LEVELS["Intermediate"])
|
| 102 |
+
lexer_hint = detected_lang.split()[0].lower() if detected_lang.strip() else ""
|
| 103 |
|
| 104 |
+
system_prompt = f"You are an expert programming tutor. {level_instruction}"
|
| 105 |
+
user_prompt = f"""Explain the following code.
|
| 106 |
|
| 107 |
+
```{lexer_hint}
|
|
|
|
|
|
|
| 108 |
{code}
|
| 109 |
```
|
| 110 |
|
|
|
|
| 125 |
Keep your explanation clear, accurate, and educational."""
|
| 126 |
|
| 127 |
try:
|
| 128 |
+
completion = with_retry(
|
| 129 |
+
client.chat_completion,
|
| 130 |
+
messages=[
|
| 131 |
+
{"role": "system", "content": system_prompt},
|
| 132 |
+
{"role": "user", "content": user_prompt},
|
| 133 |
+
],
|
| 134 |
+
max_tokens=1500,
|
| 135 |
temperature=0.7,
|
| 136 |
top_p=0.95,
|
|
|
|
| 137 |
)
|
| 138 |
+
explanation = completion.choices[0].message.content.strip()
|
|
|
|
| 139 |
|
| 140 |
# Add language badge
|
| 141 |
explanation = f"**Detected Language:** `{detected_lang}`\n\n---\n\n{explanation}"
|
|
|
|
| 145 |
|
| 146 |
return explanation, formatted_code
|
| 147 |
|
| 148 |
+
except InferenceError as e:
|
| 149 |
+
return f"**{e}**", format_code_html(code, detected_lang)
|
|
|
|
| 150 |
|
| 151 |
|
| 152 |
# ---------------------------------------------------------------------------
|
hf_client.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Helpers for talking to the HuggingFace Inference API.
|
| 2 |
+
|
| 3 |
+
Centralises the three things every Space here needs and previously got wrong:
|
| 4 |
+
|
| 5 |
+
* **Authentication** -- reads ``HF_TOKEN`` from the environment. The serverless
|
| 6 |
+
Inference Providers backend rejects most anonymous calls (401/402), so a token
|
| 7 |
+
is effectively required. Add an ``HF_TOKEN`` secret in the Space settings.
|
| 8 |
+
* **Timeouts** -- a hung provider request no longer blocks the worker forever.
|
| 9 |
+
* **Retries** -- transient errors (model cold-start 503s, rate limits) are
|
| 10 |
+
retried with exponential back-off instead of failing on the first hit.
|
| 11 |
+
|
| 12 |
+
The ``huggingface_hub`` import is deferred into :func:`make_client` so the pure
|
| 13 |
+
helpers (:func:`friendly_error`, :func:`with_retry`) can be imported and unit
|
| 14 |
+
tested without the dependency installed.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import logging
|
| 20 |
+
import os
|
| 21 |
+
import time
|
| 22 |
+
from typing import Callable, Optional, TypeVar
|
| 23 |
+
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
DEFAULT_TIMEOUT = 120 # seconds
|
| 27 |
+
MAX_RETRIES = 3
|
| 28 |
+
BACKOFF_BASE = 2.0 # seconds
|
| 29 |
+
|
| 30 |
+
T = TypeVar("T")
|
| 31 |
+
|
| 32 |
+
_TRANSIENT_MARKERS = (
|
| 33 |
+
"loading",
|
| 34 |
+
"currently loading",
|
| 35 |
+
"starting",
|
| 36 |
+
"503",
|
| 37 |
+
"rate limit",
|
| 38 |
+
"429",
|
| 39 |
+
"too many requests",
|
| 40 |
+
"quota",
|
| 41 |
+
"timeout",
|
| 42 |
+
"timed out",
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class InferenceError(RuntimeError):
|
| 47 |
+
"""Raised when an inference call fails after exhausting retries."""
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def get_token() -> Optional[str]:
|
| 51 |
+
"""Return the HF token from the environment, if configured."""
|
| 52 |
+
return os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def make_client(model: Optional[str] = None, timeout: int = DEFAULT_TIMEOUT):
|
| 56 |
+
"""Create an :class:`InferenceClient` authenticated with the Space token."""
|
| 57 |
+
from huggingface_hub import InferenceClient # deferred: keeps helpers dep-free
|
| 58 |
+
|
| 59 |
+
token = get_token()
|
| 60 |
+
if token is None:
|
| 61 |
+
logger.warning(
|
| 62 |
+
"No HF_TOKEN found in the environment; inference calls will likely be "
|
| 63 |
+
"rejected. Add an HF_TOKEN secret in the Space settings."
|
| 64 |
+
)
|
| 65 |
+
return InferenceClient(model=model, token=token, timeout=timeout)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _is_transient(exc: Exception) -> bool:
|
| 69 |
+
msg = str(exc).lower()
|
| 70 |
+
return any(marker in msg for marker in _TRANSIENT_MARKERS)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def friendly_error(exc: Exception) -> str:
|
| 74 |
+
"""Map a raw inference exception to an actionable, user-facing message."""
|
| 75 |
+
msg = str(exc).lower()
|
| 76 |
+
if any(k in msg for k in ("rate limit", "429", "too many requests", "quota")):
|
| 77 |
+
return (
|
| 78 |
+
"The model is rate-limited right now. Wait a moment and try again, or "
|
| 79 |
+
"use an HF_TOKEN that has inference credits."
|
| 80 |
+
)
|
| 81 |
+
if any(k in msg for k in ("currently loading", "loading", "503", "starting")):
|
| 82 |
+
return "The model is warming up (cold start). Please try again in ~20 seconds."
|
| 83 |
+
if any(k in msg for k in ("401", "unauthorized", "402", "authentication")):
|
| 84 |
+
return (
|
| 85 |
+
"Inference was rejected for authentication. Set a valid HF_TOKEN secret "
|
| 86 |
+
"in the Space settings."
|
| 87 |
+
)
|
| 88 |
+
if "timeout" in msg or "timed out" in msg:
|
| 89 |
+
return "The request timed out -- the model may be busy. Please try again."
|
| 90 |
+
return f"Inference failed: {exc}"
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def with_retry(
|
| 94 |
+
fn: Callable[..., T],
|
| 95 |
+
*args,
|
| 96 |
+
retries: int = MAX_RETRIES,
|
| 97 |
+
sleep: Callable[[float], None] = time.sleep,
|
| 98 |
+
**kwargs,
|
| 99 |
+
) -> T:
|
| 100 |
+
"""Call *fn* with exponential back-off on transient errors.
|
| 101 |
+
|
| 102 |
+
Raises :class:`InferenceError` with a friendly message if all attempts fail.
|
| 103 |
+
"""
|
| 104 |
+
last_exc: Optional[Exception] = None
|
| 105 |
+
for attempt in range(retries):
|
| 106 |
+
try:
|
| 107 |
+
return fn(*args, **kwargs)
|
| 108 |
+
except Exception as exc: # noqa: BLE001 - re-raised via InferenceError
|
| 109 |
+
last_exc = exc
|
| 110 |
+
if not _is_transient(exc) or attempt == retries - 1:
|
| 111 |
+
break
|
| 112 |
+
wait = BACKOFF_BASE * (2**attempt)
|
| 113 |
+
logger.info(
|
| 114 |
+
"Transient inference error (attempt %d/%d); retrying in %.1fs: %s",
|
| 115 |
+
attempt + 1,
|
| 116 |
+
retries,
|
| 117 |
+
wait,
|
| 118 |
+
exc,
|
| 119 |
+
)
|
| 120 |
+
sleep(wait)
|
| 121 |
+
raise InferenceError(friendly_error(last_exc)) from last_exc
|
requirements.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
gradio>=5.31.0
|
| 2 |
-
huggingface_hub>=0.25.0
|
| 3 |
-
pygments>=2.17.0
|
|
|
|
| 1 |
+
gradio>=5.31.0,<6.0.0
|
| 2 |
+
huggingface_hub>=0.25.0,<1.0.0
|
| 3 |
+
pygments>=2.17.0,<3.0.0
|