feat: add LLM proxy toggle

This commit is contained in:
jxxghp
2026-05-27 06:57:09 +08:00
parent 896631d63e
commit 1f7fb304dd
10 changed files with 277 additions and 55 deletions
+90 -18
View File
@@ -137,6 +137,57 @@ def _get_httpx_proxy_key() -> str:
return "proxies"
def _resolve_llm_proxy(use_proxy: bool | None = None) -> str | None:
"""
解析本次 LLM 调用应使用的系统代理地址。
"""
should_use_proxy = settings.LLM_USE_PROXY if use_proxy is None else use_proxy
return settings.PROXY_HOST if should_use_proxy and settings.PROXY_HOST else None
def _build_httpx_proxy_kwargs(proxy_url: str | None) -> dict[str, str]:
"""
构造兼容当前 httpx 版本的代理参数。
"""
if not proxy_url:
return {}
return {_get_httpx_proxy_key(): proxy_url}
def _build_google_client_args(proxy_url: str | None) -> dict[str, Any]:
"""
构造 Google SDK 透传给 httpx 的客户端参数。
"""
return {
"trust_env": False,
**_build_httpx_proxy_kwargs(proxy_url),
}
def _build_httpx_client(
proxy_url: str | None,
*,
async_client: bool = False,
timeout: float | None = None,
):
"""
构造显式代理策略的 httpx 客户端。
当关闭 LLM 代理时也返回 trust_env=False 的客户端,避免 httpx 自动读取
进程环境变量中的代理配置。
"""
import httpx
client_cls = httpx.AsyncClient if async_client else httpx.Client
kwargs: dict[str, Any] = {
"trust_env": False,
**_build_httpx_proxy_kwargs(proxy_url),
}
if timeout is not None:
kwargs["timeout"] = timeout
return client_cls(**kwargs)
def _deepseek_thinking_toggle(extra_body: Any) -> bool | None:
"""
解析 DeepSeek extra_body 中显式传入的 thinking 开关。
@@ -733,6 +784,7 @@ class LLMHelper:
base_url: str | None = None,
base_url_preset: str | None = None,
user_agent: str | None = None,
use_proxy: bool | None = None,
):
"""
获取LLM实例
@@ -747,6 +799,7 @@ class LLMHelper:
:param base_url: API Base URL。未显式传入时使用当前配置项 LLM_BASE_URL。
:param base_url_preset: Base URL 预设。未显式传入时使用当前配置项 LLM_BASE_URL_PRESET。
:param user_agent: OpenAI兼容接口请求 User-Agent。未显式传入时使用配置项 LLM_USER_AGENT。
:param use_proxy: 是否为本次 LLM 调用使用系统代理。未显式传入时使用配置项 LLM_USE_PROXY。
:return: LLM实例
"""
provider_name = str(provider if provider is not None else settings.LLM_PROVIDER).lower()
@@ -772,6 +825,7 @@ class LLMHelper:
base_url=base_url_value,
base_url_preset_id=base_url_preset_value,
user_agent=user_agent_value,
use_proxy=use_proxy,
)
except Exception as err:
logger.debug(f"LLM provider 目录不可用,回退到旧运行时逻辑: {err}")
@@ -797,6 +851,7 @@ class LLMHelper:
model=model_name,
runtime=runtime,
)
llm_proxy = _resolve_llm_proxy(use_proxy)
if runtime["runtime"] == "google":
# 修补 Gemini 2.5 思考模型的 thought_signature 兼容性
@@ -807,18 +862,13 @@ class LLMHelper:
# 会导致工具调用时报错 400
from langchain_google_genai import ChatGoogleGenerativeAI
client_args = None
if settings.PROXY_HOST:
proxy_key = _get_httpx_proxy_key()
client_args = {proxy_key: settings.PROXY_HOST}
model = ChatGoogleGenerativeAI(
model=model_name,
api_key=runtime["api_key"],
retries=3,
temperature=settings.LLM_TEMPERATURE,
streaming=streaming,
client_args=client_args,
client_args=_build_google_client_args(llm_proxy),
**thinking_kwargs,
)
elif runtime["runtime"] == "deepseek":
@@ -833,6 +883,8 @@ class LLMHelper:
temperature=settings.LLM_TEMPERATURE,
streaming=streaming,
stream_usage=True,
http_client=_build_httpx_client(llm_proxy),
http_async_client=_build_httpx_client(llm_proxy, async_client=True),
**thinking_kwargs,
)
elif runtime["runtime"] in {"anthropic_compatible", "copilot_anthropic"}:
@@ -846,7 +898,7 @@ class LLMHelper:
temperature=settings.LLM_TEMPERATURE,
streaming=streaming,
stream_usage=True,
anthropic_proxy=settings.PROXY_HOST,
anthropic_proxy=llm_proxy,
default_headers=default_headers,
**thinking_kwargs,
)
@@ -867,7 +919,15 @@ class LLMHelper:
temperature=settings.LLM_TEMPERATURE,
streaming=streaming,
stream_usage=True,
openai_proxy=settings.PROXY_HOST,
openai_proxy=llm_proxy,
**(
{}
if llm_proxy
else {
"http_client": _build_httpx_client(llm_proxy),
"http_async_client": _build_httpx_client(llm_proxy, async_client=True),
}
),
default_headers=default_headers,
use_responses_api=use_responses_api,
**thinking_kwargs,
@@ -945,6 +1005,7 @@ class LLMHelper:
base_url: str | None = None,
base_url_preset: str | None = None,
user_agent: str | None = None,
use_proxy: bool | None = None,
) -> dict:
"""
使用当前已保存配置执行一次最小 LLM 调用。
@@ -961,6 +1022,7 @@ class LLMHelper:
base_url=base_url,
base_url_preset=base_url_preset,
user_agent=user_agent,
use_proxy=use_proxy,
)
try:
response = await asyncio.wait_for(llm.ainvoke(prompt), timeout=timeout)
@@ -992,6 +1054,7 @@ class LLMHelper:
base_url: str | None = None,
base_url_preset: str | None = None,
user_agent: str | None = None,
use_proxy: bool | None = None,
force_refresh: bool = False,
) -> List[dict[str, Any]]:
"""
@@ -1010,6 +1073,7 @@ class LLMHelper:
base_url=base_url,
base_url_preset_id=base_url_preset,
user_agent=user_agent,
use_proxy=use_proxy,
force_refresh=force_refresh,
)
except Exception as err:
@@ -1017,7 +1081,10 @@ class LLMHelper:
if provider == "google":
return [
{"id": model_id, "name": model_id}
for model_id in await self._get_google_models(api_key or "")
for model_id in await self._get_google_models(
api_key or "",
use_proxy=use_proxy,
)
]
try:
from app.agent.llm.provider import LLMProviderManager
@@ -1039,24 +1106,23 @@ class LLMHelper:
api_key or "",
model_list_base_url,
user_agent=user_agent,
use_proxy=use_proxy,
)
]
@staticmethod
async def _get_google_models(api_key: str) -> List[str]:
async def _get_google_models(api_key: str, use_proxy: bool | None = None) -> List[str]:
"""获取Google模型列表(使用 google-genai SDK v1"""
try:
from google import genai
from google.genai.types import HttpOptions
http_options = None
if settings.PROXY_HOST:
proxy_key = _get_httpx_proxy_key()
proxy_args = {proxy_key: settings.PROXY_HOST}
http_options = HttpOptions(
client_args=proxy_args,
async_client_args=proxy_args,
)
llm_proxy = _resolve_llm_proxy(use_proxy)
google_client_args = _build_google_client_args(llm_proxy)
http_options = HttpOptions(
client_args=google_client_args,
async_client_args=google_client_args,
)
client = genai.Client(api_key=api_key, http_options=http_options)
models = await client.aio.models.list()
@@ -1077,6 +1143,7 @@ class LLMHelper:
api_key: str,
base_url: str = None,
user_agent: str | None = None,
use_proxy: bool | None = None,
) -> List[str]:
"""获取OpenAI兼容模型列表"""
try:
@@ -1092,6 +1159,11 @@ class LLMHelper:
None,
user_agent=user_agent,
),
http_client=_build_httpx_client(
_resolve_llm_proxy(use_proxy),
async_client=True,
timeout=15.0,
),
)
models = await client.models.list()
await client.close()