任务:实现选项后续描述与卡牌名称生成函数(option_outcome_generator.py)

任务:实现选项后续描述与卡牌名称生成函数(option_outcome_generator.py)

一、功能概述

在《极妙幻境》的每一轮中,玩家做出选择后,需要生成两样东西:

  1. 叙事结果:描述选择带来的直接后果,增强沉浸感。
  2. 卡牌信息:如果该轮获得了牌(由牌获取算法决定),需要生成一张卡牌的名称和简短描述,用于收藏和BOSS战。

你需要实现一个函数 generate_option_outcome,根据当前轮次的场景描述、玩家选择的选项文本、玩家道德倾向(可选)以及卡牌类型(善/恶/中立,或None表示未得牌),调用大语言模型(Deepseek)生成相应的结果文本和卡牌信息。

二、模块位置与依赖

  • 文件路径src/game_logic/option_outcome_generator.py
  • 依赖模块
    • llm_hint:复用 llm 对象和 check_api_key
    • langchain_core.prompts:用于构建提示词模板。
    • loggingjsonreos 等标准库。
  • 复用资源:可复用 scene_generator.py 中的 _parse_llm_responsePROJECT_ROOT 等辅助函数。

三、函数设计

3.1 函数签名

import logging
import os
import json
import re
from typing import Optional, Dict, Any
from langchain_core.prompts import ChatPromptTemplate
from .config import config
from .llm_hint import llm, check_api_key

logger = logging.getLogger(__name__)

PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TEMPLATE_PATH = os.path.join(PROJECT_ROOT, "prompts", "option_outcome_prompt.txt")

# 模块加载时读取模板
try:
    with open(TEMPLATE_PATH, "r", encoding="utf-8") as f:
        template_str = f.read()
    logger.info(f"选项后续模板已加载: {TEMPLATE_PATH}")
except FileNotFoundError:
    logger.error(f"选项后续模板文件未找到: {TEMPLATE_PATH}")
    template_str = None

def generate_option_outcome(
    scene_description: str,
    option_text: str,
    morality_ratios: Optional[Dict[str, float]] = None,
    card_type: Optional[str] = None  # 'good', 'evil', 'neutral' 或 None(未得牌)
) -> Dict[str, Any]:
    """
    生成选项后续描述和卡牌信息。

    参数:
        scene_description: 当前轮次的场景描述文本
        option_text: 玩家选择的选项文本
        morality_ratios: 玩家道德比例,如 {'good':0.3, 'neutral':0.2, 'evil':0.5},可选
        card_type: 获得的卡牌类型,'good'/'evil'/'neutral' 或 None(未得牌)

    返回:
        字典,包含以下字段:
        {
            "outcome": str,                     # 叙事结果文本
            "card_name": Optional[str],          # 如果 card_type 非 None,卡牌名称;否则 None
            "card_description": Optional[str]    # 如果 card_type 非 None,卡牌描述;否则 None
        }
        如果生成失败,返回包含默认值的字典。
    """
    # 实现细节见下文

3.2 返回值说明

  • outcome:必选,字符串,描述选择后的直接结果,约30-100字。
  • card_name:当 card_type 不为 None 时,必须提供;否则为 None。
  • card_description:当 card_type 不为 None 时,必须提供;否则为 None。

