Zenith Wang commited on
Commit
96f986b
·
1 Parent(s): 1003bfe

完全重写应用:使用更稳定的Gradio 3.50.2版本和简化的代码结构

Browse files
Files changed (2) hide show
  1. app.py +219 -157
  2. requirements.txt +3 -3
app.py CHANGED
@@ -1,178 +1,240 @@
1
  import os
2
  import io
3
  import base64
4
- from typing import List, Tuple, Optional
5
-
6
  import gradio as gr
7
  from PIL import Image
8
  from openai import OpenAI
9
 
10
-
11
  BASE_URL = "https://api.stepfun.com/v1"
12
- DEFAULT_MODEL = "step-3" # 可改为 step-r1-v-mini
13
- DEFAULT_DETAIL = "high" # high | low | auto
14
-
15
-
16
- def _get_api_key() -> Optional[str]:
17
- # 优先读环境变量(在 HF Spaces 的 Settings -> Variables and secrets 中配置)
18
- return os.environ.get("STEPFUN_API_KEY")
19
-
20
 
21
- def pil_image_to_data_uri(img: Image.Image) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  buffer = io.BytesIO()
23
- # 统一编码为 JPEG,降低大小并确保浏览器/模型兼容
24
- rgb_img = img.convert("RGB")
25
- rgb_img.save(buffer, format="JPEG", quality=90)
26
- b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
27
- return f"data:image/jpeg;base64,{b64}"
28
-
29
-
30
- def build_messages(
31
- chat_history: List[Tuple[str, str]],
32
- user_text: str,
33
- image: Optional[Image.Image],
34
- system_prompt: Optional[str],
35
- detail: str,
36
- ) -> list:
37
- messages: List[dict] = []
38
-
39
- if system_prompt:
40
- messages.append({"role": "system", "content": system_prompt})
41
-
42
- # 将历史轮次压缩为仅文本内容(简单稳妥)
43
- for user_turn, assistant_turn in chat_history:
44
- if user_turn:
 
 
 
 
 
 
 
 
45
  messages.append({
46
- "role": "user",
47
- "content": [{"type": "text", "text": user_turn}],
48
  })
49
- if assistant_turn:
50
- messages.append({
51
- "role": "assistant",
52
- "content": [{"type": "text", "text": assistant_turn}],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  })
54
-
55
- # 当前用户输入:可包含图片 + 文本
56
- content: List[dict] = []
57
- if image is not None:
58
- data_uri = pil_image_to_data_uri(image)
59
- content.append({
60
- "type": "image_url",
61
- "image_url": {"url": data_uri, "detail": detail},
 
 
 
 
 
 
 
 
62
  })
63
- if user_text:
64
- content.append({"type": "text", "text": user_text})
65
-
66
- if not content:
67
- # 保底,避免空消息
68
- content.append({"type": "text", "text": ""})
69
-
70
- messages.append({"role": "user", "content": content})
71
- return messages
72
-
73
-
74
- def stream_response(
75
- user_text: str,
76
- image: Optional[Image.Image],
77
- model: str,
78
- detail: str,
79
- system_prompt: str,
80
- chat_history: List[Tuple[str, str]],
81
- ):
82
- api_key = _get_api_key()
83
- if not api_key:
84
- error_text = "未检测到 STEPFUN_API_KEY,请在 Space 的 Settings -> Variables and secrets 中配置后重试。"
85
- # 将错误作为助手消息显示
86
- display_user = (user_text or "") + ("\n[已附带图片]" if image is not None else "")
87
- new_history = chat_history + [(display_user, error_text)]
88
- yield new_history, ""
89
- return
90
-
91
- client = OpenAI(api_key=api_key, base_url=BASE_URL)
92
-
93
- # 将用户消息先追加到对话框
94
- display_user = (user_text or "") + ("\n[已附带图片]" if image is not None else "")
95
- chat_history = chat_history + [(display_user, "")] # 预先占位一条助手回复
96
- yield chat_history, ""
97
-
98
- try:
99
- messages = build_messages(chat_history[:-1], user_text=user_text, image=image, system_prompt=system_prompt, detail=detail)
100
- stream = client.chat.completions.create(
101
- model=model or DEFAULT_MODEL,
102
  messages=messages,
103
- stream=True,
104
  )
105
-
106
- assistant_acc = []
107
- for chunk in stream:
108
- delta = None
109
- try:
110
- delta = chunk.choices[0].delta
111
- except Exception:
112
- pass
113
- if delta and getattr(delta, "content", None):
114
- assistant_acc.append(delta.content)
115
- # 实时更新最后一条消息
116
- chat_history[-1] = (display_user, "".join(assistant_acc))
117
- yield chat_history, ""
118
-
119
  except Exception as e:
