mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-07-04 14:21:27 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebfa1d247c | ||
|
|
cdb85ef9b7 | ||
|
|
7006522c13 | ||
|
|
530c958afc | ||
|
|
57d861b578 | ||
|
|
99664298b9 | ||
|
|
a6fe5a7022 |
@@ -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` |
|
||||
|
||||
@@ -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] = []
|
||||
|
||||
@@ -80,6 +80,21 @@ def _clean_json_schema_properties(obj: Any) -> Any:
|
||||
def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""构建工具"""
|
||||
|
||||
def _has_function_call(contents: List[Dict[str, Any]]) -> bool:
|
||||
"""检查内容中是否包含 functionCall"""
|
||||
if not contents or not isinstance(contents, list):
|
||||
return False
|
||||
for content in contents:
|
||||
if not content or not isinstance(content, dict) or "parts" not in content:
|
||||
continue
|
||||
parts = content.get("parts", [])
|
||||
if not parts or not isinstance(parts, list):
|
||||
continue
|
||||
for part in parts:
|
||||
if isinstance(part, dict) and "functionCall" in part:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _merge_tools(tools: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
record = dict()
|
||||
for item in tools:
|
||||
@@ -119,15 +134,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"):
|
||||
if tool.get("functionDeclarations") or _has_function_call(payload.get("contents", [])):
|
||||
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":
|
||||
@@ -206,8 +238,11 @@ def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
||||
else:
|
||||
# 客户端没有提供思考配置,使用默认配置
|
||||
if model.endswith("-non-thinking"):
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
elif model in settings.THINKING_BUDGET_MAP:
|
||||
if "gemini-2.5-pro" in model:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 128}
|
||||
else:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
elif _get_real_model(model) in settings.THINKING_BUDGET_MAP:
|
||||
if settings.SHOW_THINKING_PROCESS:
|
||||
payload["generationConfig"]["thinkingConfig"] = {
|
||||
"thinkingBudget": settings.THINKING_BUDGET_MAP.get(model,1000),
|
||||
|
||||
@@ -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,9 +201,12 @@ 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 _get_real_model(request.model) in settings.THINKING_BUDGET_MAP:
|
||||
if settings.SHOW_THINKING_PROCESS:
|
||||
payload["generationConfig"]["thinkingConfig"] = {
|
||||
"thinkingBudget": settings.THINKING_BUDGET_MAP.get(request.model, 1000),
|
||||
|
||||
@@ -58,6 +58,21 @@ def _clean_json_schema_properties(obj: Any) -> Any:
|
||||
def _build_tools(model: str, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""构建工具"""
|
||||
|
||||
def _has_function_call(contents: List[Dict[str, Any]]) -> bool:
|
||||
"""检查内容中是否包含 functionCall"""
|
||||
if not contents or not isinstance(contents, list):
|
||||
return False
|
||||
for content in contents:
|
||||
if not content or not isinstance(content, dict) or "parts" not in content:
|
||||
continue
|
||||
parts = content.get("parts", [])
|
||||
if not parts or not isinstance(parts, list):
|
||||
continue
|
||||
for part in parts:
|
||||
if isinstance(part, dict) and "functionCall" in part:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _merge_tools(tools: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
record = dict()
|
||||
for item in tools:
|
||||
@@ -97,15 +112,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"):
|
||||
if tool.get("functionDeclarations") or _has_function_call(payload.get("contents", [])):
|
||||
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":
|
||||
@@ -144,8 +176,11 @@ def _build_payload(model: str, request: GeminiRequest) -> Dict[str, Any]:
|
||||
else:
|
||||
# 客户端没有提供思考配置,使用默认配置
|
||||
if model.endswith("-non-thinking"):
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
elif model in settings.THINKING_BUDGET_MAP:
|
||||
if "gemini-2.5-pro" in model:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 128}
|
||||
else:
|
||||
payload["generationConfig"]["thinkingConfig"] = {"thinkingBudget": 0}
|
||||
elif _get_real_model(model) in settings.THINKING_BUDGET_MAP:
|
||||
if settings.SHOW_THINKING_PROCESS:
|
||||
payload["generationConfig"]["thinkingConfig"] = {
|
||||
"thinkingBudget": settings.THINKING_BUDGET_MAP.get(model,1000),
|
||||
|
||||
@@ -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