feat: upgrade客服多店铺分流、批量报价与稳定性防护
This commit is contained in:
@@ -14,11 +14,19 @@ import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
from utils.metrics_tracker import emit as metrics_emit
|
||||
|
||||
|
||||
from utils.service_base import BaseService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
load_dotenv()
|
||||
GEMINI_IMAGE_MODEL = os.getenv("GEMINI_IMAGE_MODEL", "gemini-3.1-flash-image-preview")
|
||||
GEMINI_IMAGE_FALLBACK_MODEL = os.getenv("GEMINI_IMAGE_FALLBACK_MODEL", "gemini-2.5-flash-image")
|
||||
GEMINI_IMAGE_SIZE = os.getenv("GEMINI_IMAGE_SIZE", "1K")
|
||||
GEMINI_THINKING_LEVEL = os.getenv("GEMINI_THINKING_LEVEL", "MINIMAL")
|
||||
GEMINI_PERSON_GENERATION = os.getenv("GEMINI_PERSON_GENERATION", "")
|
||||
|
||||
|
||||
class GeminiExtractV2Service(BaseService):
|
||||
@@ -46,17 +54,26 @@ class GeminiExtractV2Service(BaseService):
|
||||
"name": "西风接口$0.014",
|
||||
"api_key": "sk-uRuvzLfIHsc3BiHZ2cyebk0cYsZ8NR9rLL326QqXCKIy9EpK",
|
||||
"api_url": "https://api.apiqik.online/v1beta/models",
|
||||
"api_model": "gemini-2.5-flash-image", # 更稳定的模型
|
||||
"max_retries": 2, # 贵接口少重试
|
||||
"api_model": GEMINI_IMAGE_MODEL,
|
||||
"max_retries": 2,
|
||||
"cost": "中",
|
||||
"use_gemini_format": True # 使用Gemini原生API格式
|
||||
},
|
||||
{
|
||||
"name": "西风接口Fallback",
|
||||
"api_key": "sk-uRuvzLfIHsc3BiHZ2cyebk0cYsZ8NR9rLL326QqXCKIy9EpK",
|
||||
"api_url": "https://api.apiqik.online/v1beta/models",
|
||||
"api_model": GEMINI_IMAGE_FALLBACK_MODEL,
|
||||
"max_retries": 1,
|
||||
"cost": "中",
|
||||
"use_gemini_format": True
|
||||
},
|
||||
|
||||
{
|
||||
"name": "最贵的",
|
||||
"api_key": "sk-8i7uYE0RtnQwDImV8a5f7014DcAb46F6BcEb72Df92218aC8",
|
||||
"api_url": "https://api.laozhang.ai/v1/chat/completions",
|
||||
"api_model": "gemini-2.5-flash-image-preview",
|
||||
"api_model": GEMINI_IMAGE_MODEL,
|
||||
"max_retries": 1,
|
||||
"cost": "高"
|
||||
}
|
||||
@@ -90,6 +107,9 @@ class GeminiExtractV2Service(BaseService):
|
||||
output_path: str,
|
||||
custom_prompt: str = None,
|
||||
aspect_ratio: str = "1:1",
|
||||
image_size: str = "",
|
||||
person_generation: str = "",
|
||||
thinking_level: str = "",
|
||||
) -> tuple[bool, str, dict]:
|
||||
"""
|
||||
使用多API配置进行印花图案提取
|
||||
@@ -113,6 +133,7 @@ class GeminiExtractV2Service(BaseService):
|
||||
# 按优先级逐个尝试API配置
|
||||
for config_index, config in enumerate(self.API_CONFIGS):
|
||||
logger.info(f"尝试使用API: {config['name']} (成本: {config['cost']})")
|
||||
metrics_emit("gemini_request", model=config.get("api_model", ""), provider=config.get("name", ""))
|
||||
|
||||
# 对每个API配置进行重试
|
||||
for attempt in range(config['max_retries']):
|
||||
@@ -126,12 +147,25 @@ class GeminiExtractV2Service(BaseService):
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
|
||||
# 有效比例列表(Auto 不传 aspectRatio)
|
||||
valid_ratios = {"1:1", "9:16", "16:9", "3:4", "4:3", "3:2", "2:3", "5:4", "4:5"}
|
||||
valid_sizes = {"1K", "2K", "4K"}
|
||||
valid_thinking = {"MINIMAL", "LOW", "MEDIUM", "HIGH"}
|
||||
image_config = {}
|
||||
if aspect_ratio in valid_ratios:
|
||||
image_config["aspectRatio"] = aspect_ratio
|
||||
size_val = (image_size or GEMINI_IMAGE_SIZE or "").upper().strip()
|
||||
if size_val in valid_sizes:
|
||||
image_config["imageSize"] = size_val
|
||||
person_val = (person_generation or GEMINI_PERSON_GENERATION or "").strip()
|
||||
if person_val:
|
||||
# 中转接口若支持该字段会生效;不设置时不发送,保证兼容
|
||||
image_config["personGeneration"] = person_val
|
||||
thinking_val = (thinking_level or GEMINI_THINKING_LEVEL or "").upper().strip()
|
||||
thinking_config = {}
|
||||
if thinking_val in valid_thinking:
|
||||
thinking_config["thinkingLevel"] = thinking_val
|
||||
|
||||
data = {
|
||||
"contents": [
|
||||
@@ -153,9 +187,13 @@ class GeminiExtractV2Service(BaseService):
|
||||
"generationConfig": {
|
||||
"responseModalities": ["IMAGE"],
|
||||
**({"imageConfig": image_config} if image_config else {}),
|
||||
**({"thinkingConfig": thinking_config} if thinking_config else {}),
|
||||
}
|
||||
}
|
||||
logger.info(f"Gemini 生成配置: 比例={aspect_ratio} 格式=JPEG")
|
||||
logger.info(
|
||||
f"Gemini 生成配置: 比例={aspect_ratio} 尺寸={image_config.get('imageSize', '默认')} "
|
||||
f"person={image_config.get('personGeneration', '默认')} thinking={thinking_config.get('thinkingLevel', '默认')}"
|
||||
)
|
||||
else:
|
||||
# OpenAI兼容格式
|
||||
api_url = config['api_url']
|
||||
@@ -212,6 +250,25 @@ class GeminiExtractV2Service(BaseService):
|
||||
continue
|
||||
|
||||
result = await response.json()
|
||||
# Gemini 偶发只返回文本不返回图片:NO_IMAGE 时快速重试/降级
|
||||
if config.get('use_gemini_format', False):
|
||||
finish_reason = ""
|
||||
try:
|
||||
finish_reason = (
|
||||
(result.get("candidates") or [{}])[0].get("finishReason", "")
|
||||
)
|
||||
except Exception:
|
||||
finish_reason = ""
|
||||
if finish_reason == "NO_IMAGE":
|
||||
logger.warning(
|
||||
f"{config['name']} 返回 NO_IMAGE (模型={config.get('api_model')}),第{attempt + 1}次"
|
||||
)
|
||||
metrics_emit("gemini_no_image", model=config.get("api_model", ""), provider=config.get("name", ""))
|
||||
if attempt == config['max_retries'] - 1:
|
||||
logger.warning(f"{config['name']} NO_IMAGE 重试已用完,切换下一个配置")
|
||||
break
|
||||
await asyncio.sleep(1 + attempt)
|
||||
continue
|
||||
|
||||
except (aiohttp.ClientError, asyncio.TimeoutError, AssertionError) as e:
|
||||
logger.error(f"{config['name']} 网络连接错误 (第{attempt + 1}次): {str(e)}")
|
||||
@@ -235,6 +292,7 @@ class GeminiExtractV2Service(BaseService):
|
||||
|
||||
if success:
|
||||
logger.info(f"使用 {config['name']} 成功完成印花提取")
|
||||
metrics_emit("gemini_success", model=config.get("api_model", ""), provider=config.get("name", ""))
|
||||
try:
|
||||
from utils.api_cost_tracker import record
|
||||
record("gemini_extract", count=1)
|
||||
@@ -506,4 +564,4 @@ if __name__ == "__main__":
|
||||
|
||||
await service.cleanup()
|
||||
|
||||
asyncio.run(test())
|
||||
asyncio.run(test())
|
||||
|
||||
Reference in New Issue
Block a user