Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
@@ -1,31 +1,31 @@
|
|
1 |
-
|
2 |
import os
|
|
|
3 |
import base64
|
|
|
|
|
|
|
4 |
import gradio as gr
|
5 |
from PIL import Image
|
6 |
-
import httpx # Use httpx for direct API calls instead of openai SDK
|
7 |
|
8 |
-
#
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
MODEL_NAME = "step-3"
|
14 |
-
# --------------------
|
15 |
|
16 |
-
|
|
|
17 |
"""
|
18 |
-
|
19 |
-
|
20 |
-
如果不存在再尝试读取 STEPFUN_KEY。
|
21 |
"""
|
22 |
return os.getenv("OPENAI_API_KEY") or os.getenv("STEPFUN_KEY")
|
23 |
|
|
|
24 |
def _pil_to_data_url(img: Image.Image, fmt: str = "PNG") -> str:
|
25 |
"""
|
26 |
-
|
27 |
-
接收一个 PIL.Image 对象和输出格式(默认为 PNG),
|
28 |
-
返回可用于 StepFun OpenAI 兼容接口的 data:image/...;base64,... 字符串。
|
29 |
"""
|
30 |
buf = io.BytesIO()
|
31 |
img.save(buf, format=fmt)
|
@@ -33,72 +33,107 @@ def _pil_to_data_url(img: Image.Image, fmt: str = "PNG") -> str:
|
|
33 |
mime = "image/png" if fmt.upper() == "PNG" else "image/jpeg"
|
34 |
return f"data:{mime};base64,{b64}"
|
35 |
|
36 |
-
|
|
|
37 |
"""
|
38 |
-
|
39 |
-
|
40 |
-
避免使用 openai SDK 导致的 "No API found" 错误。
|
41 |
-
messages 参数应符合 OpenAI 接口规范。
|
42 |
"""
|
43 |
-
|
44 |
-
if not
|
45 |
raise RuntimeError(
|
46 |
-
"API Key
|
47 |
-
"
|
48 |
)
|
49 |
-
|
|
|
50 |
headers = {
|
51 |
-
"Authorization": f"Bearer {
|
52 |
"Content-Type": "application/json",
|
53 |
}
|
54 |
-
payload = {
|
55 |
"model": MODEL_NAME,
|
56 |
"messages": messages,
|
57 |
"temperature": temperature,
|
|
|
58 |
}
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
|
64 |
-
|
|
|
65 |
"""
|
66 |
-
|
67 |
-
首先检查上传的图像和问题文本是否有效,将图像编码为 data URL,
|
68 |
-
构造符合 OpenAI 接口规范的 messages 数组,然后通过 `_post_chat` 发送请求。
|
69 |
-
如遇异常则返回错误信息。
|
70 |
"""
|
71 |
-
if image is None:
|
72 |
-
return "
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
}
|
84 |
-
|
|
|
|
|
85 |
try:
|
86 |
-
return _post_chat(messages)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
except Exception as e:
|
88 |
-
return f"
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
if __name__ == "__main__":
|
103 |
-
#
|
104 |
-
|
|
|
1 |
+
# app.py
|
2 |
import os
|
3 |
+
import io
|
4 |
import base64
|
5 |
+
from typing import List, Dict, Any, Optional
|
6 |
+
|
7 |
+
import httpx
|
8 |
import gradio as gr
|
9 |
from PIL import Image
|
|
|
10 |
|
11 |
+
# ====== 配置(可用环境变量覆写)======
|
12 |
+
STEPFUN_ENDPOINT = os.getenv("STEPFUN_ENDPOINT", "https://api.stepfun.com/v1")
|
13 |
+
MODEL_NAME = os.getenv("STEPFUN_MODEL", "step-3") # 也可填 step-r1-v-mini
|
14 |
+
REQUEST_TIMEOUT = float(os.getenv("REQUEST_TIMEOUT", "60"))
|
15 |
+
# ===================================
|
|
|
|
|
16 |
|
17 |
+
|
18 |
+
def _get_api_key() -> Optional[str]:
|
19 |
"""
|
20 |
+
优先读 OPENAI_API_KEY(与 OpenAI 兼容),否则读 STEPFUN_KEY。
|
21 |
+
在 HF Space: Settings → Variables and secrets 添加其中一个即可。
|
|
|
22 |
"""
|
23 |
return os.getenv("OPENAI_API_KEY") or os.getenv("STEPFUN_KEY")
|
24 |
|
25 |
+
|
26 |
def _pil_to_data_url(img: Image.Image, fmt: str = "PNG") -> str:
|
27 |
"""
|
28 |
+
PIL -> data:image/...;base64,... 字符串(适配 OpenAI 兼容的 image_url 输入)
|
|
|
|
|
29 |
"""
|
30 |
buf = io.BytesIO()
|
31 |
img.save(buf, format=fmt)
|
|
|
33 |
mime = "image/png" if fmt.upper() == "PNG" else "image/jpeg"
|
34 |
return f"data:{mime};base64,{b64}"
|
35 |
|
36 |
+
|
37 |
+
def _post_chat(messages: List[Dict[str, Any]], temperature: float = 0.7, max_tokens: Optional[int] = None) -> str:
|
38 |
"""
|
39 |
+
直接请求 StepFun 的 /v1/chat/completions(OpenAI 兼容)。
|
40 |
+
返回纯字符串,避免 Gradio schema 问题。
|
|
|
|
|
41 |
"""
|
42 |
+
api_key = _get_api_key()
|
43 |
+
if not api_key:
|
44 |
raise RuntimeError(
|
45 |
+
"未检测到 API Key。请到 Space 的 Settings → Variables and secrets 添加:\n"
|
46 |
+
" OPENAI_API_KEY=你的 StepFun API Key (或使用 STEPFUN_KEY)"
|
47 |
)
|
48 |
+
|
49 |
+
url = f"{STEPFUN_ENDPOINT.rstrip('/')}/chat/completions"
|
50 |
headers = {
|
51 |
+
"Authorization": f"Bearer {api_key}",
|
52 |
"Content-Type": "application/json",
|
53 |
}
|
54 |
+
payload: Dict[str, Any] = {
|
55 |
"model": MODEL_NAME,
|
56 |
"messages": messages,
|
57 |
"temperature": temperature,
|
58 |
+
# StepFun 多数情况下无需强制 max_tokens;需要时再放开
|
59 |
}
|
60 |
+
if max_tokens is not None:
|
61 |
+
payload["max_tokens"] = max_tokens
|
62 |
+
|
63 |
+
with httpx.Client(timeout=REQUEST_TIMEOUT) as client:
|
64 |
+
resp = client.post(url, headers=headers, json=payload)
|
65 |
+
# 让 httpx 抛出更清晰的错误
|
66 |
+
resp.raise_for_status()
|
67 |
+
data = resp.json()
|
68 |
+
|
69 |
+
# 标准 OpenAI 兼容返回
|
70 |
+
try:
|
71 |
+
return str(data["choices"][0]["message"]["content"])
|
72 |
+
except Exception:
|
73 |
+
# 返回原始数据便于诊断
|
74 |
+
return f"[WARN] 无法解析返回格式:{data}"
|
75 |
|
76 |
+
|
77 |
+
def chat_with_step3(image: Optional[Image.Image], question: str, temperature: float) -> str:
|
78 |
"""
|
79 |
+
Gradio 的回调函数:接收 PIL 图片与文本,返回字符串。
|
|
|
|
|
|
|
80 |
"""
|
81 |
+
if image is None and not question.strip():
|
82 |
+
return "请上传一张图片,或至少输入一个问题。"
|
83 |
+
|
84 |
+
# 构造 messages(支持纯文本、纯图像,或图文混合)
|
85 |
+
content: List[Dict[str, Any]] = []
|
86 |
+
if image is not None:
|
87 |
+
data_url = _pil_to_data_url(image, fmt="PNG")
|
88 |
+
content.append({"type": "image_url", "image_url": {"url": data_url}})
|
89 |
+
|
90 |
+
if question.strip():
|
91 |
+
content.append({"type": "text", "text": question.strip()})
|
92 |
+
else:
|
93 |
+
content.append({"type": "text", "text": "请描述这张图片。"}) # 默认问题
|
94 |
+
|
95 |
+
messages = [{"role": "user", "content": content}]
|
96 |
+
|
97 |
try:
|
98 |
+
return _post_chat(messages, temperature=temperature)
|
99 |
+
except httpx.HTTPStatusError as e:
|
100 |
+
# 返回服务端 HTTP 错误 + 文本体,便于排查
|
101 |
+
try:
|
102 |
+
detail = e.response.text
|
103 |
+
except Exception:
|
104 |
+
detail = repr(e)
|
105 |
+
return f"[HTTP {e.response.status_code}] 接口错误:{detail}"
|
106 |
except Exception as e:
|
107 |
+
return f"调用失败:{e!r}"
|
108 |
+
|
109 |
+
|
110 |
+
# ================ Gradio UI ================
|
111 |
+
with gr.Blocks(title="Step3 (StepFun API Demo)") as demo:
|
112 |
+
gr.Markdown(
|
113 |
+
"""
|
114 |
+
# Step3 · 图文对话演示(StepFun OpenAI 兼容接口)
|
115 |
+
- 在 **Settings → Variables and secrets** 添加 `OPENAI_API_KEY`(或 `STEPFUN_KEY`)后即可使用
|
116 |
+
- 后端直连 `https://api.stepfun.com/v1/chat/completions`,不依赖 `openai` SDK
|
117 |
+
"""
|
118 |
+
)
|
119 |
+
|
120 |
+
with gr.Row():
|
121 |
+
image = gr.Image(type="pil", label="上传图片(可选)")
|
122 |
+
question = gr.Textbox(label="问题", placeholder="例如:帮我看看这是什么菜,怎么做?")
|
123 |
+
temperature = gr.Slider(0.0, 1.5, value=0.7, step=0.1, label="Temperature")
|
124 |
+
submit = gr.Button("提交", variant="primary")
|
125 |
+
output = gr.Textbox(label="模型回答", lines=8)
|
126 |
+
|
127 |
+
submit.click(fn=chat_with_step3, inputs=[image, question, temperature], outputs=[output])
|
128 |
+
|
129 |
+
gr.Markdown(
|
130 |
+
"""
|
131 |
+
**提示:**
|
132 |
+
- 如果看到 `调用失败:RuntimeError('未检测到 API Key')`,请检查 Space 的 Secrets
|
133 |
+
- 如需改模型:设置环境变量 `STEPFUN_MODEL`,或在代码顶部修改默认值
|
134 |
+
"""
|
135 |
+
)
|
136 |
|
137 |
if __name__ == "__main__":
|
138 |
+
# HF Space 环境会自动执行;本地运行也 OK
|
139 |
+
demo.launch()
|