拆分第一章和后续章节,添加承接提示词
This commit is contained in:
+97
-57
@@ -32,7 +32,8 @@ from prompt_definitions import (
|
|||||||
chunked_chapter_blueprint_prompt,
|
chunked_chapter_blueprint_prompt,
|
||||||
summary_prompt,
|
summary_prompt,
|
||||||
update_character_state_prompt,
|
update_character_state_prompt,
|
||||||
chapter_draft_prompt,
|
first_chapter_draft_prompt,
|
||||||
|
next_chapter_draft_prompt,
|
||||||
summarize_recent_chapters_prompt
|
summarize_recent_chapters_prompt
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -415,11 +416,11 @@ def compute_chunk_size(number_of_chapters: int, max_tokens: int) -> int:
|
|||||||
并确保 chunk_size 不会小于1或大于实际章节数。
|
并确保 chunk_size 不会小于1或大于实际章节数。
|
||||||
"""
|
"""
|
||||||
tokens_per_chapter = 100.0
|
tokens_per_chapter = 100.0
|
||||||
ratio = max_tokens / tokens_per_chapter # 8192 / 100 = 81.92
|
ratio = max_tokens / tokens_per_chapter # 例如:8192 / 100 = 81.92
|
||||||
# 先取到最接近的10倍
|
# 先取到最接近的10倍
|
||||||
ratio_rounded_to_10 = int(ratio // 10) * 10 # => 80
|
ratio_rounded_to_10 = int(ratio // 10) * 10 # => 80
|
||||||
# 再减10
|
# 再减10
|
||||||
chunk_size = ratio_rounded_to_10 - 10 # => 70
|
chunk_size = ratio_rounded_to_10 - 10 # => 70
|
||||||
if chunk_size < 1:
|
if chunk_size < 1:
|
||||||
chunk_size = 1
|
chunk_size = 1
|
||||||
if chunk_size > number_of_chapters:
|
if chunk_size > number_of_chapters:
|
||||||
@@ -527,7 +528,7 @@ def Chapter_blueprint_generate(
|
|||||||
logging.info("Novel_directory.txt (chapter blueprint) has been generated successfully (chunked).")
|
logging.info("Novel_directory.txt (chapter blueprint) has been generated successfully (chunked).")
|
||||||
|
|
||||||
|
|
||||||
# ============ 3) 生成章节草稿 ============
|
# ============ 3) 生成章节草稿(分「第一章」与「后续章节」) ============
|
||||||
|
|
||||||
def generate_chapter_draft(
|
def generate_chapter_draft(
|
||||||
api_key: str,
|
api_key: str,
|
||||||
@@ -550,6 +551,11 @@ def generate_chapter_draft(
|
|||||||
interface_format: str = "openai",
|
interface_format: str = "openai",
|
||||||
max_tokens: int = 2048
|
max_tokens: int = 2048
|
||||||
) -> str:
|
) -> str:
|
||||||
|
"""
|
||||||
|
根据 novel_number 判断是否为第一章。
|
||||||
|
- 若是第一章,则使用 first_chapter_draft_prompt
|
||||||
|
- 否则使用 next_chapter_draft_prompt
|
||||||
|
"""
|
||||||
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
||||||
novel_architecture_text = read_file(arch_file)
|
novel_architecture_text = read_file(arch_file)
|
||||||
|
|
||||||
@@ -562,6 +568,7 @@ def generate_chapter_draft(
|
|||||||
character_state_file = os.path.join(filepath, "character_state.txt")
|
character_state_file = os.path.join(filepath, "character_state.txt")
|
||||||
character_state_text = read_file(character_state_file)
|
character_state_text = read_file(character_state_file)
|
||||||
|
|
||||||
|
# 获取本章在目录中的信息
|
||||||
chapter_info = get_chapter_info_from_blueprint(blueprint_text, novel_number)
|
chapter_info = get_chapter_info_from_blueprint(blueprint_text, novel_number)
|
||||||
chapter_title = chapter_info["chapter_title"]
|
chapter_title = chapter_info["chapter_title"]
|
||||||
chapter_role = chapter_info["chapter_role"]
|
chapter_role = chapter_info["chapter_role"]
|
||||||
@@ -571,68 +578,97 @@ def generate_chapter_draft(
|
|||||||
plot_twist_level = chapter_info["plot_twist_level"]
|
plot_twist_level = chapter_info["plot_twist_level"]
|
||||||
chapter_summary = chapter_info["chapter_summary"]
|
chapter_summary = chapter_info["chapter_summary"]
|
||||||
|
|
||||||
|
# 准备章节目录文件夹
|
||||||
chapters_dir = os.path.join(filepath, "chapters")
|
chapters_dir = os.path.join(filepath, "chapters")
|
||||||
os.makedirs(chapters_dir, exist_ok=True)
|
os.makedirs(chapters_dir, exist_ok=True)
|
||||||
|
|
||||||
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
# 如果是第一章,不需要前情检索与前章结尾
|
||||||
short_summary, next_chapter_keywords = summarize_recent_chapters(
|
if novel_number == 1:
|
||||||
interface_format=interface_format,
|
# 使用第一章提示词
|
||||||
api_key=api_key,
|
prompt_text = first_chapter_draft_prompt.format(
|
||||||
base_url=base_url,
|
novel_number=novel_number,
|
||||||
model_name=model_name,
|
chapter_title=chapter_title,
|
||||||
temperature=temperature,
|
chapter_role=chapter_role,
|
||||||
max_tokens=max_tokens,
|
chapter_purpose=chapter_purpose,
|
||||||
chapters_text_list=recent_3_texts
|
suspense_level=suspense_level,
|
||||||
)
|
foreshadowing=foreshadowing,
|
||||||
|
plot_twist_level=plot_twist_level,
|
||||||
|
chapter_summary=chapter_summary,
|
||||||
|
|
||||||
previous_chapter_excerpt = ""
|
characters_involved=characters_involved,
|
||||||
for text_block in reversed(recent_3_texts):
|
key_items=key_items,
|
||||||
if text_block.strip():
|
scene_location=scene_location,
|
||||||
if len(text_block) > 1500:
|
time_constraint=time_constraint,
|
||||||
previous_chapter_excerpt = text_block[-1500:]
|
user_guidance=user_guidance,
|
||||||
else:
|
|
||||||
previous_chapter_excerpt = text_block
|
|
||||||
break
|
|
||||||
|
|
||||||
embedding_adapter = create_embedding_adapter(
|
novel_setting=novel_architecture_text
|
||||||
embedding_interface_format,
|
)
|
||||||
embedding_api_key,
|
|
||||||
embedding_url,
|
|
||||||
embedding_model_name
|
|
||||||
)
|
|
||||||
retrieval_query = short_summary + " " + next_chapter_keywords
|
|
||||||
relevant_context = get_relevant_context_from_vector_store(
|
|
||||||
embedding_adapter=embedding_adapter,
|
|
||||||
query=retrieval_query,
|
|
||||||
filepath=filepath,
|
|
||||||
k=embedding_retrieval_k
|
|
||||||
)
|
|
||||||
if not relevant_context.strip():
|
|
||||||
relevant_context = "(无检索到的上下文)"
|
|
||||||
|
|
||||||
prompt_text = chapter_draft_prompt.format(
|
else:
|
||||||
novel_number=novel_number,
|
# 若不是第一章,则先获取最近几章文本,并做摘要与检索
|
||||||
chapter_title=chapter_title,
|
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
||||||
chapter_role=chapter_role,
|
short_summary, next_chapter_keywords = summarize_recent_chapters(
|
||||||
chapter_purpose=chapter_purpose,
|
interface_format=interface_format,
|
||||||
suspense_level=suspense_level,
|
api_key=api_key,
|
||||||
foreshadowing=foreshadowing,
|
base_url=base_url,
|
||||||
plot_twist_level=plot_twist_level,
|
model_name=model_name,
|
||||||
chapter_summary=chapter_summary,
|
temperature=temperature,
|
||||||
|
max_tokens=max_tokens,
|
||||||
|
chapters_text_list=recent_3_texts
|
||||||
|
)
|
||||||
|
|
||||||
characters_involved=characters_involved,
|
# 从最近章节中获取最后一段内容作为前章结尾
|
||||||
key_items=key_items,
|
previous_chapter_excerpt = ""
|
||||||
scene_location=scene_location,
|
for text_block in reversed(recent_3_texts):
|
||||||
time_constraint=time_constraint,
|
if text_block.strip():
|
||||||
user_guidance=user_guidance,
|
if len(text_block) > 1500:
|
||||||
|
previous_chapter_excerpt = text_block[-1500:]
|
||||||
|
else:
|
||||||
|
previous_chapter_excerpt = text_block
|
||||||
|
break
|
||||||
|
|
||||||
novel_setting=novel_architecture_text,
|
# 从向量库检索上下文
|
||||||
global_summary=global_summary_text,
|
embedding_adapter = create_embedding_adapter(
|
||||||
character_state=character_state_text,
|
embedding_interface_format,
|
||||||
previous_chapter_excerpt=previous_chapter_excerpt,
|
embedding_api_key,
|
||||||
context_excerpt=relevant_context
|
embedding_url,
|
||||||
)
|
embedding_model_name
|
||||||
|
)
|
||||||
|
retrieval_query = short_summary + " " + next_chapter_keywords
|
||||||
|
relevant_context = get_relevant_context_from_vector_store(
|
||||||
|
embedding_adapter=embedding_adapter,
|
||||||
|
query=retrieval_query,
|
||||||
|
filepath=filepath,
|
||||||
|
k=embedding_retrieval_k
|
||||||
|
)
|
||||||
|
if not relevant_context.strip():
|
||||||
|
relevant_context = "(无检索到的上下文)"
|
||||||
|
|
||||||
|
# 使用后续章节提示词
|
||||||
|
prompt_text = next_chapter_draft_prompt.format(
|
||||||
|
novel_number=novel_number,
|
||||||
|
chapter_title=chapter_title,
|
||||||
|
chapter_role=chapter_role,
|
||||||
|
chapter_purpose=chapter_purpose,
|
||||||
|
suspense_level=suspense_level,
|
||||||
|
foreshadowing=foreshadowing,
|
||||||
|
plot_twist_level=plot_twist_level,
|
||||||
|
chapter_summary=chapter_summary,
|
||||||
|
|
||||||
|
characters_involved=characters_involved,
|
||||||
|
key_items=key_items,
|
||||||
|
scene_location=scene_location,
|
||||||
|
time_constraint=time_constraint,
|
||||||
|
user_guidance=user_guidance,
|
||||||
|
|
||||||
|
novel_setting=novel_architecture_text,
|
||||||
|
global_summary=global_summary_text,
|
||||||
|
character_state=character_state_text,
|
||||||
|
context_excerpt=relevant_context,
|
||||||
|
previous_chapter_excerpt=previous_chapter_excerpt
|
||||||
|
)
|
||||||
|
|
||||||
|
# 调用LLM生成
|
||||||
llm_adapter = create_llm_adapter(
|
llm_adapter = create_llm_adapter(
|
||||||
interface_format=interface_format,
|
interface_format=interface_format,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
@@ -645,6 +681,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.")
|
||||||
|
|
||||||
|
# 保存章节文本
|
||||||
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
||||||
clear_file_content(chapter_file)
|
clear_file_content(chapter_file)
|
||||||
save_string_to_txt(chapter_content, chapter_file)
|
save_string_to_txt(chapter_content, chapter_file)
|
||||||
@@ -652,6 +689,7 @@ def generate_chapter_draft(
|
|||||||
logging.info(f"[Draft] Chapter {novel_number} generated as a draft.")
|
logging.info(f"[Draft] Chapter {novel_number} generated as a draft.")
|
||||||
return chapter_content
|
return chapter_content
|
||||||
|
|
||||||
|
|
||||||
# ============ 4) 定稿章节 ============
|
# ============ 4) 定稿章节 ============
|
||||||
|
|
||||||
def finalize_chapter(
|
def finalize_chapter(
|
||||||
@@ -676,6 +714,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.7 * word_number:
|
if len(chapter_text) < 0.7 * word_number:
|
||||||
chapter_text = enrich_chapter_text(chapter_text, word_number, api_key, base_url, model_name, temperature, interface_format, max_tokens)
|
chapter_text = enrich_chapter_text(chapter_text, word_number, api_key, base_url, model_name, temperature, interface_format, max_tokens)
|
||||||
clear_file_content(chapter_file)
|
clear_file_content(chapter_file)
|
||||||
@@ -716,6 +755,7 @@ 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)
|
||||||
|
|
||||||
|
# 更新向量库
|
||||||
embedding_adapter = create_embedding_adapter(
|
embedding_adapter = create_embedding_adapter(
|
||||||
embedding_interface_format,
|
embedding_interface_format,
|
||||||
embedding_api_key,
|
embedding_api_key,
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ next_chapter_draft_prompt = """\
|
|||||||
前章结尾段:
|
前章结尾段:
|
||||||
{previous_chapter_excerpt}
|
{previous_chapter_excerpt}
|
||||||
|
|
||||||
请从前章结尾处继续完成第 {novel_number} 章的正文,至少设计下方2个具有动态张力的场景:
|
请依据前章结尾片段,继续完成第 {novel_number} 章的正文,至少设计下方2个具有动态张力的场景:
|
||||||
1. 对话场景:
|
1. 对话场景:
|
||||||
- 潜台词冲突(表面谈论A,实际博弈B)
|
- 潜台词冲突(表面谈论A,实际博弈B)
|
||||||
- 权力关系变化(通过非对称对话长度体现)
|
- 权力关系变化(通过非对称对话长度体现)
|
||||||
|
|||||||
Reference in New Issue
Block a user