DaBiao / api_clients.py
ljx77qaq's picture
Upload 9 files
2909694 verified
Raw
History Blame Contribute Delete
7.02 kB
import base64
import httpx
import asyncio
import logging
from pathlib import Path
from typing import Optional
from config import config
logger = logging.getLogger(__name__)
def image_to_base64(image_path: str) -> str:
"""将图片转为 base64"""
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def get_mime_type(image_path: str) -> str:
"""获取图片 MIME 类型"""
suffix = Path(image_path).suffix.lower()
mime_map = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
".bmp": "image/bmp",
}
return mime_map.get(suffix, "image/jpeg")
class BaseClient:
"""AI API 客户端基类"""
async def analyze_image(self, image_path: str, custom_prompt: Optional[str] = None) -> str:
raise NotImplementedError
class OpenAIClient(BaseClient):
"""OpenAI / OpenAI 兼容 API 客户端"""
def __init__(self, api_key: str, base_url: str, model: str):
self.api_key = api_key
self.base_url = base_url.rstrip("/")
self.model = model
async def analyze_image(self, image_path: str, custom_prompt: Optional[str] = None) -> str:
b64 = image_to_base64(image_path)
mime = get_mime_type(image_path)
prompt = custom_prompt or config.SYSTEM_PROMPT
payload = {
"model": self.model,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:{mime};base64,{b64}",
"detail": "high"
}
}
]
}
],
"max_tokens": 1024,
"temperature": 0.3,
}
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(
f"{self.base_url}/chat/completions",
json=payload,
headers=headers
)
resp.raise_for_status()
data = resp.json()
return data["choices"][0]["message"]["content"].strip()
class GeminiClient(BaseClient):
"""Google Gemini API 客户端"""
def __init__(self, api_key: str, model: str):
self.api_key = api_key
self.model = model
async def analyze_image(self, image_path: str, custom_prompt: Optional[str] = None) -> str:
b64 = image_to_base64(image_path)
mime = get_mime_type(image_path)
prompt = custom_prompt or config.SYSTEM_PROMPT
url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.model}:generateContent"
payload = {
"contents": [
{
"parts": [
{"text": prompt},
{
"inline_data": {
"mime_type": mime,
"data": b64
}
}
]
}
],
"generationConfig": {
"temperature": 0.3,
"maxOutputTokens": 1024,
}
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(
url,
json=payload,
params={"key": self.api_key}
)
resp.raise_for_status()
data = resp.json()
return data["candidates"][0]["content"]["parts"][0]["text"].strip()
class ClaudeClient(BaseClient):
"""Anthropic Claude API 客户端"""
def __init__(self, api_key: str, model: str):
self.api_key = api_key
self.model = model
async def analyze_image(self, image_path: str, custom_prompt: Optional[str] = None) -> str:
b64 = image_to_base64(image_path)
mime = get_mime_type(image_path)
prompt = custom_prompt or config.SYSTEM_PROMPT
payload = {
"model": self.model,
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": mime,
"data": b64
}
},
{"type": "text", "text": prompt}
]
}
]
}
headers = {
"x-api-key": self.api_key,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(
"https://api.anthropic.com/v1/messages",
json=payload,
headers=headers
)
resp.raise_for_status()
data = resp.json()
return data["content"][0]["text"].strip()
def get_ai_client(
provider: Optional[str] = None,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
model: Optional[str] = None,
) -> BaseClient:
"""工厂方法:获取 AI 客户端"""
provider = (provider or config.AI_PROVIDER).lower()
if provider == "openai":
return OpenAIClient(
api_key=api_key or config.OPENAI_API_KEY,
base_url=base_url or config.OPENAI_BASE_URL,
model=model or config.OPENAI_MODEL,
)
elif provider == "gemini":
return GeminiClient(
api_key=api_key or config.GEMINI_API_KEY,
model=model or config.GEMINI_MODEL,
)
elif provider == "claude":
return ClaudeClient(
api_key=api_key or config.CLAUDE_API_KEY,
model=model or config.CLAUDE_MODEL,
)
elif provider == "qwen":
return OpenAIClient(
api_key=api_key or config.QWEN_API_KEY,
base_url=base_url or config.QWEN_BASE_URL,
model=model or config.QWEN_MODEL,
)
elif provider == "custom":
return OpenAIClient(
api_key=api_key or config.CUSTOM_API_KEY,
base_url=base_url or config.CUSTOM_BASE_URL,
model=model or config.CUSTOM_MODEL,
)
else:
raise ValueError(f"不支持的 AI 提供商: {provider}")