This commit is contained in:
Felix
2026-01-14 17:27:24 +08:00
parent 50461f3631
commit 0d6e4764ca
3 changed files with 85 additions and 10 deletions

View File

@@ -2,12 +2,15 @@ import asyncio
from typing import Dict, Any, List
import json
import os
import time
import hashlib
from dashscope import MultiModalConversation
from backend.app.admin.service.file_service import file_service
from langchain_core.messages import SystemMessage, HumanMessage
from backend.core.llm import LLMFactory, AuditLogCallbackHandler
from backend.core.conf import settings
from backend.core.prompts.scene_variation import get_scene_variation_prompt
from backend.database.redis import redis_client
class SceneVariationGenerator:
"""
@@ -86,6 +89,59 @@ class SceneVariationGenerator:
except Exception as e:
return {"success": False, "error": str(e)}
class QwenImageKeyManager:
RATE_LIMIT_PER_SECOND = 2
WINDOW_SECONDS = 1
REDIS_PREFIX = "qwen:image_edit:rate"
@classmethod
def _get_keys(cls) -> List[str]:
raw = settings.QWEN_API_KEY or ""
if not raw:
raw = os.getenv("DASHSCOPE_API_KEY") or ""
parts = [p.strip() for p in raw.split(";") if p.strip()]
if parts:
return parts
if raw:
return [raw]
return []
@classmethod
def _key_id(cls, key: str) -> str:
return hashlib.sha256(key.encode()).hexdigest()[:16]
@classmethod
async def acquire_key(cls) -> str:
keys = cls._get_keys()
if not keys:
raise Exception("Qwen API key not configured")
now = int(time.time())
n = len(keys)
start = now % n
for i in range(n):
key = keys[(start + i) % n]
kid = cls._key_id(key)
redis_key = f"{cls.REDIS_PREFIX}:{kid}:{now}"
count = await redis_client.incr(redis_key)
if count == 1:
await redis_client.expire(redis_key, cls.WINDOW_SECONDS + 1)
if count <= cls.RATE_LIMIT_PER_SECOND:
return key
await asyncio.sleep(1.0 / cls.RATE_LIMIT_PER_SECOND)
now2 = int(time.time())
for i in range(n):
key = keys[(start + i) % n]
kid = cls._key_id(key)
redis_key = f"{cls.REDIS_PREFIX}:{kid}:{now2}"
count = await redis_client.incr(redis_key)
if count == 1:
await redis_client.expire(redis_key, cls.WINDOW_SECONDS + 1)
if count <= cls.RATE_LIMIT_PER_SECOND:
return key
raise Exception("Qwen image edit rate limited")
class Illustrator:
"""
Component for generating edited images based on text descriptions (Step 2 of the advanced workflow).
@@ -116,11 +172,14 @@ class Illustrator:
]
try:
# Wrap the blocking SDK call in asyncio.to_thread
if api_key:
final_api_key = api_key
else:
final_api_key = await QwenImageKeyManager.acquire_key()
response = await asyncio.to_thread(
MultiModalConversation.call,
api_key=api_key or os.getenv("DASHSCOPE_API_KEY") or settings.QWEN_API_KEY,
model="qwen-image-edit-plus", # Assuming this is the model name for image editing
api_key=final_api_key,
model="qwen-image-edit-plus",
messages=messages,
stream=False,
n=1,

View File

@@ -12,6 +12,15 @@ from backend.app.admin.service.audit_log_service import audit_log_service
from backend.core.conf import settings
from backend.common.log import log as logger
def _get_primary_qwen_api_key() -> str:
raw = settings.QWEN_API_KEY or ""
parts = [p.strip() for p in raw.split(";") if p.strip()]
if parts:
return parts[0]
return raw
class AuditLogCallbackHandler(BaseCallbackHandler):
def __init__(self, metadata: Optional[Dict[str, Any]] = None):
super().__init__()
@@ -99,7 +108,7 @@ class LLMFactory:
if model_type == 'qwen':
return ChatTongyi(
api_key=settings.QWEN_API_KEY,
api_key=_get_primary_qwen_api_key(),
model_name=settings.QWEN_TEXT_MODEL,
**kwargs
)
@@ -110,10 +119,9 @@ class LLMFactory:
**kwargs
)
else:
# Default to Qwen if unknown
logger.warning(f"Unknown model type {model_type}, defaulting to Qwen")
return ChatTongyi(
api_key=settings.QWEN_API_KEY,
api_key=_get_primary_qwen_api_key(),
model_name=settings.QWEN_TEXT_MODEL,
**kwargs
)

View File

@@ -23,6 +23,14 @@ from sqlalchemy.exc import IntegrityError
from asyncio import sleep
def _get_primary_qwen_api_key() -> str:
raw = settings.QWEN_API_KEY or ""
parts = [p.strip() for p in raw.split(";") if p.strip()]
if parts:
return parts[0]
return raw
class Qwen:
# 创建一个类级别的线程池执行器
_executor = ThreadPoolExecutor(max_workers=10)
@@ -33,7 +41,7 @@ class Qwen:
@staticmethod
async def text_to_speak(content: str, image_text_id: int | None = None, image_id: int | None = None, user_id: int | None = None, ref_type: str | None = None, ref_id: int | None = None) -> Dict[str, Any]:
api_key = settings.QWEN_API_KEY
api_key = _get_primary_qwen_api_key()
model_name = "qwen3-tts-flash"
voice = "Jennifer"
language_type = "English"
@@ -185,7 +193,7 @@ class Qwen:
@staticmethod
async def chat(messages: List[Dict[str, str]], image_id: int = 0, user_id: int = 0, api_type: str = "chat") -> Dict[str, Any]:
api_key = settings.QWEN_API_KEY
api_key = _get_primary_qwen_api_key()
model_name = settings.QWEN_TEXT_MODEL
start_time = time.time()
start_at = datetime.now()
@@ -314,7 +322,7 @@ class Qwen:
"""通用API调用方法"""
model_name = ""
api_key = settings.QWEN_API_KEY
api_key = _get_primary_qwen_api_key()
response = None
start_time = time.time()
start_at = datetime.now()
@@ -481,7 +489,7 @@ class Qwen:
# 轮询任务状态
start_time = time.time()
headers = {
"Authorization": f"Bearer {settings.QWEN_API_KEY}",
"Authorization": f"Bearer {_get_primary_qwen_api_key()}",
"Content-Type": "application/json"
}