feat: 示例问题按场景过滤,防止跨场景混淆
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

后端:
- filter_by_scene(): 关键词判断问题归属场景,过滤跨场景问题
  legal: 仲裁/诉讼/合同/税务/股权等
  decision: 扩张/定价/招人/融资/现金流/竞争等
  life: 孩子/父母/养老/健康/睡眠等
  emotion: 焦虑/失眠/自我怀疑/崩溃/拖延症等
- generate_daily_topics: prompt加场景边界说明,返回前双重过滤(老板视角+场景归属)
- /samples: prompt加场景边界说明,返回前双重过滤
- /topics: prompt加禁止清单+正确视角示例

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:32:11 +08:00
co-authored by Devin
parent 9da6f69cae
commit eab1efa323
2 changed files with 111 additions and 11 deletions
+14 -7
View File
@@ -18,7 +18,7 @@ from bossai import (
ExpertStore, ConsultationStore, RatingStore, ExpertStore, ConsultationStore, RatingStore,
DocumentGenerator, RegulationUpdateManager, FactFinder, DocumentGenerator, RegulationUpdateManager, FactFinder,
ChatStore, redact, ChatStore, redact,
DailyTopicStore, generate_daily_topics, filter_boss_perspective, DailyTopicStore, generate_daily_topics, filter_boss_perspective, filter_by_scene,
) )
# ===== 日志脱敏 ===== # ===== 日志脱敏 =====
@@ -461,10 +461,14 @@ def generate_samples(req: SampleRequest):
return {"samples": builtin.get(req.scene, builtin["legal"])} return {"samples": builtin.get(req.scene, builtin["legal"])}
scene_config = { scene_config = {
"legal": {"desc": "法律/法务", "focus": "劳动用工、合同审查、债权债务、股东纠纷、税务风险、诉讼仲裁等法律问题"}, "legal": {"desc": "法律/法务", "focus": "劳动用工、合同审查、债权债务、股东纠纷、税务风险、诉讼仲裁等法律问题",
"decision": {"desc": "经营决策", "focus": "扩张收缩、定价策略、招人裁员、融资合伙、项目承接、业务转型等经营决策"}, "boundary": "只关注法律/法务问题,不要生成纯经营策略、纯情绪疏导、纯生活问题"},
"life": {"desc": "生活助理", "focus": "家庭沟通、子女教育、父母养老、睡眠压力、出差日程、健康习惯等个人生活问题,不是企业经营问题"}, "decision": {"desc": "经营决策", "focus": "扩张收缩、定价策略、招人裁员、融资合伙、项目承接、业务转型等经营决策",
"emotion": {"desc": "情绪疏导", "focus": "压力、焦虑、愤怒、拖延、疲惫等情绪状态,老板可能倾诉的心理感受"}, "boundary": "只关注经营决策问题。不要生成纯法律问题(如合同违约金怎么约定)、纯情绪问题(如焦虑失眠、自我怀疑、凌晨怀疑人生)、纯生活问题。情绪相关的决策问题可以,但必须是'该怎么做'而非'我很难受'"},
"life": {"desc": "生活助理", "focus": "家庭沟通、子女教育、父母养老、睡眠压力、出差日程、健康习惯等个人生活问题,不是企业经营问题",
"boundary": "只关注个人生活问题,不要生成纯法律问题、纯经营决策问题、纯情绪疏导问题"},
"emotion": {"desc": "情绪疏导", "focus": "压力、焦虑、愤怒、拖延、疲惫等情绪状态,老板可能倾诉的心理感受",
"boundary": "关注情绪与心理疏导,核心是情绪感受和心理状态,不是经营策略或法律方案"},
} }
config = scene_config.get(req.scene, scene_config["legal"]) config = scene_config.get(req.scene, scene_config["legal"])
@@ -473,6 +477,7 @@ def generate_samples(req: SampleRequest):
f"你是{config['desc']}领域的专业顾问。请生成 4 个该领域的典型问题," f"你是{config['desc']}领域的专业顾问。请生成 4 个该领域的典型问题,"
f"用户(中小企业老板)可能会问的问题。\n\n" f"用户(中小企业老板)可能会问的问题。\n\n"
f"问题方向:{config['focus']}\n\n" f"问题方向:{config['focus']}\n\n"
f"场景边界:{config['boundary']}\n\n"
) )
if req.topic: if req.topic:
prompt += f"具体话题:{req.topic}\n\n" prompt += f"具体话题:{req.topic}\n\n"
@@ -538,9 +543,11 @@ def generate_samples(req: SampleRequest):
if start == -1 or end == -1: if start == -1 or end == -1:
return {"samples": builtin.get(req.scene, builtin["legal"])} return {"samples": builtin.get(req.scene, builtin["legal"])}
samples = json.loads(answer[start:end + 1]) samples = json.loads(answer[start:end + 1])
# 过滤:老板视角 + 非字符串 + 去重 # 过滤:老板视角 + 场景归属 + 非字符串
samples = filter_boss_perspective( samples = filter_by_scene(
filter_boss_perspective(
[s for s in samples if isinstance(s, str) and s.strip()] [s for s in samples if isinstance(s, str) and s.strip()]
), req.scene
)[:4] )[:4]
if len(samples) < 2: if len(samples) < 2:
return {"samples": builtin.get(req.scene, builtin["legal"])} return {"samples": builtin.get(req.scene, builtin["legal"])}
+96 -3
View File
@@ -3349,6 +3349,87 @@ def filter_boss_perspective(questions: list[str]) -> list[str]:
return valid return valid
# ===== 场景过滤:防止问题跨场景混淆 =====
# 各场景的归属关键词:问题包含这些关键词时,应归属对应场景
SCENE_KEYWORDS = {
"emotion": [
"睡不着", "失眠", "焦虑", "抑郁", "崩溃", "自我怀疑", "自我贬低",
"撑不下去", "孤独", "愤怒", "委屈", "难受", "压力很大", "心力交瘁",
"怀疑人生", "怀疑自己", "不想干了", "想放弃", "绝望", "恐惧",
"情绪", "心态", "心理", "疏导", "陪伴", "倾诉",
"凌晨三点怀疑", "破PPT", "怀疑这", "贬低自己",
"吵架后", "吵了一架", "背叛了我", "背叛感",
"害怕做决定", "下不了手", "拖延症", "就是拖延",
],
"legal": [
"仲裁", "诉讼", "起诉", "合同", "违约", "欠款", "催收",
"税务", "发票", "社保", "工伤", "辞退", "开除员工",
"股东", "股权", "分红", "退股", "合伙人撤资",
"法规", "法律", "合规", "稽查", "罚款",
"知识产权", "商标", "专利", "侵权",
"借条", "担保", "抵押", "保全", "执行",
],
"decision": [
"扩张", "开分店", "开第二家", "裁员", "招人", "融资",
"定价", "涨价", "降价", "促销", "备货",
"业务线", "转型", "外包", "预算", "现金流",
"投资", "回报率", "盈亏", "亏损业务",
"接单", "大单", "账期", "框架合同",
"竞争", "竞品", "市场份额",
"Q1", "Q2", "Q3", "Q4", "季度", "年度规划",
"客户流失", "复购率", "转化率",
],
"life": [
"孩子", "子女", "父母", "养老", "家庭", "夫妻",
"亲子", "青春期", "兴趣班", "留学", "接班",
"体检", "健康", "运动", "饮食", "睡眠",
"出差", "行程", "时间管理",
"应酬", "酒", "体重", "颈椎",
],
}
def filter_by_scene(questions: list[str], target_scene: str) -> list[str]:
"""过滤掉不属于目标场景的问题。
判断规则:
1. 问题包含其他场景的归属关键词 → 过滤掉(归属错误)
2. 问题包含目标场景的归属关键词 → 保留
3. 中性问题(不含任何场景关键词) → 保留(可能确实是该场景的问题)
特殊处理:
- emotion 场景较宽泛,只过滤明显属于 legal/decision/life 的问题
- legal 和 decision 有交叉(如"合伙人退股"),归 legal
Args:
questions: 问题列表
target_scene: 目标场景 legal/decision/life/emotion
Returns:
过滤后的属于目标场景的问题列表
"""
valid = []
for q in questions:
q = str(q).strip()
if not q:
continue
# 检查是否包含其他场景的归属关键词
wrong_scene = False
for scene, keywords in SCENE_KEYWORDS.items():
if scene == target_scene:
continue
# emotion 场景不拦截(情绪问题可能出现在任何场景的焦虑中)
if target_scene == "emotion":
continue
if any(kw in q for kw in keywords):
wrong_scene = True
break
if not wrong_scene:
valid.append(q)
return valid
def generate_daily_topics(kimi_client, store: DailyTopicStore) -> dict: def generate_daily_topics(kimi_client, store: DailyTopicStore) -> dict:
"""AI 生成当天 4 个场景的热点问题并存库。 """AI 生成当天 4 个场景的热点问题并存库。
@@ -3367,6 +3448,14 @@ def generate_daily_topics(kimi_client, store: DailyTopicStore) -> dict:
"emotion": "情绪与心理问题,聚焦中小企业老板的焦虑:创业压力、合伙人冲突、孤独感、决策焦虑、拖延症、自我怀疑、危机应对等", "emotion": "情绪与心理问题,聚焦中小企业老板的焦虑:创业压力、合伙人冲突、孤独感、决策焦虑、拖延症、自我怀疑、危机应对等",
} }
# 场景边界:明确告诉AI什么问题不该出现在当前场景
scene_boundaries = {
"legal": "本场景只关注法律/法务问题。不要生成纯经营策略问题(如'该不该扩张')、纯情绪问题(如'压力很大怎么办')、纯生活问题(如'孩子教育')。",
"decision": "本场景只关注经营决策问题。不要生成纯法律问题(如'合同违约金怎么约定')、纯情绪问题(如'焦虑失眠怎么办'、'自我怀疑'、'凌晨怀疑人生')、纯生活问题(如'父母养老')。情绪相关的决策问题可以,但必须是'该怎么做'而非'我很难受'。",
"life": "本场景只关注个人生活问题。不要生成纯法律问题、纯经营决策问题、纯情绪疏导问题。问题应围绕家庭、健康、教育、养老、时间管理等生活领域。",
"emotion": "本场景关注情绪与心理疏导。可以涉及经营中的情绪反应,但核心是'情绪感受和心理状态',不是'经营策略'或'法律方案'。",
}
# 获取当前日期用于 prompt # 获取当前日期用于 prompt
today_str = datetime.now().strftime("%Y年%m月%d日") today_str = datetime.now().strftime("%Y年%m月%d日")
# 获取当前热点事件提示(简单版:用日期季节推断) # 获取当前热点事件提示(简单版:用日期季节推断)
@@ -3389,6 +3478,7 @@ def generate_daily_topics(kimi_client, store: DailyTopicStore) -> dict:
results = {} results = {}
for scene, desc in scene_desc.items(): for scene, desc in scene_desc.items():
boundary = scene_boundaries.get(scene, "")
prompt = f"""你是左右(BossAi)的内容编辑,负责为中小企业老板生成今日热点问题。 prompt = f"""你是左右(BossAi)的内容编辑,负责为中小企业老板生成今日热点问题。
当前日期:{today_str} 当前日期:{today_str}
@@ -3396,6 +3486,8 @@ def generate_daily_topics(kimi_client, store: DailyTopicStore) -> dict:
场景:{desc} 场景:{desc}
场景边界:{boundary}
请生成25-30个该场景下老板最焦虑、最关注的热点问题。 请生成25-30个该场景下老板最焦虑、最关注的热点问题。
要求: 要求:
@@ -3440,16 +3532,17 @@ def generate_daily_topics(kimi_client, store: DailyTopicStore) -> dict:
results[scene] = {"count": 0, "source": "ai", "error": "JSON解析失败"} results[scene] = {"count": 0, "source": "ai", "error": "JSON解析失败"}
continue continue
questions = json.loads(answer[start:end + 1]) questions = json.loads(answer[start:end + 1])
# 过滤:老板视角 + 去重 # 过滤:老板视角 + 场景归属 + 去重
boss_questions = filter_boss_perspective(questions) boss_questions = filter_boss_perspective(questions)
scene_questions = filter_by_scene(boss_questions, scene)
valid = [] valid = []
seen = set() seen = set()
for q in boss_questions: for q in scene_questions:
if q not in seen: if q not in seen:
valid.append(q) valid.append(q)
seen.add(q) seen.add(q)
if len(valid) < 10: if len(valid) < 10:
results[scene] = {"count": 0, "source": "ai", "error": f"过滤后有效问题不足10个(原始{len(questions)}个)"} results[scene] = {"count": 0, "source": "ai", "error": f"过滤后有效问题不足10个(原始{len(questions)}个,老板视角{len(boss_questions)}个,场景匹配{len(scene_questions)}个)"}
continue continue
store.upsert(scene, valid[:30], source="ai") store.upsert(scene, valid[:30], source="ai")
results[scene] = {"count": len(valid[:30]), "source": "ai"} results[scene] = {"count": len(valid[:30]), "source": "ai"}