Merge pull request #67 from YILING0013/dev-2

任务与提示词流程优化
This commit is contained in:
Xianyun
2025-02-06 23:04:51 +08:00
committed by GitHub
9 changed files with 2216 additions and 1647 deletions
+120 -35
View File
@@ -1,47 +1,132 @@
# chapter_directory_parser.py
# chapter_blueprint_parser.py
# -*- coding: utf-8 -*-
import re
def get_chapter_info_from_directory(novel_directory_content: str, chapter_number: int):
def parse_chapter_blueprint(blueprint_text: str):
"""
从给定的 novel_directory_content 文本中,解析 “第X章” 行,并提取本章的标题和可能的简述。
返回一个 dict: {
"chapter_title": <字符串>,
"chapter_brief": <字符串> (若没有则为空)
解析整份章节蓝图文本,返回一个列表,每个元素是一个 dict:
{
"chapter_number": int,
"chapter_title": str,
"chapter_role": str, # 本章定位
"chapter_purpose": str, # 核心作用
"suspense_level": str, # 悬念密度
"foreshadowing": str, # 伏笔操作
"plot_twist_level": str, # 认知颠覆
"chapter_summary": str # 本章简述
}
注意:目录文本示例格式:
第1章 :潮起
第2章 :阴影浮现 - 主要角色冲突爆发
...
也可能没有简述,只有一个简单标题。
"""
# 将文本逐行拆分
lines = novel_directory_content.splitlines()
# 先按空行进行分块,以免多章之间混淆
chunks = re.split(r'\n\s*\n', blueprint_text.strip())
results = []
# 章节匹配:形如 “第5章 :xxx” or “第5章: xxx” or “第5章 xxx”
pattern = re.compile(r'^第\s*(\d+)\s*章\s*[:]?\s*(.*)$')
# 兼容是否使用方括号包裹章节标题
# 例如:
# 第1章 - 紫极光下的预兆
# 或
# 第1章 - [紫极光下的预兆]
chapter_number_pattern = re.compile(r'^第\s*(\d+)\s*章\s*-\s*\[?(.*?)\]?$')
for line in lines:
match = pattern.match(line.strip())
if match:
chap_num = int(match.group(1))
if chap_num == chapter_number:
full_title = match.group(2).strip()
if ' - ' in full_title:
parts = full_title.split(' - ', 1)
return {
"chapter_title": parts[0].strip(),
"chapter_brief": parts[1].strip()
}
else:
return {
"chapter_title": full_title,
"chapter_brief": ""
}
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:
lines = chunk.strip().splitlines()
if not lines:
continue
chapter_number = None
chapter_title = ""
chapter_role = ""
chapter_purpose = ""
suspense_level = ""
foreshadowing = ""
plot_twist_level = ""
chapter_summary = ""
# 先匹配第一行(或前几行),找到章号和标题
header_match = chapter_number_pattern.match(lines[0].strip())
if not header_match:
# 不符合“第X章 - 标题”的格式,跳过
continue
chapter_number = int(header_match.group(1))
chapter_title = header_match.group(2).strip()
# 从后面的行匹配其他字段
for line in lines[1:]:
line_stripped = line.strip()
if not line_stripped:
continue
m_role = role_pattern.match(line_stripped)
if m_role:
chapter_role = m_role.group(1).strip()
continue
m_purpose = purpose_pattern.match(line_stripped)
if m_purpose:
chapter_purpose = m_purpose.group(1).strip()
continue
m_suspense = suspense_pattern.match(line_stripped)
if m_suspense:
suspense_level = m_suspense.group(1).strip()
continue
m_foreshadow = foreshadow_pattern.match(line_stripped)
if m_foreshadow:
foreshadowing = m_foreshadow.group(1).strip()
continue
m_twist = twist_pattern.match(line_stripped)
if m_twist:
plot_twist_level = m_twist.group(1).strip()
continue
m_summary = summary_pattern.match(line_stripped)
if m_summary:
chapter_summary = m_summary.group(1).strip()
continue
results.append({
"chapter_number": chapter_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
})
# 按照 chapter_number 排序后返回
results.sort(key=lambda x: x["chapter_number"])
return results
def get_chapter_info_from_blueprint(blueprint_text: str, target_chapter_number: int):
"""
在已经加载好的章节蓝图文本中,找到对应章号的结构化信息,返回一个 dict。
若找不到则返回一个默认的结构。
"""
all_chapters = parse_chapter_blueprint(blueprint_text)
for ch in all_chapters:
if ch["chapter_number"] == target_chapter_number:
return ch
# 默认返回
return {
"chapter_title": f"{chapter_number}",
"chapter_brief": ""
"chapter_number": target_chapter_number,
"chapter_title": f"{target_chapter_number}",
"chapter_role": "",
"chapter_purpose": "",
"suspense_level": "",
"foreshadowing": "",
"plot_twist_level": "",
"chapter_summary": ""
}
+120
View File
@@ -0,0 +1,120 @@
# embedding_adapters.py
# -*- coding: utf-8 -*-
import logging
import requests
import traceback
from typing import List
from langchain_openai import OpenAIEmbeddings
def ensure_openai_base_url_has_v1(url: str) -> str:
"""
若用户输入的 url 不包含 '/v1',则在末尾追加 '/v1'
"""
import re
url = url.strip()
if not url:
return url
if not re.search(r'/v\d+$', url):
if '/v1' not in url:
url = url.rstrip('/') + '/v1'
return url
class BaseEmbeddingAdapter:
"""
Embedding 接口统一基类
"""
def embed_documents(self, texts: List[str]) -> List[List[float]]:
raise NotImplementedError
def embed_query(self, query: str) -> List[float]:
raise NotImplementedError
class OpenAIEmbeddingAdapter(BaseEmbeddingAdapter):
"""
基于 OpenAIEmbeddings(或兼容接口)的适配器
"""
def __init__(self, api_key: str, base_url: str, model_name: str):
self._embedding = OpenAIEmbeddings(
openai_api_key=api_key,
openai_api_base=ensure_openai_base_url_has_v1(base_url),
model=model_name
)
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return self._embedding.embed_documents(texts)
def embed_query(self, query: str) -> List[float]:
return self._embedding.embed_query(query)
class OllamaEmbeddingAdapter(BaseEmbeddingAdapter):
"""
其接口路径为 /api/embeddings
"""
def __init__(self, model_name: str, base_url: str):
self.model_name = model_name
self.base_url = base_url.rstrip("/")
def embed_documents(self, texts: List[str]) -> List[List[float]]:
embeddings = []
for text in texts:
vec = self._embed_single(text)
embeddings.append(vec)
return embeddings
def embed_query(self, query: str) -> List[float]:
return self._embed_single(query)
def _embed_single(self, text: str) -> List[float]:
"""
调用 Ollama 本地服务 /api/embeddings 接口,获取文本 embedding
"""
url = self.base_url
if "api/embeddings" not in url:
url = f"{url}/api/embeddings"
data = {
"model": self.model_name,
"prompt": text
}
try:
response = requests.post(url, json=data)
response.raise_for_status()
result = response.json()
if "embedding" not in result:
raise ValueError("No 'embedding' field in Ollama response.")
return result["embedding"]
except requests.exceptions.RequestException as e:
logging.error(f"Ollama embeddings request error: {e}\n{traceback.format_exc()}")
return []
class MLStudioEmbeddingAdapter(BaseEmbeddingAdapter):
def __init__(self, api_key: str, base_url: str, model_name: str):
self._embedding = OpenAIEmbeddings(
openai_api_key=api_key,
openai_api_base=ensure_openai_base_url_has_v1(base_url),
model=model_name
)
def embed_documents(self, texts: List[str]) -> List[List[float]]:
return self._embedding.embed_documents(texts)
def embed_query(self, query: str) -> List[float]:
return self._embedding.embed_query(query)
def create_embedding_adapter(
interface_format: str,
api_key: str,
base_url: str,
model_name: str
) -> BaseEmbeddingAdapter:
"""
工厂函数:根据 interface_format 返回不同的 embedding 适配器实例
"""
if interface_format.lower() == "openai":
return OpenAIEmbeddingAdapter(api_key, base_url, model_name)
elif interface_format.lower() == "ollama":
return OllamaEmbeddingAdapter(model_name, base_url)
elif interface_format.lower() == "ml studio":
return MLStudioEmbeddingAdapter(api_key, base_url, model_name)
else:
raise ValueError(f"Unknown embedding interface_format: {interface_format}")
-59
View File
@@ -1,59 +0,0 @@
# embedding_ollama.py
import requests
import traceback
from typing import List
class OllamaEmbeddings:
def __init__(self, model_name: str, base_url: str):
self.model_name = model_name
self.base_url = base_url
def embed(self, texts: List[str]) -> List[List[float]]:
"""
批量将多段文本转换为embedding向量
"""
embeddings = []
for text in texts:
embeddings.append(self.embed_single_document(text))
return embeddings
def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""
兼容langchain的接口写法
"""
return self.embed(texts)
def embed_query(self, query: str) -> List[float]:
"""
将单条 query 转换为 embedding 向量
"""
return self.embed_single_document(query)
def embed_single_document(self, text: str) -> List[float]:
"""
调用 Ollama 本地服务接口,获取文本的 embedding。
"""
if self.base_url.endswith("/"):
self.base_url = self.base_url.rstrip("/")
if "api/embeddings" in self.base_url:
# 如果 base_url 已经包含 'api/embeddings',则保持不变
url = f"{self.base_url.rstrip('/')}/api/embeddings"
else:
if "/v1" in self.base_url:
self.base_url = self.base_url.split("/v1")[0]
if "/api" in self.base_url:
self.base_url = self.base_url.split("/api")[0]
url = f"{self.base_url}/api/embeddings"
data = {
"model": self.model_name,
"prompt": text
}
try:
response = requests.post(url, json=data)
response.raise_for_status()
result = response.json()
if "embedding" not in result:
raise ValueError("No 'embedding' field in Ollama response.")
return result["embedding"]
except requests.exceptions.RequestException as e:
raise Exception(f"Ollama embeddings request error: {e}\n{traceback.format_exc()}")
+149
View File
@@ -0,0 +1,149 @@
# llm_adapters.py
# -*- coding: utf-8 -*-
import logging
from typing import Optional
from langchain_openai import ChatOpenAI
def ensure_openai_base_url_has_v1(url: str) -> str:
"""
若用户输入的 url 不包含 '/v1',则在末尾追加 '/v1'
"""
import re
url = url.strip()
if not url:
return url
if not re.search(r'/v\d+$', url):
if '/v1' not in url:
url = url.rstrip('/') + '/v1'
return url
class BaseLLMAdapter:
"""
统一的 LLM 接口基类,为不同后端(OpenAI、Ollama、ML Studio 等)提供一致的方法签名。
"""
def invoke(self, prompt: str) -> str:
raise NotImplementedError("Subclasses must implement .invoke(prompt) method.")
class DeepSeekAdapter(BaseLLMAdapter):
"""
适配官方/OpenAI兼容接口(使用 langchain.ChatOpenAI
"""
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
self.base_url = ensure_openai_base_url_has_v1(base_url)
self.api_key = api_key
self.model_name = model_name
self.max_tokens = max_tokens
self.temperature = temperature
self._client = ChatOpenAI(
model=self.model_name,
api_key=self.api_key,
base_url=self.base_url,
max_tokens=self.max_tokens,
temperature=self.temperature
)
def invoke(self, prompt: str) -> str:
response = self._client.invoke(prompt)
if not response:
logging.warning("No response from DeepSeekAdapter.")
return ""
return response.content
class OpenAIAdapter(BaseLLMAdapter):
"""
适配官方/OpenAI兼容接口(使用 langchain.ChatOpenAI
"""
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
self.base_url = ensure_openai_base_url_has_v1(base_url)
self.api_key = api_key
self.model_name = model_name
self.max_tokens = max_tokens
self.temperature = temperature
self._client = ChatOpenAI(
model=self.model_name,
api_key=self.api_key,
base_url=self.base_url,
max_tokens=self.max_tokens,
temperature=self.temperature
)
def invoke(self, prompt: str) -> str:
response = self._client.invoke(prompt)
if not response:
logging.warning("No response from OpenAIAdapter.")
return ""
return response.content
class OllamaAdapter(BaseLLMAdapter):
"""
Ollama 同样有一个 OpenAI-like /v1/chat 接口,可直接使用 ChatOpenAI。
但是通常 Ollama 默认本地服务在 http://localhost:11434,如果符合OpenAI风格即可直接传参。
"""
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
self.base_url = ensure_openai_base_url_has_v1(base_url)
self.api_key = api_key
self.model_name = model_name
self.max_tokens = max_tokens
self.temperature = temperature
self._client = ChatOpenAI(
model=self.model_name,
api_key=self.api_key,
base_url=self.base_url,
max_tokens=self.max_tokens,
temperature=self.temperature
)
def invoke(self, prompt: str) -> str:
response = self._client.invoke(prompt)
if not response:
logging.warning("No response from OllamaAdapter.")
return ""
return response.content
class MLStudioAdapter(BaseLLMAdapter):
def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7):
self.base_url = ensure_openai_base_url_has_v1(base_url)
self.api_key = api_key
self.model_name = model_name
self.max_tokens = max_tokens
self.temperature = temperature
self._client = ChatOpenAI(
model=self.model_name,
api_key=self.api_key,
base_url=self.base_url,
max_tokens=self.max_tokens,
temperature=self.temperature
)
def invoke(self, prompt: str) -> str:
response = self._client.invoke(prompt)
if not response:
logging.warning("No response from MLStudioAdapter.")
return ""
return response.content
def create_llm_adapter(
interface_format: str,
base_url: str,
model_name: str,
api_key: str,
temperature: float,
max_tokens: int
) -> BaseLLMAdapter:
"""
工厂函数:根据 interface_format 返回不同的适配器实例。
"""
if interface_format.lower() == "deepseek":
return DeepSeekAdapter(api_key, base_url, model_name, max_tokens, temperature)
elif interface_format.lower() == "openai":
return OpenAIAdapter(api_key, base_url, model_name, max_tokens, temperature)
elif interface_format.lower() == "ollama":
return OllamaAdapter(api_key, base_url, model_name, max_tokens, temperature)
elif interface_format.lower() == "ml studio":
return MLStudioAdapter(api_key, base_url, model_name, max_tokens, temperature)
else:
raise ValueError(f"Unknown interface_format: {interface_format}")
+4 -3
View File
@@ -14,7 +14,8 @@ hiddenimports = ['typing_extensions',
'pydantic',
'pydantic.deprecated.decorator',
'tiktoken_ext.openai_public',
'tiktoken_ext'
'tiktoken_ext',
'chromadb.utils.embedding_functions.onnx_mini_lm_l6_v2'
]
tmp_ret = collect_all('chromadb')
@@ -44,7 +45,7 @@ exe = EXE(
a.scripts,
[],
exclude_binaries=True,
name='AI_NovelGenerator_V1.3.2',
name='AI_NovelGenerator_V1.3.3',
debug=True,
bootloader_ignore_signals=False,
strip=False,
@@ -65,5 +66,5 @@ coll = COLLECT(
strip=False,
upx=True,
upx_exclude=[],
name='AI_NovelGenerator_V1.3.2'
name='AI_NovelGenerator_V1.3.3'
)
+484 -445
View File
File diff suppressed because it is too large Load Diff
+338 -90
View File
@@ -1,83 +1,212 @@
# prompt_definitions.py
# -*- coding: utf-8 -*-
"""
集中存放所有提示词(Prompt)新版本更精确更具创新性结合部分雪花写作法等理论
集中存放所有提示词 (Prompt)整合雪花写作法角色弧光理论悬念三要素模型等
并包含新增加的短期摘要/下一章关键字提炼提示词以及章节正文写作提示词
"""
# =============== 1. 整体设定 ===================
set_prompt = """\
请根据主题: {topic}类型: {genre}章数: {number_of_chapters}每章字数: {word_number} 来设计小说的总体结构和世界观
写作时请参考雪花写作法等创作理论结合以下要点
# =============== 摘要与下一章关键字提炼 ===============
summarize_recent_chapters_prompt = """\
你是一名资深长篇小说编辑请分析以下合并文本可能包含最近几章内容
{combined_text}
小说标题与大致篇幅总字数范围
类型与基调都市魔幻战争轻松暗黑等
主要写作风格视角叙述方式语言风格
世界观设定背景时间地理环境社会结构科技或魔法水平重要历史等
整体故事框架可提及常见叙事结构三幕英雄之旅等
主线与副线的初步构想以及它们如何交织
关键角色群像定位与主要冲突关系
结局的可能方向圆满悲剧开放式等
现在请你基于目前故事的进展完成以下两件事
1) 用最多200字写一个简洁明了的当前情节短期摘要
2) 提炼下一章的关键字例如关键物品重要人物地点事件情节可以用逗号分隔或条目列出
以简洁逻辑清晰的方式输出保留足够细节以支撑后续创作
按如下格式输出不需要额外解释
短期摘要: <这里写短期摘要>
下一章关键字: <这里写下一章关键字>
"""
# =============== 2. 角色设定 ===================
character_prompt = """\
基于已生成的小说整体设定
{novel_setting}
# =============== 1. 核心种子设定(雪花第1层)===================
core_seed_prompt = """\
作为专业作家请用"雪花写作法"第一步构建故事核心
主题{topic}
类型{genre}
篇幅{number_of_chapters}每章{word_number}
进一步扩展角色设置至少包含三名核心角色对每位角色说明
角色背景外貌与主要性格特征
内在冲突目标与动机
暗藏的秘密或潜在弱点可与世界观或其他角色有关
与其他角色的关系或对立点如何推动或阻碍情节发展
用单句公式概括故事本质例如
"当[主角]遭遇[核心事件],必须[关键行动],否则[灾难后果];与此同时,[隐藏的更大危机]正在发酵。"
请重点突出角色的多重面向为后续情节埋下伏笔
要求
1. 必须包含显性冲突与潜在危机
2. 体现人物核心驱动力
3. 暗示世界观关键矛盾
4. 使用25-100字精准表达
仅返回故事核心文本不要解释任何内容
"""
# =============== 3. 暗线与伏笔 ===================
dark_lines_prompt = """\
在当前世界观与角色关系中
{character_info}
# =============== 2. 角色动力学设定(角色弧光模型)===================
character_dynamics_prompt = """\
基于核心种子
{core_seed}
构思若干暗线伏笔或隐藏冲突结合雪花写作法细节逐步扩展的思路要求
每条暗线给出初始迹象后续发展与可能的爆发条件
与角色背景世界观或关键事件相呼应
保持合理的悬念设置与现有设定不冲突
为后续剧情保留足够展开空间强调持续影响故事进程
设计3-6个具有动态变化潜力的核心角色每个角色需包含
特征
- 背景外貌性别年龄职业等
- 暗藏的秘密或潜在弱点(可与世界观或其他角色有关)
请避免一次性透漏全部细节以逐步揭示的方式为后文做铺垫
核心驱动力三角
- 表面追求物质目标
- 深层渴望情感需求
- 灵魂需求哲学层面
角色弧线设计
初始状态 触发事件 认知失调 蜕变节点 最终状态
关系冲突网
- 与其他角色的关系或对立点
- 与至少两个其他角色的价值观冲突
- 一个合作纽带
- 一个隐藏的背叛可能性
要求
仅给出最终文本不要解释任何内容
"""
# =============== 4. 最终设定整合 ===================
finalize_setting_prompt = """\
基于以下内容请整合并输出终稿小说设定
1. 整体设定
{novel_setting_base}
2. 角色设定
{character_setting}
3. 暗线与伏笔
{dark_lines}
# =============== 3. 世界构建矩阵(三维度交织法)===================
world_building_prompt = """\
为服务核心冲突"{core_seed}"请构建三维交织的世界观
整合要求
以整体视角整理世界观角色与暗线形成一个有机结合的故事框架
注意角色动机与暗线如何与世界观互相呼应并兼顾主线与副线节奏
语言通顺层次分明直接输出文本不使用Markdown格式
1. 物理维度
- 空间结构地理×社会阶层分布图
- 时间轴关键历史事件年表
- 法则体系物理/魔法/社会规则的漏洞点
2. 社会维度
- 权力结构断层线可引发冲突的阶层/种族/组织矛盾
- 文化禁忌可被打破的禁忌及其后果
- 经济命脉资源争夺焦点
3. 隐喻维度
- 贯穿全书的视觉符号系统如反复出现的意象
- 氣候/环境变化映射的心理状态
- 建筑风格暗示的文明困境
要求
每个维度至少包含3个可与角色决策产生互动的动态元素
仅给出最终文本不要解释任何内容
"""
# =============== 5. 小说目录 ===================
novel_directory_prompt = """\
根据以下最终小说设定
{final_novel_setting}
# =============== 4. 情节架构(三幕式悬念)===================
plot_architecture_prompt = """\
基于以下元素构建三幕式悬念架构
核心种子{core_seed}
角色体系{character_dynamics}
世界观{world_building}
请列出共 {number_of_chapters} 章的目录并保证每章的标题或简述能呼应主要冲突角色成长及暗线埋设
目录格式示例
第1章 <标题> - <简要说明>
第2章 <标题> - <简要说明>
...
{number_of_chapters} <标题> - <简要说明>
要求按以下结构设计
第一幕触发
- 日常状态中的异常征兆3处铺垫
- 引出故事展示主线暗线副线的开端
- 关键事件打破平衡的催化剂需改变至少3个角色的关系
- 错误抉择主角的认知局限导致的错误反应
每章可酌情加入一句简述引导读者了解本章看点直接输出文本不使用Markdown
第二幕对抗
- 剧情升级主线+副线的交叉点
- 双重压力外部障碍升级+内部挫折
- 虚假胜利看似解决实则深化危机的转折点
- 灵魂黑夜世界观认知颠覆时刻
第三幕解决
- 代价显现解决危机必须牺牲的核心价值
- 嵌套转折至少包含三层认知颠覆表面解新危机终极抉择
- 余波留下2个开放式悬念因子
每个阶段需包含3个关键转折点及其对应的伏笔回收方案
仅给出最终文本不要解释任何内容
"""
# =============== 5. 章节目录生成(悬念节奏曲线)===================
chapter_blueprint_prompt = """\
根据小说架构\n
{novel_architecture}
设计{number_of_chapters}章的节奏分布
1. 章节集群划分
- 每3-5章构成一个悬念单元包含完整的小高潮
- 单元之间设置"认知过山车"连续2章紧张1章缓冲
- 关键转折章需预留多视角铺垫
2. 每章需明确
- 章节定位角色/事件/主题等
- 核心悬念类型信息差/道德困境/时间压力等
- 情感基调迁移如从怀疑恐惧决绝
- 伏笔操作埋设/强化/回收
- 认知颠覆强度1-5
输出格式示例
第n章 - [标题]
本章定位[角色/事件/主题/...]
核心作用[推进/转折/揭示/...]
悬念密度[紧凑/渐进/爆发/...]
伏笔操作埋设(A线索)强化(B矛盾)...
认知颠覆
本章简述[一句话概括]
第n+1 - [标题]
本章定位[角色/事件/主题/...]
核心作用[推进/转折/揭示/...]
悬念密度[紧凑/渐进/爆发/...]
伏笔操作埋设(A线索)强化(B矛盾)...
认知颠覆
本章简述[一句话概括]
要求
- 使用精炼语言描述每章字数控制在100字以内
- 合理安排节奏确保整体悬念曲线的连贯性
- 在生成{number_of_chapters}章前不要出现结局章节
仅给出最终文本不要解释任何内容
"""
chunked_chapter_blueprint_prompt = """\
根据小说架构\n
{novel_architecture}
需要生成总共{number_of_chapters}章的节奏分布
当前已有章节目录若未空则说明是初始生成\n
{chapter_list}
现在请设计第{n}章到第{m}的节奏分布
1. 章节集群划分
- 每3-5章构成一个悬念单元包含完整的小高潮
- 单元之间设置"认知过山车"连续2章紧张1章缓冲
- 关键转折章需预留多视角铺垫
2. 每章需明确
- 章节定位角色/事件/主题等
- 核心悬念类型信息差/道德困境/时间压力等
- 情感基调迁移如从怀疑恐惧决绝
- 伏笔操作埋设/强化/回收
- 认知颠覆强度1-5
输出格式示例
第n章 - [标题]
本章定位[角色/事件/主题/...]
核心作用[推进/转折/揭示/...]
悬念密度[紧凑/渐进/爆发/...]
伏笔操作埋设(A线索)强化(B矛盾)...
认知颠覆
本章简述[一句话概括]
第n+1 - [标题]
本章定位[角色/事件/主题/...]
核心作用[推进/转折/揭示/...]
悬念密度[紧凑/渐进/爆发/...]
伏笔操作埋设(A线索)强化(B矛盾)...
认知颠覆
本章简述[一句话概括]
要求
- 使用精炼语言描述每章字数控制在100字以内
- 合理安排节奏确保整体悬念曲线的连贯性
- 在生成{number_of_chapters}章前不要出现结局章节
仅给出最终文本不要解释任何内容
"""
# =============== 6. 全局摘要更新 ===================
@@ -88,7 +217,14 @@ summary_prompt = """\
这是当前的全局摘要可为空
{global_summary}
请根据本章新增内容更新全局摘要保留既有重要信息同时融入新剧情要点勿剧透未来控制在不超过3000字的范围内语言简练流畅
请根据本章新增内容更新全局摘要
要求
- 保留既有重要信息同时融入新剧情要点
- 以简洁连贯的语言描述全书进展
- 客观描绘不展开联想或解释
- 字数控制在2000字以内
仅返回全局摘要文本不要解释任何内容
"""
# =============== 7. 角色状态更新 ===================
@@ -99,45 +235,157 @@ update_character_state_prompt = """\
这是当前的角色状态文档可为空
{old_state}
请更新角色状态内容包括
角色的物品能力或心理状态变化
角色间关系的最新进展或冲突
是否触发或加深了某些暗线或关键事件
任何新增角色或临时出场人物的基本信息
请更新角色状态内容格式
角色A属性
物品:
某物(道具)描述
XX长剑(武器)描述
...
能力
技能1描述
技能2描述
...
状态
身体状态
Buff/Debuff
心理状态描述
请直接在已有文档基础上进行增删语言尽量简洁有条理
主要角色间关系网
角色B描述
角色C描述
...
触发或加深的事件
事件1描述
事件2描述
...
角色B属性
物品
...
能力
...
状态
...
主要角色间关系网
...
触发或加深的事件
...
角色C属性
......
新出场角色
- 任何新增角色或临时出场人物的基本信息
要求
- 请直接在已有文档基础上进行增删
- 不改变原有结构语言尽量简洁有条理
仅返回更新后的角色状态文本不要解释任何内容
"""
# =============== 8. 章节大纲设计 ===================
chapter_outline_prompt = """\
这是当前小说的重要信息
- 小说设定{novel_setting}
- 角色状态{character_state}
- 全局摘要{global_summary}
# =============== 8. 章节正文写作 ===================
我们即将写第 {novel_number} 标题{chapter_title}简述若有{chapter_brief}
# 8.1 第一章草稿提示
first_chapter_draft_prompt = """\
即将创作 {novel_number} {chapter_title}
本章定位{chapter_role}
核心作用{chapter_purpose}
悬念密度{suspense_level}
伏笔操作{foreshadowing}
认知颠覆{plot_twist_level}
本章简述{chapter_summary}
请按照以下思路设计本章大纲
1. 本章的主要冲突或情节目标如何与标题呼应
2. 出场角色及其动机对剧情走向的推动或阻碍
3. 暗线或伏笔如何有所进展或被揭示
4. 本章结尾的悬念或转折如何为下一章做铺垫
可用元素
- 核心人物(可能未指定){characters_involved}
- 关键道具(可能未指定){key_items}
- 空间坐标(可能未指定){scene_location}
- 时间压力(可能未指定){time_constraint}
请以简要分点方式输出不要使用Markdown格式
参考文档
- 小说设定
{novel_setting}
请完成第 {novel_number} 章的正文至少设计下方2个具有动态张力的场景
1. 对话场景
- 潜台词冲突表面谈论A实际博弈B
- 权力关系变化通过非对称对话长度体现
- 至少1处双关语暗示未来危机
2. 动作场景
- 环境交互细节至少3个感官描写
- 节奏控制短句加速+比喻减速
- 动作揭示人物隐藏特质
3. 心理场景
- 认知失调的具体表现行为矛盾
- 隐喻系统的运用连接世界观符号
- 决策前的价值天平描写
文末设置一个"钩链转折"结尾时回收旧悬念/创造新悬念/抛出新危机/颠覆某个认知/神转折等
格式要求
- 仅返回章节正文文本
- 不使用分章节小标题
- 不要使用markdown格式
额外指导(可能未指定){user_guidance}
"""
# =============== 9. 章节正文写作 ===================
chapter_write_prompt = """\
以下信息供你参考
1. 小说设定{novel_setting}
2. 角色状态{character_state}
3. 全局摘要{global_summary}
4. 本章大纲{chapter_outline}
# 8.2 后续章节草稿提示
next_chapter_draft_prompt = """\
参考文档
- 小说设定
{novel_setting}
请写出第 {novel_number} 章的正文标题为{chapter_title}
字数不少于 {word_number} 与标题和简述相呼应
保持连贯叙述可增加环境心理对话等细节描写
适度呼应前文暗线或角色矛盾为后续发展留出悬念
- 全局摘
{global_summary}
直接输出正文内容不使用分章节小标题章节末模仿正常小说中断或转场
- 角色状态
{character_state}
本地知识库检索到的片段
{context_excerpt}
即将创作 {novel_number} {chapter_title}
本章定位{chapter_role}
核心作用{chapter_purpose}
悬念密度{suspense_level}
伏笔操作{foreshadowing}
认知颠覆{plot_twist_level}
本章简述{chapter_summary}
可用元素
- 核心人物(可能未指定){characters_involved}
- 关键道具(可能未指定){key_items}
- 空间坐标(可能未指定){scene_location}
- 时间压力(可能未指定){time_constraint}
前章结尾段
{previous_chapter_excerpt}
请依据前章结尾片段继续完成第 {novel_number} 章的正文至少设计下方2个具有动态张力的场景
1. 对话场景
- 潜台词冲突表面谈论A实际博弈B
- 权力关系变化通过非对称对话长度体现
- 至少1处双关语暗示未来危机
2. 动作场景
- 环境交互细节至少3个感官描写
- 节奏控制短句加速+比喻减速
- 动作揭示人物隐藏特质
3. 心理场景
- 认知失调的具体表现行为矛盾
- 隐喻系统的运用连接世界观符号
- 决策前的价值天平描写
文末设置一个"钩链转折"结尾时回收旧悬念/创造新悬念/抛出新危机/颠覆某个认知/神转折等
格式要求
- 仅返回章节正文文本
- 不使用分章节小标题
- 不要使用markdown格式
额外指导(可能未指定){user_guidance}
"""
+37
View File
@@ -0,0 +1,37 @@
# tooltips.py
# -*- coding: utf-8 -*-
tooltips = {
"api_key": "在这里填写你的API Key。如果使用OpenAI官方接口,请在 https://platform.openai.com/account/api-keys 获取。",
"base_url": "模型的接口地址。若使用OpenAI官方:https://api.openai.com/v1。若使用Ollama本地部署,则类似 http://localhost:11434/v1。",
"interface_format": "指定LLM接口兼容格式,可选DeepSeek\OpenAI\Ollama\ML Studio等。\n\n注意:"+
"OpenAI 兼容是指的可以通过该标准请求的任何接口,不是只允许使用api.openai.com接口\n"+
"例如Ollama接口格式也兼容OpenAI,可以无需修改直接使用\n"+
"ML Studio接口格式与OpenAI接口格式也一致。",
"model_name": "要使用的模型名称,例如deepseek-reasoner、gpt-4o等。如果是Ollama等,请填写你下载好的本地模型名。",
"temperature": "生成文本的随机度。数值越大越具有发散性,越小越严谨。",
"max_tokens": "限制单次生成的最大Token数。范围1~100000,请根据模型上下文及需求填写合适值。\n"+
"以下是一些常见模型的最大值:\n"+
"o1100,000\n"+
"o1-mini65,536\n"+
"gpt-4o16384\n"+
"gpt-4o-mini16384\n"+
"deepseek-reasoner8192\n"+
"deepseek-chat4096\n",
"embedding_api_key": "调用Embedding模型时所需的API Key。",
"embedding_interface_format": "Embedding模型接口风格,比如OpenAI或Ollama。",
"embedding_url": "Embedding模型接口地址。",
"embedding_model_name": "Embedding模型名称,如text-embedding-ada-002。",
"embedding_retrieval_k": "向量检索时返回的Top-K结果数量。",
"topic": "小说的大致主题或主要故事背景描述。",
"genre": "小说的题材类型,如玄幻、都市、科幻等。",
"num_chapters": "小说期望的章节总数。",
"word_number": "每章的目标字数。",
"filepath": "生成文件存储的根目录路径。所有txt文件、向量库等放在该目录下。",
"chapter_num": "当前正在处理的章节号,用于生成草稿或定稿操作。",
"user_guidance": "为本章提供的一些额外指令或写作引导。",
"characters_involved": "本章需要重点描写或影响剧情的角色名单。",
"key_items": "在本章中出现的重要道具、线索或物品。",
"scene_location": "本章主要发生的地点或场景描述。",
"time_constraint": "本章剧情中涉及的时间压力或时限设置。"
}
+940 -991
View File
File diff suppressed because it is too large Load Diff