This commit is contained in:
dekun
2026-05-27 16:06:45 +08:00
parent 6ca94ef416
commit 5aa9a9eb8a
+66 -20
View File
@@ -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 from __future__ import annotations
import base64 import base64
import os import os
from typing import List, Optional, Sequence from typing import List, Optional, Sequence
from urllib.parse import urljoin
import requests 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("/") def _env_str(name: str, default: str = "") -> str:
OPENAI_API_KEY = (os.getenv("OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY") or "").strip() v = os.getenv(name)
OPENAI_MODEL = (os.getenv("OPENAI_MODEL", "gemma4:e4b") or "gemma4:e4b").strip() 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: 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]: def _read_image_base64(image_path: str) -> Optional[str]:
@@ -47,17 +79,18 @@ def _collect_images(
def _openai_chat_url() -> str: def _openai_chat_url() -> str:
base = OPENAI_API_BASE or "https://op.bz121.com/v1" base = _openai_api_base()
if base.endswith("/chat/completions"): if base.endswith("/chat/completions"):
return base return base
return f"{base}/chat/completions" return f"{base}/chat/completions"
def _generate_openai(prompt: str, images: List[str], temperature: float) -> str: def _generate_openai(prompt: str, images: List[str], temperature: float) -> str:
if not OPENAI_API_KEY: api_key = _openai_api_key()
return "AI 调用失败:未配置 OPENAI_API_KEY" if not api_key:
return "AI 调用失败:未配置 OPENAI_API_KEY(请在当前实例目录 .env 中设置,修改后需重启服务)"
headers = { headers = {
"Authorization": f"Bearer {OPENAI_API_KEY}", "Authorization": f"Bearer {api_key}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
if images: if images:
@@ -73,7 +106,7 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str:
else: else:
messages = [{"role": "user", "content": prompt}] messages = [{"role": "user", "content": prompt}]
body = { body = {
"model": OPENAI_MODEL, "model": _openai_model(),
"messages": messages, "messages": messages,
"temperature": temperature, "temperature": temperature,
"stream": False, "stream": False,
@@ -82,7 +115,7 @@ def _generate_openai(prompt: str, images: List[str], temperature: float) -> str:
_openai_chat_url(), _openai_chat_url(),
headers=headers, headers=headers,
json=body, json=body,
timeout=AI_TIMEOUT_SECONDS, timeout=_ai_timeout_seconds(),
) )
r.raise_for_status() r.raise_for_status()
data = r.json() 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: def _generate_ollama(prompt: str, images: List[str], temperature: float) -> str:
payload = { payload = {
"model": AI_MODEL, "model": _ollama_model(),
"prompt": prompt, "prompt": prompt,
"stream": False, "stream": False,
"options": {"temperature": temperature}, "options": {"temperature": temperature},
} }
if images: if images:
payload["images"] = 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() r.raise_for_status()
return (r.json().get("response") or "").strip() or "AI 生成失败" 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: def ai_provider_label() -> str:
if _use_openai(): if _use_openai():
return f"OpenAI 兼容 · {OPENAI_MODEL} @ {OPENAI_API_BASE}" return f"OpenAI 兼容 · {_openai_model()} @ {_openai_api_base()}"
return f"Ollama · {AI_MODEL}" 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(),
}