From 5aa9a9eb8a6b89e6713f8a0144e5b5764d7bca28 Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 27 May 2026 16:06:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ai_client.py | 86 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/ai_client.py b/ai_client.py index 1db9584..a699b32 100644 --- a/ai_client.py +++ b/ai_client.py @@ -1,26 +1,58 @@ -"""大模型调用:OpenAI 兼容接口(默认)或本机 Ollama 二选一。""" +"""大模型调用:OpenAI 兼容接口(默认)或本机 Ollama 二选一。 + +配置从 os.environ 惰性读取:各实例 app.py 在 import 本模块后才 load_env_file(.env), +若在 import 时缓存变量会导致 OPENAI_API_KEY 始终为空。 +""" from __future__ import annotations import base64 import os from typing import List, Optional, Sequence -from urllib.parse import urljoin import requests -AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120")) -AI_PROVIDER = (os.getenv("AI_PROVIDER", "openai") or "openai").strip().lower() -OPENAI_API_BASE = (os.getenv("OPENAI_API_BASE", "https://op.bz121.com/v1") or "").strip().rstrip("/") -OPENAI_API_KEY = (os.getenv("OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY") or "").strip() -OPENAI_MODEL = (os.getenv("OPENAI_MODEL", "gemma4:e4b") or "gemma4:e4b").strip() +def _env_str(name: str, default: str = "") -> str: + v = os.getenv(name) + if v is None: + return default + return str(v).strip() -OLLAMA_API = os.getenv("OLLAMA_API", "http://127.0.0.1:11434/api/generate") -AI_MODEL = os.getenv("AI_MODEL", "huihui_ai/deepseek-r1-abliterated:latest") + +def _ai_timeout_seconds() -> int: + try: + return max(10, int(_env_str("AI_TIMEOUT_SECONDS", "120") or "120")) + except ValueError: + return 120 + + +def _ai_provider() -> str: + return (_env_str("AI_PROVIDER", "openai") or "openai").lower() + + +def _openai_api_base() -> str: + base = _env_str("OPENAI_API_BASE", "https://op.bz121.com/v1") or "https://op.bz121.com/v1" + return base.rstrip("/") + + +def _openai_api_key() -> str: + return _env_str("OPENAI_API_KEY") or _env_str("AI_API_KEY") + + +def _openai_model() -> str: + return _env_str("OPENAI_MODEL", "gemma4:e4b") or "gemma4:e4b" + + +def _ollama_api() -> str: + return _env_str("OLLAMA_API", "http://127.0.0.1:11434/api/generate") or "http://127.0.0.1:11434/api/generate" + + +def _ollama_model() -> str: + return _env_str("AI_MODEL", "huihui_ai/deepseek-r1-abliterated:latest") or "huihui_ai/deepseek-r1-abliterated:latest" def _use_openai() -> bool: - return AI_PROVIDER in ("openai", "openai_compatible", "gateway") + return _ai_provider() in ("openai", "openai_compatible", "gateway") def _read_image_base64(image_path: str) -> Optional[str]: @@ -47,17 +79,18 @@ def _collect_images( def _openai_chat_url() -> str: - base = OPENAI_API_BASE or "https://op.bz121.com/v1" + base = _openai_api_base() if base.endswith("/chat/completions"): return base return f"{base}/chat/completions" def _generate_openai(prompt: str, images: List[str], temperature: float) -> str: - if not OPENAI_API_KEY: - return "AI 调用失败:未配置 OPENAI_API_KEY" + api_key = _openai_api_key() + if not api_key: + return "AI 调用失败:未配置 OPENAI_API_KEY(请在当前实例目录 .env 中设置,修改后需重启服务)" headers = { - "Authorization": f"Bearer {OPENAI_API_KEY}", + "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } if images: @@ -73,7 +106,7 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str: else: messages = [{"role": "user", "content": prompt}] body = { - "model": OPENAI_MODEL, + "model": _openai_model(), "messages": messages, "temperature": temperature, "stream": False, @@ -82,7 +115,7 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str: _openai_chat_url(), headers=headers, json=body, - timeout=AI_TIMEOUT_SECONDS, + timeout=_ai_timeout_seconds(), ) r.raise_for_status() data = r.json() @@ -95,14 +128,14 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str: def _generate_ollama(prompt: str, images: List[str], temperature: float) -> str: payload = { - "model": AI_MODEL, + "model": _ollama_model(), "prompt": prompt, "stream": False, "options": {"temperature": temperature}, } if images: payload["images"] = images - r = requests.post(OLLAMA_API, json=payload, timeout=AI_TIMEOUT_SECONDS) + r = requests.post(_ollama_api(), json=payload, timeout=_ai_timeout_seconds()) r.raise_for_status() return (r.json().get("response") or "").strip() or "AI 生成失败" @@ -173,5 +206,18 @@ def ai_short_advice(prompt_text: str) -> str: def ai_provider_label() -> str: if _use_openai(): - return f"OpenAI 兼容 · {OPENAI_MODEL} @ {OPENAI_API_BASE}" - return f"Ollama · {AI_MODEL}" + return f"OpenAI 兼容 · {_openai_model()} @ {_openai_api_base()}" + return f"Ollama · {_ollama_model()}" + + +def ai_config_status() -> dict: + """调试用:当前进程内读到的 AI 配置(不含密钥明文)。""" + key = _openai_api_key() + return { + "provider": _ai_provider(), + "openai_base": _openai_api_base(), + "openai_model": _openai_model(), + "openai_key_configured": bool(key), + "ollama_api": _ollama_api(), + "ollama_model": _ollama_model(), + }