init: RAG 知识库服务初始提交
- 后端 API(Flask + Gunicorn) - RAG 引擎(混合检索 + 云端 Reranker + 引用溯源) - 文档解析(MinerU + 多格式支持) - Docker 生产部署配置 - 排除前端项目、敏感配置、模型文件
This commit is contained in:
476
core/reasoning_reflector.py
Normal file
476
core/reasoning_reflector.py
Normal file
@@ -0,0 +1,476 @@
|
||||
"""
|
||||
推理反思模块(Re²Search)
|
||||
|
||||
在答案生成过程中,自动识别未经验证的声明,触发补充检索进行验证。
|
||||
|
||||
核心机制:
|
||||
1. 从生成的答案中提取关键声明(claims)
|
||||
2. 识别哪些声明缺乏检索证据支持
|
||||
3. 对未验证声明触发补充检索
|
||||
4. 根据补充信息修正或增强答案
|
||||
|
||||
使用方式:
|
||||
from core.reasoning_reflector import ReasoningReflector, reflect_and_verify
|
||||
|
||||
reflector = ReasoningReflector(llm_client)
|
||||
result = reflector.reflect(query, answer, contexts)
|
||||
|
||||
if result.has_unverified_claims:
|
||||
# 触发补充检索
|
||||
...
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
from enum import Enum
|
||||
from config import RAG_CHAT_MODEL
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClaimType(Enum):
|
||||
"""声明类型"""
|
||||
FACTUAL = "factual" # 事实性声明(可验证)
|
||||
OPINION = "opinion" # 观点性声明(主观)
|
||||
INFERENCE = "inference" # 推论性声明(逻辑推导)
|
||||
HEDGED = "hedged" # 保留性声明(可能、也许)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Claim:
|
||||
"""单个声明"""
|
||||
content: str # 声明内容
|
||||
claim_type: ClaimType # 声明类型
|
||||
is_verified: bool # 是否已验证
|
||||
supporting_contexts: List[str] # 支持该声明的上下文
|
||||
confidence: float # 置信度(0-1)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReflectionResult:
|
||||
"""反思结果"""
|
||||
original_answer: str # 原始答案
|
||||
claims: List[Claim] # 提取的声明列表
|
||||
unverified_claims: List[Claim] # 未验证的声明
|
||||
has_unverified_claims: bool # 是否存在未验证声明
|
||||
verification_queries: List[str] # 建议的验证查询
|
||||
reflection_summary: str # 反思总结
|
||||
should_supplement: bool # 是否需要补充检索
|
||||
|
||||
|
||||
class ReasoningReflector:
|
||||
"""
|
||||
推理反思器
|
||||
|
||||
在答案生成后,分析答案中的声明,识别未经验证的部分,
|
||||
触发补充检索以提高答案质量。
|
||||
"""
|
||||
|
||||
# 需要关注的关键词(可能表示未验证声明)
|
||||
UNVERIFIED_MARKERS = [
|
||||
"可能", "也许", "大概", "应该", "估计",
|
||||
"通常", "一般", "往往", "多半",
|
||||
"我认为", "我猜测", "似乎", "看起来"
|
||||
]
|
||||
|
||||
# 事实性声明关键词
|
||||
FACTUAL_MARKERS = [
|
||||
"是", "有", "包括", "规定", "要求", "标准",
|
||||
"流程", "步骤", "时间", "金额", "数量"
|
||||
]
|
||||
|
||||
def __init__(self, llm_client=None, model: str = None):
|
||||
"""
|
||||
初始化反思器
|
||||
|
||||
Args:
|
||||
llm_client: LLM 客户端
|
||||
model: 模型名称
|
||||
"""
|
||||
self.llm_client = llm_client
|
||||
self.model = model or RAG_CHAT_MODEL
|
||||
|
||||
def reflect(self, query: str, answer: str,
|
||||
contexts: List[str] = None) -> ReflectionResult:
|
||||
"""
|
||||
对答案进行推理反思
|
||||
|
||||
Args:
|
||||
query: 用户查询
|
||||
answer: 生成的答案
|
||||
contexts: 检索上下文
|
||||
|
||||
Returns:
|
||||
ReflectionResult: 反思结果
|
||||
"""
|
||||
if not answer:
|
||||
return self._empty_reflection()
|
||||
|
||||
# 使用 LLM 进行声明提取和验证
|
||||
if self.llm_client:
|
||||
return self._llm_reflect(query, answer, contexts)
|
||||
else:
|
||||
# 降级:基于规则的反思
|
||||
return self._rule_based_reflect(query, answer, contexts)
|
||||
|
||||
def _llm_reflect(self, query: str, answer: str,
|
||||
contexts: List[str] = None) -> ReflectionResult:
|
||||
"""
|
||||
使用 LLM 进行深度反思
|
||||
"""
|
||||
# 构建上下文摘要
|
||||
context_summary = ""
|
||||
if contexts:
|
||||
context_summary = "\n\n".join([
|
||||
f"[上下文 {i+1}] {ctx[:300]}..."
|
||||
for i, ctx in enumerate(contexts[:5])
|
||||
])
|
||||
|
||||
prompt = f"""请对以下答案进行推理反思分析。
|
||||
|
||||
## 用户问题
|
||||
{query}
|
||||
|
||||
## 生成的答案
|
||||
{answer}
|
||||
|
||||
## 检索到的上下文
|
||||
{context_summary if context_summary else "(无检索上下文)"}
|
||||
|
||||
## 分析要求
|
||||
1. 从答案中提取关键声明(claims)
|
||||
2. 判断每个声明是否有上下文支持
|
||||
3. 识别未验证的声明
|
||||
4. 生成验证查询建议
|
||||
|
||||
请以 JSON 格式返回:
|
||||
```json
|
||||
{{
|
||||
"claims": [
|
||||
{{
|
||||
"content": "声明内容",
|
||||
"type": "factual/opinion/inference/hedged",
|
||||
"verified": true/false,
|
||||
"supporting_evidence": "支持证据或'无'",
|
||||
"confidence": 0.8
|
||||
}}
|
||||
],
|
||||
"unverified_claims_count": 2,
|
||||
"verification_queries": ["建议的验证查询1", "建议的验证查询2"],
|
||||
"summary": "反思总结",
|
||||
"should_supplement": true/false
|
||||
}}
|
||||
```"""
|
||||
|
||||
try:
|
||||
from core.llm_utils import call_llm
|
||||
content = call_llm(
|
||||
self.llm_client,
|
||||
prompt,
|
||||
self.model,
|
||||
temperature=0.1,
|
||||
max_tokens=1000
|
||||
)
|
||||
if content is None:
|
||||
return self._rule_based_reflect(query, answer, contexts)
|
||||
return self._parse_llm_response(answer, content)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM 反思失败: {e}")
|
||||
return self._rule_based_reflect(query, answer, contexts)
|
||||
|
||||
def _parse_llm_response(self, original_answer: str,
|
||||
content: str) -> ReflectionResult:
|
||||
"""解析 LLM 返回的 JSON"""
|
||||
import json
|
||||
import re
|
||||
|
||||
# 提取 JSON 块
|
||||
json_match = re.search(r'```json\s*([\s\S]*?)\s*```', content)
|
||||
if json_match:
|
||||
json_str = json_match.group(1)
|
||||
else:
|
||||
json_str = content
|
||||
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
except json.JSONDecodeError:
|
||||
return self._default_reflection(original_answer)
|
||||
|
||||
# 解析声明
|
||||
claims = []
|
||||
unverified_claims = []
|
||||
|
||||
for claim_data in data.get("claims", []):
|
||||
claim_type_str = claim_data.get("type", "factual")
|
||||
claim_type = {
|
||||
"factual": ClaimType.FACTUAL,
|
||||
"opinion": ClaimType.OPINION,
|
||||
"inference": ClaimType.INFERENCE,
|
||||
"hedged": ClaimType.HEDGED
|
||||
}.get(claim_type_str, ClaimType.FACTUAL)
|
||||
|
||||
claim = Claim(
|
||||
content=claim_data.get("content", ""),
|
||||
claim_type=claim_type,
|
||||
is_verified=claim_data.get("verified", False),
|
||||
supporting_contexts=[claim_data.get("supporting_evidence", "")],
|
||||
confidence=claim_data.get("confidence", 0.5)
|
||||
)
|
||||
claims.append(claim)
|
||||
|
||||
if not claim.is_verified:
|
||||
unverified_claims.append(claim)
|
||||
|
||||
return ReflectionResult(
|
||||
original_answer=original_answer,
|
||||
claims=claims,
|
||||
unverified_claims=unverified_claims,
|
||||
has_unverified_claims=len(unverified_claims) > 0,
|
||||
verification_queries=data.get("verification_queries", []),
|
||||
reflection_summary=data.get("summary", ""),
|
||||
should_supplement=data.get("should_supplement", False)
|
||||
)
|
||||
|
||||
def _rule_based_reflect(self, query: str, answer: str,
|
||||
contexts: List[str] = None) -> ReflectionResult:
|
||||
"""
|
||||
基于规则的反思(降级方案)
|
||||
"""
|
||||
claims = []
|
||||
unverified_claims = []
|
||||
verification_queries = []
|
||||
|
||||
# 简单句子分割
|
||||
sentences = self._split_sentences(answer)
|
||||
|
||||
for sentence in sentences:
|
||||
if len(sentence.strip()) < 10:
|
||||
continue
|
||||
|
||||
# 检测声明类型
|
||||
claim_type = self._detect_claim_type(sentence)
|
||||
|
||||
# 检测是否已验证
|
||||
is_verified = self._check_verification(sentence, contexts)
|
||||
confidence = self._estimate_confidence(sentence, is_verified)
|
||||
|
||||
claim = Claim(
|
||||
content=sentence.strip(),
|
||||
claim_type=claim_type,
|
||||
is_verified=is_verified,
|
||||
supporting_contexts=[],
|
||||
confidence=confidence
|
||||
)
|
||||
claims.append(claim)
|
||||
|
||||
if not is_verified and claim_type in [ClaimType.FACTUAL, ClaimType.INFERENCE]:
|
||||
unverified_claims.append(claim)
|
||||
# 生成验证查询
|
||||
verification_queries.append(f"验证:{sentence.strip()[:50]}")
|
||||
|
||||
has_unverified = len(unverified_claims) > 0
|
||||
should_supplement = has_unverified and len(unverified_claims) <= 3
|
||||
|
||||
summary = f"共提取 {len(claims)} 个声明,其中 {len(unverified_claims)} 个未验证"
|
||||
if has_unverified:
|
||||
summary += ",建议补充检索验证"
|
||||
|
||||
return ReflectionResult(
|
||||
original_answer=answer,
|
||||
claims=claims,
|
||||
unverified_claims=unverified_claims,
|
||||
has_unverified_claims=has_unverified,
|
||||
verification_queries=verification_queries[:3],
|
||||
reflection_summary=summary,
|
||||
should_supplement=should_supplement
|
||||
)
|
||||
|
||||
def _split_sentences(self, text: str) -> List[str]:
|
||||
"""分割句子"""
|
||||
import re
|
||||
# 按中英文标点分割
|
||||
sentences = re.split(r'[。!?\.\!\?]\s*', text)
|
||||
return [s.strip() for s in sentences if s.strip()]
|
||||
|
||||
def _detect_claim_type(self, sentence: str) -> ClaimType:
|
||||
"""检测声明类型"""
|
||||
# 保留性声明
|
||||
if any(marker in sentence for marker in self.UNVERIFIED_MARKERS):
|
||||
return ClaimType.HEDGED
|
||||
|
||||
# 事实性声明
|
||||
if any(marker in sentence for marker in self.FACTUAL_MARKERS):
|
||||
return ClaimType.FACTUAL
|
||||
|
||||
# 默认为推论
|
||||
return ClaimType.INFERENCE
|
||||
|
||||
def _check_verification(self, sentence: str,
|
||||
contexts: List[str] = None) -> bool:
|
||||
"""检查声明是否已验证"""
|
||||
if not contexts:
|
||||
return False
|
||||
|
||||
# 简单关键词匹配
|
||||
keywords = self._extract_keywords(sentence)
|
||||
if not keywords:
|
||||
return False
|
||||
|
||||
for ctx in contexts:
|
||||
matches = sum(1 for kw in keywords if kw in ctx)
|
||||
if matches >= len(keywords) * 0.5:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _extract_keywords(self, text: str) -> List[str]:
|
||||
"""提取关键词"""
|
||||
try:
|
||||
import jieba
|
||||
keywords = []
|
||||
for word in jieba.cut(text):
|
||||
word = word.strip()
|
||||
if len(word) >= 2 and word.isalpha():
|
||||
keywords.append(word)
|
||||
return list(set(keywords))[:10]
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
def _estimate_confidence(self, sentence: str, is_verified: bool) -> float:
|
||||
"""估计置信度"""
|
||||
base_confidence = 0.8 if is_verified else 0.4
|
||||
|
||||
# 保留性声明降低置信度
|
||||
if any(marker in sentence for marker in self.UNVERIFIED_MARKERS):
|
||||
base_confidence -= 0.2
|
||||
|
||||
return max(0.1, min(1.0, base_confidence))
|
||||
|
||||
def _empty_reflection(self) -> ReflectionResult:
|
||||
"""空反思结果"""
|
||||
return ReflectionResult(
|
||||
original_answer="",
|
||||
claims=[],
|
||||
unverified_claims=[],
|
||||
has_unverified_claims=False,
|
||||
verification_queries=[],
|
||||
reflection_summary="无答案可供反思",
|
||||
should_supplement=False
|
||||
)
|
||||
|
||||
def _default_reflection(self, answer: str) -> ReflectionResult:
|
||||
"""默认反思结果(解析失败时)"""
|
||||
return ReflectionResult(
|
||||
original_answer=answer,
|
||||
claims=[],
|
||||
unverified_claims=[],
|
||||
has_unverified_claims=False,
|
||||
verification_queries=[],
|
||||
reflection_summary="反思解析失败",
|
||||
should_supplement=False
|
||||
)
|
||||
|
||||
def get_info(self) -> dict:
|
||||
"""获取反思器信息"""
|
||||
return {
|
||||
"model": self.model,
|
||||
"has_llm": self.llm_client is not None,
|
||||
"unverified_markers_count": len(self.UNVERIFIED_MARKERS),
|
||||
"factual_markers_count": len(self.FACTUAL_MARKERS)
|
||||
}
|
||||
|
||||
|
||||
def create_reflector() -> ReasoningReflector:
|
||||
"""
|
||||
创建反思器实例
|
||||
|
||||
Returns:
|
||||
ReasoningReflector: 反思器实例
|
||||
"""
|
||||
try:
|
||||
from openai import OpenAI
|
||||
try:
|
||||
from config import API_KEY, BASE_URL, RAG_CHAT_MODEL
|
||||
MODEL = RAG_CHAT_MODEL
|
||||
except ImportError:
|
||||
from config import API_KEY, BASE_URL
|
||||
MODEL = "qwen3.5-flash" # fallback
|
||||
|
||||
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
|
||||
return ReasoningReflector(llm_client=client, model=MODEL)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"创建反思器失败,使用降级模式: {e}")
|
||||
return ReasoningReflector()
|
||||
|
||||
|
||||
def reflect_and_verify(query: str, answer: str,
|
||||
contexts: List[str] = None) -> ReflectionResult:
|
||||
"""
|
||||
便捷函数:反思并验证答案
|
||||
|
||||
Args:
|
||||
query: 用户查询
|
||||
answer: 生成的答案
|
||||
contexts: 检索上下文
|
||||
|
||||
Returns:
|
||||
ReflectionResult: 反思结果
|
||||
"""
|
||||
reflector = create_reflector()
|
||||
return reflector.reflect(query, answer, contexts)
|
||||
|
||||
|
||||
# ==================== 测试 ====================
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
print("=" * 60)
|
||||
print("推理反思测试")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试用例
|
||||
test_query = "公司的报销制度是怎样的?"
|
||||
test_answer = """
|
||||
根据公司规定,员工可以报销差旅费用。
|
||||
|
||||
报销流程通常包括:提交申请、部门审批、财务审核、打款。
|
||||
|
||||
可能需要提供发票和审批单,大概在30天内完成。
|
||||
|
||||
我认为公司对报销标准有明确要求,但具体金额我不太确定。
|
||||
"""
|
||||
test_contexts = [
|
||||
"公司报销制度规定员工可以报销差旅费用,需提供发票和审批单。",
|
||||
"报销流程:提交申请 -> 部门审批 -> 财务审核 -> 打款。"
|
||||
]
|
||||
|
||||
reflector = ReasoningReflector() # 不使用 LLM 的规则反思
|
||||
|
||||
print(f"\n反思器信息: {reflector.get_info()}")
|
||||
print()
|
||||
|
||||
result = reflector.reflect(test_query, test_answer, test_contexts)
|
||||
|
||||
print(f"反思总结: {result.reflection_summary}")
|
||||
print(f"需要补充检索: {'是' if result.should_supplement else '否'}")
|
||||
print(f"\n声明分析:")
|
||||
for i, claim in enumerate(result.claims, 1):
|
||||
status = "✅ 已验证" if claim.is_verified else "⚠️ 未验证"
|
||||
print(f" {i}. [{claim.claim_type.value}] {status}")
|
||||
print(f" 内容: {claim.content[:50]}...")
|
||||
print(f" 置信度: {claim.confidence:.2f}")
|
||||
|
||||
if result.verification_queries:
|
||||
print(f"\n建议验证查询:")
|
||||
for q in result.verification_queries:
|
||||
print(f" - {q}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 测试完成")
|
||||
print("=" * 60)
|
||||
Reference in New Issue
Block a user