feat: QYLAW 法律法规知识库
- 语义检索(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>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""检索 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,
|
||||
}
|
||||
Reference in New Issue
Block a user