返回 VideoClaw
vlm_gemini.py
根目录 / video-claw / video-claw / backend / models / vlm_gemini.py
1 # -*- coding: utf-8 -*-
2 """
3 Google Gemini 多模态大模型 API 客户端 (OpenAI 兼容格式)
4 支持 gemini-2.5-flash-image, gemini-2.5-pro 等视觉模型
5
6 可用模型:
7 - gemini-2.5-flash-image (性价比最高)
8 - gemini-2.5-pro (效果最好)
9 - gemini-3-pro-preview
10 - gemini-3-pro-image-preview
11 """
12
13 import os
14 import sys
15
16 models_dir = os.path.dirname(os.path.abspath(__file__))
17 backend_dir = os.path.dirname(models_dir)
18 if backend_dir not in sys.path:
19 sys.path.insert(0, backend_dir)
20
21 import time
22 import base64
23 import logging
24 import httpx
25 from openai import OpenAI
26 from typing import Dict, List, Optional
27 from config import Config
28
29 logger = logging.getLogger(__name__)
30
31
32 class GeminiVLClient:
33 """
34 Gemini VLM 客户端,使用 OpenAI 兼容格式调用
35 """
36 def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
37 """
38 Gemini 多模态客户端
39 :param api_key: Gemini API Key
40 :param base_url: 自定义 Base URL(可选,用于代理)
41 """
42 self.api_key = api_key or Config.GEMINI_API_KEY
43 default_url = "https://generativelanguage.googleapis.com/v1beta"
44 self.base_url = base_url or Config.GOOGLE_GEMINI_BASE_URL or default_url
45 if self.base_url and not self.base_url.endswith("/v1"):
46 self.base_url = self.base_url.rstrip("/") + "/v1"
47 kwargs = {"api_key": self.api_key, "base_url": self.base_url}
48 proxy = Config.provider_proxy("gemini")
49 if proxy:
50 kwargs["http_client"] = httpx.Client(proxy=proxy)
51 self.client = OpenAI(**kwargs)
52 self.max_attempts = 10
53
54 def _encode_image(self, image_path: str) -> str:
55 """将本地图片编码为 base64"""
56 abs_path = os.path.abspath(image_path)
57 with open(abs_path, "rb") as f:
58 return base64.b64encode(f.read()).decode("utf-8")
59
60 def _get_mime_type(self, image_path: str) -> str:
61 """根据文件扩展名获取 MIME 类型"""
62 ext = os.path.splitext(image_path)[1].lower()
63 mime_types = {
64 ".jpg": "image/jpeg",
65 ".jpeg": "image/jpeg",
66 ".png": "image/png",
67 ".webp": "image/webp",
68 ".gif": "image/gif"
69 }
70 return mime_types.get(ext, "image/jpeg")
71
72 def chat(self, text: str, images: List[str], model: str = "gemini-2.5-flash-image",
73 parameters: Optional[Dict] = None) -> str:
74 """
75 使用 Gemini 进行多模态对话(文本+图片)
76 :param text: 文本内容
77 :param images: 图片路径列表(支持本地路径或URL)
78 :param model: 模型名(如 gemini-2.5-flash-image, gemini-2.5-pro)
79 :param parameters: 其他API参数
80 :return: API响应内容
81 """
82 # 构建消息格式
83 content: list = [{"type": "text", "text": text}]
84
85 # 处理图片
86 if images:
87 for img_path in images:
88 if img_path.startswith("data:"):
89 # Base64 数据 URL,直接使用
90 content.append({
91 "type": "image_url",
92 "image_url": {"url": img_path}
93 })
94 elif img_path.startswith("http"):
95 # URL 图片
96 content.append({
97 "type": "image_url",
98 "image_url": {"url": img_path}
99 })
100 else:
101 # 本地图片 - 转为 base64
102 mime_type = self._get_mime_type(img_path)
103 base64_data = self._encode_image(img_path)
104 data_url = f"data:{mime_type};base64,{base64_data}"
105 content.append({
106 "type": "image_url",
107 "image_url": {"url": data_url}
108 })
109
110 messages = [{"role": "user", "content": content}]
111
112 attempts = 0
113 while attempts < self.max_attempts:
114 try:
115 # 直接使用模型名
116 response = self.client.chat.completions.create(
117 model=model,
118 messages=messages,
119 temperature=parameters.get("temperature", 0.7) if parameters else 0.7
120 )
121
122 if response.choices and len(response.choices) > 0:
123 return response.choices[0].message.content
124
125 except Exception as e:
126 logger.warning("Gemini VLM request failed: %s", e)
127 attempts += 1
128 if attempts < self.max_attempts:
129 time.sleep(10)
130
131 raise Exception("GeminiVL: 达到最大重试次数,仍未获得有效响应。")
132
133
134 if __name__ == "__main__":
135 import sys
136 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
137 from config import Config
138
139 # 支持的 VLM 模型列表
140 MODELS = ["gemini-2.5-flash-image", "gemini-2.0-flash"]
141
142 print("=== Gemini VL 多模态可用性测试 ===")
143 api_key = Config.GEMINI_API_KEY
144 if not api_key:
145 print("✗ GEMINI_API_KEY 未设置,跳过")
146 sys.exit(1)
147 print(f" API Key: {api_key[:6]}***")
148 client = GeminiVLClient(api_key=api_key)
149
150 # 测试图片(使用示例图片)
151 img_path = ""
152 if not os.path.exists(img_path):
153 print(f"✗ 测试图片不存在: {img_path}")
154 img_path = "backend/code/result/image/test_avail/test_input.png"
155 if not os.path.exists(img_path):
156 print("✗ 跳过图片测试(无测试图片)")
157 sys.exit(0)
158
159 text = "请描述这张图片的内容"
160 print(f"\n[多模态] Prompt: {text}")
161 print(f" 图片: {img_path}")
162
163 for model in MODELS:
164 print(f"\n--- 测试模型: {model} ---")
165 t0 = time.time()
166 try:
167 result = client.chat(text=text, images=[img_path], model=model)
168 elapsed = time.time() - t0
169 if result:
170 print(f"✓ 返回结果 ({elapsed:.1f}s): {result[:200]}")
171 else:
172 print(f"✗ 返回空结果 ({elapsed:.1f}s)")
173 except Exception as e:
174 print(f"✗ 失败: {e}")
175
175 lines PYTHON