增加Kimi多模态Api,这样可以灵活选择!
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
VLM Provider 抽象层
|
||||
支持多种视觉语言模型后端:Ollama、Kimi、OpenAI GPT-4V 等
|
||||
"""
|
||||
|
||||
from .base import VLMProvider
|
||||
from .ollama_provider import OllamaProvider
|
||||
from .kimi_provider import KimiProvider
|
||||
|
||||
__all__ = ["VLMProvider", "OllamaProvider", "KimiProvider"]
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
VLM Provider 抽象基类
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
|
||||
class VLMProvider(ABC):
|
||||
"""
|
||||
视觉语言模型 Provider 抽象接口
|
||||
所有 VLM 后端实现都应该继承此类并实现 chat_vision 方法
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def chat_vision(
|
||||
self,
|
||||
prompt: str,
|
||||
image_paths: List[str],
|
||||
temperature: float = 0.1,
|
||||
timeout_sec: int = 120,
|
||||
) -> str:
|
||||
"""
|
||||
调用 VLM 进行图像理解
|
||||
|
||||
Args:
|
||||
prompt: 用户提示词
|
||||
image_paths: 图片路径列表(支持单张或多张)
|
||||
temperature: 采样温度(0.0-1.0)
|
||||
timeout_sec: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
模型生成的文本响应
|
||||
|
||||
Raises:
|
||||
Exception: 调用失败时抛出异常
|
||||
"""
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}>"
|
||||
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Kimi VLM Provider
|
||||
使用 Moonshot AI 的 Kimi 1.5 视觉模型
|
||||
"""
|
||||
|
||||
import base64
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
from .base import VLMProvider
|
||||
|
||||
|
||||
class KimiProvider(VLMProvider):
|
||||
"""
|
||||
Kimi (Moonshot AI) 视觉模型提供商
|
||||
使用 OpenAI SDK 兼容接口
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str,
|
||||
base_url: str = "https://api.moonshot.cn/v1",
|
||||
model: str = "moonshot-v1-32k-vision-preview",
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
api_key: Moonshot AI API Key
|
||||
base_url: API 端点地址
|
||||
model: 模型名称
|
||||
"""
|
||||
if not api_key:
|
||||
raise ValueError("Kimi API Key 不能为空")
|
||||
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url
|
||||
self.model = model
|
||||
|
||||
def chat_vision(
|
||||
self,
|
||||
prompt: str,
|
||||
image_paths: List[str],
|
||||
temperature: float = 0.1,
|
||||
timeout_sec: int = 120,
|
||||
) -> str:
|
||||
"""调用 Kimi API 进行视觉推理"""
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Kimi Provider 需要 openai 库,请安装: pip install openai"
|
||||
)
|
||||
|
||||
print(
|
||||
f"[Kimi] Prepare chat model={self.model} temp={temperature} "
|
||||
f"images={len(image_paths)} timeout={timeout_sec}s"
|
||||
)
|
||||
|
||||
# 初始化 OpenAI 客户端
|
||||
client = OpenAI(
|
||||
api_key=self.api_key,
|
||||
base_url=self.base_url,
|
||||
timeout=timeout_sec,
|
||||
)
|
||||
|
||||
# 构建消息内容(文本 + 图片)
|
||||
# Kimi 支持 image_url 格式,使用 base64 编码
|
||||
content_parts = [{"type": "text", "text": prompt}]
|
||||
|
||||
for img_path in image_paths:
|
||||
try:
|
||||
with open(img_path, "rb") as f:
|
||||
img_data = f.read()
|
||||
b64_str = base64.b64encode(img_data).decode("utf-8")
|
||||
|
||||
# 根据文件扩展名判断 MIME 类型
|
||||
if img_path.lower().endswith((".jpg", ".jpeg")):
|
||||
mime = "image/jpeg"
|
||||
elif img_path.lower().endswith(".png"):
|
||||
mime = "image/png"
|
||||
else:
|
||||
mime = "image/jpeg" # 默认
|
||||
|
||||
data_uri = f"data:{mime};base64,{b64_str}"
|
||||
content_parts.append({
|
||||
"type": "image_url",
|
||||
"image_url": {"url": data_uri}
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[Kimi] Warning: Failed to load image {img_path}: {e}")
|
||||
continue
|
||||
|
||||
# 调用 API
|
||||
t0 = time.time()
|
||||
try:
|
||||
completion = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": content_parts,
|
||||
}
|
||||
],
|
||||
temperature=temperature,
|
||||
)
|
||||
|
||||
dt = time.time() - t0
|
||||
text = completion.choices[0].message.content or ""
|
||||
|
||||
result = text.replace("\n", " ")
|
||||
print(f"[Kimi] Response elapsed={dt:.2f}s length={len(text)} preview={result}")
|
||||
|
||||
if not text:
|
||||
raise RuntimeError("Kimi 返回内容为空")
|
||||
|
||||
return text
|
||||
|
||||
except Exception as e:
|
||||
dt = time.time() - t0
|
||||
print(f"[Kimi][Error] during request elapsed={dt:.2f}s: {e}")
|
||||
raise
|
||||
|
||||
def __repr__(self):
|
||||
return f"<KimiProvider model={self.model} base_url={self.base_url}>"
|
||||
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Ollama VLM Provider
|
||||
支持本地/自建 Ollama 服务
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from .base import VLMProvider
|
||||
|
||||
|
||||
class OllamaProvider(VLMProvider):
|
||||
"""
|
||||
Ollama 本地/自建服务提供商
|
||||
使用 OpenAI-compatible API (/v1/chat/completions)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ollama_url: str = None,
|
||||
model: str = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
ollama_url: Ollama 服务地址,默认从环境变量 OLLAMA_URL 读取
|
||||
model: 模型名称,默认从环境变量 VLM_MODEL 读取
|
||||
"""
|
||||
self.ollama_url = ollama_url or os.getenv("OLLAMA_URL", "http://192.168.1.250:11434")
|
||||
self.model = model or os.getenv("VLM_MODEL", "qwen2.5vl:32b")
|
||||
|
||||
def chat_vision(
|
||||
self,
|
||||
prompt: str,
|
||||
image_paths: List[str],
|
||||
temperature: float = 0.1,
|
||||
timeout_sec: int = 120,
|
||||
) -> str:
|
||||
"""调用 Ollama 服务进行视觉推理"""
|
||||
# 导入原有的 ollama_client 逻辑(复用代码)
|
||||
from .. import ollama_client
|
||||
|
||||
# 临时覆盖模块常量(确保使用实例配置)
|
||||
original_url = ollama_client.OLLAMA_URL
|
||||
original_model = ollama_client.DEFAULT_MODEL
|
||||
try:
|
||||
ollama_client.OLLAMA_URL = self.ollama_url
|
||||
ollama_client.DEFAULT_MODEL = self.model
|
||||
return ollama_client.chat_vision(
|
||||
prompt=prompt,
|
||||
image_paths=image_paths,
|
||||
temperature=temperature,
|
||||
model=self.model,
|
||||
timeout_sec=timeout_sec,
|
||||
)
|
||||
finally:
|
||||
# 恢复原始值
|
||||
ollama_client.OLLAMA_URL = original_url
|
||||
ollama_client.DEFAULT_MODEL = original_model
|
||||
|
||||
def __repr__(self):
|
||||
return f"<OllamaProvider url={self.ollama_url} model={self.model}>"
|
||||
Reference in New Issue
Block a user