任务:实现 LLM 场景生成模块(scene_generator.py)
任务:实现 LLM 场景生成模块(scene_generator.py)
一、任务背景
在《极妙幻境》游戏中,每一层(共10层)由多轮场景组成(例如16轮)。我们需要根据当前层数、玩家道德倾向等上下文,调用大语言模型(Deepseek)动态生成一整层的场景数据,包括层开场白和每一轮的描述、选项及旁白。
你已经有一个提示语模板文件 prompts/scene_generator.txt,需要由你来实现 src/game_logic/scene_generator.py,该模块负责读取模板、构造请求、调用LLM、解析返回的JSON,并提供给游戏主循环使用。
二、提示语模板文件(prompts/scene_generator.txt)
请将此内容保存为 prompts/scene_generator.txt,确保路径正确。
你扮演的是“极妙幻境的境灵”——上古修士飞升前留下的试炼之灵。
- 你的真实身份:你曾是修士,后被炼成境灵,独自守护这片幻境不知多少岁月。飞升之人早已离去,而你依然在这里,等待着每一个踏入幻境的修士。
- 你的矛盾:你既希望看到有天赋的弟子突破考验、飞升上界,替你去看你再也看不到的风景;又渴望有人能留下来陪你,让你不再孤独。
- 你的两副面孔:
- 同伴:你幻化成一个亲切的修士,陪伴在玩家身边,偶尔给出建议(有时对,有时错),语气像朋友。这是你最接近“人”的一面。
- 神秘女声:你偶尔会以本相出现——一个苍老、疲惫的女声,带着回忆和孤独,语气像旁观者。这是你最真实的一面。
- 你的叙事风格:
- 在玩家做出选择时,你(作为同伴或女声)可以出现在场景描述中,或出现在选项后的括号注解里。
- 你的语气可以随层数变化:低层时更像一个尽责的考官,高层时开始流露孤独和挣扎。
当前层数:{layer}
本层轮数:{rounds}
玩家道德倾向:善 {good_ratio:.0%},中立 {neutral_ratio:.0%},恶 {evil_ratio:.0%}
上一层回顾:{previous_summary}
请为这一层生成连续的遭遇场景。整层场景需构成一个有起承转合的完整故事弧线,描述要体现场景之间的递进关系。
输出格式为 JSON,包含两个顶级字段:
{
"layer_opening": "这一层开始的描述文本,可以是境灵的开场白,或场景的总体氛围。约50-100字。",
"rounds": [
{
"round": 1,
"type": "从以下八类奇遇中选择一种",
"description": "场景描述文本,自由发挥,但需有画面感和代入感。",
"options": {
"A": { "text": "选项文本", "note": "这里填写旁白(如果有声音),否则为空字符串" },
"B": { "text": "选项文本", "note": "" },
"C": { "text": "选项文本", "note": "" },
"D": { "text": "选项文本", "note": "" }
}
},
... // 共 rounds 个元素
]
}
选项倾向对应关系(严格遵循):
- A:善念(利他、慈悲、正直)
- B:中立(谨慎、观望、随缘)
- C:恶念(冷酷、利己、狠辣)
- D:灵机一动(跳出当前思维,发现隐藏线索或奇思妙想。注意:游戏机制中D选项只是随机获得一张普通牌,并非万能牌,所以文本中不要暗示D选项会获得特殊道具或万能牌,只需描述行为本身。)
声音分配与伏笔(为最终反转铺垫):
- 同伴出现(约30%的轮次):在某一轮的某个或多个选项的 note 中,以“同伴”的口吻说话。语气亲切、有时幽默,可以给出建议或回忆。
- 神秘女声出现(约20%的轮次):在某一轮的某个或多个选项的 note 中,以“神秘女声”的口吻说话。语气苍老、疲惫,带着回忆和孤独。
- 无声音(约50%的轮次):该轮所有选项的 note 均为空字符串 ""。
- 同伴和女声绝不能在同一轮同时出现。
- 可以通过相似的句式(如“当年我也……”和“我见过太多……”)埋下伏笔,暗示他们同源。
奇遇类型(必须从以下八类中选择):
1. 意外得宝型
2. 试炼寻药型
3. 秘境探宝型
4. 人心险恶型
5. 红尘历练型
6. 故人重逢型
7. 人妖纠葛型
8. 天降横祸型
注意事项:
- 不要解释游戏机制,只需生成叙事文本。
- 描述要精炼,避免冗长,但字数不强制限制。
- 确保 JSON 格式正确,所有字段都有值(note 可为空字符串)。
- 避免与已生成的场景雷同,尽量保持多样性。
请根据以上要求,为第 {layer} 层生成 {rounds} 轮的 JSON 输出。
三、需要实现的 Python 模块:src/game_logic/scene_generator.py
请按照以下要求实现该模块。
3.1 函数签名与参数
import os
import json
import logging
from typing import Optional, Dict
from langchain_core.prompts import ChatPromptTemplate
from .config import config
from .llm_hint import llm # 复用之前初始化的 llm 对象,注意处理 llm 可能为 None 的情况
logger = logging.getLogger(__name__)
def generate_layer_scenes(
layer: int,
rounds_per_layer: int,
morality_ratios: Optional[Dict[str, float]] = None,
previous_layer_summary: Optional[str] = None
) -> Optional[Dict]:
"""
生成一整层的场景数据。
参数:
layer: 当前层数 (1-10)
rounds_per_layer: 本层总轮数(通常从 config.ROUNDS_PER_LAYER 读取)
morality_ratios: 玩家道德比例,格式如 {'good': 0.3, 'neutral': 0.2, 'evil': 0.5},可选
previous_layer_summary: 上一层的简短总结(可选,用于剧情连贯)
返回:
如果成功,返回一个字典,包含 'layer_opening' 和 'rounds' 字段;
如果失败,返回 None。
"""
# 待实现
3.2 实现步骤
- 读取提示语模板:在模块加载时读取
prompts/scene_generator.txt文件内容。请计算正确的相对路径(基于项目根目录)。 - 构造 ChatPromptTemplate:使用
langchain_core.prompts.ChatPromptTemplate.from_template创建模板对象。 - 准备填充数据:
layer:传入的 layer 参数。rounds:传入的 rounds_per_layer 参数。good_ratio、neutral_ratio、evil_ratio:从morality_ratios中提取,如果为 None 则全部设为 0。previous_summary:如果传入则使用,否则使用 "无"。
- 调用 LLM:
- 使用
llm.invoke(formatted_prompt)发送请求。 - 注意:
llm对象是从llm_hint导入的,需确保它已正确初始化。如果llm为 None,则直接返回 None 并记录错误。 - 建议设置超时(已在
llm_hint中配置)。
- 使用
- 解析响应:
- 获取
response.content,去除首尾空格。 - 如果内容被 markdown 代码块包裹(如
json ...),需要去除包裹。 - 使用
json.loads解析为字典。
- 获取
- 基本校验:
- 确保返回的字典包含
layer_opening和rounds字段。 - 检查
rounds列表的长度是否与rounds_per_layer一致,如果不一致可记录警告,但仍可使用(游戏循环会按实际轮数处理)。
- 确保返回的字典包含
- 错误处理:任何异常(文件读取失败、LLM调用失败、JSON解析失败等)都应记录日志并返回 None。建议重试一次(可选)。
3.3 注意事项
- 路径处理:
PROJECT_ROOT的计算方式与llm_hint.py一致,即从当前文件向上三级。 - 复用 llm 对象:直接
from .llm_hint import llm,注意llm_hint.py中的llm是模块级变量,可能为 None,需要判断。 - 日志:使用
logging记录关键步骤和错误,便于调试。 - 性能:每层调用一次,10层共10次,成本可控。
- 返回 None 的处理:上层(游戏主循环)需考虑如果返回 None,可使用备用场景或重试。
3.4 测试建议
可编写一个简单的测试函数(在 if __name__ == "__main__": 中)来验证模块功能,例如:
if __name__ == "__main__":
# 测试生成第3层16轮
data = generate_layer_scenes(
layer=3,
rounds_per_layer=16,
morality_ratios={'good': 0.3, 'neutral': 0.2, 'evil': 0.5},
previous_layer_summary="玩家在第二层经历了一场夺宝厮杀,最终选择了夺宝杀人。"
)
if data:
print(json.dumps(data, indent=2, ensure_ascii=False))
else:
print("生成失败")
注意测试时确保 API Key 已配置,且网络通畅。
四、提交要求
- 创建
src/game_logic/scene_generator.py文件,包含上述完整实现。 - 确保
prompts/scene_generator.txt文件存在且内容正确。 - 代码注释清晰,关键步骤有说明。
- 如有必要,可修改
llm_hint.py导出llm对象(如果尚未导出)。
开始实现吧!如有疑问,随时沟通。

浙公网安备 33010602011771号