641e33b834
- 语义检索(FAISS + embedding)+ 精确查找(法规名+条号) - RAG 问答(SSE 流式,支持 thinking 折叠显示) - 法规浏览(原文阅读) - 历史记录(检索+对话持久化到 SQLite) - 设置页(系统提示词/模板/LLM 参数可配置) - 检索质量评估脚本 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
"""检索 API — /api/search"""
|
|
from fastapi import APIRouter, Request, HTTPException, Query
|
|
from typing import Optional
|
|
|
|
from app.services.retriever import Retriever
|
|
from app.services.embedding import embed_text
|
|
from app.services.history import add_search_history
|
|
from app.config import MAX_QUERY_LENGTH
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/search")
|
|
async def search(
|
|
request: Request,
|
|
query: str = Query(..., min_length=1, max_length=MAX_QUERY_LENGTH, description="查询文本"),
|
|
mode: str = Query("semantic", description="检索模式:semantic(语义) / keyword(关键词精确)"),
|
|
category: Optional[str] = Query(None, description="法规类别过滤"),
|
|
province: Optional[str] = Query(None, description="省份过滤"),
|
|
city: Optional[str] = Query(None, description="市级过滤"),
|
|
top_k: int = Query(20, ge=1, le=100, description="返回数量"),
|
|
page: int = Query(1, ge=1, description="页码"),
|
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
|
):
|
|
"""检索法规条文(语义检索或关键词精确搜索)
|
|
|
|
- mode=semantic: 向量化查询 → FAISS 检索 → metadata 过滤(默认)
|
|
- mode=keyword: 按法规名+条号精确匹配,支持"郑州市劳动用工条例 第三十二条"
|
|
"""
|
|
trace_id = getattr(request.state, "trace_id", "")
|
|
|
|
retriever = Retriever.get_instance()
|
|
if not retriever.is_ready():
|
|
raise HTTPException(status_code=503, detail="索引未加载,请等待或检查索引文件")
|
|
|
|
if mode == "keyword":
|
|
# 关键词精确搜索
|
|
results = retriever.keyword_search(
|
|
query=query,
|
|
top_k=top_k * 3 if (category or province or city) else top_k,
|
|
category=category,
|
|
province=province,
|
|
city=city,
|
|
)
|
|
else:
|
|
# 语义检索
|
|
try:
|
|
query_vec = await embed_text(query)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=503,
|
|
detail=f"embedding 服务不可用: {str(e)}",
|
|
)
|
|
fetch_k = top_k * 3 if (category or province or city) else top_k
|
|
results = retriever.search(
|
|
query_vec=query_vec,
|
|
top_k=fetch_k,
|
|
category=category,
|
|
province=province,
|
|
city=city,
|
|
)
|
|
|
|
# 分页
|
|
total = len(results)
|
|
start = (page - 1) * page_size
|
|
end = start + page_size
|
|
page_results = results[start:end]
|
|
|
|
# 记录检索历史
|
|
add_search_history(
|
|
query=query,
|
|
category=category,
|
|
province=province,
|
|
city=city,
|
|
result_count=total,
|
|
top_k=top_k,
|
|
)
|
|
|
|
return {
|
|
"code": 0,
|
|
"message": "ok",
|
|
"data": {
|
|
"results": page_results,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
},
|
|
"trace_id": trace_id,
|
|
}
|