Zenithwang commited on
Commit
8987e34
·
verified ·
1 Parent(s): 338c686

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -67
app.py CHANGED
@@ -1,31 +1,31 @@
1
- import io
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
- # 推荐在 HF Space 的 Settings - Variables and secrets 里设置:
10
- # Name: OPENAI_API_KEY Value: 你的 StepFun API Key
11
- # 如果前台定义变量 (比如 STEPFUN_KEY),下面会依然被读取。
12
- STEPFUN_ENDPOINT = "https://api.stepfun.com/v1"
13
- MODEL_NAME = "step-3"
14
- # --------------------
15
 
16
- def _get_api_key() -> str:
 
17
  """
18
- 获取 API KEY,如果没有设置则返回 None
19
- 首先尝试读取环境变量 OPENAI_API_KEY(OpenAI SDK 的默认约定),
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
- PIL 图片转换成 base64 Data URL。
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
- def _post_chat(messages: list, temperature: float = 0.7) -> str:
 
37
  """
38
- 调用 StepFun 的 chat/completions 接口并返回模型回复。
39
- 使用 httpx 库向 StepFun 的 OpenAI 兼容接口发送请求,
40
- 避免使用 openai SDK 导致的 "No API found" 错误。
41
- messages 参数应符合 OpenAI 接口规范。
42
  """
43
- key = _get_api_key()
44
- if not key:
45
  raise RuntimeError(
46
- "API Key 未设置\n请到 Space 的 Settings - Variables and secrets 添加:\n"
47
- "Name=OPENAI_API_KEY, Value=你的 StepFun API Key(或用 STEPFUN_KEY 也可)。"
48
  )
49
- url = f"{STEPFUN_ENDPOINT}/chat/completions"
 
50
  headers = {
51
- "Authorization": f"Bearer {key}",
52
  "Content-Type": "application/json",
53
  }
54
- payload = {
55
  "model": MODEL_NAME,
56
  "messages": messages,
57
  "temperature": temperature,
 
58
  }
59
- resp = httpx.post(url, headers=headers, json=payload, timeout=60)
60
- resp.raise_for_status()
61
- data = resp.json()
62
- return data["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- def chat_with_step3(image: Image.Image, question: str) -> str:
 
65
  """
66
- 调用 StepFun step-3 模型进行推理。
67
- 首先检查上传的图像和问题文本是否有效,将图像编码为 data URL,
68
- 构造符合 OpenAI 接口规范的 messages 数组,然后通过 `_post_chat` 发送请求。
69
- 如遇异常则返回错误信息。
70
  """
71
- if image is None:
72
- return "请先上传图片。"
73
- if not question:
74
- question = "请描述这张图片。"
75
- data_url = _pil_to_data_url(image, fmt="PNG")
76
- messages = [
77
- {
78
- "role": "user",
79
- "content": [
80
- {"type": "image_url", "image_url": {"url": data_url}},
81
- {"type": "text", "text": question},
82
- ],
83
- },
84
- ]
 
 
85
  try:
86
- return _post_chat(messages)
 
 
 
 
 
 
 
87
  except Exception as e:
88
- return f"调用失败: {e!r}"
89
-
90
- # 构建 Gradio 界面
91
- iface = gr.Interface(
92
- fn=chat_with_step3,
93
- inputs=[
94
- gr.Image(type="pil", label="Upload image"),
95
- gr.Textbox(label="Question", placeholder="问点什么..."),
96
- ],
97
- outputs=gr.Textbox(label="Answer"),
98
- title="Step3",
99
- description="step-3",
100
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  if __name__ == "__main__":
103
- # HF Spaces 里不需要 share=True
104
- iface.launch()
 
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()