完成对生成逻辑的优化,但也发现新的问题
前后章节的衔接有问题,这里想着应该要把前一个章节内容发送作为参考,不然中断感很重
This commit is contained in:
@@ -21,13 +21,19 @@ def parse_chapter_blueprint(blueprint_text: str):
|
|||||||
chunks = re.split(r'\n\s*\n', blueprint_text.strip())
|
chunks = re.split(r'\n\s*\n', blueprint_text.strip())
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
chapter_number_pattern = re.compile(r'^第\s*(\d+)\s*章\s*-\s*\[(.*?)\]') # 捕获章号与标题
|
# 兼容是否使用方括号包裹章节标题
|
||||||
role_pattern = re.compile(r'^本章定位:\s*(.*)$')
|
# 例如:
|
||||||
purpose_pattern = re.compile(r'^核心作用:\s*(.*)$')
|
# 第1章 - 紫极光下的预兆
|
||||||
suspense_pattern = re.compile(r'^悬念密度:\s*(.*)$')
|
# 或
|
||||||
foreshadow_pattern = re.compile(r'^伏笔操作:\s*(.*)$')
|
# 第1章 - [紫极光下的预兆]
|
||||||
twist_pattern = re.compile(r'^认知颠覆:\s*(.*)$')
|
chapter_number_pattern = re.compile(r'^第\s*(\d+)\s*章\s*-\s*\[?(.*?)\]?$')
|
||||||
summary_pattern = re.compile(r'^本章简述:\s*\[(.*)\]$')
|
|
||||||
|
role_pattern = re.compile(r'^本章定位:\s*\[?(.*)\]?$')
|
||||||
|
purpose_pattern = re.compile(r'^核心作用:\s*\[?(.*)\]?$')
|
||||||
|
suspense_pattern = re.compile(r'^悬念密度:\s*\[?(.*)\]?$')
|
||||||
|
foreshadow_pattern = re.compile(r'^伏笔操作:\s*\[?(.*)\]?$')
|
||||||
|
twist_pattern = re.compile(r'^认知颠覆:\s*\[?(.*)\]?$')
|
||||||
|
summary_pattern = re.compile(r'^本章简述:\s*\[?(.*)\]?$')
|
||||||
|
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
lines = chunk.strip().splitlines()
|
lines = chunk.strip().splitlines()
|
||||||
@@ -44,9 +50,9 @@ def parse_chapter_blueprint(blueprint_text: str):
|
|||||||
chapter_summary = ""
|
chapter_summary = ""
|
||||||
|
|
||||||
# 先匹配第一行(或前几行),找到章号和标题
|
# 先匹配第一行(或前几行),找到章号和标题
|
||||||
header_match = chapter_number_pattern.match(lines[0].strip()) if lines else None
|
header_match = chapter_number_pattern.match(lines[0].strip())
|
||||||
if not header_match:
|
if not header_match:
|
||||||
# 不符合格式,跳过
|
# 不符合“第X章 - 标题”的格式,跳过
|
||||||
continue
|
continue
|
||||||
|
|
||||||
chapter_number = int(header_match.group(1))
|
chapter_number = int(header_match.group(1))
|
||||||
|
|||||||
+34
-39
@@ -431,8 +431,6 @@ def Chapter_blueprint_generate(
|
|||||||
return
|
return
|
||||||
|
|
||||||
# 从内容中尽量提取 number_of_chapters
|
# 从内容中尽量提取 number_of_chapters
|
||||||
# 如果之前已经存储了 number_of_chapters,可以在外面传入,这里做简化:
|
|
||||||
# 这里用正则或者其他逻辑提取,但演示时直接写 10 也可
|
|
||||||
match_chaps = re.search(r'约(\d+)章', architecture_text)
|
match_chaps = re.search(r'约(\d+)章', architecture_text)
|
||||||
if match_chaps:
|
if match_chaps:
|
||||||
number_of_chapters = int(match_chaps.group(1))
|
number_of_chapters = int(match_chaps.group(1))
|
||||||
@@ -440,10 +438,7 @@ def Chapter_blueprint_generate(
|
|||||||
number_of_chapters = 10 # fallback
|
number_of_chapters = 10 # fallback
|
||||||
|
|
||||||
# 提取三幕式文本
|
# 提取三幕式文本
|
||||||
# 在写入时,我们将 4) 三幕式情节架构 作为传给 prompt 的核心
|
|
||||||
# 这里做一个简易匹配
|
|
||||||
plot_arch_text = ""
|
plot_arch_text = ""
|
||||||
# 假设 "#=== 4) 三幕式情节架构 ===" 是分隔点
|
|
||||||
pat_plot = r'#=== 4\) 三幕式情节架构 ===\n([\s\S]+)$'
|
pat_plot = r'#=== 4\) 三幕式情节架构 ===\n([\s\S]+)$'
|
||||||
m = re.search(pat_plot, architecture_text)
|
m = re.search(pat_plot, architecture_text)
|
||||||
if m:
|
if m:
|
||||||
@@ -556,7 +551,6 @@ def update_plot_arcs(
|
|||||||
|
|
||||||
|
|
||||||
# ========== 3) 生成章节草稿 ==========
|
# ========== 3) 生成章节草稿 ==========
|
||||||
|
|
||||||
def generate_chapter_draft(
|
def generate_chapter_draft(
|
||||||
api_key: str,
|
api_key: str,
|
||||||
base_url: str,
|
base_url: str,
|
||||||
@@ -570,6 +564,10 @@ def generate_chapter_draft(
|
|||||||
key_items: str,
|
key_items: str,
|
||||||
scene_location: str,
|
scene_location: str,
|
||||||
time_constraint: str,
|
time_constraint: str,
|
||||||
|
embedding_api_key: str,
|
||||||
|
embedding_url: str,
|
||||||
|
embedding_interface_format: str,
|
||||||
|
embedding_model_name: str,
|
||||||
embedding_retrieval_k: int = 2
|
embedding_retrieval_k: int = 2
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -577,7 +575,7 @@ def generate_chapter_draft(
|
|||||||
- novel_architecture 取自 Novel_architecture.txt
|
- novel_architecture 取自 Novel_architecture.txt
|
||||||
- blueprint 取自 Novel_directory.txt
|
- blueprint 取自 Novel_directory.txt
|
||||||
- global_summary, character_state 分别取自全局摘要、角色状态文件
|
- global_summary, character_state 分别取自全局摘要、角色状态文件
|
||||||
- 向量库检索上下文
|
- 从向量库检索上下文(embedding_*参数)
|
||||||
- 用户还可以额外提供四个可选元素:核心人物、关键道具、空间坐标、时间压力
|
- 用户还可以额外提供四个可选元素:核心人物、关键道具、空间坐标、时间压力
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -609,25 +607,21 @@ def generate_chapter_draft(
|
|||||||
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
||||||
merged_query_str = "回顾剧情:\n" + "\n".join(recent_3_texts) + "\n" + user_guidance
|
merged_query_str = "回顾剧情:\n" + "\n".join(recent_3_texts) + "\n" + user_guidance
|
||||||
|
|
||||||
# 4) 检索向量库上下文
|
# 4) 检索向量库上下文 (使用embedding_*参数)
|
||||||
relevant_context = get_relevant_context_from_vector_store(
|
relevant_context = get_relevant_context_from_vector_store(
|
||||||
api_key=api_key,
|
api_key=embedding_api_key,
|
||||||
base_url=base_url,
|
base_url=embedding_url,
|
||||||
query=merged_query_str,
|
query=merged_query_str,
|
||||||
embedding_model_name=model_name,
|
interface_format=embedding_interface_format,
|
||||||
|
embedding_model_name=embedding_model_name,
|
||||||
filepath=filepath,
|
filepath=filepath,
|
||||||
k=embedding_retrieval_k
|
k=embedding_retrieval_k
|
||||||
)
|
)
|
||||||
|
|
||||||
if not relevant_context.strip():
|
if not relevant_context.strip():
|
||||||
relevant_context = "(无检索到的上下文)"
|
relevant_context = "(无检索到的上下文)"
|
||||||
|
|
||||||
# 5) 构造prompt,调用 scene_dynamics_prompt
|
# 5) 构造prompt
|
||||||
# 在这里,我们拆分架构文本,以便给模型提供:
|
# 拆分 world_building_text, novel_architecture_text 等等
|
||||||
# - “世界观”与“小说设定”可以从 arch_file 中的相应片段读取
|
|
||||||
# 这里为了简化,直接把 novel_architecture_text 整体塞入 novel_setting
|
|
||||||
# 也可更精细地拆分 "#=== 3) 世界观 ===" 片段给 world_building
|
|
||||||
# 下方仅作示例。
|
|
||||||
world_building_text = ""
|
world_building_text = ""
|
||||||
match_world = re.search(r'#=== 3\) 世界观 ===\n([\s\S]+?)\n#===', novel_architecture_text)
|
match_world = re.search(r'#=== 3\) 世界观 ===\n([\s\S]+?)\n#===', novel_architecture_text)
|
||||||
if match_world:
|
if match_world:
|
||||||
@@ -658,9 +652,8 @@ def generate_chapter_draft(
|
|||||||
character_state=character_state_text
|
character_state=character_state_text
|
||||||
)
|
)
|
||||||
|
|
||||||
# 因为我们还想让模型了解向量库检索到的上下文,可以合并到最后
|
# 合并检索到的上下文和用户指导
|
||||||
prompt_text += f"\n\n【检索到的上下文】\n{relevant_context}"
|
prompt_text += f"\n\n【检索到的上下文】\n{relevant_context}"
|
||||||
# 也可合并用户指导
|
|
||||||
prompt_text += f"\n\n【用户指导】\n{user_guidance}\n"
|
prompt_text += f"\n\n【用户指导】\n{user_guidance}\n"
|
||||||
|
|
||||||
model = ChatOpenAI(
|
model = ChatOpenAI(
|
||||||
@@ -674,8 +667,7 @@ def generate_chapter_draft(
|
|||||||
if not chapter_content.strip():
|
if not chapter_content.strip():
|
||||||
logging.warning("Generated chapter draft is empty.")
|
logging.warning("Generated chapter draft is empty.")
|
||||||
|
|
||||||
# 6) 写入 chapters 目录
|
# 6) 写入 chapters
|
||||||
chapters_dir = os.path.join(filepath, "chapters")
|
|
||||||
os.makedirs(chapters_dir, exist_ok=True)
|
os.makedirs(chapters_dir, exist_ok=True)
|
||||||
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
||||||
|
|
||||||
@@ -695,6 +687,9 @@ def finalize_chapter(
|
|||||||
model_name: str,
|
model_name: str,
|
||||||
temperature: float,
|
temperature: float,
|
||||||
filepath: str,
|
filepath: str,
|
||||||
|
embedding_api_key: str,
|
||||||
|
embedding_url: str,
|
||||||
|
embedding_interface_format: str,
|
||||||
embedding_model_name: str
|
embedding_model_name: str
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -707,7 +702,7 @@ def finalize_chapter(
|
|||||||
logging.warning(f"Chapter {novel_number} is empty, cannot finalize.")
|
logging.warning(f"Chapter {novel_number} is empty, cannot finalize.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 如果长度比目标少很多,可考虑在此扩写
|
# 若篇幅过短,可尝试扩写
|
||||||
if len(chapter_text) < 0.6 * word_number:
|
if len(chapter_text) < 0.6 * word_number:
|
||||||
chapter_text = enrich_chapter_text(chapter_text, word_number, api_key, base_url, model_name, temperature)
|
chapter_text = enrich_chapter_text(chapter_text, word_number, api_key, base_url, model_name, temperature)
|
||||||
clear_file_content(chapter_file)
|
clear_file_content(chapter_file)
|
||||||
@@ -750,12 +745,13 @@ def finalize_chapter(
|
|||||||
clear_file_content(character_state_file)
|
clear_file_content(character_state_file)
|
||||||
save_string_to_txt(new_char_state, character_state_file)
|
save_string_to_txt(new_char_state, character_state_file)
|
||||||
|
|
||||||
# 3) 更新向量库
|
# 3) 更新向量库 (embedding相关)
|
||||||
update_vector_store(
|
update_vector_store(
|
||||||
api_key=api_key,
|
api_key=embedding_api_key,
|
||||||
base_url=base_url,
|
base_url=embedding_url,
|
||||||
new_chapter=chapter_text,
|
new_chapter=chapter_text,
|
||||||
model_name=embedding_model_name, # 用于embedding
|
interface_format=embedding_interface_format,
|
||||||
|
embedding_model_name=embedding_model_name,
|
||||||
filepath=filepath
|
filepath=filepath
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -827,15 +823,14 @@ def advanced_split_content(content: str,
|
|||||||
return final_segments
|
return final_segments
|
||||||
|
|
||||||
def import_knowledge_file(
|
def import_knowledge_file(
|
||||||
api_key: str,
|
embedding_api_key: str,
|
||||||
base_url: str,
|
embedding_url: str,
|
||||||
interface_format: str,
|
embedding_interface_format: str,
|
||||||
embedding_model_name: str,
|
embedding_model_name: str,
|
||||||
file_path: str,
|
file_path: str,
|
||||||
embedding_base_url: str,
|
|
||||||
filepath: str
|
filepath: str
|
||||||
):
|
):
|
||||||
logging.info(f"开始导入知识库文件: {file_path}, 接口格式: {interface_format}, 模型: {embedding_model_name}")
|
logging.info(f"开始导入知识库文件: {file_path}, 接口格式: {embedding_interface_format}, 模型: {embedding_model_name}")
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
logging.warning(f"知识库文件不存在: {file_path}")
|
logging.warning(f"知识库文件不存在: {file_path}")
|
||||||
return
|
return
|
||||||
@@ -847,20 +842,20 @@ def import_knowledge_file(
|
|||||||
|
|
||||||
paragraphs = advanced_split_content(content)
|
paragraphs = advanced_split_content(content)
|
||||||
|
|
||||||
# 若向量库不存在则初始化,否则追加
|
# 尝试加载已有的向量库
|
||||||
store = load_vector_store(
|
store = load_vector_store(
|
||||||
api_key=api_key,
|
api_key=embedding_api_key,
|
||||||
base_url=base_url if base_url else "http://localhost:11434/v1",
|
base_url=embedding_url if embedding_url else "http://localhost:11434/v1",
|
||||||
interface_format=interface_format,
|
interface_format=embedding_interface_format,
|
||||||
embedding_model_name=embedding_model_name,
|
embedding_model_name=embedding_model_name,
|
||||||
filepath=filepath
|
filepath=filepath
|
||||||
)
|
)
|
||||||
if not store:
|
if not store:
|
||||||
logging.info("Vector store does not exist. Initializing a new one for knowledge import...")
|
logging.info("Vector store does not exist. Initializing a new one for knowledge import...")
|
||||||
init_vector_store(
|
init_vector_store(
|
||||||
api_key=api_key,
|
api_key=embedding_api_key,
|
||||||
base_url=base_url if base_url else "http://localhost:11434/v1",
|
base_url=embedding_url if embedding_url else "http://localhost:11434/v1",
|
||||||
interface_format=interface_format,
|
interface_format=embedding_interface_format,
|
||||||
embedding_model_name=embedding_model_name,
|
embedding_model_name=embedding_model_name,
|
||||||
texts=paragraphs,
|
texts=paragraphs,
|
||||||
filepath=filepath
|
filepath=filepath
|
||||||
|
|||||||
@@ -597,11 +597,13 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_generate_chapter)
|
self.disable_button_safe(self.btn_generate_chapter)
|
||||||
try:
|
try:
|
||||||
|
# LLM相关
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
base_url = self.base_url_var.get().strip()
|
base_url = self.base_url_var.get().strip()
|
||||||
model_name = self.model_name_var.get().strip()
|
model_name = self.model_name_var.get().strip()
|
||||||
temperature = self.temperature_var.get()
|
temperature = self.temperature_var.get()
|
||||||
|
|
||||||
|
# 章节信息
|
||||||
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
||||||
word_number = self.safe_get_int(self.word_number_var, 3000)
|
word_number = self.safe_get_int(self.word_number_var, 3000)
|
||||||
user_guidance = self.user_guide_text.get("0.0", "end").strip()
|
user_guidance = self.user_guide_text.get("0.0", "end").strip()
|
||||||
@@ -612,6 +614,10 @@ class NovelGeneratorGUI:
|
|||||||
scene_loc = self.scene_location_var.get().strip()
|
scene_loc = self.scene_location_var.get().strip()
|
||||||
time_constr = self.time_constraint_var.get().strip()
|
time_constr = self.time_constraint_var.get().strip()
|
||||||
|
|
||||||
|
# Embedding相关
|
||||||
|
embedding_api_key = self.embedding_api_key_var.get().strip()
|
||||||
|
embedding_url = self.embedding_url_var.get().strip()
|
||||||
|
embedding_interface_format = self.embedding_interface_format_var.get().strip()
|
||||||
embedding_model_name = self.embedding_model_name_var.get().strip()
|
embedding_model_name = self.embedding_model_name_var.get().strip()
|
||||||
embedding_k = self.safe_get_int(self.embedding_retrieval_k_var, 4)
|
embedding_k = self.safe_get_int(self.embedding_retrieval_k_var, 4)
|
||||||
|
|
||||||
@@ -629,6 +635,10 @@ class NovelGeneratorGUI:
|
|||||||
key_items=key_items,
|
key_items=key_items,
|
||||||
scene_location=scene_loc,
|
scene_location=scene_loc,
|
||||||
time_constraint=time_constr,
|
time_constraint=time_constr,
|
||||||
|
embedding_api_key=embedding_api_key,
|
||||||
|
embedding_url=embedding_url,
|
||||||
|
embedding_interface_format=embedding_interface_format,
|
||||||
|
embedding_model_name=embedding_model_name,
|
||||||
embedding_retrieval_k=embedding_k
|
embedding_retrieval_k=embedding_k
|
||||||
)
|
)
|
||||||
if draft_text:
|
if draft_text:
|
||||||
@@ -659,13 +669,19 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_finalize_chapter)
|
self.disable_button_safe(self.btn_finalize_chapter)
|
||||||
try:
|
try:
|
||||||
|
# LLM相关
|
||||||
api_key = self.api_key_var.get().strip()
|
api_key = self.api_key_var.get().strip()
|
||||||
base_url = self.base_url_var.get().strip()
|
base_url = self.base_url_var.get().strip()
|
||||||
model_name = self.model_name_var.get().strip()
|
model_name = self.model_name_var.get().strip()
|
||||||
temperature = self.temperature_var.get()
|
temperature = self.temperature_var.get()
|
||||||
|
|
||||||
|
# Embedding相关
|
||||||
|
embedding_api_key = self.embedding_api_key_var.get().strip()
|
||||||
|
embedding_url = self.embedding_url_var.get().strip()
|
||||||
|
embedding_interface_format = self.embedding_interface_format_var.get().strip()
|
||||||
embedding_model_name = self.embedding_model_name_var.get().strip()
|
embedding_model_name = self.embedding_model_name_var.get().strip()
|
||||||
|
|
||||||
|
# 章节参数
|
||||||
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
chap_num = self.safe_get_int(self.chapter_num_var, 1)
|
||||||
word_number = self.safe_get_int(self.word_number_var, 3000)
|
word_number = self.safe_get_int(self.word_number_var, 3000)
|
||||||
|
|
||||||
@@ -686,6 +702,9 @@ class NovelGeneratorGUI:
|
|||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
filepath=filepath,
|
filepath=filepath,
|
||||||
|
embedding_api_key=embedding_api_key,
|
||||||
|
embedding_url=embedding_url,
|
||||||
|
embedding_interface_format=embedding_interface_format,
|
||||||
embedding_model_name=embedding_model_name
|
embedding_model_name=embedding_model_name
|
||||||
)
|
)
|
||||||
self.safe_log(f"✅ 第{chap_num}章定稿完成(已更新全局摘要、角色状态、向量库)。")
|
self.safe_log(f"✅ 第{chap_num}章定稿完成(已更新全局摘要、角色状态、向量库)。")
|
||||||
@@ -754,19 +773,18 @@ class NovelGeneratorGUI:
|
|||||||
def task():
|
def task():
|
||||||
self.disable_button_safe(self.btn_import_knowledge)
|
self.disable_button_safe(self.btn_import_knowledge)
|
||||||
try:
|
try:
|
||||||
api_key = self.embedding_api_key_var.get().strip()
|
emb_api_key = self.embedding_api_key_var.get().strip()
|
||||||
base_url = self.embedding_url_var.get().strip()
|
emb_url = self.embedding_url_var.get().strip()
|
||||||
interface_format = self.embedding_interface_format_var.get().strip()
|
emb_format = self.embedding_interface_format_var.get().strip()
|
||||||
embedding_model_name = self.embedding_model_name_var.get().strip()
|
emb_model = self.embedding_model_name_var.get().strip()
|
||||||
|
|
||||||
self.safe_log(f"开始导入知识库文件: {selected_file}")
|
self.safe_log(f"开始导入知识库文件: {selected_file}")
|
||||||
import_knowledge_file(
|
import_knowledge_file(
|
||||||
api_key=api_key,
|
embedding_api_key=emb_api_key,
|
||||||
base_url=base_url,
|
embedding_url=emb_url,
|
||||||
interface_format=interface_format,
|
embedding_interface_format=emb_format,
|
||||||
embedding_model_name=embedding_model_name,
|
embedding_model_name=emb_model,
|
||||||
file_path=selected_file,
|
file_path=selected_file,
|
||||||
embedding_base_url=base_url,
|
|
||||||
filepath=self.filepath_var.get().strip()
|
filepath=self.filepath_var.get().strip()
|
||||||
)
|
)
|
||||||
self.safe_log("✅ 知识库文件导入完成。")
|
self.safe_log("✅ 知识库文件导入完成。")
|
||||||
|
|||||||
Reference in New Issue
Block a user