TravelContentCreator/utils/prompt_manager.py

306 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Manages the construction of prompts for different AI generation tasks.
"""
import os
import traceback
import logging # Add logging
from .resource_loader import ResourceLoader # Use relative import within the same package
class PromptManager:
"""Handles the loading and construction of prompts."""
def __init__(self,
topic_system_prompt_path: str,
topic_user_prompt_path: str,
content_system_prompt_path: str,
prompts_dir: str,
resource_dir_config: list,
topic_gen_num: int = 1, # Default values if needed
topic_gen_date: str = ""
):
self.topic_system_prompt_path = topic_system_prompt_path
self.topic_user_prompt_path = topic_user_prompt_path
self.content_system_prompt_path = content_system_prompt_path
self.prompts_dir = prompts_dir
self.resource_dir_config = resource_dir_config
self.topic_gen_num = topic_gen_num
self.topic_gen_date = topic_gen_date
def get_topic_prompts(self):
"""Constructs the system and user prompts for topic generation."""
logging.info("Constructing prompts for topic generation...")
try:
# --- System Prompt ---
if not self.topic_system_prompt_path:
logging.error("Topic system prompt path not provided during PromptManager initialization.")
return None, None
system_prompt = ResourceLoader.load_file_content(self.topic_system_prompt_path)
if not system_prompt:
logging.error(f"Failed to load topic system prompt from '{self.topic_system_prompt_path}'.")
return None, None
# --- User Prompt ---
if not self.topic_user_prompt_path:
logging.error("Topic user prompt path not provided during PromptManager initialization.")
return None, None
base_user_prompt = ResourceLoader.load_file_content(self.topic_user_prompt_path)
if base_user_prompt is None:
logging.error(f"Failed to load base topic user prompt from '{self.topic_user_prompt_path}'.")
return None, None
# --- Build the dynamic part of the user prompt (Logic moved from prepare_topic_generation) ---
user_prompt_dynamic = "你拥有的创作资料如下:\n"
# Add genPrompts directory structure
if self.prompts_dir and os.path.isdir(self.prompts_dir):
try:
gen_prompts_list = os.listdir(self.prompts_dir)
for gen_prompt_folder in gen_prompts_list:
folder_path = os.path.join(self.prompts_dir, gen_prompt_folder)
if os.path.isdir(folder_path):
try:
# List files, filter out subdirs if needed
gen_prompts_files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
user_prompt_dynamic += f"{gen_prompt_folder}\n{gen_prompts_files}\n"
except OSError as e:
logging.warning(f"Could not list directory {folder_path}: {e}")
except OSError as e:
logging.warning(f"Could not list base prompts directory {self.prompts_dir}: {e}")
else:
logging.warning(f"Prompts directory '{self.prompts_dir}' not found or invalid.")
# Add resource directory contents
for dir_info in self.resource_dir_config:
source_type = dir_info.get("type", "UnknownType")
source_file_paths = dir_info.get("file_path", [])
for file_path in source_file_paths:
# Use ResourceLoader's static method
file_content = ResourceLoader.load_file_content(file_path)
if file_content:
user_prompt_dynamic += f"{source_type}信息:\n{os.path.basename(file_path)}\n{file_content}\n\n"
else:
logging.warning(f"Could not load resource file {file_path}")
# Add dateline information (optional)
user_prompt_dir = os.path.dirname(self.topic_user_prompt_path)
dateline_path = os.path.join(user_prompt_dir, "2025各月节日宣传节点时间表.md") # Consider making this configurable
if os.path.exists(dateline_path):
dateline_content = ResourceLoader.load_file_content(dateline_path)
if dateline_content:
user_prompt_dynamic += f"\n{dateline_content}"
# Combine dynamic part, base template, and final parameters
user_prompt = user_prompt_dynamic + base_user_prompt
user_prompt += f"\n选题数量:{self.topic_gen_num}\n选题日期:{self.topic_gen_date}\n"
# --- End of moved logic ---
logging.info(f"Topic prompts constructed. System: {len(system_prompt)} chars, User: {len(user_prompt)} chars.")
return system_prompt, user_prompt
except Exception as e:
logging.exception("Error constructing topic prompts:")
return None, None
def get_content_prompts(self, topic_item):
"""Constructs the system and user prompts for content generation based on a topic item."""
logging.info(f"Constructing content prompts for topic: {topic_item.get('object', 'N/A')}...")
try:
# --- System Prompt ---
if not self.content_system_prompt_path:
logging.error("Content system prompt path not provided during PromptManager initialization.")
return None, None
system_prompt = ResourceLoader.load_file_content(self.content_system_prompt_path)
if not system_prompt:
logging.error(f"Failed to load content system prompt from '{self.content_system_prompt_path}'.")
return None, None
# --- User Prompt (Logic moved from ResourceLoader.build_user_prompt) ---
user_prompt = ""
prompts_dir = self.prompts_dir
resource_dir_config = self.resource_dir_config
if not prompts_dir or not os.path.isdir(prompts_dir):
logging.warning(f"Prompts directory '{prompts_dir}' not found or invalid. Content user prompt might be incomplete.")
# Decide whether to return error or continue with potentially incomplete prompt
# 1. 添加Demand部分 (直接使用 topic_item['logic'] 的描述性文本)
try:
demand_description = topic_item.get('logic')
if demand_description:
user_prompt += f"Demand Logic:\n{demand_description}\n"
else:
logging.warning("Warning: 'logic' key missing or empty in topic_item for Demand prompt.")
except Exception as e:
logging.exception("Error processing Demand description:")
# 2. Object Info - 先列出所有可用文件,再注入匹配文件的内容
try:
object_name_from_topic = topic_item.get('object') # e.g., "尚书第建筑群"
object_file_basenames = []
matched_object_file_path = None
matched_object_basename = None
# 遍历查找 Object 文件
for dir_info in resource_dir_config:
if dir_info.get("type") == "Object":
for file_path in dir_info.get("file_path", []):
basename = os.path.basename(file_path)
object_file_basenames.append(basename)
# 尝试匹配当前 topic 的 object (仅当尚未找到匹配时)
if object_name_from_topic and not matched_object_file_path:
cleaned_resource_name = basename
if cleaned_resource_name.startswith("景点信息-"):
cleaned_resource_name = cleaned_resource_name[len("景点信息-"):]
if cleaned_resource_name.endswith(".txt"):
cleaned_resource_name = cleaned_resource_name[:-len(".txt")]
if cleaned_resource_name and cleaned_resource_name in object_name_from_topic:
matched_object_file_path = file_path
matched_object_basename = basename
# 注意:这里不 break继续收集所有文件名
# 构建提示词 - Part 1: 文件列表
if object_file_basenames:
user_prompt += "Object信息:\n"
# user_prompt += f"{object_file_basenames}\n\n" # 直接打印列表可能不够清晰
for fname in object_file_basenames:
user_prompt += f"- {fname}\n"
user_prompt += "\n" # 加一个空行
logging.info(f"Listed {len(object_file_basenames)} available object files.")
else:
logging.warning("No resource directory entry found with type 'Object', or it has no file paths.")
# 构建提示词 - Part 2: 注入匹配文件内容
if matched_object_file_path:
logging.info(f"Attempting to load content for matched object file: {matched_object_basename}")
matched_object_content = ResourceLoader.load_file_content(matched_object_file_path)
if matched_object_content:
user_prompt += f"{matched_object_basename}\n{matched_object_content}\n\n"
logging.info(f"Successfully loaded and injected content for: {matched_object_basename}")
else:
logging.warning(f"Object file matched ({matched_object_basename}) but could not be loaded or is empty.")
elif object_name_from_topic: # 只有当 topic 中指定了 object 但没找到匹配文件时才警告
logging.warning(f"Could not find a matching Object resource file to inject content for '{object_name_from_topic}'. Only the list of files was provided.")
except KeyError:
logging.warning("Warning: 'object' key potentially missing in topic_item.")
except Exception as e:
logging.exception("Error processing Object prompt section:")
# 3. 添加Product信息 (if applicable)
try:
product_name = topic_item.get('product')
product_logic_description = topic_item.get('product_logic') # Directly use this description
if product_name:
# Add Product Logic description first (if available)
if product_logic_description:
user_prompt += f"Product Logic:\n{product_logic_description}\n"
else:
logging.warning(f"Warning: 'product_logic' key missing or empty for product '{product_name}'.")
# Then, load Product Info file
product_file_path = None
for dir_info in resource_dir_config:
if dir_info.get("type") == "Product":
for file_path in dir_info.get("file_path", []):
if product_name in os.path.basename(file_path):
product_file_path = file_path
break
if product_file_path: break
if product_file_path:
product_content = ResourceLoader.load_file_content(product_file_path)
if product_content:
user_prompt += f"Product Info:\n{product_content}\n"
else:
logging.warning(f"Product file could not be loaded: {product_file_path}")
else:
logging.warning(f"Product file path not found in config for: {product_name}")
# Removed KeyError check for product_logic as it's now optional text
except KeyError:
logging.warning("Warning: Missing 'product' key in topic_item for Product prompt.")
except Exception as e:
logging.exception("Error processing Product prompt:")
# 4. 添加Style信息 (加载文件 based on topic_item['style'] from Style/ folder)
try:
style_filename = topic_item.get('style')
if style_filename:
# Ensure .txt extension
if not style_filename.lower().endswith('.txt'):
style_file = f"{style_filename}.txt"
else:
style_file = style_filename
style_path = os.path.join(prompts_dir, "Style", style_file) # Look in Style/ subfolder
style_content = ResourceLoader.load_file_content(style_path)
if style_content:
user_prompt += f"Style Info:\n{style_content}\n" # Changed label for clarity
else:
logging.warning(f"Style file not found or empty: {style_path}")
else:
logging.warning("Warning: 'style' key missing or empty in topic_item.")
except Exception as e:
logging.exception("Error processing Style prompt:")
# 5. 添加Target Audience信息 (加载文件 based on topic_item['target_audience'] from Demand/ folder)
try:
target_audience_filename = topic_item.get('target_audience')
if target_audience_filename:
# Ensure .txt extension (or use the check as done for style)
if not target_audience_filename.lower().endswith('.txt'):
target_audience_file = f"{target_audience_filename}.txt"
else:
target_audience_file = target_audience_filename
# Assuming target audience files are in the 'Demand' subdirectory based on old prompt
# Load from Demand/ subfolder as confirmed
target_audience_path = os.path.join(prompts_dir, "Demand", target_audience_file)
target_audience_content = ResourceLoader.load_file_content(target_audience_path)
if target_audience_content:
user_prompt += f"Target Audience Info:\n{target_audience_content}\n"
else:
logging.warning(f"Target Audience file not found or empty: {target_audience_path}")
else:
logging.warning("Warning: 'target_audience' key missing or empty in topic_item.")
except Exception as e:
logging.exception("Error processing Target Audience prompt:")
# 6. 添加Refer信息 (加载 Refer/ 目录下所有文件的内容)
try:
refer_dir = os.path.join(prompts_dir, "Refer")
if os.path.isdir(refer_dir):
refer_content_all = ""
refer_files = [f for f in os.listdir(refer_dir) if os.path.isfile(os.path.join(refer_dir, f))]
logging.info(f"Found {len(refer_files)} files in Refer directory: {refer_files}")
for refer_file in refer_files:
refer_path = os.path.join(refer_dir, refer_file)
content = ResourceLoader.load_file_content(refer_path)
if content:
refer_content_all += f"--- Refer File: {refer_file} ---\n{content}\n\n"
else:
logging.warning(f"Could not load Refer file: {refer_path}")
if refer_content_all:
user_prompt += f"Refer Info:\n{refer_content_all}"
else:
logging.warning("No content loaded from Refer directory.")
else:
logging.warning(f"Refer directory not found: {refer_dir}")
except Exception as e:
logging.exception("Error processing Refer directory:")
# --- End of prompt construction logic ---
logging.info(f"Content prompts constructed. System: {len(system_prompt)} chars, User: {len(user_prompt)} chars.")
return system_prompt, user_prompt
except KeyError as e:
# Catch potential KeyErrors from accessing topic_item if a required key is missing early on
logging.error(f"Error constructing content prompts: Missing essential key '{e}' in topic_item: {topic_item}")
return None, None
except Exception as e:
logging.exception("Error constructing content prompts:")
return None, None