From e8f8d52f5c94b2247abded003a7c4f1ed85225ad Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Thu, 24 Apr 2025 20:35:25 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86content=5Fgen,=20?= =?UTF-8?q?=E7=94=A8=E4=BA=86ai=5Fagent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/__pycache__/contentGen.cpython-312.pyc | Bin 26221 -> 5023 bytes core/contentGen.py | 727 ++---------------- output/2025-04-24_19-44-15.json | 26 + test_content_generator.py | 118 +++ .../content_generator.cpython-312.pyc | Bin 0 -> 23116 bytes .../tweet_generator.cpython-312.pyc | Bin 29602 -> 29470 bytes utils/content_generator.py | 529 +++++++++++++ 7 files changed, 738 insertions(+), 662 deletions(-) create mode 100644 output/2025-04-24_19-44-15.json create mode 100644 test_content_generator.py create mode 100644 utils/__pycache__/content_generator.cpython-312.pyc create mode 100644 utils/content_generator.py diff --git a/core/__pycache__/contentGen.cpython-312.pyc b/core/__pycache__/contentGen.cpython-312.pyc index c5755bf747f786ceb80db76b85f61d7bc1574112..aa6f51fd686bcee0f055a117dc11f3c95314ebdd 100644 GIT binary patch literal 5023 zcmcIo>u(&@6`$FMcXs{Q4KaSij=i>7IZGaP6gMO;BB3-SfnbsXs&G|}COhNJn%M_; zW)r-!z<`_B#HmApgoHe+&^WbgtV*R2V)6&{i&w_-YEUJldUowkwY;e+@u}y`?Ci@9 zRY1A6&fIe!=iYPfxxaJn{-eCyK|vX8aU2_|ps0T#V-!oKlJ0=Yd5WiaTBN%5FWpT8 zZxT&GbGL=2Ow@jgHxE+0W!$9C>NfY$Uh5JvU0zyoJrfSeLP*{(gapFLVdB$==`=Hk zN*@LOJS9*lz1w7`^gq6Wrw8e7GjAHCx-Ga)x0QGDR%k2XOMtWSHlPg80JZZj-VQwu z-T_=GUkbF0F9TZ6mjkWnvv}7LGc4q@=Ix;b>e!fWrf~~0P`&h9c$zoy<_XJq#vuI^ zo9m^4Gqz9ln}9(j*`4?);Ig~n8Q{u|+1kkLUbH@^(bs45+M{L=?{H^|I&@jRd0HJg zm;Bq`lka_+`tWLU=&X8S?B91r)RVtkdUtGL`flpOvE;d_)Wtz{@=EIWQ`x!MiaPpE z@}sHFERV9oQ8^NoSw28I-HTs;pq{#&`s#z^l}ihEFROp~IQ7Y-I{i_>gkYE#L^i|) zg-*A=D6E(qyRKe2t)4xTx|v%S#dDDWdz6y|HcG@!_tS?Ds#kBPt`EHOt3w4saoB4@ zpFZNVlS_a2G~4a((MocU|KbHKOMCn~w36QAwJ46<{*_W$2u9HRqeM`wayY_96jy`@ zk`VFFxGKrkXIj+~*CvV1crwv4ulO$__}fskJ*1rr5)AgL$dS1bp+ zo_$fV`vv(BbPGgbSk2rltCZjbXfg3UijxyXSP8Z#v4K!eSXn2@Bf$5r7y-v@Ozyn-7EnGRwwli!HUy=(6Er!$1b8 zdks{TH&NY`sHsal@@QhC`<~re%;tnu~IvRdOpmHAWbuWC2OK%pNay|X@cm^lv`_0N?Sl+BW z)B~V5MQfl>eec@h^z_mnFRM4sB}YdW?@lGpom%+n^DeKMAd)CfG0gGWL4%9J>5_VA z^_s{=>|Bptf)`!z!CCdO5l%iPp^9!dX@C!D1`>0xI;A3+Z7@n|1u{T=$CQmah8^=v zbDU|OY1#P=^F*StujH4+BGG+3IZO61kGUvq4Ho_LYRZl*1M)#^y1AdDlnEQY66z0`z;fu@k|d` z%pAqgrH2kZjOaF7dJ>ue2sB&*oYt`bVL-7VP6EU#r9tiumIyKl2$I*T+t}sTlMTSu zup|pa((yw>5}Xa=G{9Aflc=sc|JM6&0e}9``-c*hRf(FG<#m*6 za~goeu}scQvJItZQ7)VR=t#61I%1Wpj1JxK#H=2Yd@c3+J4}^{bV&U@aSr44}c;q z?bG{>YBmBOuaOgeD_vr z`eKoSmAKF1H;#tL4STlIKp`? zS{|agGQsmEnjvtv__)z8_b-5pHLhujoTLB@l4^nE8KEi8G+o;~7M>RUSGX%U# zZLgmYLPvgmBvx}wmLr{QZDQERiN_#M>fF`d-rjb6yJFq1uPjR%p_?=#@gTt*ieREZb|As%K@iOd zUS@(PMD`*t7Bz@$@(?%* z&8NR3jYK9)w2ct_0fihu;zojj;d|yu-40KY=YhrNmV_^#Wiw6F-%|E(sfvG6&;PeX Opy>nj3Z;Ft+5ZDSY2NSv literal 26221 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 0: - return result - except: - continue - - # 备用模式2:查找 { 开头 和 } 结尾,并尝试解析 - content = content.strip() - square_bracket_start = content.find('[') - square_bracket_end = content.rfind(']') - - if square_bracket_start != -1 and square_bracket_end != -1: - potential_json = content[square_bracket_start:square_bracket_end + 1] - try: - return json.loads(potential_json) - except: - print("尝试提取方括号内容失败") - - # 最后一种尝试:查找所有可能的 JSON 结构并尝试解析 - json_structures = re.findall(r'({.*?})', content, re.DOTALL) - if json_structures: - items = [] - for i, struct in enumerate(json_structures): - try: - item = json.loads(struct) - # 验证结构包含预期字段 - if 'main_title' in item and ('texts' in item or 'index' in item): - items.append(item) - except: - continue - - if items: - return items - - # 都失败了,打印错误并引发异常 - print(f"无法解析内容,返回原始文本: {content[:200]}...") - raise ValueError("无法从响应中提取有效的 JSON 格式") - - except Exception as e: - print(f"解析内容时出错: {e}") - print(f"原始内容: {content[:200]}...") # 仅显示前200个字符 - raise e + """分割JSON内容""" + return self._impl.split_content(content) def generate_posters(self, poster_num, tweet_content, system_prompt=None, max_retries=3): - """ - 生成海报内容 - - 参数: - poster_num: 海报数量 - tweet_content: 推文内容 - system_prompt: 系统提示,默认为None则使用预设提示 - max_retries: 最大重试次数 - - 返回: - 生成的海报内容 - """ - full_response = "" - timeout = 60 # 请求超时时间(秒) - - if not system_prompt: - # 使用默认系统提示词 - system_prompt = """ - 你是一名资深海报设计师,有丰富的爆款海报设计经验,你现在要为旅游景点做宣传,在小红书上发布大量宣传海报。你的主要工作目标有2个: - 1、你要根据我给你的图片描述和笔记推文内容,设计图文匹配的海报。 - 2、为海报设计文案,文案的<第一个小标题>和<第二个小标题>之间你需要检查是否逻辑关系合理,你将通过先去生成<第二个小标题>关于景区亮点的部分,再去综合判断<第一个小标题>应该如何搭配组合更符合两个小标题的逻辑再生成<第一个小标题>。 - - 其中,生成三类标题文案的通用性要求如下: - 1、生成的<大标题>字数必须小于8个字符 - 2、生成的<第一个小标题>字数和<第二个小标题>字数,两者都必须小8个字符 - 3、标题和文案都应符合中国社会主义核心价值观 - - 接下来先开始生成<大标题>部分,由于海报是用来宣传旅游景点,生成的海报<大标题>必须使用以下8种格式之一: - ①地名+景点名(例如福建厦门鼓浪屿/厦门鼓浪屿); - ②地名+景点名+plog; - ③拿捏+地名+景点名; - ④地名+景点名+攻略; - ⑤速通+地名+景点名 - ⑥推荐!+地名+景点名 - ⑦勇闯!+地名+景点名 - ⑧收藏!+地名+景点名 - 你需要随机挑选一种格式生成对应景点的文案,但是格式除了上面8种不可以有其他任何格式;同时尽量保证每一种格式出现的频率均衡。 - 接下来先去生成<第二个小标题>,<第二个小标题>文案的创作必须遵循以下原则: - 请根据笔记内容和图片识别,用极简的文字概括这篇笔记和图片中景点的特色亮点,其中你可以参考以下词汇进行创作,这段文案字数控制6-8字符以内; - - 特色亮点可能会出现的词汇不完全举例:非遗、古建、绝佳山水、祈福圣地、研学圣地、解压天堂、中国小瑞士、秘境竹筏游等等类型词汇 - - 接下来再去生成<第一个小标题>,<第一个小标题>文案的创作必须遵循以下原则: - 这部分文案创作公式有5种,分别为: - ①<受众人群画像>+<痛点词> - ②<受众人群画像> - ③<痛点词> - ④<受众人群画像>+ | +<痛点词> - ⑤<痛点词>+ | +<受众人群画像> - 请你根据实际笔记内容,结合这部分文案创作公式,需要结合<受众人群画像>和<痛点词>时,必须根据<第二个小标题>的景点特征和所对应的完整笔记推文内容主旨,特征挑选对应<受众人群画像>和<痛点词>。 - - 我给你提供受众人群画像库和痛点词库如下: - 1、受众人群画像库:情侣党、亲子游、合家游、银发族、亲子研学、学生党、打工人、周边游、本地人、穷游党、性价比、户外人、美食党、出片 - 2、痛点词库:3天2夜、必去、看了都哭了、不能错过、一定要来、问爆了、超全攻略、必打卡、强推、懒人攻略、必游榜、小众打卡、狂喜等等。 - - 你需要为每个请求至少生成{poster_num}个海报设计。请使用JSON格式输出结果,结构如下: - [ - { - "index": 1, - "main_title": "主标题内容", - "texts": ["第一个小标题", "第二个小标题"] - }, - { - "index": 2, - "main_title": "主标题内容", - "texts": ["第一个小标题", "第二个小标题"] - }, - // ... 更多海报 - ] - 确保生成的数量与用户要求的数量一致。只生成上述JSON格式内容,不要有其他任何额外内容。 - - """ - - if self.add_description: - # 创建用户内容,包括info信息和tweet_content - user_content = f""" - 以下是需要你处理的信息: - - 关于景点的描述: - {self.add_description} - - 推文内容: - {tweet_content} - - 请根据这些信息,生成{poster_num}个海报文案配置,以JSON数组格式返回。 - """ - else: - # 仅使用tweet_content - user_content = f""" - 以下是需要你处理的推文内容: - {tweet_content} - - 请根据这些信息,生成{poster_num}个海报文案配置,以JSON数组格式返回。 - """ - - self.logger.info(f"正在生成{poster_num}个海报文案配置") - - # 创建临时客户端 - temp_client = self._create_temp_client() - - if temp_client: - # 重试逻辑 - for retry in range(max_retries): - try: - self.logger.info(f"尝试生成内容 (尝试 {retry+1}/{max_retries})") - - # 定义流式响应处理回调函数 - def handle_stream_chunk(chunk, is_last=False, is_timeout=False, is_error=False, error=None): - nonlocal full_response - - if chunk: - full_response += chunk - # 实时输出到控制台 - print(chunk, end="", flush=True) - - if is_last: - print("\n") # 输出完成后换行 - if is_timeout: - print("警告: 响应流超时") - if is_error: - print(f"错误: {error}") - - # 使用AI_Agent的新回调方式 - from core.ai_agent import AI_Agent - ai_agent = AI_Agent( - self.api_base_url, - self.model_name, - self.api_key, - timeout=timeout, - max_retries=max_retries, - stream_chunk_timeout=30 # 流式块超时时间 - ) - - # 使用回调方式处理流式响应 - try: - full_response = ai_agent.generate_text_stream_with_callback( - system_prompt, - user_content, - callback=handle_stream_chunk, - temperature=self.temperature, - top_p=self.top_p, - presence_penalty=self.presence_penalty - ) - - # 如果成功生成内容,跳出重试循环 - ai_agent.close() - break - - except Exception as e: - error_msg = str(e) - self.logger.error(f"AI生成错误: {error_msg}") - ai_agent.close() - - # 继续重试逻辑 - if retry + 1 >= max_retries: - self.logger.warning("已达到最大重试次数,使用备用方案...") - # 生成备用内容 - full_response = self._generate_fallback_content(poster_num) - else: - self.logger.info(f"将在稍后重试,还剩 {max_retries - retry - 1} 次重试机会") - - except Exception as e: - error_msg = str(e) - self.logger.error(f"API连接错误 (尝试 {retry+1}/{max_retries}): {error_msg}") - - # 如果已经达到最大重试次数 - if retry + 1 >= max_retries: - self.logger.warning("已达到最大重试次数,使用备用方案...") - # 生成备用内容(简单模板) - full_response = self._generate_fallback_content(poster_num) - else: - self.logger.info(f"将在稍后重试,还剩 {max_retries - retry - 1} 次重试机会") - - # 关闭临时客户端 - self._close_client(temp_client) - - return full_response + """生成海报内容""" + return self._impl.generate_posters( + poster_num, + tweet_content, + system_prompt, + api_url=self.api_base_url, + model_name=self.model_name, + api_key=self.api_key, + timeout=60, + max_retries=max_retries + ) def _generate_fallback_content(self, poster_num): - """生成备用内容,当API调用失败时使用""" - self.logger.info("生成备用内容") - default_configs = [] - for i in range(poster_num): - default_configs.append({ - "main_title": f"景点风光 {i+1}", - "texts": ["自然美景", "人文体验"] - }) - return json.dumps(default_configs, ensure_ascii=False) - + """生成备用内容""" + return self._impl._generate_fallback_content(poster_num) + def save_result(self, full_response): - """ - 保存生成结果到文件 - - 参数: - full_response: 生成的完整响应内容 - - 返回: - 结果文件路径 - """ - # 生成时间戳 - print(full_response) - date_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - - try: - # 解析内容为JSON格式 - parsed_data = self.split_content(full_response) - - # 验证内容格式并修复 - validated_data = self._validate_and_fix_data(parsed_data) - - # 创建结果文件路径 - result_path = os.path.join(self.output_dir, f"{date_time}.json") - os.makedirs(os.path.dirname(result_path), exist_ok=True) - - # 保存结果到文件 - with open(result_path, "w", encoding="utf-8") as f: - json.dump(validated_data, f, ensure_ascii=False, indent=4) - - print(f"结果已保存到: {result_path}") - return result_path - - except Exception as e: - self.logger.error(f"保存结果到文件时出错: {e}") - # 尝试创建一个简单的备用配置 - fallback_data = [{"main_title": "景点风光", "texts": ["自然美景", "人文体验"], "index": 1}] - - # 保存备用数据 - result_path = os.path.join(self.output_dir, f"{date_time}_fallback.json") - os.makedirs(os.path.dirname(result_path), exist_ok=True) - - with open(result_path, "w", encoding="utf-8") as f: - json.dump(fallback_data, f, ensure_ascii=False, indent=4) - - print(f"出错后已保存备用数据到: {result_path}") - return result_path - + """保存生成结果到文件""" + return self._impl.save_result(full_response) + def _validate_and_fix_data(self, data): - """ - 验证并修复数据格式,确保符合预期结构 - - 参数: - data: 需要验证的数据 - - 返回: - 修复后的数据 - """ - fixed_data = [] - - # 如果数据是列表 - if isinstance(data, list): - for i, item in enumerate(data): - # 检查项目是否为字典 - if isinstance(item, dict): - # 确保必需字段存在 - fixed_item = { - "index": item.get("index", i + 1), - "main_title": item.get("main_title", f"景点风光 {i+1}"), - "texts": item.get("texts", ["自然美景", "人文体验"]) - } - - # 确保texts是列表格式 - if not isinstance(fixed_item["texts"], list): - if isinstance(fixed_item["texts"], str): - fixed_item["texts"] = [fixed_item["texts"], "美景体验"] - else: - fixed_item["texts"] = ["自然美景", "人文体验"] - - # 限制texts最多包含两个元素 - if len(fixed_item["texts"]) > 2: - fixed_item["texts"] = fixed_item["texts"][:2] - elif len(fixed_item["texts"]) < 2: - while len(fixed_item["texts"]) < 2: - fixed_item["texts"].append("美景体验") - - fixed_data.append(fixed_item) - - # 如果项目是字符串(可能是错误格式的texts值) - elif isinstance(item, str): - self.logger.warning(f"配置项 {i+1} 是字符串格式,将转换为标准格式") - fixed_item = { - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": [item, "美景体验"] - } - fixed_data.append(fixed_item) - else: - self.logger.warning(f"配置项 {i+1} 格式不支持: {type(item)},将使用默认值") - fixed_data.append({ - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": ["自然美景", "人文体验"] - }) - - # 如果数据是字典 - elif isinstance(data, dict): - fixed_item = { - "index": data.get("index", 1), - "main_title": data.get("main_title", "景点风光"), - "texts": data.get("texts", ["自然美景", "人文体验"]) - } - - # 确保texts是列表格式 - if not isinstance(fixed_item["texts"], list): - if isinstance(fixed_item["texts"], str): - fixed_item["texts"] = [fixed_item["texts"], "美景体验"] - else: - fixed_item["texts"] = ["自然美景", "人文体验"] - - # 限制texts最多包含两个元素 - if len(fixed_item["texts"]) > 2: - fixed_item["texts"] = fixed_item["texts"][:2] - elif len(fixed_item["texts"]) < 2: - while len(fixed_item["texts"]) < 2: - fixed_item["texts"].append("美景体验") - - fixed_data.append(fixed_item) - - # 如果数据是字符串或其他格式 - else: - self.logger.warning(f"数据格式不支持: {type(data)},将使用默认值") - fixed_data.append({ - "index": 1, - "main_title": "景点风光", - "texts": ["自然美景", "人文体验"] - }) - - # 确保至少有一个配置项 - if not fixed_data: - fixed_data.append({ - "index": 1, - "main_title": "景点风光", - "texts": ["自然美景", "人文体验"] - }) - - return fixed_data - + """验证并修复数据格式""" + return self._impl._validate_and_fix_data(data) + def run(self, info_directory, poster_num, tweet_content, system_prompt=None): - """ - 运行海报内容生成流程,并返回生成的配置数据。 - - 参数: - info_directory: 信息目录路径列表 (e.g., ['/path/to/description.txt']) - poster_num: 需要生成的海报配置数量 - tweet_content: 用于生成内容的推文/文章内容 - - 返回: - list | dict | None: 生成的海报配置数据 (通常是列表),如果生成或解析失败则返回 None。 - """ - self.load_infomation(info_directory) - - # Generate the raw string response from AI - full_response = self.generate_posters(poster_num, tweet_content, system_prompt) - - # Check if generation failed (indicated by return code 404 or other markers) - if full_response == 404 or not isinstance(full_response, str) or not full_response.strip(): - logging.error("Poster content generation failed or returned empty response.") - return None - - # Extract the JSON data from the raw response string - try: - result_data = self.split_content(full_response) # This should return the list/dict - - # 验证并修复结果数据格式 - fixed_data = [] - - # 如果结果是列表,检查每个项目 - if isinstance(result_data, list): - for i, item in enumerate(result_data): - # 如果项目是字典并且有required_fields,按原样添加或修复 - if isinstance(item, dict): - # 检查并确保必需字段存在 - if 'main_title' not in item: - item['main_title'] = f"景点标题 {i+1}" - logging.warning(f"配置项 {i+1} 缺少 main_title 字段,已添加默认值") - - if 'texts' not in item: - item['texts'] = ["景点特色", "游玩体验"] - logging.warning(f"配置项 {i+1} 缺少 texts 字段,已添加默认值") - - if 'index' not in item: - item['index'] = i + 1 - logging.warning(f"配置项 {i+1} 缺少 index 字段,已添加默认值") - - fixed_data.append(item) - # 如果项目是字符串(可能是错误格式的texts值) - elif isinstance(item, str): - logging.warning(f"配置项 {i+1} 是字符串格式,将转换为标准格式") - fixed_item = { - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": [item, "美景体验"] - } - fixed_data.append(fixed_item) - else: - logging.warning(f"配置项 {i+1} 格式不支持: {type(item)},将使用默认值") - fixed_data.append({ - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": ["自然美景", "人文体验"] - }) - - # 如果处理后的列表为空(极端情况),则使用默认值 - if not fixed_data: - logging.warning("处理后的配置列表为空,使用默认值") - for i in range(poster_num): - fixed_data.append({ - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": ["自然美景", "人文体验"] - }) - - logging.info(f"成功生成并修复海报配置数据,包含 {len(fixed_data)} 个项目") - return fixed_data - - # 如果结果是单个字典(不常见但可能),将其转换为列表 - elif isinstance(result_data, dict): - logging.warning(f"生成的配置数据是单个字典格式,将转换为列表") - - # 检查并确保必需字段存在 - if 'main_title' not in result_data: - result_data['main_title'] = "景点风光" - - if 'texts' not in result_data: - result_data['texts'] = ["自然美景", "人文体验"] - - if 'index' not in result_data: - result_data['index'] = 1 - - fixed_data = [result_data] - return fixed_data - - # 如果结果是其他格式(如字符串),创建默认配置 - else: - logging.warning(f"生成的配置数据格式不支持: {type(result_data)},将使用默认值") - for i in range(poster_num): - fixed_data.append({ - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": ["自然美景", "人文体验"] - }) - return fixed_data - - except Exception as e: - logging.exception(f"Failed to parse JSON from AI response in ContentGenerator: {e}\nRaw Response:\n{full_response[:500]}...") # Log error and partial response - - # 失败后创建一个默认配置 - logging.info("创建默认海报配置数据") - default_configs = [] - for i in range(poster_num): - default_configs.append({ - "index": i + 1, - "main_title": f"景点风光 {i+1}", - "texts": ["自然美景", "人文体验"] - }) - return default_configs - + """运行海报内容生成流程""" + return self._impl.run( + info_directory, + poster_num, + tweet_content, + system_prompt, + api_url=self.api_base_url, + model_name=self.model_name, + api_key=self.api_key + ) + def set_temperature(self, temperature): - self.temperature = temperature - + """设置温度参数""" + self._impl.set_temperature(temperature) + def set_top_p(self, top_p): - self.top_p = top_p - + """设置top_p参数""" + self._impl.set_top_p(top_p) + def set_presence_penalty(self, presence_penalty): - self.presence_penalty = presence_penalty + """设置存在惩罚参数""" + self._impl.set_presence_penalty(presence_penalty) def set_model_para(self, temperature, top_p, presence_penalty): - self.temperature = temperature - self.top_p = top_p - self.presence_penalty = presence_penalty - -# def main(): -# # 配置参数 -# info_directory = [ -# "/root/autodl-tmp/sanming_img/相机/甘露寺/description.txt" -# ] # 信息目录 -# poster_num = 4 # 海报数量 - -# # 推文内容 -# tweet_content = """ -# 🌿清明遛娃天花板!悬空古寺+非遗探秘 -# -# -# 清明假期带娃哪里玩?泰宁甘露寺藏着明代建筑奇迹!一柱擎天的悬空阁楼+状元祈福传说,让孩子边玩边涨知识✨ - -# 🎒行程亮点: -# ✅ 安全科普第一站:讲解"一柱插地"千年不倒的秘密,用乐高积木模型让孩子理解力学原理 -# ✅ 文化沉浸体验:穿汉服听"叶状元还愿建寺"故事,触摸3.38米粗的"状元柱"许愿 -# ✅ 自然探索路线:连接金湖栈道徒步,观察丹霞地貌与古建筑的巧妙融合 - -# 📌实用攻略: -# 📍位置:福建省三明市泰宁县金湖西路(导航搜"甘露岩寺") -# 🕒最佳时段:上午10点前抵达避开人流,下午可衔接参观明清园(80元/人) -# ⚠️注意事项:悬空栈道设置儿童安全绳租赁点,建议穿防滑鞋 - -# 💡亲子彩蛋: -# 1️⃣ 在"右鼓左钟"景观区玩声音实验,敲击不同岩石听回声差异 -# 2️⃣ 领取任务卡完成"寻找建筑中的T形拱"小游戏,集章兑换非遗木雕书签 -# 3️⃣ 结合清明节俗,用竹简模板书写祈福语系在古松枝头 - -# 周边推荐:游览完可直奔尚书第明代古民居群,对比不同时期建筑特色,晚餐推荐尝泰宁特色"灯盏糕",亲子套票更划算! - -# 清明带着孩子来场穿越850年的建筑探险,把课本里的力学知识变成触手可及的历史课堂!🌸 - -# #清明节周边游 #亲子科普游 #福建遛娃 #泰宁旅行攻略 #建筑启蒙 -# -# """ - -# # 创建海报生成器 -# generator = ContentGenerator() - -# # 运行生成流程 -# generator.run(info_directory, poster_num, tweet_content) - - -# if __name__ == "__main__": -# main() + """一次性设置所有模型参数""" + self._impl.set_model_para(temperature, top_p, presence_penalty) diff --git a/output/2025-04-24_19-44-15.json b/output/2025-04-24_19-44-15.json new file mode 100644 index 0000000..4368846 --- /dev/null +++ b/output/2025-04-24_19-44-15.json @@ -0,0 +1,26 @@ +[ + { + "index": 1, + "main_title": "泰宁甘露寺攻略", + "texts": [ + "悬空古建探秘", + "亲子研学首选" + ] + }, + { + "index": 2, + "main_title": "推荐!泰宁甘露寺", + "texts": [ + "千年古刹秘境", + "自驾打卡胜地" + ] + }, + { + "index": 3, + "main_title": "泰宁甘露寺", + "texts": [ + "非遗文化沉浸", + "状元祈福之旅" + ] + } +] \ No newline at end of file diff --git a/test_content_generator.py b/test_content_generator.py new file mode 100644 index 0000000..3d929fb --- /dev/null +++ b/test_content_generator.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# 导入新旧实现 +from core.contentGen import ContentGenerator as OldContentGenerator +from utils.content_generator import ContentGenerator as NewContentGenerator + +def test_both_implementations(): + """测试新旧实现的兼容性""" + # 创建测试文本内容 + test_content = """ + 🌿清明遛娃天花板!悬空古寺+非遗探秘 + + + 清明假期带娃哪里玩?泰宁甘露寺藏着明代建筑奇迹!一柱擎天的悬空阁楼+状元祈福传说,让孩子边玩边涨知识✨ + + 🎒行程亮点: + ✅ 安全科普第一站:讲解"一柱插地"千年不倒的秘密,用乐高积木模型让孩子理解力学原理 + ✅ 文化沉浸体验:穿汉服听"叶状元还愿建寺"故事,触摸3.38米粗的"状元柱"许愿 + + """ + + print("=" * 50) + print("测试新旧ContentGenerator实现") + print("=" * 50) + + # 创建输出目录 + import os + os.makedirs("./test_output", exist_ok=True) + + # 测试参数 + api_url = "http://localhost:8000/v1" # 替换为实际URL + model_name = "qwenQWQ" # 替换为实际模型 + api_key = "EMPTY" # 替换为实际密钥 + poster_num = 2 # 生成2个海报配置 + + # 1. 测试旧实现(现在委托给新实现) + print("\n1. 测试旧实现 (core.contentGen.ContentGenerator)") + old_generator = OldContentGenerator( + model_name=model_name, + api_base_url=api_url, + api_key=api_key, + output_dir="./test_output/old" + ) + + # 设置生成参数 + old_generator.set_model_para(0.7, 0.8, 1.2) + + # 运行生成 + print("正在使用旧实现生成海报配置...") + old_result = old_generator.run([], poster_num, test_content) + + if old_result: + print(f"旧实现成功生成 {len(old_result)} 个配置项") + for i, config in enumerate(old_result): + print(f" 配置 {i+1}: {config.get('main_title')} - {config.get('texts')}") + else: + print("旧实现生成失败") + + # 2. 测试新实现 + print("\n2. 测试新实现 (utils.content_generator.ContentGenerator)") + new_generator = NewContentGenerator( + output_dir="./test_output/new", + temperature=0.7, + top_p=0.8, + presence_penalty=1.2 + ) + + # 运行生成 + print("正在使用新实现生成海报配置...") + new_result = new_generator.run( + [], + poster_num, + test_content, + api_url=api_url, + model_name=model_name, + api_key=api_key + ) + + if new_result: + print(f"新实现成功生成 {len(new_result)} 个配置项") + for i, config in enumerate(new_result): + print(f" 配置 {i+1}: {config.get('main_title')} - {config.get('texts')}") + else: + print("新实现生成失败") + + print("\n3. 比较结果") + if old_result and new_result: + import json + + # 格式化输出结果比较 + print("\n旧实现结果:") + print(json.dumps(old_result, ensure_ascii=False, indent=2)) + + print("\n新实现结果:") + print(json.dumps(new_result, ensure_ascii=False, indent=2)) + + # 结构比较 + old_format = [type(config) for config in old_result] + new_format = [type(config) for config in new_result] + + print(f"\n结构比较: 旧实现: {old_format}, 新实现: {new_format}") + print(f"数量比较: 旧实现: {len(old_result)}, 新实现: {len(new_result)}") + + # 检查所有必要的字段 + field_check = True + for result in [old_result, new_result]: + for config in result: + if not all(key in config for key in ["index", "main_title", "texts"]): + field_check = False + break + + print(f"字段检查: {'通过' if field_check else '失败'}") + + print("\n测试完成!") + +if __name__ == "__main__": + test_both_implementations() \ No newline at end of file diff --git a/utils/__pycache__/content_generator.cpython-312.pyc b/utils/__pycache__/content_generator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4afd604ffa64ae8f5283d49a1684042bbbb2f7e4 GIT binary patch literal 23116 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 CtMGLI diff --git a/utils/content_generator.py b/utils/content_generator.py new file mode 100644 index 0000000..530c96b --- /dev/null +++ b/utils/content_generator.py @@ -0,0 +1,529 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import json +import logging +import traceback +from datetime import datetime +import sys +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from core.ai_agent import AI_Agent + +class ContentGenerator: + """ + 海报文本内容生成器 + 使用AI_Agent代替直接管理OpenAI客户端,简化代码结构 + """ + def __init__(self, + output_dir="/root/autodl-tmp/poster_generate_result", + temperature=0.7, + top_p=0.8, + presence_penalty=1.2): + """ + 初始化内容生成器 + + 参数: + output_dir: 输出结果保存目录 + temperature: 生成温度参数 + top_p: top_p参数 + presence_penalty: 惩罚参数 + """ + self.output_dir = output_dir + self.temperature = temperature + self.top_p = top_p + self.presence_penalty = presence_penalty + self.add_description = "" + + # 设置日志 + logging.basicConfig(level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + self.logger = logging.getLogger(__name__) + + def load_infomation(self, info_directory_path): + """ + 加载额外描述文件 + + 参数: + info_directory_path: 信息目录路径列表 + """ + self.add_description = "" # 重置描述文本 + for path in info_directory_path: + try: + with open(path, "r", encoding="utf-8") as f: + self.add_description += f.read() + self.logger.info(f"成功加载描述文件: {path}") + except Exception as e: + self.logger.warning(f"加载描述文件失败: {path}, 错误: {e}") + self.add_description = "" + + def split_content(self, content): + """ + 分割结果, 返回去除 + ```json + ```的json内容 + + 参数: + content: 需要分割的内容 + + 返回: + 分割后的json内容 + """ + try: + # 首先尝试直接解析整个内容,以防已经是干净的 JSON + try: + return json.loads(content) + except json.JSONDecodeError: + pass # 不是干净的 JSON,继续处理 + + # 常规模式:查找 ```json 和 ``` 之间的内容 + if "```json" in content: + json_str = content.split("```json")[1].split("```")[0].strip() + try: + return json.loads(json_str) + except json.JSONDecodeError as e: + self.logger.warning(f"常规格式解析失败: {e}, 尝试其他方法") + + # 备用模式1:查找连续的 [ 开头和 ] 结尾的部分 + import re + json_pattern = r'(\[.*?\])' + json_matches = re.findall(json_pattern, content, re.DOTALL) + if json_matches: + for match in json_matches: + try: + result = json.loads(match) + if isinstance(result, list) and len(result) > 0: + return result + except: + continue + + # 备用模式2:查找 [ 开头 和 ] 结尾,并尝试解析 + content = content.strip() + square_bracket_start = content.find('[') + square_bracket_end = content.rfind(']') + + if square_bracket_start != -1 and square_bracket_end != -1: + potential_json = content[square_bracket_start:square_bracket_end + 1] + try: + return json.loads(potential_json) + except: + self.logger.warning("尝试提取方括号内容失败") + + # 最后一种尝试:查找所有可能的 JSON 结构并尝试解析 + json_structures = re.findall(r'({.*?})', content, re.DOTALL) + if json_structures: + items = [] + for i, struct in enumerate(json_structures): + try: + item = json.loads(struct) + # 验证结构包含预期字段 + if 'main_title' in item and ('texts' in item or 'index' in item): + items.append(item) + except: + continue + + if items: + return items + + # 都失败了,打印错误并引发异常 + self.logger.error(f"无法解析内容,返回原始文本: {content[:200]}...") + raise ValueError("无法从响应中提取有效的 JSON 格式") + + except Exception as e: + self.logger.error(f"解析内容时出错: {e}") + self.logger.debug(f"原始内容: {content[:200]}...") # 仅显示前200个字符 + raise e + + def generate_posters(self, + poster_num, + content_data_list, + system_prompt=None, + api_url="http://localhost:8000/v1", + model_name="qwenQWQ", + api_key="EMPTY", + timeout=60, + max_retries=3): + """ + 生成海报内容 + + 参数: + poster_num: 海报数量 + content_data_list: 内容数据列表(字典或字符串) + system_prompt: 系统提示,默认为None则使用预设提示 + api_url: API基础URL + model_name: 使用的模型名称 + api_key: API密钥 + timeout: 请求超时时间 + max_retries: 最大重试次数 + + 返回: + 生成的海报内容 + """ + # 构建默认系统提示词 + if not system_prompt: + system_prompt = """ + 你是一名资深海报设计师,有丰富的爆款海报设计经验,你现在要为旅游景点做宣传,在小红书上发布大量宣传海报。你的主要工作目标有2个: + 1、你要根据我给你的图片描述和笔记推文内容,设计图文匹配的海报。 + 2、为海报设计文案,文案的<第一个小标题>和<第二个小标题>之间你需要检查是否逻辑关系合理,你将通过先去生成<第二个小标题>关于景区亮点的部分,再去综合判断<第一个小标题>应该如何搭配组合更符合两个小标题的逻辑再生成<第一个小标题>。 + + 其中,生成三类标题文案的通用性要求如下: + 1、生成的<大标题>字数必须小于8个字符 + 2、生成的<第一个小标题>字数和<第二个小标题>字数,两者都必须小8个字符 + 3、标题和文案都应符合中国社会主义核心价值观 + + 接下来先开始生成<大标题>部分,由于海报是用来宣传旅游景点,生成的海报<大标题>必须使用以下8种格式之一: + ①地名+景点名(例如福建厦门鼓浪屿/厦门鼓浪屿); + ②地名+景点名+plog; + ③拿捏+地名+景点名; + ④地名+景点名+攻略; + ⑤速通+地名+景点名 + ⑥推荐!+地名+景点名 + ⑦勇闯!+地名+景点名 + ⑧收藏!+地名+景点名 + 你需要随机挑选一种格式生成对应景点的文案,但是格式除了上面8种不可以有其他任何格式;同时尽量保证每一种格式出现的频率均衡。 + 接下来先去生成<第二个小标题>,<第二个小标题>文案的创作必须遵循以下原则: + 请根据笔记内容和图片识别,用极简的文字概括这篇笔记和图片中景点的特色亮点,其中你可以参考以下词汇进行创作,这段文案字数控制6-8字符以内; + + 特色亮点可能会出现的词汇不完全举例:非遗、古建、绝佳山水、祈福圣地、研学圣地、解压天堂、中国小瑞士、秘境竹筏游等等类型词汇 + + 接下来再去生成<第一个小标题>,<第一个小标题>文案的创作必须遵循以下原则: + 这部分文案创作公式有5种,分别为: + ①<受众人群画像>+<痛点词> + ②<受众人群画像> + ③<痛点词> + ④<受众人群画像>+ | +<痛点词> + ⑤<痛点词>+ | +<受众人群画像> + 请你根据实际笔记内容,结合这部分文案创作公式,需要结合<受众人群画像>和<痛点词>时,必须根据<第二个小标题>的景点特征和所对应的完整笔记推文内容主旨,特征挑选对应<受众人群画像>和<痛点词>。 + + 我给你提供受众人群画像库和痛点词库如下: + 1、受众人群画像库:情侣党、亲子游、合家游、银发族、亲子研学、学生党、打工人、周边游、本地人、穷游党、性价比、户外人、美食党、出片 + 2、痛点词库:3天2夜、必去、看了都哭了、不能错过、一定要来、问爆了、超全攻略、必打卡、强推、懒人攻略、必游榜、小众打卡、狂喜等等。 + + 你需要为每个请求至少生成{poster_num}个海报设计。请使用JSON格式输出结果,结构如下: + [ + { + "index": 1, + "main_title": "主标题内容", + "texts": ["第一个小标题", "第二个小标题"] + }, + { + "index": 2, + "main_title": "主标题内容", + "texts": ["第一个小标题", "第二个小标题"] + }, + // ... 更多海报 + ] + 确保生成的数量与用户要求的数量一致。只生成上述JSON格式内容,不要有其他任何额外内容。 + """ + + # 提取内容文本(如果是列表内容数据) + tweet_content = "" + if isinstance(content_data_list, list): + for item in content_data_list: + if isinstance(item, dict): + title = item.get('title', '') + content = item.get('content', '') + tweet_content += f"\n{title}\n\n\n{content}\n\n\n" + elif isinstance(item, str): + tweet_content += item + "\n\n" + elif isinstance(content_data_list, str): + tweet_content = content_data_list + + # 构建用户提示 + if self.add_description: + user_content = f""" + 以下是需要你处理的信息: + + 关于景点的描述: + {self.add_description} + + 推文内容: + {tweet_content} + + 请根据这些信息,生成{poster_num}个海报文案配置,以JSON数组格式返回。 + """ + else: + user_content = f""" + 以下是需要你处理的推文内容: + {tweet_content} + + 请根据这些信息,生成{poster_num}个海报文案配置,以JSON数组格式返回。 + """ + + self.logger.info(f"正在生成{poster_num}个海报文案配置") + + # 创建AI_Agent实例 + ai_agent = AI_Agent( + api_url, + model_name, + api_key, + timeout=timeout, + max_retries=max_retries, + stream_chunk_timeout=30 # 流式块超时时间 + ) + + full_response = "" + try: + # 使用AI_Agent的non-streaming方法 + self.logger.info(f"调用AI生成海报配置,模型: {model_name}") + full_response, tokens, time_cost = ai_agent.work( + system_prompt, + user_content, + "", # 历史消息(空) + self.temperature, + self.top_p, + self.presence_penalty + ) + + self.logger.info(f"AI生成完成,耗时: {time_cost:.2f}s, 预估令牌数: {tokens}") + + if not full_response: + self.logger.warning("AI返回空响应,使用备用内容") + full_response = self._generate_fallback_content(poster_num) + except Exception as e: + self.logger.exception(f"AI生成过程发生错误: {e}") + full_response = self._generate_fallback_content(poster_num) + finally: + # 确保关闭AI Agent + ai_agent.close() + + return full_response + + def _generate_fallback_content(self, poster_num): + """生成备用内容,当API调用失败时使用""" + self.logger.info("生成备用内容") + default_configs = [] + for i in range(poster_num): + default_configs.append({ + "index": i + 1, + "main_title": f"景点风光 {i+1}", + "texts": ["自然美景", "人文体验"] + }) + return json.dumps(default_configs, ensure_ascii=False) + + def save_result(self, full_response, custom_output_dir=None): + """ + 保存生成结果到文件 + + 参数: + full_response: 生成的完整响应内容 + custom_output_dir: 自定义输出目录(可选) + + 返回: + 结果文件路径 + """ + # 生成时间戳 + date_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + output_dir = custom_output_dir or self.output_dir + + try: + # 解析内容为JSON格式 + parsed_data = self.split_content(full_response) + + # 验证内容格式并修复 + validated_data = self._validate_and_fix_data(parsed_data) + + # 创建结果文件路径 + result_path = os.path.join(output_dir, f"{date_time}.json") + os.makedirs(os.path.dirname(result_path), exist_ok=True) + + # 保存结果到文件 + with open(result_path, "w", encoding="utf-8") as f: + json.dump(validated_data, f, ensure_ascii=False, indent=4) + + self.logger.info(f"结果已保存到: {result_path}") + return result_path + + except Exception as e: + self.logger.error(f"保存结果到文件时出错: {e}") + # 尝试创建一个简单的备用配置 + fallback_data = [{"main_title": "景点风光", "texts": ["自然美景", "人文体验"], "index": 1}] + + # 保存备用数据 + result_path = os.path.join(output_dir, f"{date_time}_fallback.json") + os.makedirs(os.path.dirname(result_path), exist_ok=True) + + with open(result_path, "w", encoding="utf-8") as f: + json.dump(fallback_data, f, ensure_ascii=False, indent=4) + + self.logger.info(f"出错后已保存备用数据到: {result_path}") + return result_path + + def _validate_and_fix_data(self, data): + """ + 验证并修复数据格式,确保符合预期结构 + + 参数: + data: 需要验证的数据 + + 返回: + 修复后的数据 + """ + fixed_data = [] + + # 如果数据是列表 + if isinstance(data, list): + for i, item in enumerate(data): + # 检查项目是否为字典 + if isinstance(item, dict): + # 确保必需字段存在 + fixed_item = { + "index": item.get("index", i + 1), + "main_title": item.get("main_title", f"景点风光 {i+1}"), + "texts": item.get("texts", ["自然美景", "人文体验"]) + } + + # 确保texts是列表格式 + if not isinstance(fixed_item["texts"], list): + if isinstance(fixed_item["texts"], str): + fixed_item["texts"] = [fixed_item["texts"], "美景体验"] + else: + fixed_item["texts"] = ["自然美景", "人文体验"] + + # 限制texts最多包含两个元素 + if len(fixed_item["texts"]) > 2: + fixed_item["texts"] = fixed_item["texts"][:2] + elif len(fixed_item["texts"]) < 2: + while len(fixed_item["texts"]) < 2: + fixed_item["texts"].append("美景体验") + + fixed_data.append(fixed_item) + + # 如果项目是字符串(可能是错误格式的texts值) + elif isinstance(item, str): + self.logger.warning(f"配置项 {i+1} 是字符串格式,将转换为标准格式") + fixed_item = { + "index": i + 1, + "main_title": f"景点风光 {i+1}", + "texts": [item, "美景体验"] + } + fixed_data.append(fixed_item) + else: + self.logger.warning(f"配置项 {i+1} 格式不支持: {type(item)},将使用默认值") + fixed_data.append({ + "index": i + 1, + "main_title": f"景点风光 {i+1}", + "texts": ["自然美景", "人文体验"] + }) + + # 如果数据是字典 + elif isinstance(data, dict): + fixed_item = { + "index": data.get("index", 1), + "main_title": data.get("main_title", "景点风光"), + "texts": data.get("texts", ["自然美景", "人文体验"]) + } + + # 确保texts是列表格式 + if not isinstance(fixed_item["texts"], list): + if isinstance(fixed_item["texts"], str): + fixed_item["texts"] = [fixed_item["texts"], "美景体验"] + else: + fixed_item["texts"] = ["自然美景", "人文体验"] + + # 限制texts最多包含两个元素 + if len(fixed_item["texts"]) > 2: + fixed_item["texts"] = fixed_item["texts"][:2] + elif len(fixed_item["texts"]) < 2: + while len(fixed_item["texts"]) < 2: + fixed_item["texts"].append("美景体验") + + fixed_data.append(fixed_item) + + # 如果数据是字符串或其他格式 + else: + self.logger.warning(f"数据格式不支持: {type(data)},将使用默认值") + fixed_data.append({ + "index": 1, + "main_title": "景点风光", + "texts": ["自然美景", "人文体验"] + }) + + # 确保至少有一个配置项 + if not fixed_data: + fixed_data.append({ + "index": 1, + "main_title": "景点风光", + "texts": ["自然美景", "人文体验"] + }) + + return fixed_data + + def run(self, info_directory, poster_num, content_data, system_prompt=None, + api_url="http://localhost:8000/v1", model_name="qwenQWQ", api_key="EMPTY"): + """ + 运行海报内容生成流程,并返回生成的配置数据。 + + 参数: + info_directory: 信息目录路径列表 (e.g., ['/path/to/description.txt']) + poster_num: 需要生成的海报配置数量 + content_data: 用于生成内容的文章内容(可以是字符串或字典列表) + system_prompt: 系统提示词,默认为None使用内置提示词 + api_url: API基础URL + model_name: 使用的模型名称 + api_key: API密钥 + + 返回: + list | dict | None: 生成的海报配置数据 (通常是列表),如果生成或解析失败则返回 None。 + """ + try: + # 加载描述信息 + self.load_infomation(info_directory) + + # 生成海报内容 + self.logger.info(f"开始生成海报内容,数量: {poster_num}") + full_response = self.generate_posters( + poster_num, + content_data, + system_prompt, + api_url, + model_name, + api_key + ) + + # 检查生成是否失败 + if not isinstance(full_response, str) or not full_response.strip(): + self.logger.error("海报内容生成失败或返回空响应") + return None + + # 从原始响应字符串中提取JSON数据 + result_data = self.split_content(full_response) + + # 验证并修复数据 + fixed_data = self._validate_and_fix_data(result_data) + + self.logger.info(f"成功生成并修复海报配置数据,包含 {len(fixed_data)} 个项目") + return fixed_data + + except Exception as e: + self.logger.exception(f"海报内容生成过程中发生错误: {e}") + traceback.print_exc() + + # 失败后创建一个默认配置 + self.logger.info("创建默认海报配置数据") + default_configs = [] + for i in range(poster_num): + default_configs.append({ + "index": i + 1, + "main_title": f"景点风光 {i+1}", + "texts": ["自然美景", "人文体验"] + }) + return default_configs + + def set_temperature(self, temperature): + """设置温度参数""" + self.temperature = temperature + + def set_top_p(self, top_p): + """设置top_p参数""" + self.top_p = top_p + + def set_presence_penalty(self, presence_penalty): + """设置存在惩罚参数""" + self.presence_penalty = presence_penalty + + def set_model_para(self, temperature, top_p, presence_penalty): + """一次性设置所有模型参数""" + self.temperature = temperature + self.top_p = top_p + self.presence_penalty = presence_penalty \ No newline at end of file