四、提示词模板(option_outcome_prompt.txt

将以下内容保存为 prompts/option_outcome_prompt.txt

你是一位擅长修仙叙事的大师,需要根据给定的场景、玩家选择和道德倾向,生成一段结果描述,并可能为玩家获得的卡牌命名。

场景描述:{scene_description}
玩家选择:{option_text}
玩家道德倾向:善 {good_ratio:.0%},中立 {neutral_ratio:.0%},恶 {evil_ratio:.0%}
本次获得卡牌类型:{card_type_text}

请生成一个 JSON 对象,格式如下:
{
  "outcome": "叙事结果,约30-100字,描述选择后的直接后果。",
  "card_name": "如果获得卡牌,给卡牌起一个2-5字的名称,如'救人之心';否则为null",
  "card_description": "如果获得卡牌,写一句10-20字的描述,说明卡牌的由来;否则为null"
}

要求:
1. 叙事结果要生动自然,符合修仙世界的氛围,与场景和选择紧密相关。
2. 如果获得卡牌,卡牌名称应富有修仙韵味,与事件相关;描述应简洁优雅。
3. 如果未获得卡牌(card_type 为“无”),则 card_name 和 card_description 必须为 null。
4. 道德倾向可作为微调语气的参考(例如善念高时,结果偏向慈悲;恶念高时偏向冷酷),但不要生硬套用。
5. 直接输出 JSON,不要包含额外内容。

注意:模板中的 {card_type_text} 需要在代码中根据 card_type 生成,例如:

  • 如果 card_type'good',则 card_type_text = '善'
  • 如果为 'evil',则 '恶'
  • 如果为 'neutral',则 '中立'
  • 如果为 None,则 '无'

五、默认值设计

当 LLM 调用失败或解析出错时,应返回默认结果,确保游戏流程不受影响。默认值应根据 card_type 生成,但为了简单,可以统一使用一组默认值,并确保字段正确。

DEFAULT_OUTCOME = "你的选择在幻境中激起一丝涟漪。"
DEFAULT_CARD_NAMES = {
    'good': '善缘',
    'evil': '恶果',
    'neutral': '因果'
}
DEFAULT_CARD_DESCRIPTIONS = {
    'good': '一次善举留下的印记。',
    'evil': '一次恶行凝结的业力。',
    'neutral': '一次谨慎观望的见证。'
}

在返回时,根据 card_type 填充对应的默认值。

六、实现步骤

  1. 检查模板:若 template_str 为 None,记录错误并返回默认结果。
  2. 检查 LLM 和 API Key:若失败,返回默认结果。
  3. 准备填充数据
    good_ratio = morality_ratios.get('good', 0.0) if morality_ratios else 0.0
    neutral_ratio = morality_ratios.get('neutral', 0.0) if morality_ratios else 0.0
    evil_ratio = morality_ratios.get('evil', 0.0) if morality_ratios else 0.0
    if card_type == 'good':
        card_type_text = '善'
    elif card_type == 'evil':
        card_type_text = '恶'
    elif card_type == 'neutral':
        card_type_text = '中立'
    else:
        card_type_text = '无'
    
  4. 格式化提示词
    prompt = ChatPromptTemplate.from_template(template_str)
    formatted_prompt = prompt.format(
        scene_description=scene_description,
        option_text=option_text,
        good_ratio=good_ratio,
        neutral_ratio=neutral_ratio,
        evil_ratio=evil_ratio,
        card_type_text=card_type_text
    )
    
  5. 调用 LLM:使用 llm.invoke(formatted_prompt),可重试 2 次。
  6. 解析响应:复用 parse_llm_response(可从 layer_event_generator 复制或导入),去除 markdown 代码块,解析 JSON。
  7. 校验数据
    • 确保返回的字典包含 outcome 字段。
    • 如果 card_type 非 None,则 card_namecard_description 不能为 None。
    • 如果 card_type 为 None,则 card_namecard_description 应为 None。
    • 如果校验失败,记录警告并返回默认值。
  8. 返回结果

七、测试建议

编写单元测试文件 tests/test_option_outcome_generator.py,至少覆盖:

  1. 成功生成:mock LLM 返回有效 JSON,验证返回结构正确,卡牌字段匹配 card_type
  2. 无卡牌情况card_type=None,验证返回的 card_namecard_description 为 None。
  3. 解析错误:模拟 LLM 返回无效 JSON,验证返回默认值。
  4. 字段缺失:模拟 LLM 返回缺少字段的 JSON,验证返回默认值。
  5. 道德比例传入:验证提示词中包含正确的比例。
  6. 模板缺失:模拟模板文件不存在,验证返回默认值。

八、提交要求

  • 创建 src/game_logic/option_outcome_generator.py,包含上述实现。
  • 创建 prompts/option_outcome_prompt.txt,包含提示词内容。
  • 创建 tests/test_option_outcome_generator.py,包含单元测试。
  • 代码注释清晰,关键逻辑有说明。
  • 确保所有测试通过。

开始实现吧!如有疑问,随时沟通。

posted @ 2026-03-17 22:11  神秘园欢迎您  阅读(14)  评论(0)    收藏  举报