修复知识库搜索:表格内容不再被切分,表格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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user