Hugging Face api 사용 gradio_client JSONDecodeError

kimeasyn·2025년 10월 10일
post-thumbnail

Hugging Face Space에서 gradio_client로 API를 호출하려고 했는데,
Client() 선언만 했는데도 JSONDecodeError가 났다.

from gradio_client.client import Client

client = Client(src="**/space", hf_token="hf_*******")

에러
JSONDecodeError: Expecting value: line 1 column 1 (char 0)

응답이 빈 문자열이라 JSON 파싱이 안 됐다.
r.text 자체가 ''로 비어 있었다.

원인

gradio_client 내부에서 API 정보를 불러올 때 URL이 이렇게 되어 있다.

API_INFO_URL = "info?all_endpoints=True"

그런데 Gradio 최신 버전에서는 /info가 아니라 /gradio_api/info를 써야 한다.
즉, 버전이 달라서 잘못된 경로를 호출하게 된 거다.

그래서 r.json()에서 JSONDecodeError가 발생했다.

해결

내부 코드를 직접 바꾸려니 온갖 함수와 상속관계를 다 풀어내야해서
wrapper 클래스를 하나 만들었다.
/config에서 api_prefix(/gradio_api)를 읽어올 수 있길래 지정하고
그 경로로 /info를 다시 호출하도록 했다.

import httpx
from typing import Any
from pathlib import Path

class SafeClient:
    def __init__(
        self,
        src: str,
        hf_token=None,
        *,
        headers=None,
        ssl_verify: bool = True,
        download_files: str | Path = "/tmp",
    ):
        self.src = src.rstrip("/")
        self.hf_token = hf_token
        self.ssl_verify = ssl_verify
        self.headers = headers or {"User-Agent": "safe-gradio-client/0.1"}
        if hf_token:
            self.headers["Authorization"] = f"Bearer {hf_token}"
        self.download_files = download_files

        # config에서 api_prefix 확인
        config = self._fetch_json(f"{self.src}/config")
        self.api_prefix = config.get("api_prefix", "")
        self.enable_queue = config.get("enable_queue", False)

        # info 호출
        info_url = f"{self.src}{self.api_prefix}/info"
        self._info = self._fetch_json(info_url) or {
            "named_endpoints": {},
            "unnamed_endpoints": []
        }

    def _fetch_json(self, url: str) -> dict[str, Any]:
        try:
            r = httpx.get(url, headers=self.headers, verify=self.ssl_verify, timeout=10)
            if r.status_code == 200 and "application/json" in r.headers.get("content-type", ""):
                return r.json()
        except Exception:
            pass
        return {}

    def view_api(self) -> dict[str, Any]:
        return self._info

    def predict(self, *inputs, api_name="/predict"):
        base = f"{self.src}{self.api_prefix}"
        if self.enable_queue:
            url = f"{base}/queue/join"
        else:
            url = f"{base}/predict/"
        payload = {"data": list(inputs)}

        r = httpx.post(url, headers=self.headers, json=payload, verify=self.ssl_verify, timeout=120)
        try:
            return r.json()
        except Exception:
            return {"error": "Invalid JSON response", "raw": r.text[:500]}

사용 예시

client = SafeClient(src="org/space-app", hf_token="hf_***")

print(client.view_api())
result = client.predict("/api-test")
print(result)

3줄요약

Gradio API 경로가 /info → /gradio_api/info로 바뀜

기존 gradio_client는 하드코딩된 경로라서 깨짐

SafeClient로 api_prefix 자동 감지해서 해결

queue 모드(enable_queue=True)도 정상 작동

필요하면 나중에 gradio_client에 PR 넣을 생각.
지금은 그냥 이 wrapper로 쓰는 게 제일 편하다.

profile
들썩들썩 떠들썩

0개의 댓글