feat: 提示词和 AI 参数移至数据库管理
- AiSetting 模型新增 6 个提示词字段 + 3 个阶段温度字段 - main.py 迁移逻辑改为检测 ai_settings 表的新字段并填充默认值 - generate.py 从数据库读取生成提示词,去掉硬编码 - optimizer 从 API 读取各阶段提示词和温度,删除 prompts.py - crawler 从 API 读取提取/改写提示词和温度,删除 prompts.py - settings/active 端点去掉 token 认证(供爬虫/优化器使用) - 后台设置页新增提示词编辑区和温度调节控件 - 新增 _ensure_default_settings 自动创建默认配置
This commit is contained in:
+76
-23
@@ -8,15 +8,6 @@ import time
|
||||
import httpx
|
||||
from openai import OpenAI
|
||||
|
||||
from optimizer.prompts import (
|
||||
QUALITY_CHECK_SYSTEM_PROMPT,
|
||||
QUALITY_CHECK_USER_PROMPT,
|
||||
POLISH_SYSTEM_PROMPT,
|
||||
POLISH_USER_PROMPT,
|
||||
EVALUATE_SYSTEM_PROMPT,
|
||||
EVALUATE_USER_PROMPT,
|
||||
)
|
||||
|
||||
|
||||
class Optimizer:
|
||||
def __init__(self, api_base: str, username: str, password: str):
|
||||
@@ -26,6 +17,7 @@ class Optimizer:
|
||||
self.token = None
|
||||
self.ai_client = None
|
||||
self.model_name = ""
|
||||
self.ai_config = None # 完整 AI 配置(含提示词和温度)
|
||||
self.types = []
|
||||
self.crowds = []
|
||||
# 统计
|
||||
@@ -67,6 +59,7 @@ class Optimizer:
|
||||
print("[*] 登录成功")
|
||||
|
||||
ai_config = self._get("/api/admin/settings/active")
|
||||
self.ai_config = ai_config
|
||||
self.ai_client = OpenAI(
|
||||
base_url=ai_config["api_base"],
|
||||
api_key=ai_config["api_key"],
|
||||
@@ -78,6 +71,22 @@ class Optimizer:
|
||||
self.crowds = self._get("/api/categories/crowds")
|
||||
print(f"[*] 分类: {len(self.types)} 种类型, {len(self.crowds)} 种人群")
|
||||
|
||||
def _get_prompt(self, key: str, default: str) -> str:
|
||||
"""从数据库配置中读取提示词,没有则返回默认"""
|
||||
if self.ai_config:
|
||||
val = self.ai_config.get(key)
|
||||
if val and val.strip():
|
||||
return val
|
||||
return default
|
||||
|
||||
def _get_temp(self, key: str, default: float) -> float:
|
||||
"""从数据库配置中读取温度"""
|
||||
if self.ai_config:
|
||||
val = self.ai_config.get(key)
|
||||
if val is not None:
|
||||
return float(val)
|
||||
return default
|
||||
|
||||
# === 读取笑话 ===
|
||||
def get_jokes(self, status: str | None = None, limit: int | None = None,
|
||||
ids: list[int] | None = None) -> list[dict]:
|
||||
@@ -120,14 +129,32 @@ class Optimizer:
|
||||
# === Stage 1: 质量检测 ===
|
||||
def quality_check(self, content: str) -> dict:
|
||||
"""判断笑话是否有笑点,返回 {"has_punchline": bool, "reason": str}"""
|
||||
system_prompt = self._get_prompt("optimizer_quality_prompt",
|
||||
"""你是一个幽默内容审核专家。判断以下内容是否是一个合格的笑话/段子。
|
||||
|
||||
合格标准(满足任一即可):
|
||||
1. 有明确的笑点或反转(punchline)
|
||||
2. 有幽默的语言表达或双关
|
||||
3. 有意外结局或情理之中意料之外
|
||||
|
||||
不合格标准(符合任一即判定不合格):
|
||||
1. 纯粹的事实陈述,没有任何幽默元素
|
||||
2. 只是对话片段,没有笑点
|
||||
3. 普通故事或叙事,没有幽默设计
|
||||
4. 说教或道理阐述
|
||||
5. 内容不完整或难以理解
|
||||
|
||||
始终返回 JSON 格式:{"has_punchline": true/false, "reason": "简要说明判断理由"}""")
|
||||
temperature = self._get_temp("optimizer_quality_temperature", 0.3)
|
||||
|
||||
def _call():
|
||||
return self.ai_client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=[
|
||||
{"role": "system", "content": QUALITY_CHECK_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": QUALITY_CHECK_USER_PROMPT.format(content=content[:2000])},
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": f"请判断以下内容是否为合格笑话:\n\n{content[:2000]}\n\n返回 JSON 格式。"},
|
||||
],
|
||||
temperature=0.3,
|
||||
temperature=temperature,
|
||||
max_tokens=200,
|
||||
)
|
||||
resp = self._safe_api_call(_call)
|
||||
@@ -137,14 +164,24 @@ class Optimizer:
|
||||
# === Stage 2: AI 润色 ===
|
||||
def polish(self, content: str) -> str:
|
||||
"""润色笑话内容"""
|
||||
system_prompt = self._get_prompt("optimizer_polish_prompt",
|
||||
"""你是一个专业的幽默文案编辑。请润色以下笑话,要求:
|
||||
1. 保持核心笑点不变
|
||||
2. 优化语言表达,使其更通顺、更精炼
|
||||
3. 增强节奏感和幽默效果,但不改变原意
|
||||
4. 字数控制在原内容的 80%-120%
|
||||
5. 不要添加额外解释或评论
|
||||
6. 直接输出润色后的内容,不要加任何前缀""")
|
||||
temperature = self._get_temp("optimizer_polish_temperature", 0.8)
|
||||
|
||||
def _call():
|
||||
return self.ai_client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=[
|
||||
{"role": "system", "content": POLISH_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": POLISH_USER_PROMPT.format(content=content)},
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": f"请润色以下笑话:\n\n{content}"},
|
||||
],
|
||||
temperature=0.8,
|
||||
temperature=temperature,
|
||||
max_tokens=1024,
|
||||
)
|
||||
resp = self._safe_api_call(_call)
|
||||
@@ -156,24 +193,40 @@ class Optimizer:
|
||||
type_names = [t.get("name", "") for t in self.types]
|
||||
crowd_names = [c.get("name", "") for c in self.crowds]
|
||||
|
||||
system_prompt = self._get_prompt("optimizer_evaluate_prompt",
|
||||
"""你是一个笑话分类和评价专家。对给定的笑话进行分析,返回 JSON 格式的分类和评分结果。
|
||||
|
||||
要求:
|
||||
1. types: 从提供的类型列表中选择所有匹配的类型名称(数组,可以选多个)
|
||||
2. crowds: 从提供的人群列表中选择所有匹配的人群名称(数组,可以选多个)
|
||||
3. score: 1-10 分,基于幽默程度、创意和表达效果
|
||||
4. comment: 简短评语(10字以内)
|
||||
|
||||
始终返回 JSON 格式。""")
|
||||
temperature = self._get_temp("optimizer_evaluate_temperature", 0.3)
|
||||
|
||||
user_content = f"""笑话内容:
|
||||
{content[:2000]}
|
||||
|
||||
可选类型:{', '.join(type_names)}
|
||||
可选人群:{', '.join(crowd_names)}
|
||||
|
||||
返回 JSON 格式:{{"types": ["类型1", "类型2"], "crowds": ["人群1", "人群2"], "score": 8, "comment": "简短评语"}}"""
|
||||
|
||||
def _call():
|
||||
return self.ai_client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=[
|
||||
{"role": "system", "content": EVALUATE_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": EVALUATE_USER_PROMPT.format(
|
||||
content=content[:2000],
|
||||
known_types=", ".join(type_names),
|
||||
known_crowds=", ".join(crowd_names),
|
||||
)},
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_content},
|
||||
],
|
||||
temperature=0.3,
|
||||
temperature=temperature,
|
||||
max_tokens=300,
|
||||
)
|
||||
resp = self._safe_api_call(_call)
|
||||
raw = resp.choices[0].message.content.strip()
|
||||
result = self._parse_json(raw, {"types": [], "crowds": [], "score": 5, "comment": ""})
|
||||
# Backward compatibility: if LLM returns old single format, convert to array
|
||||
# Backward compatibility
|
||||
if isinstance(result.get("types"), str):
|
||||
result["types"] = [result["types"]] if result["types"] else []
|
||||
if isinstance(result.get("crowds"), str):
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
"""AI 提示词模板 — 笑话质量检测、润色、评价分类。"""
|
||||
|
||||
# ===== Stage 1: 质量检测 =====
|
||||
QUALITY_CHECK_SYSTEM_PROMPT = """你是一个幽默内容审核专家。判断以下内容是否是一个合格的笑话/段子。
|
||||
|
||||
合格标准(满足任一即可):
|
||||
1. 有明确的笑点或反转(punchline)
|
||||
2. 有幽默的语言表达或双关
|
||||
3. 有意外结局或情理之中意料之外
|
||||
|
||||
不合格标准(符合任一即判定不合格):
|
||||
1. 纯粹的事实陈述,没有任何幽默元素
|
||||
2. 只是对话片段,没有笑点
|
||||
3. 普通故事或叙事,没有幽默设计
|
||||
4. 说教或道理阐述
|
||||
5. 内容不完整或难以理解
|
||||
|
||||
始终返回 JSON 格式:{"has_punchline": true/false, "reason": "简要说明判断理由"}"""
|
||||
|
||||
QUALITY_CHECK_USER_PROMPT = """请判断以下内容是否为合格笑话:
|
||||
|
||||
{content}
|
||||
|
||||
返回 JSON 格式。"""
|
||||
|
||||
# ===== Stage 2: AI 润色 =====
|
||||
POLISH_SYSTEM_PROMPT = """你是一个专业的幽默文案编辑。请润色以下笑话,要求:
|
||||
1. 保持核心笑点不变
|
||||
2. 优化语言表达,使其更通顺、更精炼
|
||||
3. 增强节奏感和幽默效果,但不改变原意
|
||||
4. 字数控制在原内容的 80%-120%
|
||||
5. 不要添加额外解释或评论
|
||||
6. 直接输出润色后的内容,不要加任何前缀"""
|
||||
|
||||
POLISH_USER_PROMPT = """请润色以下笑话:
|
||||
|
||||
{content}
|
||||
|
||||
只输出润色后的笑话内容。"""
|
||||
|
||||
# ===== Stage 3: 评价分类 =====
|
||||
EVALUATE_SYSTEM_PROMPT = """你是一个笑话分类和评价专家。对给定的笑话进行分析,返回 JSON 格式的分类和评分结果。
|
||||
|
||||
要求:
|
||||
1. types: 从提供的类型列表中选择所有匹配的类型名称(数组,可以选多个)
|
||||
2. crowds: 从提供的人群列表中选择所有匹配的人群名称(数组,可以选多个)
|
||||
3. score: 1-10 分,基于幽默程度、创意和表达效果
|
||||
4. comment: 简短评语(10字以内)
|
||||
|
||||
始终返回 JSON 格式。"""
|
||||
|
||||
EVALUATE_USER_PROMPT = """笑话内容:
|
||||
{content}
|
||||
|
||||
可选类型:{known_types}
|
||||
可选人群:{known_crowds}
|
||||
|
||||
返回 JSON 格式:{{"types": ["类型1", "类型2"], "crowds": ["人群1", "人群2"], "score": 8, "comment": "简短评语"}}"""
|
||||
Reference in New Issue
Block a user