mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-04 23:19:17 +08:00
feat: 添加图片生成功能及相关配置
- 添加图片生成相关配置和环境变量 - 新增图片上传服务和模型定义 - 扩展模型服务以支持图片生成模型 - 添加图片生成响应处理器 - 更新README文档以反映新功能 - 添加GitHub Actions发布工作流
This commit is contained in:
@@ -84,6 +84,47 @@ class OpenAIResponseHandler(ResponseHandler):
|
||||
if stream:
|
||||
return _handle_openai_stream_response(response, model, finish_reason)
|
||||
return _handle_openai_normal_response(response, model, finish_reason)
|
||||
|
||||
def handle_image_chat_response(self, image_str: str, model: str, stream=False, finish_reason="stop"):
|
||||
if stream:
|
||||
return _handle_openai_stream_image_response(image_str,model,finish_reason)
|
||||
return _handle_openai_normal_image_response(image_str,model,finish_reason)
|
||||
|
||||
|
||||
def _handle_openai_stream_image_response(image_str: str,model: str,finish_reason: str) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": f"chatcmpl-{uuid.uuid4()}",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": int(time.time()),
|
||||
"model": model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {"content": image_str} if image_str else {},
|
||||
"finish_reason": finish_reason
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
def _handle_openai_normal_image_response(image_str: str,model: str,finish_reason: str) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": f"chatcmpl-{uuid.uuid4()}",
|
||||
"object": "chat.completion",
|
||||
"created": int(time.time()),
|
||||
"model": model,
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": image_str
|
||||
},
|
||||
"finish_reason": finish_reason
|
||||
}],
|
||||
"usage": {
|
||||
"prompt_tokens": 0,
|
||||
"completion_tokens": 0,
|
||||
"total_tokens": 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _extract_text(response: Dict[str, Any], model: str, stream: bool = False) -> str:
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
import base64
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.logger import get_image_create_logger
|
||||
from app.core.uploader import ImageUploaderFactory
|
||||
from app.schemas.openai_models import ImageGenerationRequest
|
||||
|
||||
logger = get_image_create_logger()
|
||||
|
||||
|
||||
class ImageCreateService:
|
||||
def __init__(self, aspect_ratio="1:1"):
|
||||
self.image_model = settings.CREATE_IMAGE_MODEL
|
||||
self.paid_key = settings.PAID_KEY
|
||||
self.aspect_ratio = aspect_ratio
|
||||
|
||||
def generate_images(self, request: ImageGenerationRequest):
|
||||
client = genai.Client(api_key=self.paid_key)
|
||||
if request.size == "1024x1024":
|
||||
self.aspect_ratio = "1:1"
|
||||
elif request.size == "1792x1024":
|
||||
self.aspect_ratio = "16:9"
|
||||
elif request.size == "1027x1792":
|
||||
self.aspect_ratio = "9:16"
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid size: {request.size}. Supported sizes are 1024x1024, 1792x1024, and 1024x1792."
|
||||
)
|
||||
|
||||
response = client.models.generate_images(
|
||||
model=self.image_model,
|
||||
prompt=request.prompt,
|
||||
config=types.GenerateImagesConfig(
|
||||
number_of_images=request.n,
|
||||
output_mime_type="image/png",
|
||||
aspect_ratio=self.aspect_ratio,
|
||||
safety_filter_level="BLOCK_LOW_AND_ABOVE",
|
||||
person_generation="ALLOW_ADULT",
|
||||
# language="auto"
|
||||
),
|
||||
)
|
||||
|
||||
if response.generated_images:
|
||||
images_data = []
|
||||
for index, generated_image in enumerate(response.generated_images):
|
||||
image_data = generated_image.image.image_bytes
|
||||
image_uploader = None
|
||||
if settings.UPLOAD_PROVIDER == "smms":
|
||||
image_uploader = ImageUploaderFactory.create(provider=settings.UPLOAD_PROVIDER,api_key=settings.SMMS_SECRET_TOKEN)
|
||||
current_date = time.strftime("%Y/%m/%d")
|
||||
filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png"
|
||||
upload_response = image_uploader.upload(image_data,filename)
|
||||
|
||||
# base64_image = base64.b64encode(image_data).decode('utf-8')
|
||||
images_data.append({
|
||||
"url": f"{upload_response.data.url}",
|
||||
"revised_prompt": request.prompt
|
||||
})
|
||||
|
||||
response_data = {
|
||||
"created": int(time.time()), # Current timestamp
|
||||
"data": images_data
|
||||
}
|
||||
return response_data
|
||||
else:
|
||||
raise Exception("I can't generate these images")
|
||||
|
||||
def generate_images_chat(self, request: ImageGenerationRequest) -> str:
|
||||
response = self.generate_images(request)
|
||||
image_datas = response["data"]
|
||||
if image_datas:
|
||||
markdown_images = []
|
||||
for index, image_data in enumerate(image_datas):
|
||||
markdown_images.append(f"")
|
||||
return "\n".join(markdown_images)
|
||||
|
||||
@@ -15,7 +15,11 @@ class KeyManager:
|
||||
self.failure_count_lock = asyncio.Lock()
|
||||
self.key_failure_counts: Dict[str, int] = {key: 0 for key in api_keys}
|
||||
self.MAX_FAILURES = settings.MAX_FAILURES
|
||||
self.paid_key = settings.PAID_KEY
|
||||
|
||||
async def get_paid_key(self) -> str:
|
||||
return self.paid_key
|
||||
|
||||
async def get_next_key(self) -> str:
|
||||
"""获取下一个API key"""
|
||||
async with self.key_cycle_lock:
|
||||
|
||||
@@ -2,10 +2,10 @@ import requests
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, Dict, Any
|
||||
from app.core.logger import get_model_logger
|
||||
from app.core.config import settings
|
||||
|
||||
logger = get_model_logger()
|
||||
|
||||
|
||||
class ModelService:
|
||||
def __init__(self, model_search: list):
|
||||
self.model_search = model_search
|
||||
@@ -52,6 +52,11 @@ class ModelService:
|
||||
"parent": None,
|
||||
}
|
||||
openai_format["data"].append(openai_model)
|
||||
|
||||
if settings.CREATE_IMAGE_MODEL:
|
||||
image_model = openai_model.copy()
|
||||
image_model["id"] = f"{settings.CREATE_IMAGE_MODEL}-chat"
|
||||
openai_format["data"].append(image_model)
|
||||
|
||||
if model_id in self.model_search:
|
||||
search_model = openai_model.copy()
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import json
|
||||
from typing import Dict, Any, AsyncGenerator, List, Union
|
||||
from app.core.logger import get_openai_logger
|
||||
from app.services.chat.message_converter import OpenAIMessageConverter
|
||||
from app.services.chat.response_handler import OpenAIResponseHandler
|
||||
from app.services.chat.api_client import GeminiApiClient
|
||||
from app.schemas.openai_models import ChatRequest
|
||||
from app.schemas.openai_models import ChatRequest, ImageGenerationRequest
|
||||
from app.core.config import settings
|
||||
from app.services.image_create_service import ImageCreateService
|
||||
from app.services.key_manager import KeyManager
|
||||
|
||||
logger = get_openai_logger()
|
||||
@@ -31,9 +31,9 @@ def _build_tools(
|
||||
model = request.model
|
||||
|
||||
if (
|
||||
settings.TOOLS_CODE_EXECUTION_ENABLED
|
||||
and not (model.endswith("-search") or "-thinking" in model)
|
||||
and not _has_image_parts(messages)
|
||||
settings.TOOLS_CODE_EXECUTION_ENABLED
|
||||
and not (model.endswith("-search") or "-thinking" in model)
|
||||
and not _has_image_parts(messages)
|
||||
):
|
||||
tools.append({"code_execution": {}})
|
||||
if model.endswith("-search"):
|
||||
@@ -86,16 +86,17 @@ def _build_payload(
|
||||
class OpenAIChatService:
|
||||
"""聊天服务"""
|
||||
|
||||
def __init__(self, base_url: str, key_manager: KeyManager):
|
||||
self.message_converter = OpenAIMessageConverter()
|
||||
def __init__(self, base_url: str, key_manager: KeyManager = None):
|
||||
|
||||
self.response_handler = OpenAIResponseHandler(config=None)
|
||||
self.api_client = GeminiApiClient(base_url)
|
||||
self.key_manager = key_manager
|
||||
self.image_create_service = ImageCreateService()
|
||||
|
||||
async def create_chat_completion(
|
||||
self,
|
||||
request: ChatRequest,
|
||||
api_key: str,
|
||||
self,
|
||||
request: ChatRequest,
|
||||
api_key: str,
|
||||
) -> Union[Dict[str, Any], AsyncGenerator[str, None]]:
|
||||
"""创建聊天完成"""
|
||||
# 转换消息格式
|
||||
@@ -109,7 +110,7 @@ class OpenAIChatService:
|
||||
return self._handle_normal_completion(request.model, payload, api_key)
|
||||
|
||||
def _handle_normal_completion(
|
||||
self, model: str, payload: Dict[str, Any], api_key: str
|
||||
self, model: str, payload: Dict[str, Any], api_key: str
|
||||
) -> Dict[str, Any]:
|
||||
"""处理普通聊天完成"""
|
||||
response = self.api_client.generate_content(payload, model, api_key)
|
||||
@@ -118,7 +119,7 @@ class OpenAIChatService:
|
||||
)
|
||||
|
||||
async def _handle_stream_completion(
|
||||
self, model: str, payload: Dict[str, Any], api_key: str
|
||||
self, model: str, payload: Dict[str, Any], api_key: str
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""处理流式聊天完成,添加重试逻辑"""
|
||||
retries = 0
|
||||
@@ -126,7 +127,7 @@ class OpenAIChatService:
|
||||
while retries < max_retries:
|
||||
try:
|
||||
async for line in self.api_client.stream_generate_content(
|
||||
payload, model, api_key
|
||||
payload, model, api_key
|
||||
):
|
||||
# print(line)
|
||||
if line.startswith("data:"):
|
||||
@@ -154,3 +155,38 @@ class OpenAIChatService:
|
||||
yield f"data: {json.dumps({'error': 'Streaming failed after retries'})}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
break
|
||||
|
||||
async def create_image_chat_completion(
|
||||
self,
|
||||
request: ChatRequest,
|
||||
) -> Union[Dict[str, Any], AsyncGenerator[str, None]]:
|
||||
|
||||
image_generate_request = ImageGenerationRequest()
|
||||
image_generate_request.prompt = request.messages[-1]["content"]
|
||||
image_res = self.image_create_service.generate_images_chat(image_generate_request)
|
||||
|
||||
if request.stream:
|
||||
return self._handle_stream_image_completion(request.model,image_res)
|
||||
else:
|
||||
return self._handle_normal_image_completion(request.model,image_res)
|
||||
|
||||
async def _handle_stream_image_completion(
|
||||
self, model: str, image_data: str
|
||||
) -> AsyncGenerator[str, None]:
|
||||
if image_data:
|
||||
openai_chunk = self.response_handler.handle_image_chat_response(
|
||||
image_data, model, stream=True, finish_reason=None
|
||||
)
|
||||
if openai_chunk:
|
||||
yield f"data: {json.dumps(openai_chunk)}\n\n"
|
||||
yield f"data: {json.dumps(self.response_handler.handle_response({}, model, stream=True, finish_reason='stop'))}\n\n"
|
||||
yield "data: [DONE]\n\n"
|
||||
logger.info("Image chat streaming completed successfully")
|
||||
|
||||
def _handle_normal_image_completion(
|
||||
self, model: str, image_data: str
|
||||
) -> Dict[str, Any]:
|
||||
|
||||
return self.response_handler.handle_image_chat_response(
|
||||
image_data, model, stream=False, finish_reason="stop"
|
||||
)
|
||||
Reference in New Issue
Block a user