import os import base64 import gradio as gr from openai import OpenAI def encode_image(image_path): """将本地图片文件转换为 Base64 编码""" with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode('utf-8') def detect_language(text, history): """ 智能检测用户语言: 1. 优先从当前提问检测 2. 若当前无文字,从最近一轮用户历史中推断 3. 默认返回中文 """ # 1. 检查当前提问 if text: clean_text = str(text).strip() if clean_text: # 计算中文字符和英文字母比例 chinese_chars = sum(1 for char in clean_text if '\u4e00' <= char <= '\u9fff') alpha_chars = sum(1 for char in clean_text if char.isalpha()) if alpha_chars > 0: # 中文字符占比 > 30% 判定为中文 if chinese_chars / alpha_chars > 0.3: return "zh" else: return "en" else: # 没有字母(纯数字/符号),默认中文 return "zh" # 2. 当前无文字,从历史中推断 for msg in reversed(history[-6:]): if msg.get("role") == "user": content = msg.get("content", "") if isinstance(content, dict): content = content.get("text", "") if content: clean_content = str(content).strip() chinese_chars = sum(1 for char in clean_content if '\u4e00' <= char <= '\u9fff') alpha_chars = sum(1 for char in clean_content if char.isalpha()) if alpha_chars > 0 and chinese_chars / alpha_chars > 0.3: return "zh" elif alpha_chars > 0: return "en" # 3. 默认中文 return "zh" def predict(message, history): api_key = os.getenv("SILICONFLOW_API_KEY", "") if not api_key: yield "❌ 未检测到 API Key!请先在 Space Settings -> Secrets 中添加 SILICONFLOW_API_KEY!" return api_key = api_key.strip() try: client = OpenAI( api_key=api_key, base_url="https://api.siliconflow.cn/v1" ) text_content = "" files = [] # Gradio 多模态输入解析 if isinstance(message, dict): text_content = message.get("text", "") files = message.get("files", []) else: text_content = str(message) # 智能检测语言(含历史记忆) user_lang = detect_language(text_content, history) # 🔥 调试输出(可删除) print(f"检测到语言: {user_lang}, 用户输入: {text_content[:50]}") # 构建当前请求的 Message Payload current_content = [] # 1. 解析当前上传的图片 for f in files: file_path = f.get("path") if isinstance(f, dict) else f if file_path: try: base64_img = encode_image(file_path) current_content.append({ "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_img}"} }) except Exception as img_err: print(f"图片读取失败: {img_err}") # 2. 解析文本说明 if not text_content or not str(text_content).strip(): if user_lang == "en": prompt_text = "Please observe this image carefully and tell me what plant this is and its key features." else: prompt_text = "请仔细观察这张图片,告诉我这是什么植物?有哪些特征?" else: prompt_text = str(text_content).strip() current_content.append({"type": "text", "text": prompt_text}) # 3. 根据用户语言设置 System Prompt if user_lang == "en": system_prompt = ( "You are a professional botanist and nature expert. " "Identify the plant in the image and provide its scientific name, " "family, genus, and key features in **English**. " "Be concise and accurate." ) else: system_prompt = ( "你是一位专业的植物学家和自然科普专家。" "请精准识别图片中的植物,并用简洁明了的中文回答其名称、科属及特点。" ) messages = [ {"role": "system", "content": system_prompt} ] # 仅保留最近 2 轮纯文本历史 for msg in history[-4:]: role = "user" if msg.get("role") == "user" else "assistant" content_val = msg.get("content", "") if isinstance(content_val, dict): content_val = content_val.get("text", "") if content_val and not str(content_val).startswith("data:image"): messages.append({"role": role, "content": str(content_val)}) # 放入最新一轮包含图片的请求 messages.append({"role": "user", "content": current_content}) # 4. 调用视觉模型 response = client.chat.completions.create( model="Qwen/Qwen3-VL-32B-Instruct", messages=messages, stream=True, temperature=0.2 ) # 5. 流式输出 partial_text = "" for chunk in response: if chunk.choices and chunk.choices[0].delta.content: partial_text += chunk.choices[0].delta.content yield partial_text except Exception as e: err_msg = str(e) if "30001" in err_msg or "insufficient" in err_msg.lower(): yield "⚠️ **请求失败:硅基流动账号余额不足**" elif "30003" in err_msg or "Model disabled" in err_msg: yield "⚠️ **模型不可用,请检查模型名称是否正确**" else: yield f"❌ 请求失败: {err_msg}" # 创建 Gradio 界面 demo = gr.ChatInterface( fn=predict, title="🌱 AI 植物识别专家 (智能双语)", description="🌍 智能识别你的提问语言:中文问→中文答,英文问→英文答。连续对话自动记忆语言偏好!", multimodal=True ) if __name__ == "__main__": demo.launch(ssr_mode=False)