From 46b6a107304448ddfc8815d868c2c791277b9579 Mon Sep 17 00:00:00 2001 From: Jeason <1710884619@qq.com> Date: Thu, 2 Apr 2026 23:06:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=9F=E6=88=B7=E7=BA=A7=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E6=8F=90=E7=A4=BA=E8=AF=8D=20+=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 系统提示词按租户配置: - 去掉所有硬编码的'奇瑞汽车'品牌名 - realtime_chat 的 _build_chat_prompt 按 tenant_id 动态获取提示词 - _generate_response 和 _generate_response_stream 都传递 tenant_id - 默认提示词为通用客服助手(不含品牌名) - 租户编辑弹窗新增系统提示词配置区 2. 模块权限树: - Tenant config 新增 modules 字段(14个模块的开关) - GET /api/tenants/my-modules 返回当前用户所属租户的模块权限 - 前端 applyModulePermissions() 初始化时隐藏无权限的侧边栏标签 - 租户编辑弹窗新增模块权限 checkbox 配置区 - 默认全部模块开启,取消勾选即隐藏 3. 其他清理: - llm_client.py 通用提示词去掉品牌名 - react_agent.py SYSTEM_PROMPT 去掉'车辆'限定 --- data/tsp_assistant.db | Bin 217088 -> 217088 bytes src/agent/react_agent.py | 2 +- src/core/llm_client.py | 2 +- src/dialogue/realtime_chat.py | 28 ++++++++------ src/web/blueprints/tenants.py | 54 +++++++++++++++++++++++++++ src/web/static/js/dashboard.js | 19 ++++++++++ src/web/static/js/modules/tenants.js | 24 ++++++++++++ src/web/templates/dashboard.html | 26 +++++++++++++ 8 files changed, 141 insertions(+), 14 deletions(-) diff --git a/data/tsp_assistant.db b/data/tsp_assistant.db index 89ee44e98c7084597ecd3537cdffdc0cf0bd7566..3ed182e9c95fd79ba41c2c34ee80c6584d75f3a8 100644 GIT binary patch delta 4016 zcmeH~e{54#6vyAaTi11KR~ADMQJBGi$hP!%%M@H8h7e;Q{<9D@Gs`*wd{?pT{l>_N?5y5w^!D7MS}?;F>2HV@W(&)-uGVA4C7w}7d_ou2SFt!`e*xc zoA>U!_uPBV_j68fZDT6im@2SIb&X*dBech%sr#QcqieIT`}|6yzQv$#*6Ym{jV^V^ zU)xZP)OTSB^Gr%P^P3vUFI_>c>#OS+EI-UB4f4bHu2g-)K6#5>C$D4nJ;5BgR_~v;D8&N4q+X9_cuO-|u*% z^Vpm3bR5@dbfzYap-H1_(CJ!=yR$S~^jeEX|G*n_i^0$gW_Rt<{KFfKUPFDHXbaWJ zE4MNBgZ3TF?u`ng9a*i+rqV9ioocj`%`4Cj?t^ONmQ^wgy2I7hp(+Ho%o4j^jecIQ z`i8-(IYwQhn!9&_|54RbTPpUUlChG1_qi-rzl)4)>t^&CXWEROMKUnzN5NmydumJu zlM%z?eg!=4oo_($0IFlFXOLdj4Bb1q)){nFt;AK7seP-8R?&(yx9$r1w5lH7LfhSQ z&?;x-O`s}A2k~abROJ`wiV`bsF)D@P7H-3Bu*LnDYa75PF?fSFa$kn9LZ;|xRKxrO zo_2QZ2G%_(Mn*|G%?Emfsq?fuF4~>MH3I^PY(hLY^rNj?Qi}MK!l@h$yUD~bKjkBR zQ8F^Nl=YI#f{;kSL>N%W#K=rUNS=Y4Lb{hu$HWU0LViRT_tL%r8jUSwhikM;S+_Vj zNE|MHJRu||gxsXC*h3~x7Z&=&kR2X*&pfr+3+WIanxhK=!d)VcO(~I`UdoQt=wLcM zZv$i0-Als@)rx} zc8U+hsfz=%q{mBxzCtbxOM)OZWU`x%hF5~@k%C;vu)m+h8>&lU1aosuFT&6@U1)Ed zx`QO<O^9y-{M$=@m=%HCGLLW!`gP>X=Z3E*ZogkhM**`{c zqCkIU5JAAmAjH13Dh0Nf)&kpNRru{P@K}W}f{xiymQX z@<k=HDx=s=du%*>$G+j_kVZI#i@z zFn9MH{v4Of&>9WpGE^=@*=us$87P#i@KoDxZ-ac1C*Xe>)?=f3wfH?KknwlYauN5%JRi delta 760 zcma)(Ur1A77{t4R)-Ur1*~Yg+UEG8K+=2^!Jc^m4Lmn-tc8dQ_;Tk0dBJ&WSL_b0a2H4T}{7 z;Ph@#sa=In`8Xv@)Frh~HCvO`dL_%^Ruo$Ah7Kd*hMmB-aJmMGjl~9YBoql_Gm{dN zyBEA*ii1qW6a@KlwpfMHn1aWu%oi*Y<*DV6R3~JO!VEkS(%A)2Txx*2f3YgBqQC4tGkUQD5YE=ZTJ3_rR4Qa}gox zXmOJ%u9b<^w7{O_R>rVaC|5jhy;wgv*?C@ z0nN0cw=lp+`(mB_1E+d>7-mU6lkRt;J+ZoPDvw2*Jy@KFI#Yv{|6c)fmodVb)X?Y2 zll^%@ str: return "\n".join(lines) -SYSTEM_PROMPT = f"""你是 TSP 智能客服助手,帮助用户解决车辆售后问题、查询知识库、管理客诉信息。 +SYSTEM_PROMPT = f"""你是 TSP 智能客服助手,帮助用户解决售后问题、查询知识库、管理客诉信息。 你可以使用以下工具来完成任务: {_build_tools_prompt()} diff --git a/src/core/llm_client.py b/src/core/llm_client.py index 80713c1..15c554d 100644 --- a/src/core/llm_client.py +++ b/src/core/llm_client.py @@ -140,7 +140,7 @@ class LLMClient: knowledge_base: Optional[List[str]] = None, ) -> Dict[str, Any]: """快捷生成回复""" - system_prompt = "你是一个专业的客服助手,请根据用户问题提供准确、有帮助的回复。" + system_prompt = "你是一个专业的智能客服助手,请根据用户问题提供准确、有帮助的回复。" if context: system_prompt += f"\n\n上下文信息: {context}" if knowledge_base: diff --git a/src/dialogue/realtime_chat.py b/src/dialogue/realtime_chat.py index 724bc05..899dd0d 100644 --- a/src/dialogue/realtime_chat.py +++ b/src/dialogue/realtime_chat.py @@ -164,7 +164,8 @@ class RealtimeChatManager: user_message, knowledge_results, session["context"], - session["work_order_id"] + session["work_order_id"], + tenant_id=session_tenant ) # 创建助手消息 @@ -228,14 +229,11 @@ class RealtimeChatManager: logger.error(f"搜索知识库失败: {e}") return [] - def _generate_response(self, user_message: str, knowledge_results: List[Dict], context: List[Dict], work_order_id: Optional[int] = None) -> Dict[str, Any]: + def _generate_response(self, user_message: str, knowledge_results: List[Dict], context: List[Dict], work_order_id: Optional[int] = None, tenant_id: str = None) -> Dict[str, Any]: """生成回复""" try: - # 检查是否有相关的工单AI建议 ai_suggestions = self._get_workorder_ai_suggestions(work_order_id) - - # 构建提示词 - prompt = self._build_chat_prompt(user_message, knowledge_results, context, ai_suggestions) + prompt = self._build_chat_prompt(user_message, knowledge_results, context, ai_suggestions, tenant_id=tenant_id) # 调用大模型 response = self.llm_client.chat_completion( @@ -272,11 +270,11 @@ class RealtimeChatManager: "ai_suggestions": [] } - def _generate_response_stream(self, user_message: str, knowledge_results: List[Dict], context: List[Dict], work_order_id: Optional[int] = None): + def _generate_response_stream(self, user_message: str, knowledge_results: List[Dict], context: List[Dict], work_order_id: Optional[int] = None, tenant_id: str = None): """流式生成回复,yield 每个 token 片段""" try: ai_suggestions = self._get_workorder_ai_suggestions(work_order_id) - prompt = self._build_chat_prompt(user_message, knowledge_results, context, ai_suggestions) + prompt = self._build_chat_prompt(user_message, knowledge_results, context, ai_suggestions, tenant_id=tenant_id) for chunk in self.llm_client.chat_completion_stream( messages=[{"role": "user", "content": prompt}], @@ -364,10 +362,16 @@ class RealtimeChatManager: # 发送完成事件 yield f"data: {json.dumps({'done': True, 'confidence_score': confidence, 'message_id': assistant_msg.message_id}, ensure_ascii=False)}\n\n" - def _build_chat_prompt(self, user_message: str, knowledge_results: List[Dict], context: List[Dict], ai_suggestions: List[str] = None) -> str: - """构建聊天提示词""" - prompt = f""" -你是一个专业的奇瑞汽车客服助手。请根据用户的问题和提供的知识库信息,给出专业、友好的回复。 + def _build_chat_prompt(self, user_message: str, knowledge_results: List[Dict], context: List[Dict], ai_suggestions: List[str] = None, tenant_id: str = None) -> str: + """构建聊天提示词(按租户使用不同的系统提示词)""" + # 获取租户级系统提示词 + try: + from src.web.blueprints.tenants import get_tenant_system_prompt + system_prompt = get_tenant_system_prompt(tenant_id) if tenant_id else get_tenant_system_prompt('default') + except Exception: + system_prompt = "你是一个专业的智能客服助手。请根据用户的问题和提供的知识库信息,给出专业、友好的回复。" + + prompt = f"""{system_prompt} 用户问题:{user_message} diff --git a/src/web/blueprints/tenants.py b/src/web/blueprints/tenants.py index 9e3b989..008de53 100644 --- a/src/web/blueprints/tenants.py +++ b/src/web/blueprints/tenants.py @@ -43,6 +43,15 @@ def list_tenants(): return jsonify({"error": str(e)}), 500 +@tenants_bp.route('/my-modules', methods=['GET']) +def get_my_modules(): + """获取当前登录用户所属租户的模块权限""" + from flask import session as flask_session + tenant_id = flask_session.get('tenant_id', DEFAULT_TENANT) + modules = get_tenant_modules(tenant_id) + return jsonify({"success": True, "tenant_id": tenant_id, "modules": modules}) + + @tenants_bp.route('/feishu-groups', methods=['GET']) def list_feishu_groups(): """获取机器人所在的所有飞书群,并标注每个群当前绑定的租户""" @@ -205,3 +214,48 @@ def get_tenant_feishu_config(tenant_id: str) -> dict: except Exception as e: logger.error(f"获取租户飞书配置失败: {e}") return {} + + +# 默认系统提示词(不含品牌名) +DEFAULT_SYSTEM_PROMPT = "你是一个专业的智能客服助手。请根据用户的问题和提供的知识库信息,给出专业、友好的回复。" + +# 默认模块权限(全部开启) +DEFAULT_MODULES = { + "dashboard": True, "chat": True, "agent": True, + "alerts": True, "knowledge": True, "workorders": True, + "feishu-sync": True, "conversation-history": True, + "token-monitor": True, "ai-monitor": True, + "system-optimizer": True, "analytics": True, + "settings": True, "tenant-management": True +} + + +def get_tenant_system_prompt(tenant_id: str) -> str: + """获取租户的系统提示词,未配置则返回默认提示词""" + try: + with db_manager.get_session() as session: + tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first() + if tenant and tenant.config: + cfg = json.loads(tenant.config) + prompt = cfg.get('system_prompt') + if prompt: + return prompt + except Exception as e: + logger.warning(f"获取租户提示词失败: {e}") + return DEFAULT_SYSTEM_PROMPT + + +def get_tenant_modules(tenant_id: str) -> dict: + """获取租户的模块权限配置""" + try: + with db_manager.get_session() as session: + tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first() + if tenant and tenant.config: + cfg = json.loads(tenant.config) + modules = cfg.get('modules') + if modules: + # 合并默认值(新增模块自动开启) + return {**DEFAULT_MODULES, **modules} + except Exception as e: + logger.warning(f"获取租户模块权限失败: {e}") + return DEFAULT_MODULES.copy() diff --git a/src/web/static/js/dashboard.js b/src/web/static/js/dashboard.js index 04bbcd0..28f83cb 100644 --- a/src/web/static/js/dashboard.js +++ b/src/web/static/js/dashboard.js @@ -105,6 +105,7 @@ class TSPDashboard { this.restorePageState(); this.initLanguage(); this.initSmartUpdate(); + this.applyModulePermissions(); window.addEventListener('beforeunload', () => { this.destroyAllCharts(); this.cleanupConnections(); }); } @@ -115,6 +116,24 @@ class TSPDashboard { updatePageLanguage(this.currentLanguage); } + async applyModulePermissions() { + try { + const resp = await fetch('/api/tenants/my-modules'); + const data = await resp.json(); + if (!data.success) return; + const modules = data.modules || {}; + // 隐藏无权限的侧边栏标签和对应内容区 + document.querySelectorAll('[data-tab]').forEach(link => { + const tabName = link.dataset.tab; + if (modules[tabName] === false) { + link.style.display = 'none'; + const tabContent = document.getElementById(`${tabName}-tab`); + if (tabContent) tabContent.style.display = 'none'; + } + }); + } catch (e) { /* 权限加载失败不影响使用 */ } + } + init() { this.bindEvents(); this.populateTenantSelectors(); diff --git a/src/web/static/js/modules/tenants.js b/src/web/static/js/modules/tenants.js index 9126ced..5e6614a 100644 --- a/src/web/static/js/modules/tenants.js +++ b/src/web/static/js/modules/tenants.js @@ -44,6 +44,8 @@ Object.assign(TSPDashboard.prototype, { document.getElementById('tenant-name-input').value = ''; document.getElementById('tenant-desc-input').value = ''; document.getElementById('tenant-feishu-chatgroups').value = ''; + document.getElementById('tenant-system-prompt').value = ''; + document.querySelectorAll('.tenant-module-cb').forEach(cb => cb.checked = true); document.getElementById('feishu-groups-list').innerHTML = '点击"刷新群列表"从飞书拉取机器人所在的群'; new bootstrap.Modal(document.getElementById('tenantModal')).show(); }, @@ -62,6 +64,13 @@ Object.assign(TSPDashboard.prototype, { document.getElementById('tenant-name-input').value = tenant.name || ''; document.getElementById('tenant-desc-input').value = tenant.description || ''; document.getElementById('tenant-feishu-chatgroups').value = (tenant.config?.feishu?.chat_groups || []).join('\n'); + // 系统提示词 + document.getElementById('tenant-system-prompt').value = tenant.config?.system_prompt || ''; + // 模块权限 + document.querySelectorAll('.tenant-module-cb').forEach(cb => { + const mod = cb.value; + cb.checked = tenant.config?.modules?.[mod] !== false; // 默认开启 + }); } } catch (e) { console.warn('加载租户数据失败:', e); } // 自动加载飞书群列表 @@ -152,6 +161,21 @@ Object.assign(TSPDashboard.prototype, { if (Object.keys(config.feishu).length === 0) delete config.feishu; } + // 系统提示词 + const systemPrompt = document.getElementById('tenant-system-prompt').value.trim(); + if (systemPrompt) { + config.system_prompt = systemPrompt; + } else { + delete config.system_prompt; + } + + // 模块权限 + const modules = {}; + document.querySelectorAll('.tenant-module-cb').forEach(cb => { + modules[cb.value] = cb.checked; + }); + config.modules = modules; + if (!name) { this.showNotification('租户名称不能为空', 'error'); return; } try { diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html index 11e36ab..6dd8b0f 100644 --- a/src/web/templates/dashboard.html +++ b/src/web/templates/dashboard.html @@ -2273,6 +2273,32 @@ +
+
系统提示词
+
+ +
机器人回复时使用的角色设定,不同租户可以有不同的品牌定位
+
+
+
模块权限 (取消勾选可隐藏对应功能)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+