fix(rag): 替换答案中 [图片] 占位符为 markdown 图片语法
LLM 上下文中的表格包含 [图片] 占位符(MinerU 解析产物),LLM 原样 输出后前端 markdown-it 只渲染为纯文本。新增 _replace_table_image_placeholders 函数,在 finish 事件发送前按顺序将 [图片] 替换为 , 前端可直接渲染为 <img> 标签。
This commit is contained in:
@@ -26,6 +26,7 @@ Example:
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time as _time
|
import time as _time
|
||||||
from typing import List, Dict, Any, Optional, Tuple
|
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 可直接渲染为 <img> 标签。
|
||||||
|
|
||||||
|
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 = "",
|
def _filter_images_by_answer(selected_images: List[Dict], answer: str, query: str = "",
|
||||||
primary_sections: List[str] = None) -> List[Dict]:
|
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)
|
_has_figure_in_answer = any(kw in answer for kw in _FIGURE_ANSWER_KEYWORDS)
|
||||||
if not _has_figure_in_answer:
|
if not _has_figure_in_answer:
|
||||||
_has_figure_in_answer = bool(re.search(r'[图表]\s*\d+', 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)
|
_has_figure_in_query = any(kw in query for kw in _FIGURE_QUERY_KEYWORDS)
|
||||||
if not _has_figure_in_query:
|
if not _has_figure_in_query:
|
||||||
_has_figure_in_query = bool(re.search(r'[图表]\s*\d+', query))
|
_has_figure_in_query = bool(re.search(r'[图表]\s*\d+', query))
|
||||||
@@ -2916,6 +2960,10 @@ def rag():
|
|||||||
# 9. 过滤敏感信息(违禁词等)
|
# 9. 过滤敏感信息(违禁词等)
|
||||||
filtered_answer = filter_response(citation_result.get("answer_with_refs", clean_answer))
|
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. 保存消息到会话(仅开发环境)
|
# 9. 保存消息到会话(仅开发环境)
|
||||||
if session_id and session_repo_ref:
|
if session_id and session_repo_ref:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user