fix(server-release): 从 main 同步表格图片修复

纯逻辑同步,不涉及 API 格式变更:

1. chat_routes.py:
   - 新增 _replace_table_image_placeholders(): 将答案中 [图片]
     和 [N张图片] 占位符替换为 ![id](url) markdown 图片语法
   - _filter_images_by_answer: 补充 [图片]/![ 图片意图检测
   - import re 提升到模块级

2. manager.py:
   - 跨页表格 HTML 合并修复: BS 解析后追加 <tr> 行(原来直接
     拼接两个 <table> 导致第二表行被 html_table_to_markdown 丢弃)
   - 表头跳过逻辑改为对比第一行
   - 新增 text_level/bbox/table_type/table_nest_level/sub_type 元数据
This commit is contained in:
lacerate551
2026-06-21 00:52:33 +08:00
parent b5bcb3c941
commit 184511f4ec
2 changed files with 95 additions and 2 deletions

View File

@@ -26,6 +26,7 @@ Example:
import json
import os
import queue
import re
import threading
import time as _time
from typing import List, Dict, Any, Optional, Tuple
@@ -1244,6 +1245,62 @@ _FIGURE_ANSWER_KEYWORDS = frozenset([
])
def _replace_table_image_placeholders(answer: str, images: List[Dict]) -> str:
"""
将回答中 markdown 表格内的图片占位符替换为实际图片语法。
LLM 看到的上下文里表格单元格图片以 [图片] 或 [N张图片]MinerU 解析产物)表示,
LLM 通常会原样输出,有时还会加上 ![图片](图片URL) 格式。
此函数按顺序将占位符映射到 images 列表中的 URL
生成 ![img](/images/xxx.png) 语法,前端 markdown-it 可直接渲染为 <img> 标签。
处理的占位符格式:
- [图片] → ![id](url) (单张)
- ![图片](图片URL) → ![id](url) LLM 加了 ! 和假 URL
- [3张图片] → ![id1](u1) ![id2](u2) ![id3](u3)
- ![3张图片](图片URL) → 同上 LLM 加了前缀和假 URL
Args:
answer: LLM 生成的回答文本
images: select_images + _filter_images_by_answer 后的最终图片列表
Returns:
替换后的回答文本
"""
if not images or '图片' not in answer:
return answer
img_iter = iter(images)
replaced_count = 0
def _replacer(match):
nonlocal replaced_count
n_str = match.group(1) # 数字部分None 表示单张
n = int(n_str) if n_str else 1
parts = []
for _ in range(n):
try:
img = next(img_iter)
url = img.get('url', '')
img_id = img.get('id', 'img')
parts.append(f'![{img_id}]({url})')
replaced_count += 1
except StopIteration:
parts.append('[图片]') # 图片用完了
return ' '.join(parts)
# !? — 可选前导 !LLM 有时加)
# \[(\d+)?张?图片\] — [图片] 或 [3张图片](张 可选)
# (?:\((?:图片URL|https?://[^)]+)\))? — 可选的 LLM 从上下文复制的假 URL
# 匹配 (图片URL) 或 (https://via.placeholder.com/150) 等
result = re.sub(r'!?\[(\d+)?张?图片\](?:\((?:图片URL|https?://[^)]+)\))?', _replacer, answer)
if replaced_count > 0:
logger.info(f"[图片占位符替换] 替换了 {replaced_count}/{len(images)} 个图片为 markdown 图片语法")
return result
def _filter_images_by_answer(selected_images: List[Dict], answer: str, query: str = "",
primary_sections: List[str] = None) -> List[Dict]:
"""
@@ -2623,6 +2680,10 @@ def rag():
# 9. 过滤敏感信息(违禁词等)
filtered_answer = filter_response(citation_result.get("answer_with_refs", clean_answer))
# 9.5 替换表格内 [图片] 占位符为 markdown 图片语法
if rich_media.get("images"):
filtered_answer = _replace_table_image_placeholders(filtered_answer, rich_media["images"])
# 9. 保存消息到会话(仅开发环境)
if session_id and session_repo_ref:
try: