feat: 租户级系统提示词 + 模块权限树

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 去掉'车辆'限定
This commit is contained in:
2026-04-02 23:06:59 +08:00
parent c3d709afeb
commit 46b6a10730
8 changed files with 141 additions and 14 deletions

Binary file not shown.

View File

@@ -77,7 +77,7 @@ def _build_tools_prompt() -> str:
return "\n".join(lines) return "\n".join(lines)
SYSTEM_PROMPT = f"""你是 TSP 智能客服助手,帮助用户解决车辆售后问题、查询知识库、管理客诉信息。 SYSTEM_PROMPT = f"""你是 TSP 智能客服助手,帮助用户解决售后问题、查询知识库、管理客诉信息。
你可以使用以下工具来完成任务: 你可以使用以下工具来完成任务:
{_build_tools_prompt()} {_build_tools_prompt()}

View File

@@ -140,7 +140,7 @@ class LLMClient:
knowledge_base: Optional[List[str]] = None, knowledge_base: Optional[List[str]] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""快捷生成回复""" """快捷生成回复"""
system_prompt = "你是一个专业的客服助手,请根据用户问题提供准确、有帮助的回复。" system_prompt = "你是一个专业的智能客服助手,请根据用户问题提供准确、有帮助的回复。"
if context: if context:
system_prompt += f"\n\n上下文信息: {context}" system_prompt += f"\n\n上下文信息: {context}"
if knowledge_base: if knowledge_base:

View File

@@ -164,7 +164,8 @@ class RealtimeChatManager:
user_message, user_message,
knowledge_results, knowledge_results,
session["context"], session["context"],
session["work_order_id"] session["work_order_id"],
tenant_id=session_tenant
) )
# 创建助手消息 # 创建助手消息
@@ -228,14 +229,11 @@ class RealtimeChatManager:
logger.error(f"搜索知识库失败: {e}") logger.error(f"搜索知识库失败: {e}")
return [] 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: try:
# 检查是否有相关的工单AI建议
ai_suggestions = self._get_workorder_ai_suggestions(work_order_id) ai_suggestions = self._get_workorder_ai_suggestions(work_order_id)
prompt = self._build_chat_prompt(user_message, knowledge_results, context, ai_suggestions, tenant_id=tenant_id)
# 构建提示词
prompt = self._build_chat_prompt(user_message, knowledge_results, context, ai_suggestions)
# 调用大模型 # 调用大模型
response = self.llm_client.chat_completion( response = self.llm_client.chat_completion(
@@ -272,11 +270,11 @@ class RealtimeChatManager:
"ai_suggestions": [] "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 片段""" """流式生成回复yield 每个 token 片段"""
try: try:
ai_suggestions = self._get_workorder_ai_suggestions(work_order_id) 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( for chunk in self.llm_client.chat_completion_stream(
messages=[{"role": "user", "content": prompt}], 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" 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: 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:
"""构建聊天提示词""" """构建聊天提示词(按租户使用不同的系统提示词)"""
prompt = f""" # 获取租户级系统提示词
你是一个专业的奇瑞汽车客服助手。请根据用户的问题和提供的知识库信息,给出专业、友好的回复。 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} 用户问题:{user_message}

View File

@@ -43,6 +43,15 @@ def list_tenants():
return jsonify({"error": str(e)}), 500 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']) @tenants_bp.route('/feishu-groups', methods=['GET'])
def list_feishu_groups(): def list_feishu_groups():
"""获取机器人所在的所有飞书群,并标注每个群当前绑定的租户""" """获取机器人所在的所有飞书群,并标注每个群当前绑定的租户"""
@@ -205,3 +214,48 @@ def get_tenant_feishu_config(tenant_id: str) -> dict:
except Exception as e: except Exception as e:
logger.error(f"获取租户飞书配置失败: {e}") logger.error(f"获取租户飞书配置失败: {e}")
return {} 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()

View File

@@ -105,6 +105,7 @@ class TSPDashboard {
this.restorePageState(); this.restorePageState();
this.initLanguage(); this.initLanguage();
this.initSmartUpdate(); this.initSmartUpdate();
this.applyModulePermissions();
window.addEventListener('beforeunload', () => { this.destroyAllCharts(); this.cleanupConnections(); }); window.addEventListener('beforeunload', () => { this.destroyAllCharts(); this.cleanupConnections(); });
} }
@@ -115,6 +116,24 @@ class TSPDashboard {
updatePageLanguage(this.currentLanguage); 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() { init() {
this.bindEvents(); this.bindEvents();
this.populateTenantSelectors(); this.populateTenantSelectors();

View File

@@ -44,6 +44,8 @@ Object.assign(TSPDashboard.prototype, {
document.getElementById('tenant-name-input').value = ''; document.getElementById('tenant-name-input').value = '';
document.getElementById('tenant-desc-input').value = ''; document.getElementById('tenant-desc-input').value = '';
document.getElementById('tenant-feishu-chatgroups').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 = '<small class="text-muted">点击"刷新群列表"从飞书拉取机器人所在的群</small>'; document.getElementById('feishu-groups-list').innerHTML = '<small class="text-muted">点击"刷新群列表"从飞书拉取机器人所在的群</small>';
new bootstrap.Modal(document.getElementById('tenantModal')).show(); 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-name-input').value = tenant.name || '';
document.getElementById('tenant-desc-input').value = tenant.description || ''; document.getElementById('tenant-desc-input').value = tenant.description || '';
document.getElementById('tenant-feishu-chatgroups').value = (tenant.config?.feishu?.chat_groups || []).join('\n'); 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); } } catch (e) { console.warn('加载租户数据失败:', e); }
// 自动加载飞书群列表 // 自动加载飞书群列表
@@ -152,6 +161,21 @@ Object.assign(TSPDashboard.prototype, {
if (Object.keys(config.feishu).length === 0) delete config.feishu; 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; } if (!name) { this.showNotification('租户名称不能为空', 'error'); return; }
try { try {

View File

@@ -2273,6 +2273,32 @@
</div> </div>
<input type="hidden" id="tenant-feishu-chatgroups"> <input type="hidden" id="tenant-feishu-chatgroups">
</div> </div>
<hr>
<h6 class="text-muted">系统提示词</h6>
<div class="mb-3">
<textarea class="form-control" id="tenant-system-prompt" rows="3" placeholder="如你是XX品牌的客服助手专注于为用户提供XX相关的服务...(留空使用默认通用提示词)"></textarea>
<div class="form-text">机器人回复时使用的角色设定,不同租户可以有不同的品牌定位</div>
</div>
<hr>
<h6 class="text-muted">模块权限 <small class="text-muted">(取消勾选可隐藏对应功能)</small></h6>
<div class="mb-3" id="tenant-modules-checkboxes">
<div class="row">
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="dashboard" id="mod-dashboard" checked><label class="form-check-label" for="mod-dashboard">仪表板</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="chat" id="mod-chat" checked><label class="form-check-label" for="mod-chat">智能对话</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="knowledge" id="mod-knowledge" checked><label class="form-check-label" for="mod-knowledge">知识库</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="workorders" id="mod-workorders" checked><label class="form-check-label" for="mod-workorders">工单管理</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="conversation-history" id="mod-conversation-history" checked><label class="form-check-label" for="mod-conversation-history">对话历史</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="alerts" id="mod-alerts" checked><label class="form-check-label" for="mod-alerts">预警管理</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="feishu-sync" id="mod-feishu-sync" checked><label class="form-check-label" for="mod-feishu-sync">飞书同步</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="agent" id="mod-agent" checked><label class="form-check-label" for="mod-agent">Agent管理</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="token-monitor" id="mod-token-monitor" checked><label class="form-check-label" for="mod-token-monitor">Token监控</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="ai-monitor" id="mod-ai-monitor" checked><label class="form-check-label" for="mod-ai-monitor">AI监控</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="analytics" id="mod-analytics" checked><label class="form-check-label" for="mod-analytics">数据分析</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="system-optimizer" id="mod-system-optimizer" checked><label class="form-check-label" for="mod-system-optimizer">系统优化</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="settings" id="mod-settings" checked><label class="form-check-label" for="mod-settings">系统设置</label></div></div>
<div class="col-6"><div class="form-check"><input class="form-check-input tenant-module-cb" type="checkbox" value="tenant-management" id="mod-tenant-management" checked><label class="form-check-label" for="mod-tenant-management">租户管理</label></div></div>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>