From eb177e11e5e63193bf0882727c36f7c3d6338c24 Mon Sep 17 00:00:00 2001
From: lacerate551 <128470311+lacerate551@users.noreply.github.com>
Date: Sat, 20 Jun 2026 23:20:40 +0800
Subject: [PATCH] =?UTF-8?q?fix(rag):=20=E6=9B=BF=E6=8D=A2=E7=AD=94?=
=?UTF-8?q?=E6=A1=88=E4=B8=AD=20[=E5=9B=BE=E7=89=87]=20=E5=8D=A0=E4=BD=8D?=
=?UTF-8?q?=E7=AC=A6=E4=B8=BA=20markdown=20=E5=9B=BE=E7=89=87=E8=AF=AD?=
=?UTF-8?q?=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
LLM 上下文中的表格包含 [图片] 占位符(MinerU 解析产物),LLM 原样
输出后前端 markdown-it 只渲染为纯文本。新增 _replace_table_image_placeholders
函数,在 finish 事件发送前按顺序将 [图片] 替换为 ,
前端可直接渲染为
标签。
---
api/chat_routes.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/api/chat_routes.py b/api/chat_routes.py
index 32a67fb..a78e031 100644
--- a/api/chat_routes.py
+++ b/api/chat_routes.py
@@ -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
@@ -1370,6 +1371,46 @@ _FIGURE_ANSWER_KEYWORDS = frozenset([
])
+def _replace_table_image_placeholders(answer: str, images: List[Dict]) -> str:
+ """
+ 将回答中 markdown 表格内的 [图片] 占位符替换为实际图片语法。
+
+ LLM 看到的上下文里表格单元格是 [图片](MinerU 解析产物),它也原样输出。
+ 此函数按顺序将每个 [图片] 映射到 images 列表中对应图片的 URL,
+ 生成  语法,前端 markdown-it 可直接渲染为
标签。
+
+ Args:
+ answer: LLM 生成的回答文本
+ images: select_images + _filter_images_by_answer 后的最终图片列表
+
+ Returns:
+ 替换后的回答文本
+ """
+ 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)
+
+ result = re.sub(r'\[图片\]', _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]:
"""
@@ -1411,6 +1452,9 @@ def _filter_images_by_answer(selected_images: List[Dict], answer: str, query: st
_has_figure_in_answer = any(kw in answer for kw in _FIGURE_ANSWER_KEYWORDS)
if not _has_figure_in_answer:
_has_figure_in_answer = bool(re.search(r'[图表]\s*\d+', answer))
+ # 表格内嵌图片检测:回答中出现 [图片] 占位符或 markdown 图片语法
+ if not _has_figure_in_answer:
+ _has_figure_in_answer = bool(re.search(r'\[图片\]|!\[', answer))
_has_figure_in_query = any(kw in query for kw in _FIGURE_QUERY_KEYWORDS)
if not _has_figure_in_query:
_has_figure_in_query = bool(re.search(r'[图表]\s*\d+', query))
@@ -2916,6 +2960,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: