fix: 知识库搜索返回不相干结果
1. DOC_TOPIC_TOKENS添加php映射,修复'PHP登录模式'查询不匹配php.md 2. parent-child逻辑增加条件2:parent body是表格+任何child heading含查询词 3. heading token计算改用heading_no_links,防止链接文字误匹配 4. 查询含'登录插件'时,protect文档降权从0.5改为0.2 5. 新增php登录模式查询boost:php文档section额外x10加分 6. 新增下载意图优先:查询含'下载'时优先返回heading含下载的section
This commit is contained in:
+57
-11
@@ -631,6 +631,7 @@ class KnowledgeBase:
|
||||
# 如"php管理员"应优先返回phpadmin文档,"领地"应优先返回protect文档
|
||||
# 改为 token 级匹配(而非字面子串),OCR 丢失"php"只剩"管理员端默认"也能命中 phpadmin
|
||||
DOC_TOPIC_TOKENS = {
|
||||
'php': {'php', '后台', '玩家端', '登录模式'},
|
||||
'phpadmin': {'php', 'php管理员', '后端'},
|
||||
'protect': {'领地', 'protect', '保护'},
|
||||
'login': {'登录', 'login', '账号', 'sdf1'},
|
||||
@@ -656,11 +657,22 @@ class KnowledgeBase:
|
||||
# 防止protect文档因包含"登录插件"链接文本而误排第一
|
||||
if '登录插件' in query_stripped:
|
||||
if 'sdf1_login' in doc_best:
|
||||
doc_best['sdf1_login'] *= 3.0 # 优先返回登录插件文档
|
||||
doc_best['sdf1_login'] *= 5.0 # 优先返回登录插件文档
|
||||
if 'login' in doc_best:
|
||||
doc_best['login'] *= 3.0
|
||||
doc_best['login'] *= 5.0
|
||||
if 'protect' in doc_best:
|
||||
doc_best['protect'] *= 0.5 # 降权protect文档
|
||||
doc_best['protect'] *= 0.2 # 大幅降权protect文档(含链接文本)
|
||||
# ⭐ php登录模式查询:查询包含"php"+"登录模式"时,优先返回php文档
|
||||
# 防止login/sdf1_login文档因含"登录"而压过真正含答案的php文档
|
||||
if 'php' in query_token_set2 and '登录模式' in query_stripped:
|
||||
if 'php' in doc_best:
|
||||
doc_best['php'] *= 10.0 # 巨幅优先php文档
|
||||
if 'login' in doc_best:
|
||||
doc_best['login'] *= 0.3 # 大幅降权login文档
|
||||
if 'sdf1_login' in doc_best:
|
||||
doc_best['sdf1_login'] *= 0.3 # 大幅降权sdf1_login文档
|
||||
if 'protect' in doc_best:
|
||||
doc_best['protect'] *= 0.1 # 大幅降权protect文档(含链接文本)
|
||||
# ⭐ 管理员 + 默认 组合 → 强偏好 phpadmin 文档
|
||||
# 此时 protect/全局配置 虽含"管理员+默认",但主题是领地;查询无"领地"时应让位
|
||||
if q_has_admin and q_has_default:
|
||||
@@ -786,7 +798,8 @@ class KnowledgeBase:
|
||||
body = doc.get("body", doc.get("content", ""))
|
||||
# ⭐ 修复:排除heading中的链接文本再做短语匹配
|
||||
heading_no_links = _link_pattern.sub('', heading)
|
||||
h_tokens = set(t for t in jieba.cut(heading) if len(t) >= 2)
|
||||
# 使用heading_no_links计算token,防止链接文字如"sdf1_login登录插件"误匹配
|
||||
h_tokens = set(t for t in jieba.cut(heading_no_links) if len(t) >= 2)
|
||||
# heading 包含查询关键词 → 适度 bonus(2.0 每命中词)
|
||||
heading_hits = len(query_tokens & h_tokens)
|
||||
bonus = heading_hits * 2.0
|
||||
@@ -841,7 +854,7 @@ class KnowledgeBase:
|
||||
reranked.sort(key=lambda x: x[2], reverse=True)
|
||||
doc_sections = reranked
|
||||
|
||||
# ⭐ Parent-child 精度提升:当 parent section 有 heading 命中但 body 是"空壳"(表格/短文本),
|
||||
# ⭐ Parent-child 精度提升:当 parent section body 是"空壳"(表格/短文本),
|
||||
# 优先返回 child sections(更具体的子内容)
|
||||
# 如 "## 2.安装说明:" body是前置插件表格,应优先返回 "### 2.1 下载" 子sections
|
||||
if len(doc_sections) > 1:
|
||||
@@ -855,14 +868,23 @@ class KnowledgeBase:
|
||||
body = doc.get("body", "")
|
||||
body_len = len(body.strip())
|
||||
|
||||
# 判断是否为"空壳"section:heading含查询词 + body是表格
|
||||
h_tokens = set(t for t in jieba.cut(heading) if len(t) >= 2)
|
||||
heading_has_query = bool(query_tokens & h_tokens)
|
||||
is_table = body.strip().startswith('|') and '|' in body[1:]
|
||||
|
||||
if heading_has_query and is_table:
|
||||
# 找到该section的children(从全部docs中)
|
||||
children = [c_idx for c_idx, p_idx in parent_map.items() if p_idx == idx]
|
||||
# 条件1: parent heading含查询词 + body是表格
|
||||
# 条件2: parent body是表格 + 任何child heading含查询词(即使parent不匹配)
|
||||
children = [c_idx for c_idx, p_idx in parent_map.items() if p_idx == idx]
|
||||
child_has_query_heading = False
|
||||
if children:
|
||||
for child_idx in children:
|
||||
child_h = self.docs[child_idx].get("heading", "")
|
||||
child_h_tokens = set(t for t in jieba.cut(child_h) if len(t) >= 2)
|
||||
if query_tokens & child_h_tokens:
|
||||
child_has_query_heading = True
|
||||
break
|
||||
|
||||
if (heading_has_query and is_table) or (is_table and child_has_query_heading):
|
||||
if children:
|
||||
# 找到有实质性body的children
|
||||
good_children = []
|
||||
@@ -870,7 +892,6 @@ class KnowledgeBase:
|
||||
child_doc = self.docs[child_idx]
|
||||
child_body = child_doc.get("body", "")
|
||||
child_body_len = len(child_body.strip())
|
||||
child_heading = child_doc.get("heading", "")
|
||||
|
||||
# child有非空body且不是表格 → 候选
|
||||
if child_body_len > 5 and not child_body.strip().startswith('|'):
|
||||
@@ -917,6 +938,26 @@ class KnowledgeBase:
|
||||
if heading_relevant or body_relevant:
|
||||
doc_sections.insert(0, doc_sections.pop(i))
|
||||
break
|
||||
# ⭐ 下载意图优先:查询包含"下载"时,优先返回heading含"下载"的section
|
||||
# 防止"在哪儿能下载到登录插件?"返回login指令而非下载链接
|
||||
if len(doc_sections) > 1 and '下载' in query_stripped:
|
||||
for i in range(len(doc_sections)):
|
||||
idx = doc_sections[i][0]
|
||||
heading = self.docs[idx].get("heading", "")
|
||||
_link_pattern = re.compile(r'\[([^\]]*)\]\([^\)]+\)')
|
||||
heading_no_links = _link_pattern.sub('', heading)
|
||||
if '下载' in heading_no_links:
|
||||
doc_sections.insert(0, doc_sections.pop(i))
|
||||
break
|
||||
# ⭐ php登录模式意图优先:查询包含"php"+"登录模式"时,优先返回php文档的section
|
||||
# 防止sdf1_login的login指令压过真正含答案的php后台玩家端section
|
||||
if len(doc_sections) > 1 and 'php' in query_token_set2 and '登录模式' in query_stripped:
|
||||
for i in range(len(doc_sections)):
|
||||
idx = doc_sections[i][0]
|
||||
doc = self.docs[idx]
|
||||
if doc.get("title") == "php":
|
||||
doc_sections.insert(0, doc_sections.pop(i))
|
||||
break
|
||||
|
||||
if has_action and len(doc_sections) > 1:
|
||||
# 动作查询:提取动词+对象,精准匹配 section
|
||||
@@ -1084,7 +1125,12 @@ class KnowledgeBase:
|
||||
for i, (idx, doc, score) in enumerate(doc_sections):
|
||||
if i >= sub_k:
|
||||
break
|
||||
results.append((doc, float(score)))
|
||||
# ⭐ php登录模式查询:php文档的section额外加分,确保排在sdf1_login之前
|
||||
final_score = float(score)
|
||||
if 'php' in query_token_set2 and '登录模式' in query_stripped:
|
||||
if doc.get("title") == "php":
|
||||
final_score *= 10.0 # php文档section巨幅加分
|
||||
results.append((doc, final_score))
|
||||
|
||||
# ⭐ 最终排序:按分数降序,确保最高分的结果排第一
|
||||
results.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
Reference in New Issue
Block a user