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,147 @@
|
||||
"""法规原文读取服务 — 从 law-pack 目录读取 markdown,从 SQLite 取列表"""
|
||||
import sqlite3
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import SQLITE_PATH, LAW_PACK_DIR
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LawReader:
|
||||
"""法规原文读取单例"""
|
||||
|
||||
_instance: Optional["LawReader"] = None
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls) -> "LawReader":
|
||||
if cls._instance is None:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
self._conn: Optional[sqlite3.Connection] = None
|
||||
|
||||
def _ensure_conn(self):
|
||||
if self._conn is None and SQLITE_PATH.exists():
|
||||
self._conn = sqlite3.connect(str(SQLITE_PATH), check_same_thread=False)
|
||||
self._conn.row_factory = sqlite3.Row
|
||||
|
||||
def list_laws(
|
||||
self,
|
||||
category: Optional[str] = None,
|
||||
province: Optional[str] = None,
|
||||
keyword: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Dict[str, Any]:
|
||||
"""法规列表(分页 + 过滤)"""
|
||||
self._ensure_conn()
|
||||
if self._conn is None:
|
||||
return {"results": [], "total": 0, "page": page, "page_size": page_size}
|
||||
|
||||
where = []
|
||||
params: List[Any] = []
|
||||
if category:
|
||||
where.append("category = ?")
|
||||
params.append(category)
|
||||
if province:
|
||||
where.append("province = ?")
|
||||
params.append(province)
|
||||
if keyword:
|
||||
where.append("name LIKE ?")
|
||||
params.append(f"%{keyword}%")
|
||||
|
||||
where_clause = f"WHERE {' AND '.join(where)}" if where else ""
|
||||
|
||||
# 总数
|
||||
total = self._conn.execute(
|
||||
f"SELECT COUNT(*) FROM laws {where_clause}", params
|
||||
).fetchone()[0]
|
||||
|
||||
# 分页
|
||||
offset = (page - 1) * page_size
|
||||
rows = self._conn.execute(
|
||||
f"""
|
||||
SELECT id, name, category, publish_date, province, city, clause_count, file_path
|
||||
FROM laws {where_clause}
|
||||
ORDER BY category, name
|
||||
LIMIT ? OFFSET ?
|
||||
""",
|
||||
params + [page_size, offset],
|
||||
).fetchall()
|
||||
|
||||
results = [
|
||||
{
|
||||
"law_id": row["id"],
|
||||
"name": row["name"],
|
||||
"category": row["category"],
|
||||
"publish_date": row["publish_date"],
|
||||
"province": row["province"],
|
||||
"city": row["city"],
|
||||
"clause_count": row["clause_count"],
|
||||
"file_path": row["file_path"],
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
return {
|
||||
"results": results,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
}
|
||||
|
||||
def get_law(self, law_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""法规详情(含原文 + 条文列表)"""
|
||||
self._ensure_conn()
|
||||
if self._conn is None:
|
||||
return None
|
||||
|
||||
row = self._conn.execute(
|
||||
"SELECT * FROM laws WHERE id = ?", (law_id,)
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
|
||||
# 读取原文(路径校验防穿越)
|
||||
file_path = Path(row["file_path"])
|
||||
try:
|
||||
# 确保在 law-pack 目录内
|
||||
abs_path = file_path.resolve()
|
||||
pack_root = LAW_PACK_DIR.resolve()
|
||||
if not str(abs_path).startswith(str(pack_root)):
|
||||
logger.error(f"路径穿越尝试: {file_path}")
|
||||
return None
|
||||
content = abs_path.read_text(encoding="utf-8")
|
||||
except Exception as e:
|
||||
logger.error(f"读取原文失败 {file_path}: {e}")
|
||||
content = f"[读取失败: {e}]"
|
||||
|
||||
# 条文列表
|
||||
clauses = self._conn.execute(
|
||||
"SELECT id, chapter, clause_no, content FROM clauses WHERE law_id = ? ORDER BY id",
|
||||
(law_id,),
|
||||
).fetchall()
|
||||
clause_list = [
|
||||
{
|
||||
"clause_id": c["id"],
|
||||
"chapter": c["chapter"],
|
||||
"clause_no": c["clause_no"],
|
||||
"content": c["content"],
|
||||
}
|
||||
for c in clauses
|
||||
]
|
||||
|
||||
return {
|
||||
"law_id": row["id"],
|
||||
"name": row["name"],
|
||||
"category": row["category"],
|
||||
"publish_date": row["publish_date"],
|
||||
"province": row["province"],
|
||||
"city": row["city"],
|
||||
"content": content,
|
||||
"file_path": row["file_path"],
|
||||
"clauses": clause_list,
|
||||
}
|
||||
Reference in New Issue
Block a user