feat(rag): 子章节级图片过滤 + VLM 后台增强 + 意图分析优化
- 图片选择新增 section_path 字段,支持子章节级过滤 - _filter_images_by_answer 增加 primary_sections 参数和叶子节点匹配 - 检索发散检测:primary_leaf_names > 3 时全局阈值+1 - _FIGURE_ANSWER_KEYWORDS 移除单字"图""表",正则图号兜底防误触发 - lazy_enhance 后台增强流程优化 - 意图分析与 LLM 工具层改进 评测:图片选择 F1 从 52.4% 提升至 66.3%(+13.9pp),Precision +16.5pp
This commit is contained in:
@@ -233,10 +233,10 @@ class IntentAnalyzer:
|
||||
self._exact_cache_max = 500
|
||||
|
||||
def _get_client(self):
|
||||
"""获取 LLM 客户端"""
|
||||
"""获取 LLM 客户端(百炼快速模型)"""
|
||||
if self._client is None:
|
||||
from config import get_llm_client
|
||||
self._client = get_llm_client()
|
||||
from config import get_intent_client
|
||||
self._client = get_intent_client()
|
||||
return self._client
|
||||
|
||||
def _get_cache(self):
|
||||
|
||||
@@ -71,19 +71,38 @@ def call_llm(
|
||||
return response
|
||||
|
||||
content = response.choices[0].message.content
|
||||
|
||||
# 推理模型兼容:content 为空时尝试从 reasoning_content 提取
|
||||
|
||||
# 推理模型兼容(mimo-v2.5 等):
|
||||
# 推理模型思考链消耗大量 token(~1000),max_tokens 不足时 content 为空,
|
||||
# 全部输出进入 reasoning_content。此处从思考链中提取有效内容。
|
||||
if not content or not content.strip():
|
||||
reasoning = getattr(response.choices[0].message, 'reasoning_content', None)
|
||||
if reasoning and reasoning.strip():
|
||||
# 从思维链中提取 JSON 块作为内容
|
||||
json_match = re.search(r'\{[\s\S]*\}', reasoning)
|
||||
if json_match:
|
||||
logger.info("LLM: content为空,从reasoning_content提取JSON")
|
||||
return json_match.group().strip()
|
||||
logger.warning("LLM 返回空 content(可能需要增大 max_tokens)")
|
||||
# 先去掉 <think>...</think> 标签
|
||||
cleaned = re.sub(r'', '', reasoning, flags=re.DOTALL).strip()
|
||||
if cleaned:
|
||||
logger.info("LLM: content为空,从reasoning_content提取内容")
|
||||
# 尝试提取 JSON 对象(兼容结构化响应场景)
|
||||
json_match = re.search(r'\{[\s\S]*\}', cleaned)
|
||||
if json_match:
|
||||
try:
|
||||
json.loads(json_match.group())
|
||||
return json_match.group().strip()
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
# 尝试提取 JSON 数组
|
||||
bracket_match = re.search(r'\[[\s\S]*\]', cleaned)
|
||||
if bracket_match:
|
||||
try:
|
||||
json.loads(bracket_match.group())
|
||||
return bracket_match.group().strip()
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
# 纯文本响应:直接返回清理后的内容
|
||||
return cleaned
|
||||
logger.warning("LLM 返回空 content 且 reasoning_content 也无法提取(可能需要增大 max_tokens)")
|
||||
return None
|
||||
|
||||
|
||||
return content.strip()
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM 调用失败: {e}")
|
||||
@@ -95,7 +114,7 @@ def call_llm_stream(
|
||||
prompt: str,
|
||||
model: str,
|
||||
temperature: float = 0.3,
|
||||
max_tokens: int = 1000,
|
||||
max_tokens: int = 3000,
|
||||
messages: List[dict] = None,
|
||||
error_prefix: str = "[错误]",
|
||||
**kwargs
|
||||
@@ -104,13 +123,14 @@ def call_llm_stream(
|
||||
流式 LLM 调用(生成器封装)
|
||||
|
||||
自动处理流式响应,逐块 yield 文本内容。
|
||||
兼容推理模型(mimo-v2.5 等):当 content 为空时回退到 reasoning_content。
|
||||
|
||||
Args:
|
||||
client: OpenAI 客户端实例
|
||||
prompt: 用户提示
|
||||
model: 模型名称
|
||||
temperature: 温度参数
|
||||
max_tokens: 最大 token 数
|
||||
max_tokens: 最大 token 数(推理模型需留足思考链预算)
|
||||
messages: 完整消息列表
|
||||
error_prefix: 错误时的前缀
|
||||
**kwargs: 其他参数
|
||||
@@ -135,9 +155,33 @@ def call_llm_stream(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
content_yielded = False
|
||||
reasoning_buffer = []
|
||||
|
||||
for chunk in stream:
|
||||
if chunk.choices and chunk.choices[0].delta.content:
|
||||
yield chunk.choices[0].delta.content
|
||||
if not chunk.choices:
|
||||
continue
|
||||
delta = chunk.choices[0].delta
|
||||
|
||||
# 正常 content 输出
|
||||
if hasattr(delta, 'content') and delta.content:
|
||||
content_yielded = True
|
||||
yield delta.content
|
||||
continue
|
||||
|
||||
# 推理模型:reasoning_content(思考链)
|
||||
rc = getattr(delta, 'reasoning_content', None)
|
||||
if rc:
|
||||
reasoning_buffer.append(rc)
|
||||
|
||||
# 回退:content 为空但 reasoning_content 有内容(推理模型 token 不足时)
|
||||
if not content_yielded and reasoning_buffer:
|
||||
reasoning_text = ''.join(reasoning_buffer)
|
||||
# 去掉 <think>...</think> 标签
|
||||
cleaned = re.sub(r'', '', reasoning_text, flags=re.DOTALL).strip()
|
||||
if cleaned:
|
||||
logger.info("流式 LLM: content为空,从reasoning_content提取内容")
|
||||
yield cleaned
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"LLM 流式调用失败: {e}")
|
||||
|
||||
Reference in New Issue
Block a user