进行文件的逻辑拆分
初步对ui.py以及novel_generator.py进行了拆分
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#novel_generator/chapter.py
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
章节草稿生成及获取历史章节文本、短期摘要等
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from nltk import download
|
||||
from llm_adapters import create_llm_adapter
|
||||
from prompt_definitions import first_chapter_draft_prompt, next_chapter_draft_prompt, summarize_recent_chapters_prompt
|
||||
from chapter_directory_parser import get_chapter_info_from_blueprint
|
||||
from novel_generator.common import invoke_with_cleaning
|
||||
from utils import read_file, clear_file_content, save_string_to_txt
|
||||
from novel_generator.vectorstore_utils import get_relevant_context_from_vector_store
|
||||
|
||||
def get_last_n_chapters_text(chapters_dir: str, current_chapter_num: int, n: int = 3) -> list:
|
||||
"""
|
||||
从目录 chapters_dir 中获取最近 n 章的文本内容,返回文本列表。
|
||||
"""
|
||||
texts = []
|
||||
start_chap = max(1, current_chapter_num - n)
|
||||
for c in range(start_chap, current_chapter_num):
|
||||
chap_file = os.path.join(chapters_dir, f"chapter_{c}.txt")
|
||||
if os.path.exists(chap_file):
|
||||
text = read_file(chap_file).strip()
|
||||
texts.append(text)
|
||||
else:
|
||||
texts.append("")
|
||||
return texts
|
||||
|
||||
def summarize_recent_chapters(
|
||||
interface_format: str,
|
||||
api_key: str,
|
||||
base_url: str,
|
||||
model_name: str,
|
||||
temperature: float,
|
||||
max_tokens: int,
|
||||
chapters_text_list: list,
|
||||
timeout: int = 600
|
||||
) -> tuple:
|
||||
"""
|
||||
生成 (short_summary, next_chapter_keywords)
|
||||
如果解析失败,则返回 (合并文本, "")
|
||||
"""
|
||||
combined_text = "\n".join(chapters_text_list).strip()
|
||||
if not combined_text:
|
||||
return ("", "")
|
||||
llm_adapter = create_llm_adapter(
|
||||
interface_format=interface_format,
|
||||
base_url=base_url,
|
||||
model_name=model_name,
|
||||
api_key=api_key,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
timeout=timeout
|
||||
)
|
||||
prompt = summarize_recent_chapters_prompt.format(combined_text=combined_text)
|
||||
response_text = invoke_with_cleaning(llm_adapter, prompt)
|
||||
short_summary = ""
|
||||
next_chapter_keywords = ""
|
||||
for line in response_text.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("短期摘要:"):
|
||||
short_summary = line.replace("短期摘要:", "").strip()
|
||||
elif line.startswith("下一章关键字:"):
|
||||
next_chapter_keywords = line.replace("下一章关键字:", "").strip()
|
||||
if not short_summary and not next_chapter_keywords:
|
||||
short_summary = response_text
|
||||
return (short_summary, next_chapter_keywords)
|
||||
|
||||
def generate_chapter_draft(
|
||||
api_key: str,
|
||||
base_url: str,
|
||||
model_name: str,
|
||||
filepath: str,
|
||||
novel_number: int,
|
||||
word_number: int,
|
||||
temperature: float,
|
||||
user_guidance: str,
|
||||
characters_involved: str,
|
||||
key_items: str,
|
||||
scene_location: str,
|
||||
time_constraint: str,
|
||||
embedding_api_key: str,
|
||||
embedding_url: str,
|
||||
embedding_interface_format: str,
|
||||
embedding_model_name: str,
|
||||
embedding_retrieval_k: int = 2,
|
||||
interface_format: str = "openai",
|
||||
max_tokens: int = 2048,
|
||||
timeout: int = 600
|
||||
) -> str:
|
||||
"""
|
||||
根据 novel_number 判断是否为第一章。
|
||||
- 若是第一章,则使用 first_chapter_draft_prompt
|
||||
- 否则使用 next_chapter_draft_prompt
|
||||
最终将生成文本存入 chapters/chapter_{novel_number}.txt。
|
||||
"""
|
||||
arch_file = os.path.join(filepath, "Novel_architecture.txt")
|
||||
novel_architecture_text = read_file(arch_file)
|
||||
directory_file = os.path.join(filepath, "Novel_directory.txt")
|
||||
blueprint_text = read_file(directory_file)
|
||||
global_summary_file = os.path.join(filepath, "global_summary.txt")
|
||||
global_summary_text = read_file(global_summary_file)
|
||||
character_state_file = os.path.join(filepath, "character_state.txt")
|
||||
character_state_text = read_file(character_state_file)
|
||||
|
||||
chapter_info = get_chapter_info_from_blueprint(blueprint_text, novel_number)
|
||||
chapter_title = chapter_info["chapter_title"]
|
||||
chapter_role = chapter_info["chapter_role"]
|
||||
chapter_purpose = chapter_info["chapter_purpose"]
|
||||
suspense_level = chapter_info["suspense_level"]
|
||||
foreshadowing = chapter_info["foreshadowing"]
|
||||
plot_twist_level = chapter_info["plot_twist_level"]
|
||||
chapter_summary = chapter_info["chapter_summary"]
|
||||
|
||||
chapters_dir = os.path.join(filepath, "chapters")
|
||||
os.makedirs(chapters_dir, exist_ok=True)
|
||||
|
||||
if novel_number == 1:
|
||||
prompt_text = first_chapter_draft_prompt.format(
|
||||
novel_number=novel_number,
|
||||
word_number=word_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
|
||||
)
|
||||
else:
|
||||
recent_3_texts = get_last_n_chapters_text(chapters_dir, novel_number, n=3)
|
||||
short_summary, next_chapter_keywords = summarize_recent_chapters(
|
||||
interface_format=interface_format,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
model_name=model_name,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
chapters_text_list=recent_3_texts,
|
||||
timeout=timeout
|
||||
)
|
||||
previous_chapter_excerpt = ""
|
||||
for text_block in reversed(recent_3_texts):
|
||||
if text_block.strip():
|
||||
if len(text_block) > 1500:
|
||||
previous_chapter_excerpt = text_block[-1500:]
|
||||
else:
|
||||
previous_chapter_excerpt = text_block
|
||||
break
|
||||
from llm_adapters import create_llm_adapter # 避免循环依赖
|
||||
embedding_adapter = create_llm_adapter(
|
||||
interface_format=embedding_interface_format,
|
||||
base_url=embedding_url,
|
||||
model_name=embedding_model_name,
|
||||
api_key=embedding_api_key,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
timeout=timeout
|
||||
)
|
||||
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,
|
||||
word_number=word_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_adapter = create_llm_adapter(
|
||||
interface_format=interface_format,
|
||||
base_url=base_url,
|
||||
model_name=model_name,
|
||||
api_key=api_key,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
chapter_content = invoke_with_cleaning(llm_adapter, prompt_text)
|
||||
if not chapter_content.strip():
|
||||
logging.warning("Generated chapter draft is empty.")
|
||||
chapter_file = os.path.join(chapters_dir, f"chapter_{novel_number}.txt")
|
||||
clear_file_content(chapter_file)
|
||||
save_string_to_txt(chapter_content, chapter_file)
|
||||
logging.info(f"[Draft] Chapter {novel_number} generated as a draft.")
|
||||
return chapter_content
|
||||
Reference in New Issue
Block a user