File size: 6,750 Bytes
0490acd 5d1e862 0490acd fa235be 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd 5d1e862 0490acd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
import gradio as gr
import os
import logging
import json # For potentially parsing input if not using gr.JSON, or formatting output
from huggingface_hub import InferenceClient
from huggingface_hub.utils import HfHubHTTPError # Correct import
import traceback
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# --- Hugging Face Client Setup ---
HF_TARGET_TOKEN = os.environ.get("HF_API_TOKEN")
if not HF_TARGET_TOKEN:
logger.error("CRITICAL: HF_API_TOKEN secret not found in Space environment variables!")
# Gradio app might still load, but inference will fail.
target_client = None
initialization_error = None
try:
# Only initialize if token exists
if HF_TARGET_TOKEN:
target_client = InferenceClient(token=HF_TARGET_TOKEN)
logger.info("Target InferenceClient initialized.")
else:
# Allows app to load but indicates the problem
initialization_error = "Service Unavailable: Proxy configuration error (Missing Token)."
logger.error(initialization_error)
except Exception as e:
initialization_error = f"Failed to initialize target InferenceClient: {e}"
logger.error(initialization_error)
target_client = None # Ensure it's None
# --- Core Proxy Function ---
def proxy_inference(request_data: dict):
"""
Gradio function to handle inference requests.
Expects a dictionary (from gr.JSON input) like:
{
"imageDataUrl": "data:image/...",
"candidate_labels": ["label1", "label2", ...]
}
Returns a dictionary (for gr.JSON output) like:
{"result": [...]} or {"error": "...", "details": "..."}
"""
logger.info(f"Received request data via Gradio function: {request_data}")
if initialization_error:
logger.error(f"Returning initialization error: {initialization_error}")
# Use a specific key to indicate setup error vs. runtime error
return {"setup_error": initialization_error}
if not target_client:
# Should be covered by initialization_error, but as a fallback
logger.error("Target client not available.")
return {"error": "Configuration Error", "details": "Target client not initialized."}
if not isinstance(request_data, dict):
logger.error(f"Invalid input type: expected dict, got {type(request_data)}")
return {"error": "Bad Request", "details": "Input must be a JSON object."}
# Extract data safely
image_data_url = request_data.get("imageDataUrl")
candidate_labels = request_data.get("candidate_labels", ["person", "car", "building", "animal", "tree"]) # Provide default
if not image_data_url or not isinstance(image_data_url, str) or not image_data_url.startswith('data:image'):
logger.error("Missing or invalid 'imageDataUrl' in request.")
return {"error": "Bad Request", "details": "Missing or invalid 'imageDataUrl'."}
if not isinstance(candidate_labels, list):
logger.error("Invalid 'candidate_labels', must be a list.")
return {"error": "Bad Request", "details": "'candidate_labels' must be a list."}
logger.info(f"Image URL prefix: {image_data_url[:70]}...")
logger.info(f"Labels: {candidate_labels}")
try:
# Call the actual target inference endpoint
logger.info("Calling target_client.zero_shot_image_classification...")
inference_output = target_client.zero_shot_image_classification(
image=image_data_url,
candidate_labels=candidate_labels
)
logger.info(f"Successfully received response from target API.")
# Return the successful result structure
return {"result": inference_output}
except HfHubHTTPError as e:
status_code = e.response.status_code if hasattr(e, 'response') else 500
request_id = e.request_id
error_detail = str(e)
# Attempt to get cleaner error message
if hasattr(e, 'response'):
try:
error_data = e.response.json()
error_detail = error_data.get("error", str(e))
except: pass # Keep original error if JSON parsing fails
logger.error(f"HTTP Error from target HF API: Status={status_code}, RequestID={request_id}, Error={error_detail}")
# Return error structure
return {
"error": f"Target API Error (Status {status_code})",
"details": error_detail,
"request_id": request_id
}
except Exception as e:
error_detail = str(e)
logger.error(f"Unexpected error in proxy function: {error_detail}\n{traceback.format_exc()}")
# Return error structure
return {
"error": "Internal Server Error in Proxy",
"details": error_detail
}
# --- Create Gradio Interface ---
# Use gr.JSON for input and output for better API handling
# Provide examples for documentation
input_example = {
"imageDataUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...",
"candidate_labels": ["cat", "dog", "car"]
}
output_example_success = {
"result": [{"score": 0.95, "label": "cat"}, {"score": 0.03, "label": "dog"}, {"score": 0.02, "label": "car"}]
}
output_example_error = {
"error": "Target API Error (Status 422)",
"details": "Input validation error on target server.",
"request_id": "abc-123"
}
with gr.Blocks() as demo:
gr.Markdown("# Inference Proxy\nAccepts JSON input with `imageDataUrl` and `candidate_labels`, calls the target zero-shot model, and returns JSON output.")
with gr.Row():
# Define JSON components for clear API contract
input_json = gr.JSON(label="Input Data (JSON)", value=input_example)
output_json = gr.JSON(label="Output Result (JSON)") # Examples shown in Markdown below
gr.Markdown(f"**Example Success Output:**\n```json\n{json.dumps(output_example_success, indent=2)}\n```")
gr.Markdown(f"**Example Error Output:**\n```json\n{json.dumps(output_example_error, indent=2)}\n```")
# Hidden button to trigger processing - main interaction is via API
# We link the JSON input/output directly to the function
# Gradio Interface or Button click isn't strictly needed if only using API,
# but Interface makes the API endpoint setup automatic.
# Using a dummy button helps ensure the function is linked for the API.
submit_btn = gr.Button("Process (for API)", visible=False)
submit_btn.click(
fn=proxy_inference,
inputs=input_json,
outputs=output_json,
api_name="predict" # Exposes endpoint at /api/predict/
)
# --- Launch the App ---
# share=False is default and recommended for proxy spaces unless public access needed
demo.launch(share=False) |