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>
191 lines
6.5 KiB
Python
191 lines
6.5 KiB
Python
"""提示词配置服务 — 从 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")
|