Spaces:
Runtime error
Runtime error
File size: 7,220 Bytes
40e5de3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | import streamlit as st
import requests
import json
import time
import os
from openai import OpenAI
import psutil
import platform
# 页面配置
st.set_page_config(
page_title="Qwen3.5 27B Chat",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded"
)
# 自定义 CSS
st.markdown("""
<style>
.stApp {
background-color: #f5f5f5;
}
.main-header {
color: #1f77b4;
font-size: 2.5rem;
font-weight: 600;
margin-bottom: 1rem;
}
.info-box {
background-color: #e8f4f8;
padding: 1rem;
border-radius: 0.5rem;
margin-bottom: 1rem;
}
</style>
""", unsafe_allow_html=True)
# 初始化 OpenAI 客户端
@st.cache_resource
def init_client():
"""初始化 vLLM 客户端"""
# 等待 vLLM 服务就绪
max_retries = 30
for i in range(max_retries):
try:
response = requests.get("http://localhost:8000/health")
if response.status_code == 200:
st.success("✅ 已连接到推理服务器")
break
except:
if i % 5 == 0:
st.info(f"⏳ 正在连接推理服务器... ({i+1}/{max_retries})")
time.sleep(2)
return OpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY",
)
# 侧边栏
with st.sidebar:
st.image("https://qianwen-res.oss-cn-beijing.aliyuncs.com/assets/blog/2025/qwen3.5-logo.png",
use_column_width=True)
st.markdown("## ⚙️ 配置参数")
# 模型状态
with st.expander("🖥️ 系统状态", expanded=False):
col1, col2 = st.columns(2)
with col1:
st.metric("CPU 使用率", f"{psutil.cpu_percent()}%")
with col2:
mem = psutil.virtual_memory()
st.metric("内存使用", f"{mem.percent}%")
# GPU 信息(如果可用)
try:
import torch
if torch.cuda.is_available():
gpu_memory = torch.cuda.memory_allocated() / 1024**3
gpu_total = torch.cuda.get_device_properties(0).total_memory / 1024**3
st.metric("GPU 显存", f"{gpu_memory:.1f}GB / {gpu_total:.1f}GB")
except:
pass
# 采样参数
st.markdown("### 🎛️ 采样参数")
temperature = st.slider("Temperature", 0.0, 2.0, 0.7, 0.1,
help="控制输出的随机性,越高越随机")
top_p = st.slider("Top P", 0.0, 1.0, 0.8, 0.05,
help="核采样,控制词汇选择的累积概率")
max_tokens = st.number_input("Max Tokens", 512, 32768, 4096, 512,
help="最大生成token数")
# 高级选项
with st.expander("🔧 高级选项"):
presence_penalty = st.slider("Presence Penalty", -2.0, 2.0, 0.0, 0.1,
help="话题重复惩罚")
frequency_penalty = st.slider("Frequency Penalty", -2.0, 2.0, 0.0, 0.1,
help="词频惩罚")
enable_thinking = st.checkbox("🧠 启用思考模式", True,
help="启用模型的内部思考过程")
st.divider()
st.markdown("### 📝 预设配置")
preset = st.selectbox("快速选择", ["通用对话", "创意写作", "代码生成", "精确回答"])
if preset == "通用对话":
temperature, top_p = 0.7, 0.8
elif preset == "创意写作":
temperature, top_p = 1.2, 0.95
elif preset == "代码生成":
temperature, top_p = 0.2, 0.1
elif preset == "精确回答":
temperature, top_p = 0.3, 0.5
# 主界面
st.markdown('<p class="main-header">🤖 Qwen3.5 27B Chat</p>',
unsafe_allow_html=True)
# 信息框
with st.container():
st.markdown("""
<div class="info-box">
📌 基于 Qwen3.5-27B-GPTQ-Int4 模型,4-bit 量化,可在 T4 16GB 显存高效运行。
支持多轮对话、代码生成、文档理解等功能。
</div>
""", unsafe_allow_html=True)
# 初始化客户端
client = init_client()
# 初始化聊天历史
if "messages" not in st.session_state:
st.session_state.messages = [
{"role": "system", "content": "You are Qwen3.5, a helpful assistant."}
]
# 显示聊天历史
for message in st.session_state.messages[1:]: # 跳过系统消息
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 聊天输入
if prompt := st.chat_input("输入你的问题..."):
# 添加用户消息
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 生成回复
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
try:
# 准备消息
messages_for_api = st.session_state.messages.copy()
# 调用 API
stream = client.chat.completions.create(
model="Qwen/Qwen3.5-27B-GPTQ-Int4",
messages=messages_for_api,
temperature=temperature,
top_p=top_p,
max_tokens=max_tokens,
presence_penalty=presence_penalty,
frequency_penalty=frequency_penalty,
stream=True,
extra_body={
"chat_template_kwargs": {
"enable_thinking": enable_thinking
}
} if not enable_thinking else None
)
# 处理流式响应
for chunk in stream:
if chunk.choices[0].delta.content:
full_response += chunk.choices[0].delta.content
message_placeholder.markdown(full_response + "▌")
message_placeholder.markdown(full_response)
# 添加到历史
st.session_state.messages.append({
"role": "assistant",
"content": full_response
})
except Exception as e:
st.error(f"生成失败: {str(e)}")
if "connection" in str(e).lower():
st.info("正在重新连接推理服务器,请稍候...")
st.cache_resource.clear()
time.sleep(2)
st.rerun()
# 底部工具栏
col1, col2, col3 = st.columns([1, 1, 4])
with col1:
if st.button("🗑️ 清空对话"):
st.session_state.messages = [st.session_state.messages[0]]
st.rerun()
with col2:
if st.button("📋 复制对话"):
import pyperclip
texts = [f"{m['role']}: {m['content']}"
for m in st.session_state.messages[1:]]
pyperclip.copy("\n\n".join(texts))
st.toast("已复制到剪贴板")
# 显示 token 使用统计(如果有)
if "last_response" in st.session_state:
st.caption(f"📊 最后响应: ~{len(st.session_state.last_response)} tokens")
# 心跳机制(防止休眠)
st.markdown("---")
st.caption("💡 提示:此页面保持打开可防止Space自动休眠") |