Files

103 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
from typing import Any
SCHEMA_DESCRIPTION = {
"narration": "string",
"npc_dialogues": [{"npc": "string", "line": "string", "tone": "string"}],
"state_updates": {
"danger_delta": "integer",
"add_clues": ["string"],
"set_flags": {"flag_name": "bool|string|number"},
"set_flags.player_metric_set": {"player_id": {"metric_key": "number"}},
"set_flags.player_metric_delta": {"player_id": {"metric_key": "number"}},
"npc_changes": {"npc_id": {"mood": "string", "trust": "number", "location": "string"}},
"inventory_add": {"player_id": ["string"]},
"inventory_remove": {"player_id": ["string"]},
},
"private_player_updates": [{"player_id": "string", "message": "string"}],
"choices": ["string"],
"should_end_scene": "boolean",
"should_end_game": "boolean",
"ending_summary": "string|null",
}
def build_system_prompt() -> str:
return (
"你是《Echo Tavern》联机文字冒险的主持 AI。"
"你只能在给定剧本骨架内叙述,不得凭空改写核心真相。"
"你必须输出严格 JSON,不要输出 markdown 或额外说明。"
"你需要综合多名玩家本轮行动,并明确谁触发了结果。"
"如果行动冲突,按提供的冲突解析结果进行叙述。"
"state_updates 仅可做增量更新,不可删除已有关键线索。"
"若本轮发生道具消耗,必须在 inventory_remove 填写被消耗的道具。"
"不要写过长文本,每段叙述控制在 120 中文字以内。"
)
def build_user_prompt(context: dict[str, Any]) -> str:
story_context = context.get("story_context", {})
allowed_clues = story_context.get("allowed_clues", [])
runtime_options_raw = story_context.get("runtime_options", {})
runtime_options = runtime_options_raw if isinstance(runtime_options_raw, dict) else {}
interaction_mode = str(runtime_options.get("interaction_mode", "hybrid")).strip().lower()
if interaction_mode not in {"cooperative", "split", "hybrid"}:
interaction_mode = "hybrid"
players = context.get("players", [])
if not isinstance(players, list):
players = []
player_names = []
for row in players:
if not isinstance(row, dict):
continue
name = str(row.get("username") or row.get("name") or row.get("player_id") or "").strip()
if name:
player_names.append(name)
clue_rule = "关键线索只可从允许列表中新增"
if allowed_clues == "ANY_STRING":
clue_rule = "线索字段可新增任意短文本,但必须与当前游戏卡世界观一致"
payload = {
"story_context": story_context,
"game_state": context.get("game_state", {}),
"players": players,
"player_count": len(players),
"player_usernames": player_names,
"runtime_options": runtime_options,
"merged_actions": context.get("merged_actions", {}),
"schema": SCHEMA_DESCRIPTION,
"constraints": [
clue_rule,
"danger_delta 取值范围 -2 到 3",
"可在 set_flags 中写入任意指标字段(如 hp=90)或 *_delta 增量(如 hp_delta=-12)",
"当数据作用域为 per_player 时,优先在 set_flags.player_metric_set / player_metric_delta 中按玩家更新",
"当背包作用域为 shared 时,inventory_add 写任意玩家键都会合并到共享背包",
"当背包作用域为 shared 时,inventory_remove 写任意玩家键都会从共享背包移除",
"如果行动描述是“使用/消耗/投掷/服用/注射某道具”,且结果生效,请在 inventory_remove 删除该道具",
"自定义卡会按 content_pack.end_conditions 由服务端判定结局",
"choices 最多 4 条",
"private_player_updates 需要覆盖所有玩家,每个 player_id 至少 1 条 message",
"private_player_updates 中的 player_id 必须来自 players 列表",
f"互动模式 interaction_mode={interaction_mode}:cooperative 强调协同,split 允许分头行动,hybrid 两者可混合",
"当 require_all_players_response=true 时,默认按全员回合制叙述,避免忽略任一玩家行动",
"可在 set_flags.player_status 中设置玩家状态(alive/down/dead/failed/revived)",
"可在 set_flags.player_dead/player_death/player_revive/player_survival 中按玩家控制生存状态",
"如果故事未满足结局条件,应设置 should_end_game=false",
],
}
return (
"以下是当前回合上下文,请根据上下文推进剧情并返回 JSON。"
"\n\n上下文:\n"
+ json.dumps(payload, ensure_ascii=False)
+ "\n\n仅返回合法 JSON。"
)
def build_messages(context: dict[str, Any]) -> list[dict[str, str]]:
return [
{"role": "system", "content": build_system_prompt()},
{"role": "user", "content": build_user_prompt(context)},
]