120
- chat_history[-1] = (display_user, f"[调用失败] {type(e).__name__}: {e}")
121
- yield chat_history, ""
122
-
123
-
124
- with gr.Blocks(title="StepFun - Step3 Multimodal Chat") as demo:
125
- gr.Markdown("""
126
- # StepFun Step-3 多模态对话(Hugging Face Space)
127
- - 支持上传图片 + 文本提问,后端接口兼容 OpenAI Chat Completions。
128
- - 在 Space 中运行时,请到 Settings -> Variables and secrets 配置 `STEPFUN_API_KEY`。
129
- - 可在右上角切换到 **dev mode** 查看构建/运行日志。
130
- """)
131
-
132
- with gr.Row():
133
- model = gr.Dropdown(
134
- label="模型",
135
- choices=["step-3", "step-r1-v-mini"],
136
- value=DEFAULT_MODEL,
137
- interactive=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  )
139
- detail = gr.Dropdown(
140
- label="图像细节",
141
- choices=["high", "low", "auto"],
142
- value=DEFAULT_DETAIL,
143
- interactive=True,
 
 
 
 
144
  )
 
 
 
 
 
 
 
145
 
146
- system_prompt = gr.Textbox(
147
- label="系统提示(可选)",
148
- placeholder="例如:你是一个美食专家,回答要简洁。",
149
- lines=2,
150
- )
151
-
152
- chatbot = gr.Chatbot(height=420, show_label=False, type="tuples")
153
-
154
- with gr.Row():
155
- image = gr.Image(label="上传图片(可选)", type="pil")
156
- user_text = gr.Textbox(label="你的问题", placeholder="描述你的问题……", lines=4)
157
-
158
- with gr.Row():
159
- submit = gr.Button("发送", variant="primary")
160
- clear = gr.Button("清空对话")
161
-
162
- # 清空
163
- def _clear_chat():
164
- return [], None, ""
165
-
166
- clear.click(_clear_chat, outputs=[chatbot, image, user_text])
167
-
168
- # 发送并流式生成
169
- submit.click(
170
- fn=stream_response,
171
- inputs=[user_text, image, model, detail, system_prompt, chatbot],
172
- outputs=[chatbot, user_text],
173
- )
174
-
175
-
176
  if __name__ == "__main__":
177
- # 本地调试:python app.py
178
- demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)), share=False)
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import io
3
  import base64
 
 
4
  import gradio as gr
5
  from PIL import Image
6
  from openai import OpenAI
7
 
8
+ # 配置
9
  BASE_URL = "https://api.stepfun.com/v1"
10
+ DEFAULT_MODEL = "step-3" # 可选: step-3, step-r1-v-mini
 
 
 
 
 
 
 
11
 
12
+ def get_api_key():
13
+ """获取API密钥"""
14
+ api_key = os.environ.get("STEPFUN_API_KEY")
15
+ if not api_key:
16
+ raise ValueError("请设置环境变量 STEPFUN_API_KEY")
17
+ return api_key
18
+
19
+ def image_to_base64(image):
20
+ """将PIL图像转换为base64编码"""
21
+ if image is None:
22
+ return None
23
+
24
+ # 转换为RGB格式
25
+ if image.mode != 'RGB':
26
+ image = image.convert('RGB')
27
+
28
+ # 保存到字节流
29
  buffer = io.BytesIO()
30
+ image.save(buffer, format='JPEG', quality=85)
31
+
32
+ # 编码为base64
33
+ img_str = base64.b64encode(buffer.getvalue()).decode('utf-8')
34
+ return f"data:image/jpeg;base64,{img_str}"
35
+
36
+ def chat_with_stepfun(message, image, history, model, system_prompt):
37
+ """
38
+ 处理聊天请求
39
+
40
+ Args:
41
+ message: 用户输入的文本
42
+ image: 用户上传的图片 (PIL Image)
43
+ history: 聊天历史
44
+ model: 选择的模型
45
+ system_prompt: 系统提示词
46
+
47
+ Returns:
48
+ 更新后的聊天历史
49
+ """
50
+ try:
51
+ # 获取API密钥
52
+ api_key = get_api_key()
53
+ client = OpenAI(api_key=api_key, base_url=BASE_URL)
54
+
55
+ # 构建消息列表
56
+ messages = []
57
+
58
+ # 添加系统提示
59
+ if system_prompt and system_prompt.strip():
60
  messages.append({
61
+ "role": "system",
62
+ "content": system_prompt
63
  })
64
+
65
+ # 添加历史对话
66
+ for user_msg, assistant_msg in history:
67
+ if user_msg:
68
+ messages.append({
69
+ "role": "user",
70
+ "content": user_msg
71
+ })
72
+ if assistant_msg:
73
+ messages.append({
74
+ "role": "assistant",
75
+ "content": assistant_msg
76
+ })
77
+
78
+ # 构建当前用户消息
79
+ current_content = []
80
+
81
+ # 添加图片
82
+ if image is not None:
83
+ img_base64 = image_to_base64(image)
84
+ current_content.append({
85
+ "type": "image_url",
86
+ "image_url": {
87
+ "url": img_base64,
88
+ "detail": "high"
89
+ }
90
  })
