438 lines
13 KiB
Python
Executable File
438 lines
13 KiB
Python
Executable File
"""
|
|
高级RAG API路由
|
|
集成查询扩展、实体链接、引用溯源、动态知识图谱、跨论文融合、Neo4j图算法
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
from typing import List, Dict, Optional
|
|
|
|
router = APIRouter()
|
|
|
|
# ========== 查询扩展API ==========
|
|
|
|
class QueryExpansionRequest(BaseModel):
|
|
query: str
|
|
num_expansions: int = 5
|
|
strategy: str = "expansion" # expansion, intent_focused, keyword_combination
|
|
|
|
|
|
class QueryExpansionResponse(BaseModel):
|
|
original: str
|
|
rewritten: str
|
|
alternatives: List[str]
|
|
intent: str
|
|
confidence: float
|
|
keywords: List[str]
|
|
synonyms: Dict[str, List[str]]
|
|
|
|
|
|
@router.post("/advanced/query/expand")
|
|
async def expand_query_endpoint(request: QueryExpansionRequest):
|
|
"""查询扩展端点"""
|
|
try:
|
|
from src.rag.advanced import QueryExpander, QueryRewriter
|
|
|
|
expander = QueryExpander()
|
|
expansion = expander.expand_query(request.query, request.num_expansions)
|
|
|
|
return QueryExpansionResponse(
|
|
original=request.query,
|
|
rewritten=expansion.expanded_queries[0] if expansion.expanded_queries else request.query,
|
|
alternatives=expansion.expanded_queries[1:] if len(expansion.expanded_queries) > 1 else [],
|
|
intent=expansion.intent.value,
|
|
confidence=expansion.intent_confidence,
|
|
keywords=expansion.keywords,
|
|
synonyms=expansion.synonyms
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"查询扩展失败: {str(e)}")
|
|
|
|
|
|
@router.post("/advanced/query/rewrite")
|
|
async def rewrite_query_endpoint(request: QueryExpansionRequest):
|
|
"""查询重写端点"""
|
|
try:
|
|
from src.rag.advanced import QueryRewriter
|
|
|
|
rewriter = QueryRewriter()
|
|
result = rewriter.rewrite(request.query, request.strategy)
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"查询重写失败: {str(e)}")
|
|
|
|
|
|
# ========== 实体链接API ==========
|
|
|
|
class EntityLinkingRequest(BaseModel):
|
|
text: str
|
|
|
|
|
|
class EntityLinkingResponse(BaseModel):
|
|
entities: List[Dict]
|
|
linked_count: int
|
|
total_count: int
|
|
|
|
|
|
@router.post("/advanced/entity/link")
|
|
async def entity_linking_endpoint(request: EntityLinkingRequest):
|
|
"""实体链接端点"""
|
|
try:
|
|
from src.rag.advanced import EntityLinker
|
|
|
|
linker = EntityLinker()
|
|
linked_entities = linker.link_entities(request.text)
|
|
|
|
entities = []
|
|
for le in linked_entities:
|
|
entity_info = {
|
|
"mention": le.mention.text,
|
|
"start_pos": le.mention.start_pos,
|
|
"end_pos": le.mention.end_pos,
|
|
"context": le.mention.context[:100],
|
|
"linked": le.kb_entity is not None,
|
|
"link_confidence": le.link_confidence,
|
|
"disambiguation_score": le.disambiguation_score,
|
|
}
|
|
|
|
if le.kb_entity:
|
|
entity_info.update({
|
|
"kb_id": le.kb_entity.entity_id,
|
|
"kb_name": le.kb_entity.name,
|
|
"kb_type": le.kb_entity.entity_type.value,
|
|
"kb_definition": le.kb_entity.definition,
|
|
"kb_aliases": le.kb_entity.aliases[:5],
|
|
})
|
|
|
|
entities.append(entity_info)
|
|
|
|
linked_count = sum(1 for e in entities if e["linked"])
|
|
|
|
return EntityLinkingResponse(
|
|
entities=entities,
|
|
linked_count=linked_count,
|
|
total_count=len(entities)
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"实体链接失败: {str(e)}")
|
|
|
|
|
|
# ========== 引用溯源API ==========
|
|
|
|
class CitationRequest(BaseModel):
|
|
answer: str
|
|
sources: List[Dict]
|
|
min_citations: int = 3
|
|
|
|
|
|
class CitationResponse(BaseModel):
|
|
answer: str
|
|
citations: List[Dict]
|
|
confidence: float
|
|
coverage_score: float
|
|
total_citations: int
|
|
|
|
|
|
@router.post("/advanced/citation/add")
|
|
async def add_citations_endpoint(request: CitationRequest):
|
|
"""添加引用溯源端点"""
|
|
try:
|
|
from src.rag.advanced import CitationIntegrator
|
|
|
|
integrator = CitationIntegrator()
|
|
result = integrator.integrate_citations(
|
|
request.answer,
|
|
request.sources,
|
|
request.min_citations
|
|
)
|
|
|
|
return CitationResponse(
|
|
answer=result["answer"],
|
|
citations=result["citations"],
|
|
confidence=result["confidence"],
|
|
coverage_score=result["coverage_score"],
|
|
total_citations=result["total_citations"]
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"添加引用失败: {str(e)}")
|
|
|
|
|
|
# ========== 动态知识图谱API ==========
|
|
|
|
class DynamicKGRequest(BaseModel):
|
|
sources: List[Dict]
|
|
|
|
|
|
class DynamicKGResponse(BaseModel):
|
|
entities: List[Dict]
|
|
relations: List[Dict]
|
|
total_entities: int
|
|
total_relations: int
|
|
conflicts: int
|
|
resolved: int
|
|
needs_review: int
|
|
graph_data: Dict
|
|
|
|
|
|
@router.post("/advanced/kg/build")
|
|
async def build_dynamic_kg_endpoint(request: DynamicKGRequest):
|
|
"""动态构建知识图谱端点"""
|
|
try:
|
|
from src.rag.advanced import DynamicKGBuilder
|
|
|
|
builder = DynamicKGBuilder()
|
|
result = builder.build_from_search_results(request.sources)
|
|
|
|
# 获取图谱数据
|
|
graph_data = builder.get_graph_data()
|
|
|
|
return DynamicKGResponse(
|
|
entities=result["entities"],
|
|
relations=result["relations"],
|
|
total_entities=result["total_entities"],
|
|
total_relations=result["total_relations"],
|
|
conflicts=result["conflicts"],
|
|
resolved=result["resolved"],
|
|
needs_review=result["needs_review"],
|
|
graph_data=graph_data
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"构建知识图谱失败: {str(e)}")
|
|
|
|
|
|
# ========== 跨论文融合API ==========
|
|
|
|
class CrossPaperFusionRequest(BaseModel):
|
|
papers: List[Dict]
|
|
|
|
|
|
class CrossPaperFusionResponse(BaseModel):
|
|
entities: List[Dict]
|
|
relations: List[Dict]
|
|
stats: Dict
|
|
|
|
|
|
@router.post("/advanced/fusion/cross-paper")
|
|
async def cross_paper_fusion_endpoint(request: CrossPaperFusionRequest):
|
|
"""跨论文知识融合端点"""
|
|
try:
|
|
from src.rag.advanced import CrossPaperFusion
|
|
|
|
fusion = CrossPaperFusion()
|
|
result = fusion.fuse_knowledge(request.papers)
|
|
|
|
return CrossPaperFusionResponse(
|
|
entities=result["entities"],
|
|
relations=result["relations"],
|
|
stats=result["stats"]
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"跨论文融合失败: {str(e)}")
|
|
|
|
|
|
@router.post("/advanced/fusion/provenance/{entity_id}")
|
|
async def get_provenance_endpoint(entity_id: str, request: CrossPaperFusionRequest):
|
|
"""获取实体溯源信息端点"""
|
|
try:
|
|
from src.rag.advanced import CrossPaperFusion
|
|
|
|
fusion = CrossPaperFusion()
|
|
fusion.fuse_knowledge(request.papers)
|
|
provenance = fusion.get_entity_provenance(entity_id)
|
|
|
|
return provenance
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"获取溯源信息失败: {str(e)}")
|
|
|
|
|
|
# ========== Neo4j图算法API ==========
|
|
|
|
class ShortestPathRequest(BaseModel):
|
|
start_node: str
|
|
end_node: str
|
|
relationship_type: Optional[str] = None
|
|
|
|
|
|
class ShortestPathResponse(BaseModel):
|
|
status: str
|
|
path: Optional[Dict]
|
|
message: Optional[str]
|
|
|
|
|
|
@router.post("/advanced/graph/shortest-path")
|
|
async def shortest_path_endpoint(request: ShortestPathRequest):
|
|
"""最短路径分析端点"""
|
|
try:
|
|
from src.rag.advanced import GraphAlgorithmAPI
|
|
|
|
api = GraphAlgorithmAPI()
|
|
result = api.shortest_path_endpoint(
|
|
request.start_node,
|
|
request.end_node,
|
|
request.relationship_type
|
|
)
|
|
|
|
return ShortestPathResponse(**result)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"最短路径分析失败: {str(e)}")
|
|
|
|
|
|
@router.get("/advanced/graph/communities")
|
|
async def community_detection_endpoint():
|
|
"""社区发现端点"""
|
|
try:
|
|
from src.rag.advanced import GraphAlgorithmAPI
|
|
|
|
api = GraphAlgorithmAPI()
|
|
result = api.community_detection_endpoint()
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"社区发现失败: {str(e)}")
|
|
|
|
|
|
class CentralityRequest(BaseModel):
|
|
algorithm: str = "degree" # degree, betweenness, pagerank
|
|
top_k: int = 10
|
|
|
|
|
|
@router.post("/advanced/graph/centrality")
|
|
async def centrality_analysis_endpoint(request: CentralityRequest):
|
|
"""中心性分析端点"""
|
|
try:
|
|
from src.rag.advanced import GraphAlgorithmAPI
|
|
|
|
api = GraphAlgorithmAPI()
|
|
result = api.centrality_analysis_endpoint(request.algorithm, request.top_k)
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"中心性分析失败: {str(e)}")
|
|
|
|
|
|
@router.get("/advanced/graph/stats")
|
|
async def graph_stats_endpoint():
|
|
"""图统计端点"""
|
|
try:
|
|
from src.rag.advanced import GraphAlgorithmAPI
|
|
|
|
api = GraphAlgorithmAPI()
|
|
result = api.graph_stats_endpoint()
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"获取图统计失败: {str(e)}")
|
|
|
|
|
|
# ========== 综合RAG API ==========
|
|
|
|
class AdvancedRAGRequest(BaseModel):
|
|
query: str
|
|
enable_expansion: bool = True
|
|
enable_entity_linking: bool = True
|
|
enable_citation: bool = True
|
|
enable_dynamic_kg: bool = True
|
|
top_k: int = 5
|
|
|
|
|
|
class AdvancedRAGResponse(BaseModel):
|
|
query: str
|
|
expanded_query: Optional[str]
|
|
answer: str
|
|
entities: List[Dict]
|
|
citations: List[Dict]
|
|
knowledge_graph: Optional[Dict]
|
|
confidence: float
|
|
|
|
|
|
@router.post("/advanced/rag")
|
|
async def advanced_rag_endpoint(request: AdvancedRAGRequest):
|
|
"""高级RAG综合端点"""
|
|
try:
|
|
from src.rag.advanced import (
|
|
QueryExpander,
|
|
EntityLinker,
|
|
CitationIntegrator,
|
|
DynamicKGBuilder
|
|
)
|
|
|
|
# 1. 查询扩展
|
|
expanded_query = request.query
|
|
if request.enable_expansion:
|
|
expander = QueryExpander()
|
|
expansion = expander.expand_query(request.query, num_expansions=3)
|
|
expanded_query = expansion.expanded_queries[0] if expansion.expanded_queries else request.query
|
|
|
|
# 2. 实体链接
|
|
entities = []
|
|
if request.enable_entity_linking:
|
|
linker = EntityLinker()
|
|
linked = linker.link_entities(request.query)
|
|
entities = [
|
|
{
|
|
"mention": le.mention.text,
|
|
"linked": le.kb_entity is not None,
|
|
"kb_name": le.kb_entity.name if le.kb_entity else None,
|
|
"kb_type": le.kb_entity.entity_type.value if le.kb_entity else None,
|
|
"confidence": le.link_confidence
|
|
}
|
|
for le in linked
|
|
]
|
|
|
|
# 3. 模拟检索结果(实际应该调用检索器)
|
|
mock_sources = [
|
|
{
|
|
"doc_id": f"PMID{10000000 + i}",
|
|
"content": f"Sample content for {request.query}",
|
|
"score": 0.9 - i * 0.1,
|
|
"chunk_id": f"chunk_{i}",
|
|
"metadata": {"title": f"Paper {i}", "year": "2023"}
|
|
}
|
|
for i in range(request.top_k)
|
|
]
|
|
|
|
# 4. 生成答案
|
|
answer = f"基于检索到的{len(mock_sources)}条文献,关于'{request.query}'的分析如下..."
|
|
|
|
# 5. 添加引用
|
|
citations = []
|
|
if request.enable_citation:
|
|
integrator = CitationIntegrator()
|
|
citation_result = integrator.integrate_citations(answer, mock_sources, min_citations=3)
|
|
answer = citation_result["answer"]
|
|
citations = citation_result["citations"]
|
|
|
|
# 6. 构建动态知识图谱
|
|
kg_data = None
|
|
if request.enable_dynamic_kg:
|
|
builder = DynamicKGBuilder()
|
|
kg_result = builder.build_from_search_results(mock_sources)
|
|
kg_data = builder.get_graph_data()
|
|
|
|
return AdvancedRAGResponse(
|
|
query=request.query,
|
|
expanded_query=expanded_query if request.enable_expansion else None,
|
|
answer=answer,
|
|
entities=entities,
|
|
citations=citations,
|
|
knowledge_graph=kg_data,
|
|
confidence=0.85
|
|
)
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=f"高级RAG处理失败: {str(e)}")
|