mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-07-04 06:11:32 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
530c958afc | ||
|
|
57d861b578 | ||
|
|
99664298b9 | ||
|
|
a6fe5a7022 | ||
|
|
1918dad602 | ||
|
|
69399c291e | ||
|
|
9ec33ce320 | ||
|
|
c35d3aff7d | ||
|
|
2a5744d1c4 | ||
|
|
825511506b | ||
|
|
5a98a701cb |
@@ -20,6 +20,9 @@ THINKING_BUDGET_MAP={"gemini-2.5-flash-preview-04-17": 4000}
|
||||
IMAGE_MODELS=["gemini-2.0-flash-exp"]
|
||||
SEARCH_MODELS=["gemini-2.0-flash-exp","gemini-2.0-pro-exp"]
|
||||
FILTERED_MODELS=["gemini-1.0-pro-vision-latest", "gemini-pro-vision", "chat-bison-001", "text-bison-001", "embedding-gecko-001"]
|
||||
# 是否启用网址上下文,默认启用
|
||||
URL_CONTEXT_ENABLED=true
|
||||
URL_CONTEXT_MODELS=["gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite","gemini-2.0-flash","gemini-2.0-flash-live-001"]
|
||||
TOOLS_CODE_EXECUTION_ENABLED=false
|
||||
SHOW_SEARCH_LINK=true
|
||||
SHOW_THINKING_PROCESS=true
|
||||
|
||||
@@ -192,6 +192,8 @@ If you want to run the source code directly locally for development or testing,
|
||||
| `THINKING_MODELS` | Optional, list of models that support thinking functions | `[]` |
|
||||
| `THINKING_BUDGET_MAP` | Optional, thinking function budget mapping (model_name:budget_value) | `{}` |
|
||||
| `URL_NORMALIZATION_ENABLED` | Optional, whether to enable intelligent URL routing mapping | `false` |
|
||||
| `URL_CONTEXT_ENABLED` | Optional, whether to enable URL context understanding | `false` |
|
||||
| `URL_CONTEXT_MODELS` | Optional, list of models that support URL context understanding | `[]` |
|
||||
| `BASE_URL` | Optional, Gemini API base URL, no modification needed by default | `https://generativelanguage.googleapis.com/v1beta` |
|
||||
| `MAX_FAILURES` | Optional, number of times a single key is allowed to fail | `3` |
|
||||
| `MAX_RETRIES` | Optional, maximum number of retries for failed API requests | `3` |
|
||||
|
||||
@@ -185,6 +185,8 @@ app/
|
||||
| `THINKING_MODELS` | 可选,支持思考功能的模型列表 | `[]` |
|
||||
| `THINKING_BUDGET_MAP` | 可选,思考功能预算映射 (模型名:预算值) | `{}` |
|
||||
| `URL_NORMALIZATION_ENABLED` | 可选,是否启用智能路由映射功能 | `false` |
|
||||
| `URL_CONTEXT_ENABLED` | 可选,是否启用URL上下文理解功能 | `false` |
|
||||
| `URL_CONTEXT_MODELS` | 可选,支持URL上下文理解功能的模型列表 | `[]` |
|
||||
| `BASE_URL` | 可选,Gemini API 基础 URL,默认无需修改 | `https://generativelanguage.googleapis.com/v1beta` |
|
||||
| `MAX_FAILURES` | 可选,允许单个key失败的次数 | `3` |
|
||||
| `MAX_RETRIES` | 可选,API 请求失败时的最大重试次数 | `3` |
|
||||
|
||||
@@ -51,8 +51,8 @@ class Settings(BaseSettings):
|
||||
return v
|
||||
|
||||
# API相关配置
|
||||
API_KEYS: List[str]
|
||||
ALLOWED_TOKENS: List[str]
|
||||
API_KEYS: List[str]=[]
|
||||
ALLOWED_TOKENS: List[str]=[]
|
||||
BASE_URL: str = f"https://generativelanguage.googleapis.com/{API_VERSION}"
|
||||
AUTH_TOKEN: str = ""
|
||||
MAX_FAILURES: int = 3
|
||||
@@ -75,6 +75,9 @@ class Settings(BaseSettings):
|
||||
IMAGE_MODELS: List[str] = ["gemini-2.0-flash-exp"]
|
||||
FILTERED_MODELS: List[str] = DEFAULT_FILTER_MODELS
|
||||
TOOLS_CODE_EXECUTION_ENABLED: bool = False
|
||||
# 是否启用网址上下文
|
||||
URL_CONTEXT_ENABLED: bool = True
|
||||
URL_CONTEXT_MODELS: List[str] = ["gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite","gemini-2.0-flash","gemini-2.0-flash-live-001"]
|
||||
SHOW_SEARCH_LINK: bool = True
|
||||
SHOW_THINKING_PROCESS: bool = True
|
||||
THINKING_MODELS: List[str] = []
|
||||
|
||||
@@ -329,7 +329,7 @@ async def verify_key(api_key: str, chat_service: GeminiChatService = Depends(get
|
||||
parts=[{"text": "hi"}],
|
||||
)
|
||||
],
|
||||
generation_config={"temperature": 0.7, "top_p": 1.0, "max_output_tokens": 10}
|
||||
generation_config={"temperature": 0.7, "topP": 1.0, "maxOutputTokens": 10}
|
||||
)
|
||||
|
||||
response = await chat_service.generate_content(
|
||||
@@ -339,7 +339,9 @@ async def verify_key(api_key: str, chat_service: GeminiChatService = Depends(get
|
||||
)
|
||||
|
||||
if response:
|
||||
return JSONResponse({"status": "valid"})
|
||||
# 如果密钥验证成功,则重置其失败计数
|
||||
await key_manager.reset_key_failure_count(api_key)
|
||||
return JSONResponse({"status": "valid"})
|
||||
except Exception as e:
|
||||
logger.error(f"Key verification failed: {str(e)}")
|
||||
|
||||
@@ -374,7 +376,7 @@ async def verify_selected_keys(
|
||||
try:
|
||||
gemini_request = GeminiRequest(
|
||||
contents=[GeminiContent(role="user", parts=[{"text": "hi"}])],
|
||||
generation_config={"temperature": 0.7, "top_p": 1.0, "max_output_tokens": 10}
|
||||
generation_config={"temperature": 0.7, "topP": 1.0, "maxOutputTokens": 10}
|
||||
)
|
||||
await chat_service.generate_content(
|
||||
settings.TEST_MODEL,
|
||||
@@ -382,6 +384,8 @@ async def verify_selected_keys(
|
||||
api_key
|
||||
)
|
||||
successful_keys.append(api_key)
|
||||
# 如果密钥验证成功,则重置其失败计数
|
||||
await key_manager.reset_key_failure_count(api_key)
|
||||
return api_key, "valid", None
|
||||
except Exception as e:
|
||||
error_message = str(e)
|
||||
|
||||
@@ -119,15 +119,32 @@ def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
tool["codeExecution"] = {}
|
||||
if model.endswith("-search"):
|
||||
tool["googleSearch"] = {}
|
||||
|
||||
|
||||
real_model = _get_real_model(model)
|
||||
if real_model in settings.URL_CONTEXT_MODELS and settings.URL_CONTEXT_ENABLED:
|
||||
tool["urlContext"] = {}
|
||||
|
||||
# 解决 "Tool use with function calling is unsupported" 问题
|
||||
if tool.get("functionDeclarations"):
|
||||
tool.pop("googleSearch", None)
|
||||
tool.pop("codeExecution", None)
|
||||
tool.pop("urlContext", None)
|
||||
|
||||
return [tool] if tool else []
|
||||
|
||||
|
||||
def _get_real_model(model: str) -> str:
|
||||
if model.endswith("-search"):
|
||||
model = model[:-7]
|
||||
if model.endswith("-image"):
|
||||
model = model[:-6]
|
||||
if model.endswith("-non-thinking"):
|
||||
model = model[:-13]
|
||||
if "-search" in model and "-non-thinking" in model:
|
||||
model = model[:-20]
|
||||
return model
|
||||
|
||||
|
||||
def _get_safety_settings(model: str) -> List[Dict[str, str]]:
|
||||
"""获取安全设置"""
|
||||
if model == "gemini-2.0-flash-exp":
|
||||
@@ -157,11 +174,12 @@ def _filter_empty_parts(contents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
|
||||
def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
||||
"""构建请求payload"""
|
||||
request_dict = request.model_dump(by_alias=True, exclude_none=False)
|
||||
request_dict = request.model_dump(exclude_none=False)
|
||||
if request.generationConfig:
|
||||
if request.generationConfig.maxOutputTokens is None:
|
||||
# 如果未指定最大输出长度,则不传递该字段,解决截断的问题
|
||||
request_dict["generationConfig"].pop("maxOutputTokens")
|
||||
if "maxOutputTokens" in request_dict["generationConfig"]:
|
||||
request_dict["generationConfig"].pop("maxOutputTokens")
|
||||
|
||||
# 检查是否为TTS模型
|
||||
is_tts_model = "tts" in model.lower()
|
||||
@@ -205,7 +223,10 @@ def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
||||
else:
|
||||
# 客户端没有提供思考配置,使用默认配置
|
||||
if model.endswith("-non-thinking"):
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
if "gemini-2.5-pro" in model:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 128}
|
||||
else:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
elif model in settings.THINKING_BUDGET_MAP:
|
||||
if settings.SHOW_THINKING_PROCESS:
|
||||
payload["generationConfig"]["thinkingConfig"] = {
|
||||
|
||||
@@ -87,6 +87,10 @@ def _build_tools(
|
||||
|
||||
if model.endswith("-search"):
|
||||
tool["googleSearch"] = {}
|
||||
|
||||
real_model = _get_real_model(model)
|
||||
if real_model in settings.URL_CONTEXT_MODELS and settings.URL_CONTEXT_ENABLED:
|
||||
tool["urlContext"] = {}
|
||||
|
||||
# 将 request 中的 tools 合并到 tools 中
|
||||
if request.tools:
|
||||
@@ -126,10 +130,23 @@ def _build_tools(
|
||||
if tool.get("functionDeclarations"):
|
||||
tool.pop("googleSearch", None)
|
||||
tool.pop("codeExecution", None)
|
||||
tool.pop("urlContext",None)
|
||||
|
||||
return [tool] if tool else []
|
||||
|
||||
|
||||
def _get_real_model(model: str) -> str:
|
||||
if model.endswith("-search"):
|
||||
model = model[:-7]
|
||||
if model.endswith("-image"):
|
||||
model = model[:-6]
|
||||
if model.endswith("-non-thinking"):
|
||||
model = model[:-13]
|
||||
if "-search" in model and "-non-thinking" in model:
|
||||
model = model[:-20]
|
||||
return model
|
||||
|
||||
|
||||
def _get_safety_settings(model: str) -> List[Dict[str, str]]:
|
||||
"""获取安全设置"""
|
||||
# if (
|
||||
@@ -184,7 +201,10 @@ def _build_payload(
|
||||
payload["generationConfig"]["responseModalities"] = ["Text", "Image"]
|
||||
|
||||
if request.model.endswith("-non-thinking"):
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
if "gemini-2.5-pro" in request.model:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 128}
|
||||
else:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
|
||||
if request.model in settings.THINKING_BUDGET_MAP:
|
||||
if settings.SHOW_THINKING_PROCESS:
|
||||
|
||||
@@ -97,15 +97,32 @@ def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
tool["codeExecution"] = {}
|
||||
if model.endswith("-search"):
|
||||
tool["googleSearch"] = {}
|
||||
|
||||
real_model = _get_real_model(model)
|
||||
if real_model in settings.URL_CONTEXT_MODELS and settings.URL_CONTEXT_ENABLED:
|
||||
tool["urlContext"] = {}
|
||||
|
||||
# 解决 "Tool use with function calling is unsupported" 问题
|
||||
if tool.get("functionDeclarations"):
|
||||
tool.pop("googleSearch", None)
|
||||
tool.pop("codeExecution", None)
|
||||
tool.pop("urlContext", None)
|
||||
|
||||
return [tool] if tool else []
|
||||
|
||||
|
||||
def _get_real_model(model: str) -> str:
|
||||
if model.endswith("-search"):
|
||||
model = model[:-7]
|
||||
if model.endswith("-image"):
|
||||
model = model[:-6]
|
||||
if model.endswith("-non-thinking"):
|
||||
model = model[:-13]
|
||||
if "-search" in model and "-non-thinking" in model:
|
||||
model = model[:-20]
|
||||
return model
|
||||
|
||||
|
||||
def _get_safety_settings(model: str) -> List[Dict[str, str]]:
|
||||
"""获取安全设置"""
|
||||
if model == "gemini-2.0-flash-exp":
|
||||
@@ -115,7 +132,7 @@ def _get_safety_settings(model: str) -> List[Dict[str, str]]:
|
||||
|
||||
def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
||||
"""构建请求payload"""
|
||||
request_dict = request.model_dump(by_alias=True, exclude_none=False)
|
||||
request_dict = request.model_dump(exclude_none=False)
|
||||
if request.generationConfig:
|
||||
if request.generationConfig.maxOutputTokens is None:
|
||||
# 如果未指定最大输出长度,则不传递该字段,解决截断的问题
|
||||
@@ -144,7 +161,10 @@ def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
||||
else:
|
||||
# 客户端没有提供思考配置,使用默认配置
|
||||
if model.endswith("-non-thinking"):
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
if "gemini-2.5-pro" in model:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 128}
|
||||
else:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
elif model in settings.THINKING_BUDGET_MAP:
|
||||
if settings.SHOW_THINKING_PROCESS:
|
||||
payload["generationConfig"]["thinkingConfig"] = {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import time
|
||||
import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict
|
||||
from app.service.chat.gemini_chat_service import GeminiChatService
|
||||
from app.service.tts.native.tts_response_handler import TTSResponseHandler
|
||||
from app.domain.gemini_models import GeminiRequest
|
||||
@@ -69,7 +69,7 @@ class TTSGeminiChatService(GeminiChatService):
|
||||
# 构建TTS专用的payload - 不包含tools和safetySettings
|
||||
from app.service.chat.gemini_chat_service import _filter_empty_parts
|
||||
|
||||
request_dict = request.model_dump(by_alias=True, exclude_none=False)
|
||||
request_dict = request.model_dump(exclude_none=False)
|
||||
|
||||
# 构建TTS专用的简化payload
|
||||
payload = {
|
||||
@@ -130,7 +130,7 @@ class TTSGeminiChatService(GeminiChatService):
|
||||
error_type="tts-api-error",
|
||||
error_log=error_msg,
|
||||
error_code=status_code,
|
||||
request_msg=request.model_dump(by_alias=True, exclude_none=False)
|
||||
request_msg=request.model_dump(exclude_none=False)
|
||||
)
|
||||
|
||||
logger.error(f"TTS API call failed: {error_msg}")
|
||||
|
||||
@@ -13,7 +13,7 @@ const SHOW_CLASS = "show"; // For modals
|
||||
const API_KEY_REGEX = /AIzaSy\S{33}/g;
|
||||
const PROXY_REGEX =
|
||||
/(?:https?|socks5):\/\/(?:[^:@\/]+(?::[^@\/]+)?@)?(?:[^:\/\s]+)(?::\d+)?/g;
|
||||
const VERTEX_API_KEY_REGEX = /AQ\.[a-zA-Z0-9_]{50}/g; // 新增 Vertex Express API Key 正则
|
||||
const VERTEX_API_KEY_REGEX = /AQ\.[a-zA-Z0-9_\-]{50}/g; // 新增 Vertex Express API Key 正则
|
||||
const MASKED_VALUE = "••••••••";
|
||||
|
||||
// DOM Elements - Global Scope for frequently accessed elements
|
||||
@@ -713,7 +713,13 @@ async function initConfig() {
|
||||
config.SAFETY_SETTINGS = []; // 默认为空数组
|
||||
}
|
||||
// --- 结束:处理 SAFETY_SETTINGS 默认值 ---
|
||||
|
||||
if (typeof config.URL_CONTEXT_ENABLED === "undefined") {
|
||||
config.URL_CONTEXT_ENABLED = true;
|
||||
}
|
||||
if (!config.URL_CONTEXT_MODELS || !Array.isArray(config.URL_CONTEXT_MODELS)) {
|
||||
config.URL_CONTEXT_MODELS = [];
|
||||
}
|
||||
|
||||
// --- 新增:处理自动删除错误日志配置的默认值 ---
|
||||
if (typeof config.AUTO_DELETE_ERROR_LOGS_ENABLED === "undefined") {
|
||||
config.AUTO_DELETE_ERROR_LOGS_ENABLED = false;
|
||||
@@ -1462,7 +1468,7 @@ function addArrayItem(key) {
|
||||
const modelId = addArrayItemWithValue(key, newItemValue); // This adds the DOM element
|
||||
|
||||
if (key === "THINKING_MODELS" && modelId) {
|
||||
createAndAppendBudgetMapItem(newItemValue, 0, modelId); // Default budget 0
|
||||
createAndAppendBudgetMapItem(newItemValue, -1, modelId); // Default budget -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1572,7 +1578,7 @@ function createAndAppendBudgetMapItem(mapKey, mapValue, modelId) {
|
||||
const valueInput = document.createElement("input");
|
||||
valueInput.type = "number";
|
||||
const intValue = parseInt(mapValue, 10);
|
||||
valueInput.value = isNaN(intValue) ? 0 : intValue;
|
||||
valueInput.value = isNaN(intValue) ? -1 : intValue;
|
||||
valueInput.placeholder = "预算 (整数)";
|
||||
valueInput.className = `${MAP_VALUE_INPUT_CLASS} w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50`;
|
||||
valueInput.min = -1;
|
||||
@@ -1733,7 +1739,7 @@ function collectFormData() {
|
||||
formData["THINKING_BUDGET_MAP"][keyInput.value.trim()] = isNaN(
|
||||
budgetValue
|
||||
)
|
||||
? 0
|
||||
? -1
|
||||
: budgetValue;
|
||||
}
|
||||
});
|
||||
@@ -2284,7 +2290,7 @@ function handleModelSelection(selectedModelId) {
|
||||
);
|
||||
if (currentModelHelperTarget.targetKey === "THINKING_MODELS" && modelId) {
|
||||
// Automatically add corresponding budget map item with default budget 0
|
||||
createAndAppendBudgetMapItem(selectedModelId, 0, modelId);
|
||||
createAndAppendBudgetMapItem(selectedModelId, -1, modelId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1421,6 +1421,59 @@ endblock %} {% block head_extra_styles %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 启用网址上下文 -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<label for="URL_CONTEXT_ENABLED" class="font-semibold text-gray-700"
|
||||
>启用网址上下文</label
|
||||
>
|
||||
<div
|
||||
class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="URL_CONTEXT_ENABLED"
|
||||
id="URL_CONTEXT_ENABLED"
|
||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
|
||||
/>
|
||||
<label
|
||||
for="URL_CONTEXT_ENABLED"
|
||||
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
|
||||
></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网址上下文模型列表 -->
|
||||
<div class="mb-6">
|
||||
<label
|
||||
for="URL_CONTEXT_MODELS"
|
||||
class="block font-semibold mb-2 text-gray-700"
|
||||
>网址上下文模型列表</label
|
||||
>
|
||||
<div class="array-container" id="URL_CONTEXT_MODELS_container">
|
||||
<!-- 数组项将在这里动态添加 -->
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
title="从列表选择模型添加到下方"
|
||||
class="model-helper-trigger-btn p-2 rounded-md text-violet-300 hover:bg-violet-700 transition-colors"
|
||||
data-target-array-key="URL_CONTEXT_MODELS"
|
||||
>
|
||||
<i class="fas fa-list-ul"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2"
|
||||
onclick="addArrayItem('URL_CONTEXT_MODELS')"
|
||||
>
|
||||
<i class="fas fa-plus"></i> 添加模型
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-gray-500 mt-1 block"
|
||||
>支持网址上下文功能的模型列表</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 显示搜索链接 -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<label for="SHOW_SEARCH_LINK" class="font-semibold text-gray-700"
|
||||
@@ -1523,7 +1576,7 @@ endblock %} {% block head_extra_styles %}
|
||||
</button>
|
||||
</div> -->
|
||||
<small class="text-gray-500 mt-1 block"
|
||||
>为每个思考模型设置预算(整数,最大值
|
||||
>为每个思考模型设置预算(-1为auto,最大值
|
||||
32767),此项与上方模型列表自动关联。</small
|
||||
>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user