This commit is contained in:
Your Name
2026-07-29 09:34:02 +08:00
parent 0ff8943ee2
commit f913a57529
54 changed files with 2453 additions and 378 deletions
+81 -18
View File
@@ -9,6 +9,8 @@ AI 大模型配置文件
import json
import os
from runtime_paths import application_data_dir
# ── 是否启用 AI 回复(False 时使用固定文本回复)──
AI_ENABLED = True
@@ -34,6 +36,8 @@ AI_USE_VISION = False
# ── 客服身份(出现在提示词里,可按需修改为你的真实昵称/工号)──
AI_AGENT_NAME = "高兴亮"
AI_CLOUD_AGENT_NAME = AI_AGENT_NAME
AI_LOCAL_AGENT_NAME = ""
# ── 所属医院(建议就医时统一引导到这里挂号)──
AI_HOSPITAL_NAME = "甄养堂互联网医院"
@@ -98,6 +102,10 @@ AI_SYSTEM_PROMPT_TEMPLATE = """你就是企业微信里的人工客服「{AI_AGE
同一个会话里这种话最多说一次,且每次措辞都要不一样,像临场想的,不是背台词。
- 不用不厚道地敷衍或明显在"执行任务"式转移话题;可以顺着聊一两句再自然带回正题,
或者干脆就随口接一句玩笑话,不必每次都往业务上拽。
- 先判断对方这一句是在闲聊、问业务还是问健康。闲聊时只顺着当前话题聊,严禁主动扯到血糖、
身体、医院、医生、挂号或面诊;普通问题直接回答,不要教育对方。
- 客户问「你多大了/你几岁」时,直接自然回答「四十来岁」;不要回避,不要说「别管我多大」,
更不要强行把话题拉回健康。客户说「好困啊」这类日常感叹时,简单接一句关心即可。
【你的性格底色|先有人,再有话】
- 你是个四十岁上下、干了十几年的老客服兼医生:见过各种病人,什么问题都不慌,
@@ -137,7 +145,7 @@ AI_SYSTEM_PROMPT_TEMPLATE = """你就是企业微信里的人工客服「{AI_AGE
【糖尿病专业问题|专业 + 人性化】
- 凡是涉及血糖、糖尿病、胰岛素、降糖药、糖化血红蛋白、并发症、糖尿病饮食/运动等话题,
要以糖尿病专家的身份给出准确、专业、有依据的回答,可适当放宽到 3~5 句,
要以糖尿病专家的身份给出准确、专业、有依据的回答,仍尽量控制在 1~2 句;确有安全风险时最多 3 句,
但必须仍然是微信聊天的口吻——像医生朋友在微信上跟你唠,绝不能写成科普文章或报告。
- 不急着一次给全套方案:先问清或先给最关键的一两条,说完可以自然收尾
(如「先这么调两天看看,有变化随时跟我说」),把话头留给对方。
@@ -186,7 +194,15 @@ AI_SYSTEM_PROMPT_TEMPLATE = """你就是企业微信里的人工客服「{AI_AGE
【处理原则|要真正把事推进,而不是空承诺或瞎编】
- 只针对客户【最后一条】消息回复,紧扣聊天上下文,别答非所问。
- 缺关键信息时,同一条消息里明确问对方要(单号、姓名、手机号、时间、地址、病症)。
- 只有在你确实拿到真实查询结果时,才能告知具体状态/时效;否则只承诺去核实、问清信息。"""
- 只有在你确实拿到真实查询结果时,才能告知具体状态/时效;否则只承诺去核实、问清信息。
【本轮输出硬规则|高于一般表达习惯】
- 默认只回复 1~2 句、20~60 个汉字;只处理对方眼下最关心的一件事。
- 只有对方明确担心、难受、着急或抱怨时,才先用一句自然短话接住他,再给最关键的回答;
普通闲聊和直接提问要直接接话,不要强行安慰或转到健康话题。
- 一条消息最多问一个问题;不要标题、列表、分点、长篇科普和客套收尾。
- 除急救风险外不要超过 100 字;有急救风险时最多 3 句,并保留 120/急诊等必要提醒。
- 最终只输出可以直接发给客户的聊天正文,不解释自己的写法。"""
def build_system_prompt() -> str:
@@ -202,7 +218,9 @@ AI_SYSTEM_PROMPT = build_system_prompt()
# ──────────────────────────────────────────────────────────────────────────────
# GUI 配置持久化(ai_settings.json
# ──────────────────────────────────────────────────────────────────────────────
_SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ai_settings.json")
_DATA_DIR = application_data_dir()
_SETTINGS_FILE = str(_DATA_DIR / "ai_settings.json")
_AGENT_OVERRIDE_FILE = str(_DATA_DIR / "ai_agent_override.json")
# 允许通过 GUI 修改并持久化的配置项
CONFIGURABLE_KEYS = [
@@ -214,10 +232,45 @@ CONFIGURABLE_KEYS = [
]
def _read_agent_name_override() -> str:
try:
with open(_AGENT_OVERRIDE_FILE, encoding="utf-8") as file:
data = json.load(file)
except (OSError, ValueError, TypeError):
return ""
if not isinstance(data, dict):
return ""
return str(data.get("AI_AGENT_NAME") or "").strip()
def local_agent_name_override() -> str:
return str(AI_LOCAL_AGENT_NAME or "")
def set_local_agent_name_override(value: str, *, persist: bool = True) -> str:
"""设置本机客服名称;空值表示跟随云端。"""
global AI_AGENT_NAME, AI_LOCAL_AGENT_NAME, AI_SYSTEM_PROMPT
AI_LOCAL_AGENT_NAME = str(value or "").strip()
AI_AGENT_NAME = AI_LOCAL_AGENT_NAME or AI_CLOUD_AGENT_NAME or "客服"
AI_SYSTEM_PROMPT = build_system_prompt()
if persist:
temporary = _AGENT_OVERRIDE_FILE + ".tmp"
with open(temporary, "w", encoding="utf-8") as file:
json.dump(
{"AI_AGENT_NAME": AI_LOCAL_AGENT_NAME},
file,
ensure_ascii=False,
indent=2,
)
os.replace(temporary, _AGENT_OVERRIDE_FILE)
return AI_AGENT_NAME
def save_settings():
"""将当前配置原子写入 ai_settings.jsonGUI 保存时调用)。"""
g = globals()
data = {k: g[k] for k in CONFIGURABLE_KEYS}
data["AI_AGENT_NAME"] = AI_CLOUD_AGENT_NAME
tmp = _SETTINGS_FILE + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
@@ -227,7 +280,9 @@ def save_settings():
def export_settings() -> dict:
"""返回可下发给桌面端的 AI 配置副本。"""
g = globals()
return {key: g[key] for key in CONFIGURABLE_KEYS}
data = {key: g[key] for key in CONFIGURABLE_KEYS}
data["AI_AGENT_NAME"] = AI_CLOUD_AGENT_NAME
return data
def apply_settings(settings: dict, *, persist: bool = True) -> dict:
@@ -236,15 +291,19 @@ def apply_settings(settings: dict, *, persist: bool = True) -> dict:
未知字段会被忽略,避免后台响应意外覆盖模块中的其他全局变量。
返回实际应用的字段,便于同步客户端记录版本与诊断信息。
"""
global AI_SYSTEM_PROMPT
global AI_AGENT_NAME, AI_CLOUD_AGENT_NAME, 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]
if key == "AI_AGENT_NAME":
AI_CLOUD_AGENT_NAME = str(settings[key] or "").strip() or "客服"
else:
g[key] = settings[key]
applied[key] = settings[key]
AI_AGENT_NAME = AI_LOCAL_AGENT_NAME or AI_CLOUD_AGENT_NAME
AI_SYSTEM_PROMPT = build_system_prompt()
if persist:
save_settings()
@@ -253,19 +312,23 @@ def apply_settings(settings: dict, *, persist: bool = True) -> dict:
def load_settings():
"""从 ai_settings.json 加载已保存的配置,覆盖本文件中的默认值。"""
global AI_SYSTEM_PROMPT
if not os.path.exists(_SETTINGS_FILE):
return
try:
with open(_SETTINGS_FILE, encoding="utf-8") as f:
data = json.load(f)
except Exception:
return
global AI_AGENT_NAME, AI_CLOUD_AGENT_NAME, AI_LOCAL_AGENT_NAME, AI_SYSTEM_PROMPT
data = {}
if os.path.exists(_SETTINGS_FILE):
try:
with open(_SETTINGS_FILE, encoding="utf-8") as f:
data = json.load(f)
except Exception:
data = {}
g = globals()
for k in CONFIGURABLE_KEYS:
if k in data:
g[k] = data[k]
# 昵称/医院名可能被覆盖,重新渲染提示词
if isinstance(data, dict):
for k in CONFIGURABLE_KEYS:
if k in data:
g[k] = data[k]
AI_CLOUD_AGENT_NAME = str(AI_AGENT_NAME or "").strip() or "客服"
AI_LOCAL_AGENT_NAME = _read_agent_name_override()
AI_AGENT_NAME = AI_LOCAL_AGENT_NAME or AI_CLOUD_AGENT_NAME
# 云端昵称、本地昵称或医院名可能被覆盖,重新渲染提示词
AI_SYSTEM_PROMPT = build_system_prompt()