Spaces:
Build error
Build error
import os | |
import io | |
import base64 | |
from typing import List, Dict, Any | |
import gradio as gr | |
from PIL import Image | |
import httpx | |
from fastapi import FastAPI | |
# ================== 配置 ================== | |
STEPFUN_ENDPOINT = os.getenv("STEPFUN_ENDPOINT", "https://api.stepfun.com/v1") | |
MODEL_NAME = os.getenv("MODEL_NAME", "step-3") | |
# ========================================= | |
def _get_api_key() -> str | None: | |
""" | |
优先读 OPENAI_API_KEY(兼容 OpenAI SDK 约定),其次读 STEPFUN_KEY | |
""" | |
return os.getenv("OPENAI_API_KEY") or os.getenv("STEPFUN_KEY") | |
def _pil_to_data_url(img: Image.Image, fmt: str = "PNG") -> str: | |
""" | |
PIL.Image -> data:image/...;base64,xxxxx | |
""" | |
buf = io.BytesIO() | |
img.save(buf, format=fmt) | |
b64 = base64.b64encode(buf.getvalue()).decode("utf-8") | |
mime = "image/png" if fmt.upper() == "PNG" else "image/jpeg" | |
return f"data:{mime};base64,{b64}" | |
def _post_chat(messages: List[Dict[str, Any]], temperature: float = 0.7) -> str: | |
""" | |
直连 StepFun 的 OpenAI 兼容接口:/chat/completions | |
""" | |
key = _get_api_key() | |
if not key: | |
return ( | |
"API Key 未设置。\n" | |
"请在 Space 的 Settings → Variables and secrets 中添加:\n" | |
"OPENAI_API_KEY=<你的 StepFun API Key>(或设置 STEPFUN_KEY 也可)" | |
) | |
url = f"{STEPFUN_ENDPOINT}/chat/completions" | |
headers = { | |
"Authorization": f"Bearer {key}", | |
"Content-Type": "application/json", | |
} | |
payload = { | |
"model": MODEL_NAME, | |
"messages": messages, | |
"temperature": temperature, | |
} | |
try: | |
resp = httpx.post(url, headers=headers, json=payload, timeout=60) | |
resp.raise_for_status() | |
data = resp.json() | |
return data["choices"][0]["message"]["content"] | |
except httpx.HTTPError as e: | |
return f"调用失败(HTTP):{e}" | |
except Exception as e: | |
return f"调用失败:{e}" | |
def chat_fn(image: Image.Image, question: str) -> str: | |
""" | |
Gradio 回调:上传图片 + 文本问题 → 模型答案 | |
""" | |
if image is None: | |
return "请先上传图片。" | |
q = question.strip() or "请描述这张图片。" | |
data_url = _pil_to_data_url(image, fmt="PNG") | |
messages = [ | |
{ | |
"role": "user", | |
"content": [ | |
{"type": "image_url", "image_url": {"url": data_url}}, | |
{"type": "text", "text": q}, | |
], | |
} | |
] | |
return _post_chat(messages) | |
with gr.Blocks(title="Step-3 · 图片问答") as demo: | |
gr.Markdown("## Step-3 图片问答\n上传一张图,随便问。") | |
with gr.Row(): | |
img = gr.Image(type="pil", label="上传图片") | |
with gr.Column(): | |
q = gr.Textbox(label="问题", placeholder="比如:这是什么菜?怎么做?") | |
btn = gr.Button("提交") | |
out = gr.Textbox(label="答案", lines=8) | |
btn.click(fn=chat_fn, inputs=[img, q], outputs=[out]) | |
# 也支持回车触发 | |
q.submit(fn=chat_fn, inputs=[img, q], outputs=[out]) | |
# ---- FastAPI + 挂载 Gradio(给 HF Spaces 用)---- | |
_fastapi = FastAPI() | |
app = gr.mount_gradio_app(_fastapi, demo, path="/") | |
# ---- 本地调试:只有在非 HF Spaces 才会启动 ---- | |
if __name__ == "__main__" and os.environ.get("SYSTEM") != "spaces": | |
# 本地跑:python app.py | |
# 不要在 HF Spaces 起 uvicorn,否则会端口冲突 | |
demo.queue(max_size=32).launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860"))) | |