修复知识库搜索:表格内容不再被切分,表格section优先返回

- knowledge_base.py: _split_body_by_list_items检测表格区域,不切分表格行
- knowledge_base.py: 添加独立表格section,确保表格内容可被搜索匹配
- knowledge_base.py: 表格兜底策略:最佳文档含操作/支持表格时强制加入并置顶
- knowledge_base.py: reranking后无条件优先返回含操作/支持的表格section
- .gitignore: 添加msg_history.db和诊断脚本到忽略列表
This commit is contained in:
youpaishidifu
2026-08-05 13:22:27 +08:00
parent 271ea21489
commit 4d7017be6a
5 changed files with 162 additions and 14 deletions
+101
View File
@@ -165,14 +165,54 @@ class KnowledgeBase:
列表项以 "- " 开头,每个列表项及其后续内容作为独立子section。
返回子section列表,如果列表项少于2个则返回空列表(不切分)。
⭐ 修复:Markdown表格(以 | 开头的行)不应被切分到列表项中,
应作为独立section保留,确保表格内容可被搜索匹配。
"""
lines = body.split('\n')
# ⭐ 检测表格区域:找到所有表格行的范围
table_ranges = [] # [(start, end), ...]
table_start = -1
for i, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith('|') and '|' in stripped[1:]:
if table_start == -1:
table_start = i
else:
if table_start != -1:
table_ranges.append((table_start, i - 1))
table_start = -1
if table_start != -1:
table_ranges.append((table_start, len(lines) - 1))
def is_in_table(line_idx):
"""检查行是否在表格区域内"""
for t_start, t_end in table_ranges:
if t_start <= line_idx <= t_end:
return True
return False
list_items = []
current_item = []
current_item_start = -1
for i, line in enumerate(lines):
stripped = line.strip()
# ⭐ 跳过表格行,不作为列表项处理
if is_in_table(i):
if current_item:
# 表格开始,结束当前列表项
list_items.append({
'start': current_item_start,
'end': i - 1,
'text': '\n'.join(current_item).strip(),
'title_line': current_item[0][2:].strip()
})
current_item = []
current_item_start = -1
continue
if stripped.startswith('- ') and len(stripped) > 2:
# 新的列表项开始
if current_item:
@@ -223,6 +263,23 @@ class KnowledgeBase:
"body": sub_content,
})
# ⭐ 添加独立的表格section(表格不在任何列表项中)
for t_start, t_end in table_ranges:
table_text = '\n'.join(lines[t_start:t_end + 1]).strip()
if table_text:
# 检查这个表格是否已经在某个列表项中
in_list_item = False
for item in list_items:
if t_start >= item['start'] and t_end <= item['end']:
in_list_item = True
break
if not in_list_item:
sections.append({
"heading": f"{parent_heading} - 表格",
"content": table_text,
"body": table_text,
})
return sections
def set_path(self, path: str):
@@ -436,6 +493,15 @@ class KnowledgeBase:
else:
combined_scores[i] *= 0.3 # 不包含核心名词 → 大幅惩罚
# ⭐ 表格内容奖励:当查询关于"支持/操作/方法/方式"时,包含表格的 section 巨幅加分
# 防止"领地保护支持哪些操作"匹配到不含表格的"我的领地" section
table_query_tokens = {'支持', '操作', '方法', '方式', '功能', '可以', '能够'}
if table_query_tokens & query_tokens_set:
# 检查 body 是否是 Markdown 表格(以 | 开头)
body_stripped = body.strip()
if body_stripped.startswith('|') and '|' in body_stripped[1:]:
combined_scores[i] *= 3.0 # 表格内容 → 巨幅奖励
# 短查询惩罚:分词数 ≤2 的短查询(如图标残片/短词)得分大幅降权
# 防止 OCR 碎片中的高 IDF 词(如"下载")获得超过真实用户消息的分数
n_tokens = len(tokens)
@@ -569,6 +635,29 @@ class KnowledgeBase:
doc_sections.append((i, doc, combined_scores[i]))
doc_sections.sort(key=lambda x: x[2], reverse=True)
# ⭐ 表格兜底策略:当最佳文档包含操作/支持相关表格时,强制加入并给超高分
# 防止"领地保护"因表格TF-IDF=0而丢失正确答案(表格是答案但不含查询词)
# 关键:给足够高的基础分,即使reranking bonus=0也能保持第一
if len(doc_sections) > 0:
# 检查文档是否已有包含"操作"或"支持"的表格section
has_action_table = False
for idx, _, _ in doc_sections:
body = self.docs[idx].get("body", "").strip()
if body.startswith('|') and ('操作' in body or '支持' in body):
has_action_table = True
break
if not has_action_table:
# 强制加入操作/支持相关表格section
for i, doc in enumerate(self.docs):
if doc["title"] == doc_title:
body = doc.get("body", "").strip()
if body.startswith('|') and '|' in body[1:] and ('操作' in body or '支持' in body):
# 给表格section超高分(最高分×2),确保reranking后仍排第一
max_score = max(s for _, _, s in doc_sections) if doc_sections else min_score
doc_sections.append((i, doc, max_score * 2))
break
doc_sections.sort(key=lambda x: x[2], reverse=True)
if not doc_sections:
continue
@@ -617,6 +706,18 @@ class KnowledgeBase:
reranked.sort(key=lambda x: x[2], reverse=True)
doc_sections = reranked
# ⭐ 表格优先策略(reranking后):无条件优先返回包含"操作/支持"的表格section
# 防止reranking后表格被其他section超越(表格是答案但不含查询词)
if len(doc_sections) > 1:
# 找到包含表格的section(body以|开头且含"操作"或"支持")
for i in range(len(doc_sections)):
idx = doc_sections[i][0]
body = self.docs[idx].get("body", "").strip()
if body.startswith('|') and ('操作' in body or '支持' in body):
# 表格section,移到第一位
doc_sections.insert(0, doc_sections.pop(i))
break
if has_action and len(doc_sections) > 1:
# 动作查询:提取动词+对象,精准匹配 section
# 如"怎么打印流水" → verb="打印", obj="流水",优先选 heading 同时含两者的 section