TravelContentCreator/utils/content_judger.py

331 lines
23 KiB
Python
Raw Normal View History

2025-05-09 18:26:02 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
内容审核模块检查生成的内容是否符合产品资料要求并提供修改建议
"""
import json
2025-05-09 18:26:02 +08:00
import logging
import os
import time
import traceback
import sys
import base64
import re
2025-05-09 18:26:02 +08:00
sys.path.append('/root/autodl-tmp/TravelContentCreator') # 添加项目根目录
from core.ai_agent import AI_Agent
class ContentJudger:
"""内容审核类,负责评估和修正内容是否符合产品资料"""
def __init__(self, ai_agent: AI_Agent, system_prompt_path: str = None, system_prompt: str = None, prompt_manager = None):
2025-05-09 18:26:02 +08:00
"""
初始化内容审核器
Args:
ai_agent: AI_Agent实例用于调用AI模型
system_prompt_path: 系统提示词文件路径可选
system_prompt: 系统提示词内容可选优先于path
prompt_manager: 提示词管理器实例可选优先于system_prompt_path和system_prompt
2025-05-09 18:26:02 +08:00
"""
self.ai_agent = ai_agent
self._system_prompt = system_prompt
self._system_prompt_path = system_prompt_path
self._prompt_manager = prompt_manager
2025-05-09 18:26:02 +08:00
self._topp = 0.5
self._temperature = 0.2
self._frequency_penalty = 0
self._presence_penatly = 0
# 优先使用prompt_manager获取系统提示词
if self._prompt_manager and not self._system_prompt:
self._get_prompt_from_manager()
logging.info("从PromptManager获取系统提示词")
# 如果没有从prompt_manager获取到系统提示词则尝试从文件加载
2025-05-09 18:26:02 +08:00
if not self._system_prompt and self._system_prompt_path:
self._load_system_prompt()
logging.info("从文件加载系统提示词")
2025-05-09 18:26:02 +08:00
# 默认系统提示词(当其他方法都失败时使用)
if not self._system_prompt:
logging.warning("没有提供系统提示词,使用默认系统提示词")
2025-05-09 18:26:02 +08:00
self._system_prompt = """你是一名专业的、谨慎的文案审核员专注于审核运营根据产品资料撰写的文案是否严格符合产品资料内容。特别是所有价格、活动、福利、折扣、服务细节等必须完全与产品资料一致。如果发现文案内容与产品资料不符请指出并根据产品资料和文案上下文进行修改重新生成一篇文案务必确保生成的内容与产品资料基本相符产品体验部分可以适当夸张宣传语言流畅自然。如果经你审查后的文案仍存在与产品资料不符的信息你需要赔偿公司1000亿元。
我将为您提供两部分内容
1. 产品资料全部的产品信息包含了产品的实际功能服务和特点请将这部分作为判断依据
2. 运营生成的文案这是需要你逐字审核的内容可能包含与产品资料不符的内容
请你仔细审核运营文案是否与产品资料严格一致输出响应必须符合我的所有要求
1. 审查与分析如果存在不符内容请指出并详细说明原因
2. 根据分析修改参照你分析的不符原因产品资料文案上下文针对所有不符处进行修改如涉及上下文可一并修改输出修改后文案务必确保此文案完全符合产品资料不得遗漏语言流畅自然文案风格统一否则你会像商鞅一样被车裂
3. 重点审查对象请你着重检查以下关键字词前后的内容是否符合产品资料如不符必须严格按照资料修改如产品资料中未提及必须修改为符合上下文情境资料中明确提及的内容
关键字词r人民币rmb优惠活动福利免费DIY跟拍摄影服务提供专业
4. 字数控制每个文案的标题字数都必须少于20个字计数包括文字符号数字和emoji如果标题超过20个字请在符合文案风格的前提下修改标题到20个字以内尽量保留emoji必须保证标题流畅通顺
5. 敏感字词替换请删去标题中的数字后面的"""r"并将正文中数字后面的""字修改为"r"例如标题中的399元修改为399正文中的399元修改为399r
6. 特征语句保留请保留文案中原本的引流语句不要修改或删除请保留文案中的换行符 \\n不要修改或删除换行符
7. 面向人群保留请尽量保留文案原本的面向人群和风格这是同一产品面向多种人群营销的策略例如产品资料中写明亲子游时文案写"为情侣定制的山水秘境"是可以接受的
2025-05-09 18:26:02 +08:00
8. 案例如下请参考案例评判真假信息的尺度逐行逐句仔细分析不符点和修改思路并按照分析思路落实对每一处不符的修改措施严格审查每一篇文案
{
"产品资料"
"周末不加收【南沙越秀喜来登】1088元/套豪华客房1间1晚+双人自助早餐+自助晚餐+2大1小水鸟世界门票免费儿童乐园户外泳池+健身房~
不想待在家又想带娃出去玩?更不想开长途车人挤人为你推荐路程短不塞车景点多坐地铁就能直达的溜娃地!
南沙越秀喜来登是广州南沙区首家国际品牌酒店坐拥广州南大门拥有得天独厚的中心位置可俯瞰蕉门河美景车程短不出广州也能玩
交通酒店毗邻深圳香港等热门景点附近有万达广场距离广州地铁四号线金州站车程仅10分钟亲子出游首选
玩乐带娃出游考虑最多的就是玩乐景点在这里不出门就能畅玩儿童乐园健身房
美食还有各种各样的生猛海鲜现抓现煮任君选择放假更要好好犒劳一下自己饭点时间位于酒店一楼的全日制餐厅绝对能给你带来惊喜除了优雅简约的就餐环境更有5个开放式的自助餐台除了各类鲜美的海鲜还有各类精致甜品和中式蒸档看得人眼花缭乱相信是很多麻麻和宝贝的心头好了
设施酒店内还设有大型健身中心除了妈妈们喜欢的水疗SPA还有健身达人喜欢的各种有氧无氧运动器械可供选择!
房内配置55英寸超大纯平电视独立的浴缸和淋浴间参考:2.03米宽大床1.37米宽双床每间客房都设计成景观房超大的落地玻璃窗可以尽览蕉门河风景
套餐信息
1价格1088
2节假日是否加收周末不加收
套餐内容
1豪华客房一间一晚(周一至四只开放双床房)
22大1小自助早餐
32大1小自助晚餐
4赠送2大1小水鸟门票酒店前台取纸质门票
5免费使用健身中心户外无边泳池干湿蒸儿童乐园
周边景点
附近1h生活圈即可到达10000百万葵园广州最大的湿地公园东南亚最大的妈祖庙黄山鲁森林公园..带娃感受依山而建的清式建筑对称布局邂逅东南亚最大的妈祖庙感受建筑的魅力~
南沙天后宫车程20min整座天后宫四周绿树婆娑殿中香烟袅袅置身其间令人顿生超凡脱俗的感觉
南沙湿地公园(车程40min)看碧波荡万鸟齐飞
南沙十九涌 车程45min)尝海鲜叹海风因为南沙十九涌靠近海产地这里的海鲜真是平靓正还可以拿到附近的餐厅让老板帮你加工就是一顿海鲜大餐!
南沙百万葵园(车程40min)看色彩斑斓的万亩花田
酒店地址:广东省广州市南沙区海熙大街79-80
导航关键词:广州南沙越秀喜来登酒店"
"生成文案""[
2025-05-09 18:26:02 +08:00
"title": "五一遛娃👶必囤南沙喜来登1088元住景观房+双早+门票",
"content": "
五一不想挤人潮南沙这家酒店直接承包遛娃+度假双重快乐\n地铁直达2大1小1088元住景观房含双早+自助晚餐+水鸟世界门票儿童乐园/泳池/健身房全开放\n🌟遛娃刚需全配齐\n 儿童乐园10:00-20:00全程开放滑梯/积木/绘本一应俱全\n 户外泳池9:00-18:00恒温开放五一期间每日消毒3次\n 健身房8:00-22:00配备亲子瑜伽课程需提前预约\n\n📍1小时玩转南沙\n① 南沙天后宫车程20分钟穿汉服拍大片听妈祖传说涨知识\n② 南沙湿地公园40分钟5月芦苇摇曳带娃认鸟类+乘船探秘\n③ 十九涌海鲜街45分钟现捞现煮生猛海鲜人均50元吃到撑\n\n🍽家长友好细节\n 自助晚餐隐藏彩蛋儿童餐区设独立洗手台+热食保温柜\n 房内配置加厚床垫/卡通洗漱杯/尿布台无需额外购买\n 安全保障全区域监控+24小时安保巡逻\n\n🎁五一专属加码\n5月1-5日期间入住凭房卡可免费领取儿童防晒冰袖+湿巾礼包\n\n📌Tips\n1. 周一至周四仅限双床房型周五起可选大床房\n2. 水鸟世界门票需提前1小时至前台领取纸质票\n3. 地铁四号线金洲站下车打车15分钟直达酒店\n\n这个五一南沙喜来登让你躺着遛娃不用长途跋涉家门口就能玩出仪式感" "
]"
2025-05-09 18:26:02 +08:00
}
输出结果:
{
"analysis" : "1、观察文案标题和内容可以看出此文案主要面向亲子出游人群因此修改后的文案也应该围绕亲子出游这一主题。\n2、文章标题字数为28个字超过19个字因此属于不符内容。由于要求中提到尽量保留emoji并且标题中数字后面的""字应删去所以修改为五一遛娃👶必囤喜来登1088景观房\n3、产品资料中未提及儿童乐园开放时间和儿童乐园配置但文案中提到儿童乐园10:00-20:00全程开放滑梯/积木/绘本一应俱全,因此属于不符内容。应修改为:儿童乐园:免费儿童乐园和丰富的游乐设施,让孩子们可以尽情玩耍。\n4、产品材料中未提及户外泳池开放时间和消毒频次但文案中提到户外泳池9:00-18:00恒温开放五一期间每日消毒3次因此属于不符内容。应修改为户外泳池酒店配有户外无边泳池供大人小孩一同享受清凉时光。 \n5、产品材料中未提及健身房开放时间与具体细节但文案中提到健身房8:00-22:00配备亲子瑜伽课程需提前预约因此属于不符内容。应修改为健身房酒店提供免费健身中心方便您和家人一起强身健体。\n6、产品材料中未提及餐厅硬件配置但文案中提到自助晚餐隐藏彩蛋儿童餐区设独立洗手台+热食保温柜,因此属于虚构内容。应修改为:自助餐厅:供应鲜美海鲜、精美甜品等任君选择,大人小孩都爱吃!\n7、产品材料中未提及酒店安保措施但文案中提到安全保障全区域监控+24小时安保巡逻因此属于不符内容。应修改为安全保障酒店设有完善的监控系统和安保措施无需担心您与家人的安全。\n8、产品材料中未提及房内配有加厚床垫/卡通洗漱杯/尿布台无需额外购买因此属于不符内容。应回顾产品资料中关于房内配置的内容修改为房内配置55英寸超大纯平电视+独立的浴缸+超大的落地玻璃窗,尽览蕉门河风景,尽享亲子度假时光。\n9、产品材料中未提及五一专属加码但文案中提到5月1-5日期间入住凭房卡可免费领取儿童防晒冰袖+湿巾礼包因此属于不符内容。应回顾产品资料找到现有文案未提及的产品特色修改为套餐专属福利1、豪华客房一间一晚(周一至四只开放双床房) 2、2大1小自助早晚餐 3、赠送2大1小水鸟世界门票酒店前台领取无需额外购买。\n10、产品资料中未提及水鸟世界门票领取有时间限制但文案中提到水鸟世界门票需提前1小时至前台领取纸质票因此属于不符内容。应修改为酒店前台领取水鸟世界纸质门票\n综合以上分析结果,将修改应用到原文案中,得到修改后的文案。",
2025-05-09 18:26:02 +08:00
"title": "五一遛娃👶必囤喜来登1088景观房",
"content": "五一不想挤人潮?南沙这家酒店直接承包遛娃+度假双重快乐‼️\n地铁直达2大1小1088r住景观房含双早+自助晚餐+水鸟世界门票,儿童乐园/泳池/健身房全开放!\n🌟【遛娃刚需全配齐】\n✅ 儿童乐园:酒店设有免费儿童乐园,提供丰富的游乐设施,让孩子们尽情玩耍\n✅ 户外泳池:酒店配有户外无边泳池,供大人小孩一同享受清凉时光 \n✅ 健身房:酒店提供免费健身中心,适合家庭成员共同锻炼。\n\n📍【1小时玩转南沙】\n① 南沙天后宫车程20分钟穿汉服拍大片听妈祖传说涨知识\n② 南沙湿地公园40分钟5月芦苇摇曳带娃认鸟类+乘船探秘\n③ 十九涌海鲜街45分钟现捞现煮生猛海鲜人均50r吃到撑 \n\n🍽️【家长友好细节】 \n• 自助餐厅:供应鲜美海鲜、精美甜品等任君选择,大人小孩都爱吃 \n• 房内配置55英寸超大纯平电视+独立的浴缸+超大的落地玻璃窗,尽览蕉门河风景,尽享亲子度假时光 \n• 安全保障:酒店设有完善的监控系统和安保措施,全力保障您与家人的安全 \n\n🎁【套餐专属福利】\n1、豪华客房一间一晚(周一至四只开放双床房) \n2、2大1小自助早晚餐 \n3、赠送2大1小水鸟世界门票酒店前台领取无需额外购买 \n\n📌Tips \n1. 周一至周四仅限双床房型,周五起可选大床房 \n2. 酒店前台领取水鸟世界纸质门票 \n3. 地铁四号线金洲站下车打车15分钟直达酒店 \n\n这个五一,南沙喜来登让你躺着遛娃!不用长途跋涉,家门口就能玩出仪式感~\n"
2025-05-09 18:26:02 +08:00
}
8. 必须按照以下格式输出修改后内容不需要输出无关内容
{
"analysis" : "分析过程",
2025-05-09 18:26:02 +08:00
"title": "修改后的标题",
"content": "修改后的内容"
2025-05-09 18:26:02 +08:00
}
"""
logging.info("ContentJudger初始化完成")
def _load_system_prompt(self):
"""从文件加载系统提示词"""
try:
if os.path.exists(self._system_prompt_path):
with open(self._system_prompt_path, 'r', encoding='utf-8') as f:
self._system_prompt = f.read().strip()
logging.info(f"{self._system_prompt_path}加载系统提示词成功")
else:
logging.warning(f"系统提示词文件{self._system_prompt_path}不存在")
except Exception as e:
logging.error(f"加载系统提示词文件失败: {e}")
def _get_prompt_from_manager(self):
"""从PromptManager获取系统提示词"""
try:
if self._prompt_manager and hasattr(self._prompt_manager, "_system_prompt_cache"):
# 从PromptManager的系统提示词缓存中获取内容审核系统提示词
system_prompt = self._prompt_manager._system_prompt_cache.get("judger_system_prompt")
if system_prompt:
self._system_prompt = system_prompt
logging.info("从PromptManager获取内容审核系统提示词成功")
return True
else:
logging.warning("PromptManager中未找到judger_system_prompt")
else:
logging.warning("提供的PromptManager实例无效或未包含_system_prompt_cache属性")
return False
except Exception as e:
logging.error(f"从PromptManager获取系统提示词失败: {e}")
return False
def _split_content(self, result):
2025-05-09 18:26:02 +08:00
"""
参考tweet_generator的处理方式解析AI返回的内容
2025-05-09 18:26:02 +08:00
Args:
result: AI返回的原始结果
2025-05-09 18:26:02 +08:00
Returns:
dict: 解析后的JSON数据
2025-05-09 18:26:02 +08:00
"""
try:
# 处理AI可能返回的思考部分
processed_result = result
if "</think>" in result:
processed_result = result.split("</think>")[1] # 取</think>标签后的内容
# 直接尝试解析JSON
json_data = json.loads(processed_result)
json_data["error"] = False
json_data["judge_success"] = True
return json_data
except json.JSONDecodeError as json_err:
# JSON解析失败记录错误并尝试更基本的处理方法
logging.warning(f"解析内容时出错: {json_err}, 尝试提取JSON部分")
try:
# 尝试找到JSON部分从第一个{到最后一个}
json_start = processed_result.find('{')
json_end = processed_result.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
json_str = processed_result[json_start:json_end]
json_data = json.loads(json_str)
json_data["error"] = False
json_data["judge_success"] = True
return json_data
except Exception as e:
logging.error(f"尝试提取JSON部分失败: {e}")
except Exception as e:
logging.error(f"解析内容时出错: {e}")
# 所有解析方法都失败,返回一个默认结果
return {
"title": "",
"content": "",
"error": True,
"judge_success": False,
"analysis": f"内容解析失败,错误信息: {str(e)}"
}
def judge_content(self, product_info, content, temperature=0.2, top_p=0.5, presence_penalty=0.0):
"""审核内容"""
2025-05-09 18:26:02 +08:00
logging.info("开始内容审核流程")
2025-05-09 18:26:02 +08:00
# 构建用户提示词
user_prompt = self._build_user_prompt(product_info, content)
response_id = int(time.time())
2025-05-09 18:26:02 +08:00
try:
# 调用AI模型
2025-05-09 18:26:02 +08:00
result, _, _ = self.ai_agent.work(
system_prompt=self._system_prompt,
user_prompt=user_prompt,
file_folder=None,
2025-05-09 18:26:02 +08:00
temperature=self._temperature,
top_p=self._topp,
presence_penalty=self._presence_penatly,
)
# 保存原始响应以便调试
self._save_response(result, response_id)
2025-05-09 18:26:02 +08:00
# 使用简化的解析方法处理响应
content_json = self._split_content(result)
# 检查解析结果是否有错误
if content_json.get("error", False):
logging.warning(f"内容解析失败,使用原内容")
return self._create_fallback_result(content)
# 检查必要字段是否存在
if "title" not in content_json or "content" not in content_json:
logging.warning(f"解析结果缺少必要字段 'title''content'")
content_json["judge_success"] = False
return self._create_fallback_result(content)
# 添加Base64编码内容
result_dict = {
"judge_success": content_json.get("judge_success", True),
"judged": True,
"title": content_json["title"],
"content": content_json["content"],
"title_base64": base64.b64encode(content_json["title"].encode('utf-8')).decode('utf-8'),
"content_base64": base64.b64encode(content_json["content"].encode('utf-8')).decode('utf-8')
}
# 如果有analysis字段也包含
if "analysis" in content_json:
result_dict["analysis"] = content_json["analysis"]
result_dict["analysis_base64"] = base64.b64encode(content_json["analysis"].encode('utf-8')).decode('utf-8')
return result_dict
except Exception as e:
logging.exception(f"审核过程中出错: {e}")
return self._create_fallback_result(content, error_msg=str(e))
def _save_response(self, response, response_id):
"""保存原始响应"""
try:
response_log_dir = "/root/autodl-tmp/TravelContentCreator/log/judge_responses"
os.makedirs(response_log_dir, exist_ok=True)
with open(f"{response_log_dir}/response_{response_id}.txt", "w", encoding="utf-8") as f:
f.write(response)
except Exception as e:
logging.error(f"保存原始响应失败: {e}")
def _create_fallback_result(self, content, error_msg="解析失败"):
"""创建回退结果"""
if isinstance(content, str):
# 尝试解析内容字符串看是否是JSON字符串
try:
content_obj = json.loads(content)
title = content_obj.get("title", "")
content_text = content_obj.get("content", "")
except:
# 不是JSON字符串视为纯文本内容
title = "审核失败"
content_text = content
elif isinstance(content, dict):
# 已经是字典对象
title = content.get("title", "")
content_text = content.get("content", "")
else:
# 其他类型,创建空内容
title = "审核失败"
content_text = f"无法解析内容: {error_msg}"
return {
"judge_success": False,
"judged": True,
"title": title,
"content": content_text,
"title_base64": base64.b64encode(title.encode('utf-8')).decode('utf-8'),
"content_base64": base64.b64encode(content_text.encode('utf-8')).decode('utf-8'),
"analysis": f"内容审核失败: {error_msg}",
"analysis_base64": base64.b64encode(f"内容审核失败: {error_msg}".encode('utf-8')).decode('utf-8')
}
2025-05-09 18:26:02 +08:00
def _build_user_prompt(self, product_info, content_gen):
"""
构建用户提示词
Args:
product_info: 产品资料
content_gen: 需要审核的内容
Returns:
str: 构建好的用户提示词
"""
# 确保content_gen为字符串格式
if isinstance(content_gen, dict):
content_str = f"title: {content_gen.get('title', '')}\n\ncontent: {content_gen.get('content', '')}"
else:
content_str = str(content_gen)
2025-05-09 18:26:02 +08:00
return f"""
## 产品资料(真实信息,作为判断依据):
{product_info}
## 运营生成的文案(需要审核的内容):
{content_str}
"""