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,190 @@
|
||||
"""提示词配置服务 — 从 SQLite 读写系统提示词和模板正文
|
||||
|
||||
配置项(config_key):
|
||||
- system_prompt: 系统提示词(覆盖默认)
|
||||
- template_simple: 通俗解释模板正文
|
||||
- template_professional: 专业分析模板正文
|
||||
- template_compare: 对比条文模板正文
|
||||
"""
|
||||
import sqlite3
|
||||
import time
|
||||
import logging
|
||||
from typing import Optional, Dict
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import SQLITE_PATH
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 默认值(与 rag.py 中 PROMPT_TEMPLATES 保持一致)
|
||||
DEFAULTS = {
|
||||
"system_prompt": "你是法律助手,根据提供的法规条文回答问题,必须引用法规名和条号。",
|
||||
"template_simple": (
|
||||
"你是一位耐心的法律科普助手。请根据以下法规条文,用通俗易懂的语言回答用户问题。\n"
|
||||
"要求:\n"
|
||||
"1. 避免法律术语,用日常语言解释\n"
|
||||
'2. 必须引用依据的法规名和条号,格式如"根据《XX法》第X条"\n'
|
||||
'3. 如果条文不足以回答,明确说明"根据现有法规无法完全回答"\n'
|
||||
"4. 不要编造法规\n\n"
|
||||
"【相关法规条文】\n{context}\n\n【用户问题】\n{question}"
|
||||
),
|
||||
"template_professional": (
|
||||
"你是一位专业的法律分析助手。请根据以下法规条文,对用户问题进行专业分析。\n"
|
||||
"要求:\n"
|
||||
"1. 使用规范法律术语\n"
|
||||
'2. 必须引用依据的法规名和条号,格式如"依据《XX法》第X条"\n'
|
||||
"3. 分析条文的适用条件、法律后果\n"
|
||||
"4. 如有多条相关,对比分析\n"
|
||||
"5. 指出条文的适用边界和可能的争议点\n"
|
||||
"6. 不要编造法规\n\n"
|
||||
"【相关法规条文】\n{context}\n\n【用户问题】\n{question}"
|
||||
),
|
||||
"template_compare": (
|
||||
"你是一位法规研究助手。请根据以下法规条文,对比分析不同法规对同一问题的规定。\n"
|
||||
"要求:\n"
|
||||
"1. 列出每条相关法规的具体规定\n"
|
||||
"2. 对比规定的异同\n"
|
||||
"3. 标注法规名、条号、发布日期\n"
|
||||
"4. 指出适用范围差异(如全国性 vs 地方性)\n"
|
||||
"5. 不要编造法规\n\n"
|
||||
"【相关法规条文】\n{context}\n\n【用户问题】\n{question}"
|
||||
),
|
||||
# LLM 参数默认值
|
||||
"llm_thinking_enabled": "true", # thinking 开关
|
||||
"llm_max_tokens": "4096", # 最大生成 token 数
|
||||
"llm_temperature": "0.3", # 温度
|
||||
"llm_thinking_budget": "2048", # thinking 预算(thinking 最大 token 数)
|
||||
}
|
||||
|
||||
# 模板 key 与 rag.py PROMPT_TEMPLATES 的映射
|
||||
TEMPLATE_KEY_MAP = {
|
||||
"simple": "template_simple",
|
||||
"professional": "template_professional",
|
||||
"compare": "template_compare",
|
||||
}
|
||||
|
||||
|
||||
def _get_conn() -> sqlite3.Connection:
|
||||
"""获取 SQLite 连接(自动建表)"""
|
||||
SQLITE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(SQLITE_PATH), check_same_thread=False)
|
||||
# 自动建表(若数据库不存在或表不存在)
|
||||
conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS prompt_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
config_key TEXT NOT NULL UNIQUE,
|
||||
config_value TEXT,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
""")
|
||||
return conn
|
||||
|
||||
|
||||
def get_all_configs() -> Dict[str, str]:
|
||||
"""读取所有提示词配置,未配置的用默认值填充
|
||||
|
||||
Returns:
|
||||
{config_key: config_value} 所有配置项(含默认值)
|
||||
"""
|
||||
configs = dict(DEFAULTS) # 先用默认值
|
||||
try:
|
||||
conn = _get_conn()
|
||||
rows = conn.execute(
|
||||
"SELECT config_key, config_value FROM prompt_configs"
|
||||
).fetchall()
|
||||
for row in rows:
|
||||
key, val = row[0], row[1]
|
||||
if val: # 空值视为未配置,用默认
|
||||
configs[key] = val
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"读取提示词配置失败,使用默认值: {e}")
|
||||
return configs
|
||||
|
||||
|
||||
def get_config(key: str) -> str:
|
||||
"""读取单个配置,未配置返回默认值"""
|
||||
try:
|
||||
conn = _get_conn()
|
||||
row = conn.execute(
|
||||
"SELECT config_value FROM prompt_configs WHERE config_key = ?", (key,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if row and row[0]:
|
||||
return row[0]
|
||||
except Exception as e:
|
||||
logger.warning(f"读取配置 {key} 失败: {e}")
|
||||
return DEFAULTS.get(key, "")
|
||||
|
||||
|
||||
def set_config(key: str, value: str) -> bool:
|
||||
"""写入单个配置(空值删除记录,回退到默认)
|
||||
|
||||
Returns:
|
||||
True 成功
|
||||
"""
|
||||
try:
|
||||
conn = _get_conn()
|
||||
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if value: # 有值则 upsert
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO prompt_configs (config_key, config_value, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(config_key) DO UPDATE SET
|
||||
config_value = excluded.config_value,
|
||||
updated_at = excluded.updated_at
|
||||
""",
|
||||
(key, value, ts),
|
||||
)
|
||||
else: # 空值则删除(回退默认)
|
||||
conn.execute(
|
||||
"DELETE FROM prompt_configs WHERE config_key = ?", (key,)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"写入配置 {key} 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_template_text(template: str) -> str:
|
||||
"""获取指定模板的正文
|
||||
|
||||
Args:
|
||||
template: 模板名(simple/professional/compare)
|
||||
|
||||
Returns:
|
||||
模板正文(含 {context} 和 {question} 占位符)
|
||||
"""
|
||||
key = TEMPLATE_KEY_MAP.get(template, "template_simple")
|
||||
return get_config(key)
|
||||
|
||||
|
||||
def get_llm_params() -> dict:
|
||||
"""获取 LLM 参数配置
|
||||
|
||||
Returns:
|
||||
{"thinking_enabled": bool, "max_tokens": int, "temperature": float, "thinking_budget": int}
|
||||
"""
|
||||
configs = get_all_configs()
|
||||
try:
|
||||
return {
|
||||
"thinking_enabled": configs.get("llm_thinking_enabled", "true").lower() == "true",
|
||||
"max_tokens": int(configs.get("llm_max_tokens", "4096")),
|
||||
"temperature": float(configs.get("llm_temperature", "0.3")),
|
||||
"thinking_budget": int(configs.get("llm_thinking_budget", "2048")),
|
||||
}
|
||||
except (ValueError, TypeError):
|
||||
return {
|
||||
"thinking_enabled": True,
|
||||
"max_tokens": 4096,
|
||||
"temperature": 0.3,
|
||||
"thinking_budget": 2048,
|
||||
}
|
||||
|
||||
|
||||
def get_system_prompt() -> str:
|
||||
"""获取系统提示词"""
|
||||
return get_config("system_prompt")
|
||||
Reference in New Issue
Block a user