From f0e5426b4df4ad68de1b25b49fd2814501f319de Mon Sep 17 00:00:00 2001
From: lacerate551 <128470311+lacerate551@users.noreply.github.com>
Date: Sun, 21 Jun 2026 00:40:12 +0800
Subject: [PATCH] =?UTF-8?q?fix(rag):=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?=
=?UTF-8?q?=E6=A0=BC=E5=9B=BE=E7=89=87=E6=9B=BF=E6=8D=A2=E5=92=8C=E8=B7=A8?=
=?UTF-8?q?=E9=A1=B5=E5=90=88=E5=B9=B6=E4=B8=89=E4=B8=AAbug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. chat_routes._replace_table_image_placeholders:
- 处理  LLM 格式(去掉多余!和假URL)
- 支持 [N张图片] 多张占位符(html_table_to_markdown 生成)
- 修复 !! 双感叹号导致 markdown 图片语法失效
2. manager._merge_cross_page_tables:
- 表头跳过逻辑改为对比第一行而非 find_all('th')
- 修复合并后表格中间出现重复表头行的问题
---
api/chat_routes.py | 47 +++++++++++++++++++++++++++++---------------
knowledge/manager.py | 36 +++++++++++++++++++++++++++++++--
2 files changed, 65 insertions(+), 18 deletions(-)
diff --git a/api/chat_routes.py b/api/chat_routes.py
index a78e031..3373e1a 100644
--- a/api/chat_routes.py
+++ b/api/chat_routes.py
@@ -1373,12 +1373,20 @@ _FIGURE_ANSWER_KEYWORDS = frozenset([
def _replace_table_image_placeholders(answer: str, images: List[Dict]) -> str:
"""
- 将回答中 markdown 表格内的 [图片] 占位符替换为实际图片语法。
+ 将回答中 markdown 表格内的图片占位符替换为实际图片语法。
- LLM 看到的上下文里表格单元格是 [图片](MinerU 解析产物),它也原样输出。
- 此函数按顺序将每个 [图片] 映射到 images 列表中对应图片的 URL,
+ LLM 看到的上下文里表格单元格图片以 [图片] 或 [N张图片](MinerU 解析产物)表示,
+ LLM 通常会原样输出,有时还会加上  格式。
+
+ 此函数按顺序将占位符映射到 images 列表中的 URL,
生成  语法,前端 markdown-it 可直接渲染为 标签。
+ 处理的占位符格式:
+ - [图片] →  (单张)
+ -  →  (LLM 加了 ! 和假 URL)
+ - [3张图片] →   
+ -  → 同上 (LLM 加了前缀和假 URL)
+
Args:
answer: LLM 生成的回答文本
images: select_images + _filter_images_by_answer 后的最终图片列表
@@ -1386,28 +1394,35 @@ def _replace_table_image_placeholders(answer: str, images: List[Dict]) -> str:
Returns:
替换后的回答文本
"""
- if not images or '[图片]' not in answer:
+ if not images or '图片' not in answer:
return answer
- # 按顺序替换:第 N 个 [图片] → 第 N 张图片
img_iter = iter(images)
replaced_count = 0
def _replacer(match):
nonlocal replaced_count
- try:
- img = next(img_iter)
- url = img.get('url', '')
- img_id = img.get('id', 'img')
- replaced_count += 1
- return f''
- except StopIteration:
- # 图片用完了,保留剩余占位符
- return match.group(0)
+ n_str = match.group(1) # 数字部分,None 表示单张
+ n = int(n_str) if n_str else 1
- result = re.sub(r'\[图片\]', _replacer, answer)
+ parts = []
+ for _ in range(n):
+ try:
+ img = next(img_iter)
+ url = img.get('url', '')
+ img_id = img.get('id', 'img')
+ parts.append(f'')
+ replaced_count += 1
+ except StopIteration:
+ parts.append('[图片]') # 图片用完了
+ return ' '.join(parts)
+
+ # !? — 可选前导 !(LLM 有时加)
+ # \[(\d+)?张?图片\] — [图片] 或 [3张图片](张 可选)
+ # (?:\(图片URL\))? — 可选的 LLM 从上下文复制的假 URL
+ result = re.sub(r'!?\[(\d+)?张?图片\](?:\(图片URL\))?', _replacer, answer)
if replaced_count > 0:
- logger.info(f"[图片占位符替换] 替换了 {replaced_count}/{len(images)} 个 [图片] 为 markdown 图片语法")
+ logger.info(f"[图片占位符替换] 替换了 {replaced_count}/{len(images)} 个图片为 markdown 图片语法")
return result
diff --git a/knowledge/manager.py b/knowledge/manager.py
index 66b2f2f..0fd8fe6 100644
--- a/knowledge/manager.py
+++ b/knowledge/manager.py
@@ -39,6 +39,7 @@ from pathlib import Path
import logging
import chromadb
+from bs4 import BeautifulSoup
# 从 base.py 导入基础类和常量
from .base import (
@@ -550,8 +551,39 @@ class KnowledgeBaseManager(
curr_html = getattr(current, 'table_html', '') or ''
next_html = getattr(next_chunk, 'table_html', '') or ''
if curr_html and next_html:
- # 合并两个表格的 HTML
- current.table_html = curr_html + '\n' + next_html
+ # 正确合并两个表格的 HTML:
+ # 将第二个表格的