Focus L2 agent and update V7.1 knowledge base

This commit is contained in:
2026-06-13 15:43:24 +08:00
parent 2fd88f5ebb
commit 85604454a0
16 changed files with 1519 additions and 1614 deletions

View File

@@ -7,7 +7,6 @@ from __future__ import annotations
import re
from pathlib import Path
from typing import List
from common.knowledge_loader import list_knowledge_files, load_knowledge_file
@@ -49,68 +48,179 @@ _KB_DIR = Path(__file__).parent / "knowledge_base"
_KB_METADATA: dict[str, dict] = {
"01_课程层次与问题判断.md": {
"title": "课程层次与问题判断",
"description": "帮助判断学员问题属于299启蒙课还是1280训练营层次,以及问题卡在哪个阶段",
"description": "判断299、1280七天训练营、大本营之间的层次关系,以及学员当前问题卡在哪一层。",
"keywords": [
"课程层次", "299", "1280", "启蒙课", "训练营", "问题判断",
"阶段", "层次", "基础", "进阶", "学员水平", "课程区别",
"课程层次", "299", "1280", "七天训练营", "启蒙课", "训练场",
"大本营", "层次判断", "知道但做不到", "课程信心", "没体感",
"后续训练", "回溯", "回溯现象", "未完成事件", "事实法则",
"流动法则", "现实风险层", "问题判断", "卡在哪一层",
],
},
"02_即时回复表达原则.md": {
"title": "即时回复表达原则",
"description": "辅导老师回复学员时的表达风格、语气、措辞原则和禁忌",
"description": "生成有阅读感、人情味、有分寸、可供辅导老师参考和改写的回复。",
"keywords": [
"表达", "回复", "语气", "措辞", "风格", "原则", "禁忌",
"怎么说", "话术", "沟通", "表达技巧", "回复模板",
"表达", "回复", "参考回复", "怎么回", "怎么说", "语气",
"措辞", "风格", "原则", "阅读感", "人情味", "中肯方向",
"长故事", "短问题", "群内回复", "私聊回复", "AI味",
"课程词", "慎用术语", "话术表达", "成交表达",
],
},
"03_常见学员问题回应素材.md": {
"title": "常见学员问题回应素材",
"description": "学员常见问题的分类、回应策略和具体话术示例",
"description": "妈妈父母、孩子、伴侣、课程失望、知道但做不到、想放弃等常见场景回应素材。",
"keywords": [
"常见问题", "学员问题", "回应", "话术", "示例", "策略",
"情绪", "愤怒", "委屈", "焦虑", "失望", "质疑", "抱怨",
"婚姻", "孩子", "工作", "健康", "家庭", "关系",
"常见问题", "学员问题", "回应素材", "妈妈", "父母",
"原生家庭", "孩子", "不上学", "躺平", "伴侣", "老公",
"出轨", "离婚", "课程没用", "没体感", "没变化", "失望",
"知道但做不到", "反应过度", "小事失控", "想放弃",
"不想写输出", "不想分享", "群内长篇分享", "私聊长故事",
"攻击课程", "攻击老师", "刷屏", "互相影响", "没救了",
"别人有反应我没有", "依赖老师",
],
},
"04_功课与练习反馈边界.md": {
"title": "功课与练习反馈边界",
"description": "学员功课、练习、打卡的反馈原则和边界,什么该说什么不该说",
"description": "功课、练习、释放、万能释放法、练习反馈和练习后不适的处理边界。",
"keywords": [
"功课", "练习", "打卡", "反馈", "边界", "作业",
"释放", "万能释放法", "数息法", "身体感知", "情绪觉察",
"练习指导", "功课点评", "训练",
"释放", "释放不是宣泄", "万能释放法", "万能释放公式",
"看见且释放", "没感觉", "不知道释放什么", "做完哭",
"做完抖", "做完堵", "头晕", "胸闷", "呼吸不适",
"", "", "回不来", "看到画面", "前世", "小时候画面",
"旧记忆", "功课回溯", "最小功课", "带练", "暂停练习",
],
},
"05_安全边界与现实风险.md": {
"title": "安全边界与现实风险",
"description": "学员安全风险识别、危机干预边界、何时必须上报",
"description": "安全、医疗、心理危机、法律、家暴、未成年人、严重身体不适等最高优先级边界。",
"keywords": [
"安全", "风险", "危机", "自杀", "", "伤害", "暴力",
"报警", "上报", "边界", "现实风险", "法律保护", "隐私",
"紧急情况", "危险", "保护",
"安全", "风险", "危机", "高风险", "", "自杀",
"想死", "不想活", "伤害自己", "伤害他人", "家暴",
"人身安全", "未成年人", "孩子安全", "离家出走", "暴力",
"报警", "上报", "班主任", "督导", "现实支持", "严重身体不适",
"呼吸困难", "胸闷", "精神混乱", "幻觉", "妄想", "解离",
"", "", "", "回不来", "用药", "停药", "换药",
"诊断", "治疗", "医疗", "法律", "财产", "版权", "保密",
"意识许可", "课程资料外传",
],
},
"06_1280核心术语词典.md": {
"title": "1280核心术语词典",
"description": "1280训练营核心术语的标准解释和使用场景",
"description": "1280核心术语的人话解释、适用场景、禁用场景和老师提醒。",
"keywords": [
"术语", "词典", "概念", "定义", "解释",
"看见自己", "回到自己", "万能释放", "情绪心钥", "抗拒心钥",
"原生数据", "潜意识数据", "数息法", "身体感知", "情绪觉察",
"内在模式", "释放", "觉察", "感知",
"术语", "词典", "概念", "定义", "解释", "是什么意思",
"看见自己", "回到自己", "看见且释放", "万能释放法",
"万能释放公式", "情绪心钥", "抗拒心钥", "信念心钥",
"程序", "程序心钥", "人生剧本", "回溯现象", "未完成事件",
"事实法则", "流动法则", "派生情绪", "原生情绪", "抓取",
"抗拒", "原生数据", "先天数据", "潜意识数据", "代际",
"家族数据", "数息法", "身体框架法", "认知透镜",
"知道但做不到", "自动反应", "自我的七个层面",
"百慕大能量三角形", "场域支持", "借光而行", "借事修心",
"生命数据化", "输出", "支点", "格物致知", "知行合一",
"良知",
],
},
"07_大本营承接与成交话术.md": {
"title": "大本营承接与成交话术",
"description": "大本营课程承接判断、成交话术、异议处理策略",
"description": "大本营承接时机、成交判断、课程信心修复、异议处理和老师成交提醒。",
"keywords": [
"大本营", "承接", "成交", "异议", "销售", "推课",
"后续课程", "持续训练", "课程信心", "报名", "价格", "权益",
"没时间", "没钱", "怕没效果", "家人不同意", "不想继续",
"后续课程", "继续学", "持续训练", "持续场域", "场域支持",
"借光而行", "课程信心", "课程没用", "没体感", "怕没效果",
"知道但做不到", "怕结束后回到原样", "没时间", "没钱",
"价格", "权益", "优惠", "名额", "截止时间", "报名",
"退款", "家人不同意", "觉得被销售", "不想继续",
"不适合承接", "轻轻埋种子", "明确承接", "高风险停止成交",
],
},
}
_ROUTE_RULES: list[dict] = [
{
"filename": "05_安全边界与现实风险.md",
"boost": 5.0,
"keywords": [
"自伤", "自残", "自杀", "想死", "不想活", "活不下去",
"伤害自己", "伤害他人", "杀了", "家暴", "打我", "被打",
"报警", "人身安全", "孩子安全", "未成年人", "离家出走",
"呼吸困难", "胸闷", "喘不过气", "头晕", "严重身体不适",
"幻觉", "妄想", "精神混乱", "解离", "回不来", "停药",
"换药", "用药", "诊断", "治疗", "医院", "医生", "法律",
"财产", "版权", "保密", "课程资料", "外传", "意识许可",
],
},
{
"filename": "04_功课与练习反馈边界.md",
"boost": 3.5,
"keywords": [
"功课", "作业", "练习", "带练", "打卡", "释放", "万能释放",
"看见且释放", "不知道释放什么", "没感觉", "做完哭",
"做完抖", "做完堵", "练完", "头晕", "胸闷", "呼吸不适",
"", "", "", "回不来", "看到画面", "前世", "小时候画面",
"旧记忆", "暂停练习", "最小功课",
],
},
{
"filename": "07_大本营承接与成交话术.md",
"boost": 3.5,
"keywords": [
"大本营", "承接", "成交", "推课", "销售", "后续课程",
"继续学", "继续练", "持续训练", "报名", "价格", "多少钱",
"权益", "优惠", "名额", "截止", "退款", "没时间", "没钱",
"怕没效果", "家人不同意", "觉得被销售", "不想继续",
"怕结束后回到原样", "适不适合推",
],
},
{
"filename": "06_1280核心术语词典.md",
"boost": 3.0,
"keywords": [
"是什么意思", "怎么解释", "术语", "概念", "定义",
"看见自己", "回到自己", "情绪心钥", "抗拒心钥",
"信念心钥", "程序心钥", "人生剧本", "回溯现象",
"未完成事件", "事实法则", "流动法则", "派生情绪",
"原生情绪", "原生数据", "先天数据", "潜意识数据",
"代际", "家族数据", "身体框架法", "认知透镜",
"自我的七个层面", "百慕大", "借光而行", "借事修心",
"生命数据化", "格物致知", "知行合一", "良知",
],
},
{
"filename": "03_常见学员问题回应素材.md",
"boost": 2.8,
"keywords": [
"妈妈", "父母", "原生家庭", "孩子", "不上学", "躺平",
"老公", "伴侣", "出轨", "离婚", "课程没用", "没体感",
"没变化", "失望", "知道但做不到", "反应过度",
"小事失控", "想放弃", "不想写输出", "不想分享",
"群里刷屏", "攻击课程", "攻击老师", "没救了",
"别人都有反应", "我没有反应", "依赖老师",
],
},
{
"filename": "02_即时回复表达原则.md",
"boost": 2.4,
"keywords": [
"怎么回", "怎么说", "如何回复", "参考回复", "话术",
"语气", "表达", "阅读感", "人情味", "长故事",
"短问题", "群内", "私聊", "AI味", "不要像AI",
"课程词", "慎用术语",
],
},
{
"filename": "01_课程层次与问题判断.md",
"boost": 2.2,
"keywords": [
"课程层次", "299", "1280", "七天训练营", "启蒙课",
"训练场", "卡在哪", "层次判断", "课程区别", "大本营偏向",
"回溯", "未完成事件", "事实法则", "流动法则",
"现实风险层", "后续训练需求",
],
},
]
# ───────────────────────────── 工具函数 ─────────────────────────────
def _tokenize(text: str) -> set[str]:
@@ -136,6 +246,55 @@ def _calc_relevance(query: str, content: str) -> float:
return len(overlap) / len(query_tokens)
def _normalize_text(text: str) -> str:
"""用于短语命中的轻量归一化。"""
return re.sub(r"\s+", "", text.lower())
def _count_phrase_hits(query: str, phrases: list[str]) -> int:
"""统计查询命中的业务短语数量。"""
normalized_query = _normalize_text(query)
hits = 0
for phrase in phrases:
normalized_phrase = _normalize_text(phrase)
if normalized_phrase and normalized_phrase in normalized_query:
hits += 1
return hits
def _metadata_text(filename: str) -> str:
"""拼接知识库元数据,供检索加权。"""
meta = _KB_METADATA.get(filename, {})
return " ".join(
[
meta.get("title", ""),
meta.get("description", ""),
" ".join(meta.get("keywords", [])),
]
)
def _calc_metadata_score(query: str, filename: str) -> float:
"""计算文件元数据与查询的相关度。"""
meta = _KB_METADATA.get(filename, {})
keywords = meta.get("keywords", [])
phrase_hits = _count_phrase_hits(query, keywords)
relevance = _calc_relevance(query, _metadata_text(filename))
return relevance + min(phrase_hits * 0.35, 2.0)
def _calc_route_boost(query: str, filename: str) -> float:
"""根据 V7.1 固定业务场景给目标知识库加权。"""
boost = 0.0
for rule in _ROUTE_RULES:
hits = _count_phrase_hits(query, rule["keywords"])
if not hits:
continue
if rule["filename"] == filename:
boost += rule["boost"] + min(hits * 0.3, 1.5)
return boost
def _extract_relevant_sections(content: str, query: str, max_sections: int = 3) -> list[str]:
"""从知识库内容中提取与查询最相关的章节"""
# 按二级标题分割
@@ -152,6 +311,15 @@ def _extract_relevant_sections(content: str, query: str, max_sections: int = 3)
return [s for _, s in scored[:max_sections]]
def _normalize_top_k(top_k: int) -> int:
"""限制 top_k避免模型误传过大值导致输出过长。"""
try:
value = int(top_k)
except (TypeError, ValueError):
value = 3
return max(1, min(value, 8))
# ───────────────────────────── 公开工具 ─────────────────────────────
def list_kb() -> str:
@@ -196,6 +364,7 @@ def search_kb(query: str, top_k: int = 3) -> str:
当你不确定具体文件,但知道要查什么内容时,调用此工具。
"""
top_k = _normalize_top_k(top_k)
all_files = list_knowledge_files(_KB_DIR)
results: list[tuple[float, str, str]] = []
@@ -204,16 +373,28 @@ def search_kb(query: str, top_k: int = 3) -> str:
if not content:
continue
# 计算文件级相关度
# 计算文件级、元数据级、硬路由级相关度
file_score = _calc_relevance(query, content)
metadata_score = _calc_metadata_score(query, fname)
route_boost = _calc_route_boost(query, fname)
# 提取最相关的章节
sections = _extract_relevant_sections(content, query, max_sections=2)
sections = _extract_relevant_sections(content, query, max_sections=3)
for section in sections:
section_score = _calc_relevance(query, section)
# 综合得分:文件级 + 章节级
final_score = (file_score + section_score) / 2
section_phrase_hits = _count_phrase_hits(
query,
re.findall(r"^#{1,6}\s+(.+)$", section, flags=re.MULTILINE),
)
# 综合得分:硬路由 > 元数据 > 章节 > 全文,优先保证关键业务场景命中正确文件。
final_score = (
route_boost
+ metadata_score * 0.7
+ section_score * 1.2
+ file_score * 0.3
+ min(section_phrase_hits * 0.4, 1.2)
)
results.append((final_score, fname, section))
# 排序并取 top_k