mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-06 20:42:43 +08:00
refactor: remove legacy LLM_DISABLE_THINKING and LLM_REASONING_EFFORT config, unify thinking_level handling
- Eliminate support for LLM_DISABLE_THINKING and LLM_REASONING_EFFORT in config, code, and tests - Simplify LLM thinking level logic to rely solely on LLM_THINKING_LEVEL - Refactor LLMHelper and related endpoints to remove legacy parameter handling - Update system API and test utilities to match new configuration structure - Minor code cleanup and formatting improvements
This commit is contained in:
@@ -12,6 +12,7 @@ from anyio import Path as AsyncPath
|
|||||||
from app.helper.sites import SitesHelper # noqa # noqa
|
from app.helper.sites import SitesHelper # noqa # noqa
|
||||||
from fastapi import APIRouter, Body, Depends, HTTPException, Header, Request, Response
|
from fastapi import APIRouter, Body, Depends, HTTPException, Header, Request, Response
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.chain.mediaserver import MediaServerChain
|
from app.chain.mediaserver import MediaServerChain
|
||||||
@@ -29,14 +30,14 @@ from app.db.user_oper import (
|
|||||||
get_current_active_superuser_async,
|
get_current_active_superuser_async,
|
||||||
get_current_active_user_async,
|
get_current_active_user_async,
|
||||||
)
|
)
|
||||||
from app.helper.llm import LLMHelper, LLMTestError, LLMTestTimeout
|
from app.helper.image import ImageHelper
|
||||||
|
from app.helper.llm import LLMHelper, LLMTestTimeout
|
||||||
from app.helper.mediaserver import MediaServerHelper
|
from app.helper.mediaserver import MediaServerHelper
|
||||||
from app.helper.message import MessageHelper
|
from app.helper.message import MessageHelper
|
||||||
from app.helper.progress import ProgressHelper
|
from app.helper.progress import ProgressHelper
|
||||||
from app.helper.rule import RuleHelper
|
from app.helper.rule import RuleHelper
|
||||||
from app.helper.subscribe import SubscribeHelper
|
from app.helper.subscribe import SubscribeHelper
|
||||||
from app.helper.system import SystemHelper
|
from app.helper.system import SystemHelper
|
||||||
from app.helper.image import ImageHelper
|
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.scheduler import Scheduler
|
from app.scheduler import Scheduler
|
||||||
from app.schemas import ConfigChangeEventData
|
from app.schemas import ConfigChangeEventData
|
||||||
@@ -45,7 +46,6 @@ from app.utils.crypto import HashUtils
|
|||||||
from app.utils.http import RequestUtils, AsyncRequestUtils
|
from app.utils.http import RequestUtils, AsyncRequestUtils
|
||||||
from app.utils.security import SecurityUtils
|
from app.utils.security import SecurityUtils
|
||||||
from app.utils.url import UrlUtils
|
from app.utils.url import UrlUtils
|
||||||
from pydantic import BaseModel
|
|
||||||
from version import APP_VERSION
|
from version import APP_VERSION
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -58,8 +58,6 @@ class LlmTestRequest(BaseModel):
|
|||||||
provider: Optional[str] = None
|
provider: Optional[str] = None
|
||||||
model: Optional[str] = None
|
model: Optional[str] = None
|
||||||
thinking_level: Optional[str] = None
|
thinking_level: Optional[str] = None
|
||||||
disable_thinking: Optional[bool] = None
|
|
||||||
reasoning_effort: Optional[str] = None
|
|
||||||
api_key: Optional[str] = None
|
api_key: Optional[str] = None
|
||||||
base_url: Optional[str] = None
|
base_url: Optional[str] = None
|
||||||
|
|
||||||
@@ -271,94 +269,6 @@ def _build_nettest_rules() -> list[dict[str, Any]]:
|
|||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
|
||||||
def _build_llm_test_data(
|
|
||||||
duration_ms: Optional[int] = None,
|
|
||||||
provider: Optional[str] = None,
|
|
||||||
model: Optional[str] = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
构造 LLM 测试接口的基础返回数据。
|
|
||||||
"""
|
|
||||||
data = {
|
|
||||||
"provider": provider if provider is not None else settings.LLM_PROVIDER,
|
|
||||||
"model": model if model is not None else settings.LLM_MODEL,
|
|
||||||
}
|
|
||||||
if duration_ms is not None:
|
|
||||||
data["duration_ms"] = duration_ms
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_llm_test_value(
|
|
||||||
value: Optional[str], *, empty_as_none: bool = False
|
|
||||||
) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
清理来自前端的 LLM 测试字段。
|
|
||||||
"""
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
stripped = value.strip()
|
|
||||||
if empty_as_none and not stripped:
|
|
||||||
return None
|
|
||||||
return stripped
|
|
||||||
|
|
||||||
|
|
||||||
def _build_llm_test_snapshot(payload: Optional[LlmTestRequest] = None) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
冻结当前 LLM 测试所需配置。
|
|
||||||
|
|
||||||
优先使用前端传入的临时参数;未传入时回退到已保存配置,兼容旧调用。
|
|
||||||
"""
|
|
||||||
provider = settings.LLM_PROVIDER
|
|
||||||
model = settings.LLM_MODEL
|
|
||||||
thinking_level = _normalize_llm_test_value(
|
|
||||||
getattr(settings, "LLM_THINKING_LEVEL", None), empty_as_none=True
|
|
||||||
)
|
|
||||||
disable_thinking = bool(getattr(settings, "LLM_DISABLE_THINKING", False))
|
|
||||||
reasoning_effort = _normalize_llm_test_value(
|
|
||||||
getattr(settings, "LLM_REASONING_EFFORT", None), empty_as_none=True
|
|
||||||
)
|
|
||||||
api_key = settings.LLM_API_KEY
|
|
||||||
base_url = settings.LLM_BASE_URL
|
|
||||||
enabled = bool(settings.AI_AGENT_ENABLE)
|
|
||||||
|
|
||||||
if payload:
|
|
||||||
if payload.enabled is not None:
|
|
||||||
enabled = bool(payload.enabled)
|
|
||||||
if payload.provider is not None:
|
|
||||||
provider = _normalize_llm_test_value(payload.provider) or ""
|
|
||||||
if payload.model is not None:
|
|
||||||
model = _normalize_llm_test_value(payload.model) or ""
|
|
||||||
if payload.thinking_level is not None:
|
|
||||||
thinking_level = _normalize_llm_test_value(
|
|
||||||
payload.thinking_level, empty_as_none=True
|
|
||||||
)
|
|
||||||
if payload.disable_thinking is not None:
|
|
||||||
disable_thinking = bool(payload.disable_thinking)
|
|
||||||
if payload.reasoning_effort is not None:
|
|
||||||
reasoning_effort = _normalize_llm_test_value(
|
|
||||||
payload.reasoning_effort, empty_as_none=True
|
|
||||||
)
|
|
||||||
if payload.api_key is not None:
|
|
||||||
api_key = _normalize_llm_test_value(payload.api_key, empty_as_none=True)
|
|
||||||
if payload.base_url is not None:
|
|
||||||
base_url = _normalize_llm_test_value(payload.base_url, empty_as_none=True)
|
|
||||||
|
|
||||||
if thinking_level is not None:
|
|
||||||
disable_thinking = None
|
|
||||||
reasoning_effort = None
|
|
||||||
|
|
||||||
return {
|
|
||||||
"enabled": enabled,
|
|
||||||
"provider": provider,
|
|
||||||
"model": model,
|
|
||||||
"thinking_level": thinking_level,
|
|
||||||
"disable_thinking": disable_thinking,
|
|
||||||
"reasoning_effort": reasoning_effort,
|
|
||||||
"api_key": api_key,
|
|
||||||
"base_url": base_url,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_llm_test_error(message: str, api_key: Optional[str] = None) -> str:
|
def _sanitize_llm_test_error(message: str, api_key: Optional[str] = None) -> str:
|
||||||
"""
|
"""
|
||||||
清理错误信息中的敏感字段,避免回显密钥。
|
清理错误信息中的敏感字段,避免回显密钥。
|
||||||
@@ -450,12 +360,12 @@ async def _close_nettest_response(response: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
async def fetch_image(
|
async def fetch_image(
|
||||||
url: str,
|
url: str,
|
||||||
proxy: Optional[bool] = None,
|
proxy: Optional[bool] = None,
|
||||||
use_cache: bool = False,
|
use_cache: bool = False,
|
||||||
if_none_match: Optional[str] = None,
|
if_none_match: Optional[str] = None,
|
||||||
cookies: Optional[str | dict] = None,
|
cookies: Optional[str | dict] = None,
|
||||||
allowed_domains: Optional[set[str]] = None,
|
allowed_domains: Optional[set[str]] = None,
|
||||||
) -> Optional[Response]:
|
) -> Optional[Response]:
|
||||||
"""
|
"""
|
||||||
处理图片缓存逻辑,支持HTTP缓存和磁盘缓存
|
处理图片缓存逻辑,支持HTTP缓存和磁盘缓存
|
||||||
@@ -477,6 +387,7 @@ async def fetch_image(
|
|||||||
use_cache=use_cache,
|
use_cache=use_cache,
|
||||||
cookies=cookies,
|
cookies=cookies,
|
||||||
)
|
)
|
||||||
|
|
||||||
if content:
|
if content:
|
||||||
# 检查 If-None-Match
|
# 检查 If-None-Match
|
||||||
etag = HashUtils.md5(content)
|
etag = HashUtils.md5(content)
|
||||||
@@ -489,16 +400,17 @@ async def fetch_image(
|
|||||||
media_type=UrlUtils.get_mime_type(url, "image/jpeg"),
|
media_type=UrlUtils.get_mime_type(url, "image/jpeg"),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
)
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/img/{proxy}", summary="图片代理")
|
@router.get("/img/{proxy}", summary="图片代理")
|
||||||
async def proxy_img(
|
async def proxy_img(
|
||||||
imgurl: str,
|
imgurl: str,
|
||||||
proxy: bool = False,
|
proxy: bool = False,
|
||||||
cache: bool = False,
|
cache: bool = False,
|
||||||
use_cookies: bool = False,
|
use_cookies: bool = False,
|
||||||
if_none_match: Annotated[str | None, Header()] = None,
|
if_none_match: Annotated[str | None, Header()] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_resource_token),
|
_: schemas.TokenPayload = Depends(verify_resource_token),
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
图片代理,可选是否使用代理服务器,支持 HTTP 缓存
|
图片代理,可选是否使用代理服务器,支持 HTTP 缓存
|
||||||
@@ -527,9 +439,9 @@ async def proxy_img(
|
|||||||
|
|
||||||
@router.get("/cache/image", summary="图片缓存")
|
@router.get("/cache/image", summary="图片缓存")
|
||||||
async def cache_img(
|
async def cache_img(
|
||||||
url: str,
|
url: str,
|
||||||
if_none_match: Annotated[str | None, Header()] = None,
|
if_none_match: Annotated[str | None, Header()] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_resource_token),
|
_: schemas.TokenPayload = Depends(verify_resource_token),
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""
|
"""
|
||||||
本地缓存图片文件,支持 HTTP 缓存,如果启用全局图片缓存,则使用磁盘缓存
|
本地缓存图片文件,支持 HTTP 缓存,如果启用全局图片缓存,则使用磁盘缓存
|
||||||
@@ -623,7 +535,7 @@ async def get_env_setting(_: User = Depends(get_current_active_user_async)):
|
|||||||
|
|
||||||
@router.post("/env", summary="更新系统配置", response_model=schemas.Response)
|
@router.post("/env", summary="更新系统配置", response_model=schemas.Response)
|
||||||
async def set_env_setting(
|
async def set_env_setting(
|
||||||
env: dict, _: User = Depends(get_current_active_superuser_async)
|
env: dict, _: User = Depends(get_current_active_superuser_async)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
更新系统环境变量(仅管理员)
|
更新系统环境变量(仅管理员)
|
||||||
@@ -658,9 +570,9 @@ async def set_env_setting(
|
|||||||
|
|
||||||
@router.get("/progress/{process_type}", summary="实时进度")
|
@router.get("/progress/{process_type}", summary="实时进度")
|
||||||
async def get_progress(
|
async def get_progress(
|
||||||
request: Request,
|
request: Request,
|
||||||
process_type: str,
|
process_type: str,
|
||||||
_: schemas.TokenPayload = Depends(verify_resource_token),
|
_: schemas.TokenPayload = Depends(verify_resource_token),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
实时获取处理进度,返回格式为SSE
|
实时获取处理进度,返回格式为SSE
|
||||||
@@ -695,9 +607,9 @@ async def get_setting(key: str, _: User = Depends(get_current_active_user_async)
|
|||||||
|
|
||||||
@router.post("/setting/{key}", summary="更新系统设置", response_model=schemas.Response)
|
@router.post("/setting/{key}", summary="更新系统设置", response_model=schemas.Response)
|
||||||
async def set_setting(
|
async def set_setting(
|
||||||
key: str,
|
key: str,
|
||||||
value: Annotated[Union[list, dict, bool, int, str] | None, Body()] = None,
|
value: Annotated[Union[list, dict, bool, int, str] | None, Body()] = None,
|
||||||
_: User = Depends(get_current_active_superuser_async),
|
_: User = Depends(get_current_active_superuser_async),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
更新系统设置(仅管理员)
|
更新系统设置(仅管理员)
|
||||||
@@ -731,10 +643,10 @@ async def set_setting(
|
|||||||
|
|
||||||
@router.get("/llm-models", summary="获取LLM模型列表", response_model=schemas.Response)
|
@router.get("/llm-models", summary="获取LLM模型列表", response_model=schemas.Response)
|
||||||
async def get_llm_models(
|
async def get_llm_models(
|
||||||
provider: str,
|
provider: str,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
base_url: Optional[str] = None,
|
base_url: Optional[str] = None,
|
||||||
_: User = Depends(get_current_active_user_async),
|
_: User = Depends(get_current_active_user_async),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取LLM模型列表
|
获取LLM模型列表
|
||||||
@@ -750,28 +662,33 @@ async def get_llm_models(
|
|||||||
|
|
||||||
@router.post("/llm-test", summary="测试LLM调用", response_model=schemas.Response)
|
@router.post("/llm-test", summary="测试LLM调用", response_model=schemas.Response)
|
||||||
async def llm_test(
|
async def llm_test(
|
||||||
payload: Annotated[Optional[LlmTestRequest], Body()] = None,
|
payload: Annotated[Optional[LlmTestRequest], Body()] = None,
|
||||||
_: User = Depends(get_current_active_superuser_async),
|
_: User = Depends(get_current_active_superuser_async),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
使用传入配置或当前已保存配置执行一次最小 LLM 调用。
|
使用传入配置或当前已保存配置执行一次最小 LLM 调用。
|
||||||
"""
|
"""
|
||||||
snapshot = _build_llm_test_snapshot(payload)
|
if not payload:
|
||||||
data = _build_llm_test_data(
|
return schemas.Response(success=False, message="请配置智能助手LLM相关参数后再进行测试")
|
||||||
provider=snapshot["provider"],
|
|
||||||
model=snapshot["model"],
|
if not payload.provider or not payload.model:
|
||||||
)
|
return schemas.Response(success=False, message="请配置LLM提供商和模型")
|
||||||
if not snapshot["enabled"]:
|
|
||||||
|
data = {
|
||||||
|
"provider": payload.provider,
|
||||||
|
"model": payload.model,
|
||||||
|
}
|
||||||
|
if not payload.enabled:
|
||||||
return schemas.Response(success=False, message="请先启用智能助手", data=data)
|
return schemas.Response(success=False, message="请先启用智能助手", data=data)
|
||||||
|
|
||||||
if not snapshot["api_key"]:
|
if not payload.api_key or not payload.api_key.strip():
|
||||||
return schemas.Response(
|
return schemas.Response(
|
||||||
success=False,
|
success=False,
|
||||||
message="请先配置 LLM API Key",
|
message="请先配置 LLM API Key",
|
||||||
data=data,
|
data=data,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (snapshot["model"] or "").strip():
|
if not payload.model or not payload.model.strip():
|
||||||
return schemas.Response(
|
return schemas.Response(
|
||||||
success=False,
|
success=False,
|
||||||
message="请先配置 LLM 模型",
|
message="请先配置 LLM 模型",
|
||||||
@@ -780,52 +697,36 @@ async def llm_test(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
result = await LLMHelper.test_current_settings(
|
result = await LLMHelper.test_current_settings(
|
||||||
provider=snapshot["provider"],
|
provider=payload.provider,
|
||||||
model=snapshot["model"],
|
model=payload.model,
|
||||||
thinking_level=snapshot["thinking_level"],
|
thinking_level=payload.thinking_level,
|
||||||
disable_thinking=snapshot["disable_thinking"],
|
api_key=payload.api_key,
|
||||||
reasoning_effort=snapshot["reasoning_effort"],
|
base_url=payload.base_url,
|
||||||
api_key=snapshot["api_key"],
|
|
||||||
base_url=snapshot["base_url"],
|
|
||||||
)
|
)
|
||||||
if not result.get("reply_preview"):
|
if not result.get("reply_preview"):
|
||||||
return schemas.Response(
|
return schemas.Response(
|
||||||
success=False,
|
success=False,
|
||||||
message="模型响应为空",
|
message="模型响应为空"
|
||||||
data=_build_llm_test_data(
|
|
||||||
result.get("duration_ms"),
|
|
||||||
provider=snapshot["provider"],
|
|
||||||
model=snapshot["model"],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return schemas.Response(success=True, data=result)
|
return schemas.Response(success=True, data=result)
|
||||||
except (LLMTestTimeout, TimeoutError) as err:
|
except (LLMTestTimeout, TimeoutError) as err:
|
||||||
|
logger.warning(err)
|
||||||
return schemas.Response(
|
return schemas.Response(
|
||||||
success=False,
|
success=False,
|
||||||
message="LLM 调用超时",
|
message="LLM 调用超时"
|
||||||
data=_build_llm_test_data(
|
|
||||||
getattr(err, "duration_ms", None),
|
|
||||||
provider=snapshot["provider"],
|
|
||||||
model=snapshot["model"],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return schemas.Response(
|
return schemas.Response(
|
||||||
success=False,
|
success=False,
|
||||||
message=_sanitize_llm_test_error(str(err), snapshot["api_key"]),
|
message=_sanitize_llm_test_error(str(err), payload.api_key)
|
||||||
data=_build_llm_test_data(
|
|
||||||
getattr(err, "duration_ms", None),
|
|
||||||
provider=snapshot["provider"],
|
|
||||||
model=snapshot["model"],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/message", summary="实时消息")
|
@router.get("/message", summary="实时消息")
|
||||||
async def get_message(
|
async def get_message(
|
||||||
request: Request,
|
request: Request,
|
||||||
role: Optional[str] = "system",
|
role: Optional[str] = "system",
|
||||||
_: schemas.TokenPayload = Depends(verify_resource_token),
|
_: schemas.TokenPayload = Depends(verify_resource_token),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
实时获取系统消息,返回格式为SSE
|
实时获取系统消息,返回格式为SSE
|
||||||
@@ -848,10 +749,10 @@ async def get_message(
|
|||||||
|
|
||||||
@router.get("/logging", summary="实时日志")
|
@router.get("/logging", summary="实时日志")
|
||||||
async def get_logging(
|
async def get_logging(
|
||||||
request: Request,
|
request: Request,
|
||||||
length: Optional[int] = 50,
|
length: Optional[int] = 50,
|
||||||
logfile: Optional[str] = "moviepilot.log",
|
logfile: Optional[str] = "moviepilot.log",
|
||||||
_: schemas.TokenPayload = Depends(verify_resource_token),
|
_: schemas.TokenPayload = Depends(verify_resource_token),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
实时获取系统日志
|
实时获取系统日志
|
||||||
@@ -862,7 +763,7 @@ async def get_logging(
|
|||||||
log_path = base_path / logfile
|
log_path = base_path / logfile
|
||||||
|
|
||||||
if not await SecurityUtils.async_is_safe_path(
|
if not await SecurityUtils.async_is_safe_path(
|
||||||
base_path=base_path, user_path=log_path, allowed_suffixes={".log"}
|
base_path=base_path, user_path=log_path, allowed_suffixes={".log"}
|
||||||
):
|
):
|
||||||
raise HTTPException(status_code=404, detail="Not Found")
|
raise HTTPException(status_code=404, detail="Not Found")
|
||||||
|
|
||||||
@@ -879,7 +780,7 @@ async def get_logging(
|
|||||||
|
|
||||||
# 读取历史日志
|
# 读取历史日志
|
||||||
async with aiofiles.open(
|
async with aiofiles.open(
|
||||||
log_path, mode="r", encoding="utf-8", errors="ignore"
|
log_path, mode="r", encoding="utf-8", errors="ignore"
|
||||||
) as f:
|
) as f:
|
||||||
# 优化大文件读取策略
|
# 优化大文件读取策略
|
||||||
if file_size > 100 * 1024:
|
if file_size > 100 * 1024:
|
||||||
@@ -891,7 +792,7 @@ async def get_logging(
|
|||||||
# 找到第一个完整的行
|
# 找到第一个完整的行
|
||||||
first_newline = content.find("\n")
|
first_newline = content.find("\n")
|
||||||
if first_newline != -1:
|
if first_newline != -1:
|
||||||
content = content[first_newline + 1 :]
|
content = content[first_newline + 1:]
|
||||||
else:
|
else:
|
||||||
# 小文件直接读取全部内容
|
# 小文件直接读取全部内容
|
||||||
content = await f.read()
|
content = await f.read()
|
||||||
@@ -899,7 +800,7 @@ async def get_logging(
|
|||||||
# 按行分割并添加到队列,只保留非空行
|
# 按行分割并添加到队列,只保留非空行
|
||||||
lines = [line.strip() for line in content.splitlines() if line.strip()]
|
lines = [line.strip() for line in content.splitlines() if line.strip()]
|
||||||
# 只取最后N行
|
# 只取最后N行
|
||||||
for line in lines[-max(length, 50) :]:
|
for line in lines[-max(length, 50):]:
|
||||||
lines_queue.append(line)
|
lines_queue.append(line)
|
||||||
|
|
||||||
# 输出历史日志
|
# 输出历史日志
|
||||||
@@ -908,7 +809,7 @@ async def get_logging(
|
|||||||
|
|
||||||
# 实时监听新日志
|
# 实时监听新日志
|
||||||
async with aiofiles.open(
|
async with aiofiles.open(
|
||||||
log_path, mode="r", encoding="utf-8", errors="ignore"
|
log_path, mode="r", encoding="utf-8", errors="ignore"
|
||||||
) as f:
|
) as f:
|
||||||
# 移动文件指针到文件末尾,继续监听新增内容
|
# 移动文件指针到文件末尾,继续监听新增内容
|
||||||
await f.seek(0, 2)
|
await f.seek(0, 2)
|
||||||
@@ -947,7 +848,7 @@ async def get_logging(
|
|||||||
try:
|
try:
|
||||||
# 使用 aiofiles 异步读取文件
|
# 使用 aiofiles 异步读取文件
|
||||||
async with aiofiles.open(
|
async with aiofiles.open(
|
||||||
log_path, mode="r", encoding="utf-8", errors="ignore"
|
log_path, mode="r", encoding="utf-8", errors="ignore"
|
||||||
) as file:
|
) as file:
|
||||||
text = await file.read()
|
text = await file.read()
|
||||||
# 倒序输出
|
# 倒序输出
|
||||||
@@ -979,10 +880,10 @@ async def latest_version(_: schemas.TokenPayload = Depends(verify_token)):
|
|||||||
|
|
||||||
@router.get("/ruletest", summary="过滤规则测试", response_model=schemas.Response)
|
@router.get("/ruletest", summary="过滤规则测试", response_model=schemas.Response)
|
||||||
def ruletest(
|
def ruletest(
|
||||||
title: str,
|
title: str,
|
||||||
rulegroup_name: str,
|
rulegroup_name: str,
|
||||||
subtitle: Optional[str] = None,
|
subtitle: Optional[str] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_token),
|
_: schemas.TokenPayload = Depends(verify_token),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
过滤规则测试,规则类型 1-订阅,2-洗版,3-搜索
|
过滤规则测试,规则类型 1-订阅,2-洗版,3-搜索
|
||||||
@@ -1037,11 +938,10 @@ async def nettest_targets(_: schemas.TokenPayload = Depends(verify_token)):
|
|||||||
|
|
||||||
@router.get("/nettest", summary="测试网络连通性")
|
@router.get("/nettest", summary="测试网络连通性")
|
||||||
async def nettest(
|
async def nettest(
|
||||||
target_id: Optional[str] = None,
|
target_id: Optional[str] = None,
|
||||||
url: Optional[str] = None,
|
url: Optional[str] = None,
|
||||||
proxy: Optional[bool] = None,
|
include: Optional[str] = None,
|
||||||
include: Optional[str] = None,
|
_: schemas.TokenPayload = Depends(verify_token),
|
||||||
_: schemas.TokenPayload = Depends(verify_token),
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
测试内置目标的网络连通性。
|
测试内置目标的网络连通性。
|
||||||
|
|||||||
@@ -505,12 +505,8 @@ class ConfigModel(BaseModel):
|
|||||||
LLM_PROVIDER: str = "deepseek"
|
LLM_PROVIDER: str = "deepseek"
|
||||||
# LLM模型名称
|
# LLM模型名称
|
||||||
LLM_MODEL: str = "deepseek-chat"
|
LLM_MODEL: str = "deepseek-chat"
|
||||||
# 统一思考模式/深度配置:off/auto/minimal/low/medium/high/max/xhigh
|
# 思考模式/深度配置:off/auto/minimal/low/medium/high/max/xhigh
|
||||||
LLM_THINKING_LEVEL: Optional[str] = None
|
LLM_THINKING_LEVEL: Optional[str] = 'off'
|
||||||
# 兼容旧配置:是否尽量关闭模型的思考/推理能力(新配置优先)
|
|
||||||
LLM_DISABLE_THINKING: bool = True
|
|
||||||
# 兼容旧配置:思考强度(新配置优先)
|
|
||||||
LLM_REASONING_EFFORT: Optional[str] = None
|
|
||||||
# LLM是否支持图片输入,开启后消息图片会按多模态输入发送给模型
|
# LLM是否支持图片输入,开启后消息图片会按多模态输入发送给模型
|
||||||
LLM_SUPPORT_IMAGE_INPUT: bool = True
|
LLM_SUPPORT_IMAGE_INPUT: bool = True
|
||||||
# LLM API密钥
|
# LLM API密钥
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import time
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
|
from langchain_core.messages import convert_to_messages
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
|
|
||||||
@@ -71,7 +73,8 @@ def _get_httpx_proxy_key() -> str:
|
|||||||
if "proxy" in params:
|
if "proxy" in params:
|
||||||
return "proxy"
|
return "proxy"
|
||||||
return "proxies"
|
return "proxies"
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.warning(f"检测 httpx 代理参数失败,默认使用 'proxies':{e}")
|
||||||
return "proxies"
|
return "proxies"
|
||||||
|
|
||||||
|
|
||||||
@@ -111,20 +114,6 @@ def _is_deepseek_thinking_enabled(model_name: str | None, extra_body: Any) -> bo
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _extract_input_messages(input_: Any) -> list[Any]:
|
|
||||||
"""
|
|
||||||
将 chat model 输入还原为原始 BaseMessage 序列。
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
from langchain_core.messages import convert_to_messages
|
|
||||||
|
|
||||||
return list(convert_to_messages(input_))
|
|
||||||
except Exception:
|
|
||||||
if isinstance(input_, list):
|
|
||||||
return list(input_)
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_deepseek_reasoning_content_support():
|
def _patch_deepseek_reasoning_content_support():
|
||||||
"""
|
"""
|
||||||
修补 langchain-deepseek 在 tool-call 场景下遗漏 reasoning_content 回传的问题。
|
修补 langchain-deepseek 在 tool-call 场景下遗漏 reasoning_content 回传的问题。
|
||||||
@@ -153,7 +142,7 @@ def _patch_deepseek_reasoning_content_support():
|
|||||||
payload = original_get_request_payload(self, input_, stop=stop, **kwargs)
|
payload = original_get_request_payload(self, input_, stop=stop, **kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
original_messages = _extract_input_messages(input_)
|
original_messages = convert_to_messages(input_)
|
||||||
payload_messages = payload.get("messages") or []
|
payload_messages = payload.get("messages") or []
|
||||||
model_name = getattr(self, "model_name", None) or getattr(
|
model_name = getattr(self, "model_name", None) or getattr(
|
||||||
self, "model", None
|
self, "model", None
|
||||||
@@ -180,8 +169,8 @@ def _patch_deepseek_reasoning_content_support():
|
|||||||
reasoning_content = ""
|
reasoning_content = ""
|
||||||
if index < len(original_messages):
|
if index < len(original_messages):
|
||||||
additional_kwargs = (
|
additional_kwargs = (
|
||||||
getattr(original_messages[index], "additional_kwargs", None)
|
getattr(original_messages[index], "additional_kwargs", None)
|
||||||
or {}
|
or {}
|
||||||
)
|
)
|
||||||
if isinstance(additional_kwargs, dict):
|
if isinstance(additional_kwargs, dict):
|
||||||
captured_reasoning = additional_kwargs.get("reasoning_content")
|
captured_reasoning = additional_kwargs.get("reasoning_content")
|
||||||
@@ -189,9 +178,9 @@ def _patch_deepseek_reasoning_content_support():
|
|||||||
reasoning_content = captured_reasoning
|
reasoning_content = captured_reasoning
|
||||||
|
|
||||||
message["reasoning_content"] = reasoning_content
|
message["reasoning_content"] = reasoning_content
|
||||||
except Exception as err:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"修补 langchain-deepseek reasoning_content 请求载荷时失败,将继续使用原始载荷: {err}"
|
f"修补 langchain-deepseek reasoning_content 请求载荷时失败,将继续使用原始载荷: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
@@ -208,15 +197,6 @@ class LLMHelper:
|
|||||||
{"off", "auto", "minimal", "low", "medium", "high", "max", "xhigh"}
|
{"off", "auto", "minimal", "low", "medium", "high", "max", "xhigh"}
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _should_disable_thinking(disable_thinking: bool | None = None) -> bool:
|
|
||||||
"""
|
|
||||||
判断本次调用是否应尝试关闭模型思考能力。
|
|
||||||
"""
|
|
||||||
if disable_thinking is not None:
|
|
||||||
return bool(disable_thinking)
|
|
||||||
return bool(getattr(settings, "LLM_DISABLE_THINKING", False))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_model_name(model_name: str | None) -> str:
|
def _normalize_model_name(model_name: str | None) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -224,147 +204,42 @@ class LLMHelper:
|
|||||||
"""
|
"""
|
||||||
return (model_name or "").strip().lower()
|
return (model_name or "").strip().lower()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _normalize_thinking_level_value(cls, value: str | None) -> str | None:
|
|
||||||
"""
|
|
||||||
统一清理思考级别/强度值,并兼容常见别名。
|
|
||||||
"""
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
normalized = str(value).strip().lower()
|
|
||||||
if not normalized:
|
|
||||||
return None
|
|
||||||
|
|
||||||
alias_map = {
|
|
||||||
"none": "off",
|
|
||||||
"disabled": "off",
|
|
||||||
"disable": "off",
|
|
||||||
"enabled": "auto",
|
|
||||||
"enable": "auto",
|
|
||||||
"default": "auto",
|
|
||||||
"dynamic": "auto",
|
|
||||||
}
|
|
||||||
return alias_map.get(normalized, normalized)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _normalize_thinking_level(
|
|
||||||
cls, thinking_level: str | None = None
|
|
||||||
) -> str | None:
|
|
||||||
"""
|
|
||||||
统一清理 thinking_level 配置。
|
|
||||||
"""
|
|
||||||
value = (
|
|
||||||
thinking_level
|
|
||||||
if thinking_level is not None
|
|
||||||
else getattr(settings, "LLM_THINKING_LEVEL", None)
|
|
||||||
)
|
|
||||||
normalized = cls._normalize_thinking_level_value(value)
|
|
||||||
if not normalized:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if normalized not in cls._SUPPORTED_THINKING_LEVELS:
|
|
||||||
logger.warning(f"忽略不支持的 thinking_level 配置: {normalized}")
|
|
||||||
return None
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _normalize_reasoning_effort(
|
|
||||||
cls, reasoning_effort: str | None = None
|
|
||||||
) -> str | None:
|
|
||||||
"""
|
|
||||||
统一清理 legacy reasoning_effort 配置。
|
|
||||||
"""
|
|
||||||
value = (
|
|
||||||
reasoning_effort
|
|
||||||
if reasoning_effort is not None
|
|
||||||
else getattr(settings, "LLM_REASONING_EFFORT", None)
|
|
||||||
)
|
|
||||||
return cls._normalize_thinking_level(value)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _resolve_thinking_level(
|
|
||||||
cls,
|
|
||||||
thinking_level: str | None = None,
|
|
||||||
disable_thinking: bool | None = None,
|
|
||||||
reasoning_effort: str | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
统一解析本次调用的思考配置。
|
|
||||||
|
|
||||||
优先级:
|
|
||||||
1. 新字段 `thinking_level`
|
|
||||||
2. 本次调用传入的 legacy 字段
|
|
||||||
3. 已保存的新字段 `LLM_THINKING_LEVEL`
|
|
||||||
4. 已保存的 legacy 字段
|
|
||||||
"""
|
|
||||||
explicit_level = cls._normalize_thinking_level(thinking_level)
|
|
||||||
if explicit_level:
|
|
||||||
return explicit_level
|
|
||||||
|
|
||||||
explicit_effort = (
|
|
||||||
cls._normalize_reasoning_effort(reasoning_effort)
|
|
||||||
if reasoning_effort is not None
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
if disable_thinking is not None or reasoning_effort is not None:
|
|
||||||
if disable_thinking is not None and bool(disable_thinking):
|
|
||||||
return "off"
|
|
||||||
return explicit_effort or "auto"
|
|
||||||
|
|
||||||
configured_level = cls._normalize_thinking_level(
|
|
||||||
getattr(settings, "LLM_THINKING_LEVEL", None)
|
|
||||||
)
|
|
||||||
if configured_level:
|
|
||||||
return configured_level
|
|
||||||
|
|
||||||
legacy_disable = getattr(settings, "LLM_DISABLE_THINKING", None)
|
|
||||||
legacy_effort = cls._normalize_reasoning_effort(
|
|
||||||
getattr(settings, "LLM_REASONING_EFFORT", None)
|
|
||||||
)
|
|
||||||
if legacy_disable is not None:
|
|
||||||
return "off" if bool(legacy_disable) else (legacy_effort or "auto")
|
|
||||||
|
|
||||||
return legacy_effort or "off"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _normalize_deepseek_reasoning_effort(
|
def _normalize_deepseek_reasoning_effort(
|
||||||
cls, thinking_level: str | None = None
|
cls, thinking_level: str | None = None
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
DeepSeek 文档当前建议使用 high/max;兼容常见 effort 别名。
|
DeepSeek 文档当前建议使用 high/max;兼容常见 effort 别名。
|
||||||
"""
|
"""
|
||||||
normalized = cls._normalize_thinking_level(thinking_level)
|
if not thinking_level or thinking_level in {"off", "auto"}:
|
||||||
if not normalized or normalized in {"off", "auto"}:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if normalized in {"minimal", "low", "medium", "high"}:
|
if thinking_level in {"minimal", "low", "medium", "high"}:
|
||||||
return "high"
|
return "high"
|
||||||
if normalized in {"max", "xhigh"}:
|
if thinking_level in {"max", "xhigh"}:
|
||||||
return "max"
|
return "max"
|
||||||
|
|
||||||
logger.warning(f"忽略不支持的 DeepSeek reasoning_effort 配置: {normalized}")
|
logger.warning(f"忽略不支持的 DeepSeek reasoning_effort 配置: {thinking_level}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _normalize_openai_reasoning_effort(
|
def _normalize_openai_reasoning_effort(
|
||||||
cls, thinking_level: str | None = None
|
cls, thinking_level: str | None = None
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
OpenAI reasoning_effort 支持更细粒度的 effort,统一做最近似映射。
|
OpenAI reasoning_effort 支持更细粒度的 effort,统一做最近似映射。
|
||||||
"""
|
"""
|
||||||
normalized = cls._normalize_thinking_level(thinking_level)
|
if not thinking_level or thinking_level == "auto":
|
||||||
if not normalized or normalized == "auto":
|
|
||||||
return None
|
return None
|
||||||
if normalized == "off":
|
if thinking_level == "off":
|
||||||
return "none"
|
return "none"
|
||||||
if normalized == "max":
|
if thinking_level == "max":
|
||||||
return "xhigh"
|
return "xhigh"
|
||||||
return normalized
|
return thinking_level
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_google_thinking_kwargs(
|
def _build_google_thinking_kwargs(
|
||||||
cls, model_name: str, thinking_level: str
|
cls, model_name: str, thinking_level: str
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Gemini 3 使用 thinking_level;Gemini 2.5 使用 thinking_budget。
|
Gemini 3 使用 thinking_level;Gemini 2.5 使用 thinking_budget。
|
||||||
@@ -427,7 +302,7 @@ class LLMHelper:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_kimi_thinking_kwargs(
|
def _build_kimi_thinking_kwargs(
|
||||||
cls, model_name: str, thinking_level: str
|
cls, model_name: str, thinking_level: str
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Kimi 当前公开文档仅支持思考开关,不支持显式深度调节。
|
Kimi 当前公开文档仅支持思考开关,不支持显式深度调节。
|
||||||
@@ -440,12 +315,10 @@ class LLMHelper:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_thinking_kwargs(
|
def _build_thinking_kwargs(
|
||||||
cls,
|
cls,
|
||||||
provider: str,
|
provider: str,
|
||||||
model: str | None,
|
model: str | None,
|
||||||
thinking_level: str | None = None,
|
thinking_level: str | None = None
|
||||||
disable_thinking: bool | None = None,
|
|
||||||
reasoning_effort: str | None = None,
|
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
按 provider/model 生成思考模式相关参数。
|
按 provider/model 生成思考模式相关参数。
|
||||||
@@ -455,45 +328,40 @@ class LLMHelper:
|
|||||||
"""
|
"""
|
||||||
provider_name = (provider or "").strip().lower()
|
provider_name = (provider or "").strip().lower()
|
||||||
model_name = cls._normalize_model_name(model)
|
model_name = cls._normalize_model_name(model)
|
||||||
resolved_thinking_level = cls._resolve_thinking_level(
|
|
||||||
thinking_level=thinking_level,
|
|
||||||
disable_thinking=disable_thinking,
|
|
||||||
reasoning_effort=reasoning_effort,
|
|
||||||
)
|
|
||||||
|
|
||||||
if provider_name == "deepseek":
|
if provider_name == "deepseek":
|
||||||
if resolved_thinking_level == "off":
|
if thinking_level == "off":
|
||||||
return {"extra_body": {"thinking": {"type": "disabled"}}}
|
return {"extra_body": {"thinking": {"type": "disabled"}}}
|
||||||
if resolved_thinking_level == "auto":
|
if thinking_level == "auto":
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
kwargs: dict[str, Any] = {"extra_body": {"thinking": {"type": "enabled"}}}
|
kwargs: dict[str, Any] = {"extra_body": {"thinking": {"type": "enabled"}}}
|
||||||
deepseek_effort = cls._normalize_deepseek_reasoning_effort(
|
deepseek_effort = cls._normalize_deepseek_reasoning_effort(
|
||||||
resolved_thinking_level
|
thinking_level
|
||||||
)
|
)
|
||||||
if deepseek_effort:
|
if deepseek_effort:
|
||||||
kwargs["reasoning_effort"] = deepseek_effort
|
kwargs["reasoning_effort"] = deepseek_effort
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
if model_name.startswith(("kimi-k2.5", "kimi-k2.6", "kimi-k2-thinking")):
|
if model_name.startswith(("kimi-k2.5", "kimi-k2.6", "kimi-k2-thinking")):
|
||||||
return cls._build_kimi_thinking_kwargs(model_name, resolved_thinking_level)
|
return cls._build_kimi_thinking_kwargs(model_name, thinking_level)
|
||||||
|
|
||||||
if not model_name:
|
if not model_name:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# OpenAI 原生推理模型优先走 LangChain 内置 reasoning_effort。
|
# OpenAI 原生推理模型优先走 LangChain 内置 reasoning_effort。
|
||||||
if provider_name == "openai" and model_name.startswith(
|
if provider_name == "openai" and model_name.startswith(
|
||||||
("gpt-5", "o1", "o3", "o4")
|
("gpt-5", "o1", "o3", "o4")
|
||||||
):
|
):
|
||||||
openai_effort = cls._normalize_openai_reasoning_effort(
|
openai_effort = cls._normalize_openai_reasoning_effort(
|
||||||
resolved_thinking_level
|
thinking_level
|
||||||
)
|
)
|
||||||
return {"reasoning_effort": openai_effort} if openai_effort else {}
|
return {"reasoning_effort": openai_effort} if openai_effort else {}
|
||||||
|
|
||||||
# Gemini 使用 google-genai / langchain-google-genai 内置思考控制参数。
|
# Gemini 使用 google-genai / langchain-google-genai 内置思考控制参数。
|
||||||
if provider_name == "google":
|
if provider_name == "google":
|
||||||
return cls._build_google_thinking_kwargs(
|
return cls._build_google_thinking_kwargs(
|
||||||
model_name, resolved_thinking_level
|
model_name, thinking_level
|
||||||
)
|
)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
@@ -507,18 +375,26 @@ class LLMHelper:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_llm(
|
def get_llm(
|
||||||
streaming: bool = False,
|
streaming: bool = False,
|
||||||
provider: str | None = None,
|
provider: str | None = None,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
thinking_level: str | None = None,
|
thinking_level: str | None = None,
|
||||||
disable_thinking: bool | None = None,
|
api_key: str | None = None,
|
||||||
reasoning_effort: str | None = None,
|
base_url: str | None = None,
|
||||||
api_key: str | None = None,
|
|
||||||
base_url: str | None = None,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取LLM实例
|
获取LLM实例
|
||||||
:param streaming: 是否启用流式输出
|
:param streaming: 是否启用流式输出
|
||||||
|
:param provider: LLM提供商,默认为配置项LLM_PROVIDER
|
||||||
|
:param model: 模型名称,默认为配置项LLM_MODEL
|
||||||
|
:param thinking_level: 思考模式级别,默认为 None(即自动判断
|
||||||
|
是否启用思考模式)。支持的级别包括 "off"(关闭)、"auto"(自动)、"minimal"、"low"、"medium"、"high"、"max"/"xhigh"(最大)。
|
||||||
|
不同模型对思考模式的支持和表现不同,具体映射关系请
|
||||||
|
参考代码实现。对于不支持思考模式的模型,该参数将被忽略。
|
||||||
|
:param api_key: API Key,默认为
|
||||||
|
配置项LLM_API_KEY。对于某些提供商(
|
||||||
|
如 DeepSeek),可能需要同时提供 base_url。
|
||||||
|
:param base_url: API Base URL,默认为配置项LLM_BASE_URL。
|
||||||
:return: LLM实例
|
:return: LLM实例
|
||||||
"""
|
"""
|
||||||
provider_name = str(
|
provider_name = str(
|
||||||
@@ -530,9 +406,7 @@ class LLMHelper:
|
|||||||
thinking_kwargs = LLMHelper._build_thinking_kwargs(
|
thinking_kwargs = LLMHelper._build_thinking_kwargs(
|
||||||
provider=provider_name,
|
provider=provider_name,
|
||||||
model=model_name,
|
model=model_name,
|
||||||
thinking_level=thinking_level,
|
thinking_level=thinking_level
|
||||||
disable_thinking=disable_thinking,
|
|
||||||
reasoning_effort=reasoning_effort,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not api_key_value:
|
if not api_key_value:
|
||||||
@@ -596,7 +470,7 @@ class LLMHelper:
|
|||||||
else:
|
else:
|
||||||
model.profile = {
|
model.profile = {
|
||||||
"max_input_tokens": settings.LLM_MAX_CONTEXT_TOKENS
|
"max_input_tokens": settings.LLM_MAX_CONTEXT_TOKENS
|
||||||
* 1000, # 转换为token单位
|
* 1000, # 转换为token单位
|
||||||
}
|
}
|
||||||
|
|
||||||
return model
|
return model
|
||||||
@@ -620,10 +494,10 @@ class LLMHelper:
|
|||||||
if isinstance(block, dict) or hasattr(block, "get"):
|
if isinstance(block, dict) or hasattr(block, "get"):
|
||||||
block_type = block.get("type")
|
block_type = block.get("type")
|
||||||
if block.get("thought") or block_type in (
|
if block.get("thought") or block_type in (
|
||||||
"thinking",
|
"thinking",
|
||||||
"reasoning_content",
|
"reasoning_content",
|
||||||
"reasoning",
|
"reasoning",
|
||||||
"thought",
|
"thought",
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
if block_type == "text":
|
if block_type == "text":
|
||||||
@@ -643,15 +517,13 @@ class LLMHelper:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def test_current_settings(
|
async def test_current_settings(
|
||||||
prompt: str = "请只回复 OK",
|
prompt: str = "请只回复 OK",
|
||||||
timeout: int = 20,
|
timeout: int = 20,
|
||||||
provider: str | None = None,
|
provider: str | None = None,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
thinking_level: str | None = None,
|
thinking_level: str | None = None,
|
||||||
disable_thinking: bool | None = None,
|
api_key: str | None = None,
|
||||||
reasoning_effort: str | None = None,
|
base_url: str | None = None,
|
||||||
api_key: str | None = None,
|
|
||||||
base_url: str | None = None,
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
使用当前已保存配置执行一次最小 LLM 调用。
|
使用当前已保存配置执行一次最小 LLM 调用。
|
||||||
@@ -666,8 +538,6 @@ class LLMHelper:
|
|||||||
provider=provider_name,
|
provider=provider_name,
|
||||||
model=model_name,
|
model=model_name,
|
||||||
thinking_level=thinking_level,
|
thinking_level=thinking_level,
|
||||||
disable_thinking=disable_thinking,
|
|
||||||
reasoning_effort=reasoning_effort,
|
|
||||||
api_key=api_key_value,
|
api_key=api_key_value,
|
||||||
base_url=base_url_value,
|
base_url=base_url_value,
|
||||||
)
|
)
|
||||||
@@ -695,7 +565,7 @@ class LLMHelper:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def get_models(
|
def get_models(
|
||||||
self, provider: str, api_key: str, base_url: str = None
|
self, provider: str, api_key: str, base_url: str = None
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
"""获取模型列表"""
|
"""获取模型列表"""
|
||||||
logger.info(f"获取 {provider} 模型列表...")
|
logger.info(f"获取 {provider} 模型列表...")
|
||||||
@@ -733,7 +603,7 @@ class LLMHelper:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_openai_compatible_models(
|
def _get_openai_compatible_models(
|
||||||
provider: str, api_key: str, base_url: str = None
|
provider: str, api_key: str, base_url: str = None
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
"""获取OpenAI兼容模型列表"""
|
"""获取OpenAI兼容模型列表"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1086,14 +1086,6 @@ def _env_llm_thinking_level_default() -> str:
|
|||||||
"xhigh",
|
"xhigh",
|
||||||
}:
|
}:
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
legacy_disable = _env_bool("LLM_DISABLE_THINKING", True)
|
|
||||||
legacy_effort = _normalize_choice(_env_default("LLM_REASONING_EFFORT", ""))
|
|
||||||
legacy_effort = alias_map.get(legacy_effort, legacy_effort)
|
|
||||||
if legacy_disable:
|
|
||||||
return "off"
|
|
||||||
if legacy_effort in {"minimal", "low", "medium", "high", "max", "xhigh"}:
|
|
||||||
return legacy_effort
|
|
||||||
return "auto"
|
return "auto"
|
||||||
|
|
||||||
|
|
||||||
@@ -1550,7 +1542,7 @@ def _load_auth_site_definitions_inner() -> dict[str, Any]:
|
|||||||
if str(ROOT) not in sys.path:
|
if str(ROOT) not in sys.path:
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
from app.helper.sites import SitesHelper
|
from app.helper.sites import SitesHelper # noqa
|
||||||
|
|
||||||
auth_sites = SitesHelper().get_authsites() or {}
|
auth_sites = SitesHelper().get_authsites() or {}
|
||||||
definitions: dict[str, Any] = {}
|
definitions: dict[str, Any] = {}
|
||||||
@@ -1887,7 +1879,7 @@ def _apply_local_system_config_inner(config_payload: dict[str, Any]) -> None:
|
|||||||
):
|
):
|
||||||
system_config.set(SystemConfigKey.UserSiteAuthParams, site_auth_item)
|
system_config.set(SystemConfigKey.UserSiteAuthParams, site_auth_item)
|
||||||
try:
|
try:
|
||||||
from app.helper.sites import SitesHelper
|
from app.helper.sites import SitesHelper # noqa
|
||||||
|
|
||||||
status, msg = SitesHelper().check_user(
|
status, msg = SitesHelper().check_user(
|
||||||
site_auth_item.get("site"), site_auth_item.get("params")
|
site_auth_item.get("site"), site_auth_item.get("params")
|
||||||
|
|||||||
@@ -72,8 +72,6 @@ sys.modules["app.core.config"].settings.LLM_MODEL = "deepseek-v4-pro"
|
|||||||
sys.modules["app.core.config"].settings.LLM_API_KEY = "sk-test"
|
sys.modules["app.core.config"].settings.LLM_API_KEY = "sk-test"
|
||||||
sys.modules["app.core.config"].settings.LLM_BASE_URL = "https://api.deepseek.com"
|
sys.modules["app.core.config"].settings.LLM_BASE_URL = "https://api.deepseek.com"
|
||||||
sys.modules["app.core.config"].settings.LLM_THINKING_LEVEL = None
|
sys.modules["app.core.config"].settings.LLM_THINKING_LEVEL = None
|
||||||
sys.modules["app.core.config"].settings.LLM_DISABLE_THINKING = False
|
|
||||||
sys.modules["app.core.config"].settings.LLM_REASONING_EFFORT = None
|
|
||||||
sys.modules["app.core.config"].settings.LLM_TEMPERATURE = 0.1
|
sys.modules["app.core.config"].settings.LLM_TEMPERATURE = 0.1
|
||||||
sys.modules["app.core.config"].settings.LLM_MAX_CONTEXT_TOKENS = 64
|
sys.modules["app.core.config"].settings.LLM_MAX_CONTEXT_TOKENS = 64
|
||||||
sys.modules["app.core.config"].settings.PROXY_HOST = None
|
sys.modules["app.core.config"].settings.PROXY_HOST = None
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ _stub_module(
|
|||||||
LLM_API_KEY="global-key",
|
LLM_API_KEY="global-key",
|
||||||
LLM_BASE_URL="https://global.example.com",
|
LLM_BASE_URL="https://global.example.com",
|
||||||
LLM_THINKING_LEVEL=None,
|
LLM_THINKING_LEVEL=None,
|
||||||
LLM_DISABLE_THINKING=False,
|
|
||||||
LLM_REASONING_EFFORT=None,
|
|
||||||
LLM_TEMPERATURE=0.1,
|
LLM_TEMPERATURE=0.1,
|
||||||
LLM_MAX_CONTEXT_TOKENS=64,
|
LLM_MAX_CONTEXT_TOKENS=64,
|
||||||
PROXY_HOST=None,
|
PROXY_HOST=None,
|
||||||
|
|||||||
Reference in New Issue
Block a user