feat: 示例问题老板视角过滤+画像定向+prompt禁止清单
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

后端:
- filter_boss_perspective(): 关键词过滤员工视角问题
  覆盖:加班费/被辞退/拖欠工资/工伤认定/产假/劳动仲裁/降薪扣工资等
- /samples: prompt加禁止清单+正确视角示例,返回前过滤
  画像注入加行业定向(有industry时明确要求生成行业相关问题)
- /topics: prompt加禁止清单+正确视角示例,返回前过滤
- generate_daily_topics: prompt加禁止清单+正确视角示例,返回前过滤

前端:
- 审查FALLBACK_SAMPLES: 三个场景兜底示例均为老板视角,无需修改

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-09-01 09:24:05 +08:00
co-authored by Devin
parent 86f15bc79c
commit 3d64b98ed6
2 changed files with 126 additions and 19 deletions
+45 -12
View File
@@ -18,7 +18,7 @@ from bossai import (
ExpertStore, ConsultationStore, RatingStore,
DocumentGenerator, RegulationUpdateManager, FactFinder,
ChatStore, redact,
DailyTopicStore, generate_daily_topics,
DailyTopicStore, generate_daily_topics, filter_boss_perspective,
)
# ===== 日志脱敏 =====
@@ -394,9 +394,19 @@ def generate_topics(req: TopicsRequest):
1. 话题要覆盖该场景下不同方向,不要只集中在某一类
2. 结合用户画像(如有)推荐个性化话题
3. 示例问题要具体、贴近中小企业老板实际
4. **所有问题必须从老板/雇主视角出发**(BossAi 的用户是企业老板,不是员工)。例如劳动用工应问"员工工伤后能开除吗"而非"拖欠工资怎么追讨"
4. **所有问题必须从老板/雇主视角出发**(BossAi 的用户是企业老板,不是员工)
5. 返回JSON数组格式:[{{"title":"话题名","desc":"简短描述","samples":["问题1","问题2","问题3","问题4"]}}]
6. 只返回JSON,不要其他文字{existing_str}"""
6. 只返回JSON,不要其他文字{existing_str}
**严禁生成员工视角问题:**
- 加班费怎么算、被辞退怎么维权、拖欠工资怎么追讨
- 请假被拒、工伤认定、产假怎么休、劳动仲裁怎么告公司
- 降我薪、扣我工资、罚我款、被歧视、被骚扰
**正确视角示例:**
- 老板问:员工工伤后能开除吗?(不是"工伤赔偿怎么申请")
- 老板问:辞退员工要赔多少?(不是"被辞退怎么维权")
- 老板问:客户拖欠货款如何催收?(不是"拖欠工资怎么追")"""
try:
answer = kimi.chat([{"role": "user", "content": prompt}], temperature=0.8)
@@ -411,15 +421,19 @@ def generate_topics(req: TopicsRequest):
if start == -1 or end == -1:
return {"topics": builtin_topics.get(req.scene, builtin_topics["legal"])}
topics = json.loads(answer[start:end + 1])
# 校验结构
# 校验结构 + 老板视角过滤
valid_topics = []
for t in topics:
if isinstance(t, dict) and t.get("title") and t.get("samples"):
valid_topics.append({
"title": str(t["title"])[:10],
"desc": str(t.get("desc", ""))[:30],
"samples": [str(s) for s in t["samples"] if isinstance(s, str) and s.strip()][:4],
})
boss_samples = filter_boss_perspective(
[str(s) for s in t["samples"] if isinstance(s, str) and s.strip()]
)[:4]
if boss_samples:
valid_topics.append({
"title": str(t["title"])[:10],
"desc": str(t.get("desc", ""))[:30],
"samples": boss_samples,
})
if len(valid_topics) < 3:
return {"topics": builtin_topics.get(req.scene, builtin_topics["legal"])}
return {"topics": valid_topics[:8]}
@@ -469,6 +483,17 @@ def generate_samples(req: SampleRequest):
profile_ctx = build_profile_context(profile)
if profile_ctx:
prompt += f"用户画像(请结合画像生成更贴合用户自身情况的问题):\n{profile_ctx}\n\n"
# 行业定向:如果有行业,加明确指令
if profile.industry:
industry_map = {
"retail": "零售/餐饮", "manufacturing": "制造业/工厂",
"tech": "科技/互联网", "service": "服务业",
"construction": "建筑/工程", "logistics": "物流/运输",
"finance": "金融/投资", "education": "教育/培训",
"healthcare": "医疗/健康", "agriculture": "农业/农产品",
}
industry_label = industry_map.get(profile.industry, profile.industry)
prompt += f"**用户行业为{industry_label},请生成与该行业密切相关的问题**\n\n"
except Exception:
pass
if existing:
@@ -482,7 +507,13 @@ def generate_samples(req: SampleRequest):
"2. **所有问题必须从老板/雇主视角出发**(用户是企业老板,不是员工)\n"
"3. 每个问题 15-30 字\n"
"4. 不要重复已展示过的问题\n"
"5. 只返回 JSON 数组,如 [\"问题1\", \"问题2\", \"问题3\", \"问题4\"]\n"
"5. 只返回 JSON 数组,如 [\"问题1\", \"问题2\", \"问题3\", \"问题4\"]\n\n"
"**严禁生成员工视角问题(如:加班费怎么算、被辞退怎么维权、拖欠工资怎么追讨、**\n"
"**请假被拒、工伤认定、产假怎么休、劳动仲裁怎么告公司、降我薪扣我工资等)**\n\n"
"**正确视角示例:**\n"
"- 老板问:员工工伤后能开除吗?(不是"工伤赔偿怎么申请")\n"
"- 老板问:辞退员工要赔多少?(不是"被辞退怎么维权")\n"
"- 老板问:客户拖欠货款如何催收?(不是"拖欠工资怎么追")\n"
)
try:
@@ -507,8 +538,10 @@ def generate_samples(req: SampleRequest):
if start == -1 or end == -1:
return {"samples": builtin.get(req.scene, builtin["legal"])}
samples = json.loads(answer[start:end + 1])
# 过滤非字符串和空值
samples = [s for s in samples if isinstance(s, str) and s.strip()][:4]
# 过滤:老板视角 + 非字符串 + 去重
samples = filter_boss_perspective(
[s for s in samples if isinstance(s, str) and s.strip()]
)[:4]
if len(samples) < 2:
return {"samples": builtin.get(req.scene, builtin["legal"])}
return {"samples": samples}