91
+
92
+ # 添加文本
93
+ if message and message.strip():
94
+ current_content.append({
95
+ "type": "text",
96
+ "text": message
97
+ })
98
+
99
+ # 如果没有任何内容,返回
100
+ if not current_content:
101
+ return history
102
+
103
+ # 添加当前消息
104
+ messages.append({
105
+ "role": "user",
106
+ "content": current_content
107
  })
108
+
109
+ # 调用API
110
+ response = client.chat.completions.create(
111
+ model=model,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  messages=messages,
113
+ stream=True
114
  )
115
+
116
+ # 处理流式响应
117
+ full_response = ""
118
+ for chunk in response:
119
+ if chunk.choices[0].delta.content:
120
+ full_response += chunk.choices[0].delta.content
121
+ # 实时更新界面
122
+ yield history + [(message, full_response)]
123
+
124
+ # 返回最终结果
125
+ yield history + [(message, full_response)]
126
+
 
 
127
  except Exception as e:
128
+ error_msg = f"错误: {str(e)}"
129
+ yield history + [(message, error_msg)]
130
+
131
+ def clear_chat():
132
+ """清空聊天记录"""
133
+ return None, None, []
134
+
135
+ # 创建Gradio界面
136
+ def create_interface():
137
+ with gr.Blocks(title="StepFun 多模态对话") as demo:
138
+ gr.Markdown("""
139
+ # 🚀 StepFun Step-3 多模态对话
140
+
141
+ 支持图片理解和文本对话,使用StepFun API。
142
+
143
+ **使用说明:**
144
+ 1. 在环境变量中设置 `STEPFUN_API_KEY`
145
+ 2. 可选择上传图片进行视觉理解
146
+ 3. 输入文本进行对话
147
+ """)
148
+
149
+ with gr.Row():
150
+ with gr.Column(scale=3):
151
+ # 聊天界面
152
+ chatbot = gr.Chatbot(
153
+ height=500,
154
+ bubble_full_width=False,
155
+ avatar_images=(None, None)
156
+ )
157
+
158
+ with gr.Row():
159
+ with gr.Column(scale=3):
160
+ msg = gr.Textbox(
161
+ label="输入消息",
162
+ placeholder="输入你的问题...",
163
+ lines=2
164
+ )
165
+ with gr.Column(scale=1):
166
+ img = gr.Image(
167
+ label="上传图片(可选)",
168
+ type="pil"
169
+ )
170
+
171
+ with gr.Row():
172
+ submit = gr.Button("发送", variant="primary")
173
+ clear = gr.Button("清空对话")
174
+
175
+ with gr.Column(scale=1):
176
+ # 设置面板
177
+ model = gr.Dropdown(
178
+ label="选择模型",
179
+ choices=["step-3", "step-r1-v-mini"],
180
+ value="step-3"
181
+ )
182
+
183
+ system_prompt = gr.Textbox(
184
+ label="系统提示(可选)",
185
+ placeholder="设置AI的角色或行为...",
186
+ lines=3
187
+ )
188
+
189
+ gr.Markdown("""
190
+ ### 说明
191
+ - **step-3**: 标准多模态模型
192
+ - **step-r1-v-mini**: 轻量级版本
193
+
194
+ ### 提示
195
+ - 支持中英文对话
196
+ - 图片支持JPG/PNG格式
197
+ - 可以询问图片内容
198
+ """)
199
+
200
+ # 事件绑定
201
+ submit.click(
202
+ fn=chat_with_stepfun,
203
+ inputs=[msg, img, chatbot, model, system_prompt],
204
+ outputs=[chatbot],
205
+ queue=True
206
+ ).then(
207
+ lambda: (None, None),
208
+ outputs=[msg, img]
209
  )
210
+
211
+ msg.submit(
212
+ fn=chat_with_stepfun,
213
+ inputs=[msg, img, chatbot, model, system_prompt],
214
+ outputs=[chatbot],
215
+ queue=True
216
+ ).then(
217
+ lambda: (None, None),
218
+ outputs=[msg, img]
219
  )
220
+
221
+ clear.click(
222
+ fn=clear_chat,
223
+ outputs=[msg, img, chatbot]
224
+ )
225
+
226
+ return demo
227
 
228
+ # 主程序
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  if __name__ == "__main__":
230
+ demo = create_interface()
231
+
232
+ # 获取端口
233
+ port = int(os.environ.get("PORT", 7860))
234
+
235
+ # 启动应用
236
+ demo.launch(
237
+ server_name="0.0.0.0",
238
+ server_port=port,
239
+ share=False
240
+ )
requirements.txt CHANGED
@@ -1,3 +1,3 @@
1
- openai==1.99.6
2
- gradio==4.36.1
3
- pillow==10.4.0
 
1
+ gradio==3.50.2
2
+ openai>=1.0.0
3
+ Pillow>=9.0.0