新增功能
This commit is contained in:
+130
-34
@@ -2,12 +2,14 @@
|
||||
AI 大模型配置文件
|
||||
=================
|
||||
支持所有 OpenAI 兼容接口(DeepSeek、通义千问、Moonshot、OpenAI 等)。
|
||||
本文件中的值是【默认值】;在 GUI 的「AI 高级配置」中修改并保存后,
|
||||
会写入 ai_settings.json,下次启动自动加载覆盖这里的默认值。
|
||||
本文件中的值是【默认值】;在 GUI 或管理后台修改并保存后,会写入已忽略
|
||||
Git 的 ai_settings.local.json,下次启动自动加载覆盖这里的默认值。
|
||||
"""
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
|
||||
# ── 是否启用 AI 回复(False 时使用固定文本回复)──
|
||||
AI_ENABLED = True
|
||||
@@ -17,9 +19,33 @@ AI_ENABLED = True
|
||||
# /v1/ 后已有路径时不再自动拼接 /chat/completions
|
||||
# Dify:填 .../v1/chat-messages,AI_API_KEY 用应用「访问 API」里的 Key(通常 app- 开头)
|
||||
AI_API_BASE = "https://api.deepseek.com"
|
||||
AI_API_KEY = "sk-992b66aec315400d92848a676acb0e99" # 你的 API Key
|
||||
AI_API_KEY = "" # 从后台或 ai_settings.local.json 注入
|
||||
AI_MODEL = "deepseek-chat" # 模型名称(Dify 应用侧选模型时此项可忽略)
|
||||
|
||||
# ── Grok Build 本地客服 Agent ──
|
||||
# 客服回复由本机 Grok Build Agent 生成。Agent 只能调度项目内置的受控客服 MCP,
|
||||
# 不再登录或访问外部客服 API,也没有企业微信发送能力。
|
||||
GROK_CUSTOMER_SERVICE_ENABLED = True
|
||||
GROK_CUSTOMER_SERVICE_TIMEOUT = 180
|
||||
GROK_CUSTOMER_SERVICE_MAX_TURNS = 8
|
||||
GROK_CUSTOMER_SERVICE_EFFORT = "low" # low / medium / high
|
||||
|
||||
# ── Grok Build Agent 自有模型 ──
|
||||
# Grok Build 只提供 Agent 调度能力,实际推理由这里配置的模型完成。原生支持
|
||||
# OpenAI Chat Completions / Responses、Anthropic Messages;Dify Chat Messages
|
||||
# 会由项目内置的 loopback 适配器转换成 Grok 工具调用协议。未配置或不兼容时
|
||||
# Agent 直接停用,绝不会回退到 Grok/xAI 模型。
|
||||
GROK_MODEL_ENABLED = False
|
||||
GROK_API_BASE = ""
|
||||
GROK_API_KEY = ""
|
||||
GROK_MODEL = ""
|
||||
GROK_API_BACKEND = "chat_completions" # 也可填 responses/messages/dify
|
||||
GROK_AUTH_SCHEME = "auto"
|
||||
GROK_DIFY_INPUTS: dict = {} # Dify 应用必填 inputs;其他协议忽略
|
||||
GROK_CONTEXT_WINDOW = 128000
|
||||
GROK_MAX_TOKENS = 8192
|
||||
GROK_TEMPERATURE = 0.3
|
||||
|
||||
# ── 聊天上下文记忆 ──
|
||||
# True → 按会话维护多轮上下文:把同一聊天框中之前提取到的聊天记录和我方历史回复
|
||||
# 一起发给 AI,使回答能衔接上下文(如客户分多条消息描述一件事)
|
||||
@@ -200,34 +226,87 @@ def build_system_prompt() -> str:
|
||||
AI_SYSTEM_PROMPT = build_system_prompt()
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# GUI 配置持久化(ai_settings.json)
|
||||
# GUI 配置持久化(ai_settings.local.json)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
_SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ai_settings.json")
|
||||
_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
_SETTINGS_FILE = os.path.join(_BASE_DIR, "ai_settings.local.json")
|
||||
_LEGACY_SETTINGS_FILE = os.path.join(_BASE_DIR, "ai_settings.json")
|
||||
|
||||
# 允许通过 GUI 修改并持久化的配置项
|
||||
CONFIGURABLE_KEYS = [
|
||||
"AI_ENABLED", "AI_API_BASE", "AI_API_KEY", "AI_MODEL",
|
||||
"GROK_CUSTOMER_SERVICE_ENABLED", "GROK_CUSTOMER_SERVICE_TIMEOUT",
|
||||
"GROK_CUSTOMER_SERVICE_MAX_TURNS", "GROK_CUSTOMER_SERVICE_EFFORT",
|
||||
"GROK_MODEL_ENABLED", "GROK_API_BASE", "GROK_API_KEY", "GROK_MODEL",
|
||||
"GROK_API_BACKEND", "GROK_AUTH_SCHEME", "GROK_CONTEXT_WINDOW", "GROK_MAX_TOKENS",
|
||||
"GROK_TEMPERATURE", "GROK_DIFY_INPUTS",
|
||||
"AI_USE_VISION", "AI_CONTEXT_ENABLED", "AI_CONTEXT_MAX_ROUNDS",
|
||||
"AI_COUNTER_INSULT_ENABLED", "AI_AGENT_NAME", "AI_HOSPITAL_NAME",
|
||||
"AI_MAX_TOKENS", "AI_TEMPERATURE", "AI_TIMEOUT",
|
||||
"AI_MCP_ENABLED", "AI_MCP_MAX_ROUNDS", "AI_MCP_SERVERS",
|
||||
]
|
||||
|
||||
_SETTINGS_LOCK = threading.RLock()
|
||||
_SETTINGS_REVISION = 0
|
||||
_REVISION_SNAPSHOT: dict = {}
|
||||
|
||||
|
||||
def _settings_snapshot_unlocked(keys=CONFIGURABLE_KEYS) -> dict:
|
||||
g = globals()
|
||||
return {key: copy.deepcopy(g[key]) for key in keys}
|
||||
|
||||
|
||||
def _record_revision_unlocked(snapshot: dict) -> None:
|
||||
"""Advance the process-local revision when effective settings changed."""
|
||||
global _SETTINGS_REVISION, _REVISION_SNAPSHOT
|
||||
if snapshot != _REVISION_SNAPSHOT:
|
||||
_SETTINGS_REVISION += 1
|
||||
_REVISION_SNAPSHOT = copy.deepcopy(snapshot)
|
||||
|
||||
|
||||
def _write_settings_unlocked(data: dict) -> None:
|
||||
tmp = (
|
||||
f"{_SETTINGS_FILE}.{os.getpid()}.{threading.get_ident()}.tmp"
|
||||
)
|
||||
try:
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp, _SETTINGS_FILE)
|
||||
except Exception:
|
||||
try:
|
||||
os.unlink(tmp)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
# The module defaults are revision zero's baseline. Loading a local override
|
||||
# below advances the revision only when it actually changes an effective value.
|
||||
_REVISION_SNAPSHOT = _settings_snapshot_unlocked()
|
||||
|
||||
|
||||
def save_settings():
|
||||
"""将当前配置原子写入 ai_settings.json(GUI 保存时调用)。"""
|
||||
g = globals()
|
||||
data = {k: g[k] for k in CONFIGURABLE_KEYS}
|
||||
tmp = _SETTINGS_FILE + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp, _SETTINGS_FILE)
|
||||
"""将当前配置原子写入本机私密配置(GUI/后台同步时调用)。"""
|
||||
with _SETTINGS_LOCK:
|
||||
data = _settings_snapshot_unlocked()
|
||||
_write_settings_unlocked(data)
|
||||
# This also detects legacy callers which assigned globals immediately
|
||||
# before calling save_settings().
|
||||
_record_revision_unlocked(data)
|
||||
|
||||
|
||||
def export_settings() -> dict:
|
||||
"""返回可下发给桌面端的 AI 配置副本。"""
|
||||
g = globals()
|
||||
return {key: g[key] for key in CONFIGURABLE_KEYS}
|
||||
with _SETTINGS_LOCK:
|
||||
return _settings_snapshot_unlocked()
|
||||
|
||||
|
||||
def get_settings_revision() -> int:
|
||||
"""Return the process-local revision of the effective AI configuration."""
|
||||
with _SETTINGS_LOCK:
|
||||
return _SETTINGS_REVISION
|
||||
|
||||
|
||||
def apply_settings(settings: dict, *, persist: bool = True) -> dict:
|
||||
@@ -239,34 +318,51 @@ def apply_settings(settings: dict, *, persist: bool = True) -> dict:
|
||||
global AI_SYSTEM_PROMPT
|
||||
if not isinstance(settings, dict):
|
||||
raise TypeError("AI 配置必须是 JSON 对象")
|
||||
g = globals()
|
||||
applied = {}
|
||||
for key in CONFIGURABLE_KEYS:
|
||||
if key in settings:
|
||||
g[key] = settings[key]
|
||||
applied[key] = settings[key]
|
||||
AI_SYSTEM_PROMPT = build_system_prompt()
|
||||
if persist:
|
||||
save_settings()
|
||||
return applied
|
||||
with _SETTINGS_LOCK:
|
||||
g = globals()
|
||||
next_snapshot = _settings_snapshot_unlocked()
|
||||
applied = {}
|
||||
for key in CONFIGURABLE_KEYS:
|
||||
if key in settings:
|
||||
value = copy.deepcopy(settings[key])
|
||||
next_snapshot[key] = value
|
||||
applied[key] = copy.deepcopy(value)
|
||||
|
||||
# Persist the complete prospective snapshot before publishing it to
|
||||
# other threads. A failed write therefore leaves runtime values and
|
||||
# the revision untouched.
|
||||
if persist:
|
||||
_write_settings_unlocked(next_snapshot)
|
||||
for key, value in next_snapshot.items():
|
||||
g[key] = value
|
||||
AI_SYSTEM_PROMPT = build_system_prompt()
|
||||
_record_revision_unlocked(next_snapshot)
|
||||
return applied
|
||||
|
||||
|
||||
def load_settings():
|
||||
"""从 ai_settings.json 加载已保存的配置,覆盖本文件中的默认值。"""
|
||||
global AI_SYSTEM_PROMPT
|
||||
if not os.path.exists(_SETTINGS_FILE):
|
||||
"""加载本机私密配置,并一次性迁移旧版的 tracked JSON。"""
|
||||
source = (
|
||||
_SETTINGS_FILE
|
||||
if os.path.exists(_SETTINGS_FILE)
|
||||
else _LEGACY_SETTINGS_FILE
|
||||
)
|
||||
if not os.path.exists(source):
|
||||
return
|
||||
try:
|
||||
with open(_SETTINGS_FILE, encoding="utf-8") as f:
|
||||
with open(source, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except Exception:
|
||||
return
|
||||
g = globals()
|
||||
for k in CONFIGURABLE_KEYS:
|
||||
if k in data:
|
||||
g[k] = data[k]
|
||||
# 昵称/医院名可能被覆盖,重新渲染提示词
|
||||
AI_SYSTEM_PROMPT = build_system_prompt()
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
apply_settings(data, persist=False)
|
||||
if source == _LEGACY_SETTINGS_FILE and not os.path.exists(_SETTINGS_FILE):
|
||||
try:
|
||||
save_settings()
|
||||
except OSError:
|
||||
# 只读安装仍可使用模板默认值;后台同步时会再次尝试持久化。
|
||||
pass
|
||||
|
||||
|
||||
load_settings()
|
||||
|
||||
Reference in New Issue
Block a user