refactor story teller
This commit is contained in:
@@ -90,7 +90,7 @@ class Battle(InstantAction):
|
||||
# 生成战斗小故事(同步调用,与其他动作保持一致)
|
||||
target = self._get_target(avatar_name)
|
||||
start_text = self._start_event_content if hasattr(self, '_start_event_content') else result_event.content
|
||||
story = StoryTeller.tell_from_actors(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
|
||||
story = StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
|
||||
story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True)
|
||||
|
||||
return [result_event, story_event]
|
||||
|
||||
@@ -139,7 +139,7 @@ class Breakthrough(TimedAction):
|
||||
|
||||
# 故事参与者:本体 +(可选)相关角色
|
||||
prompt = TribulationSelector.get_story_prompt(str(calamity))
|
||||
story = StoryTeller.tell_from_actors(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, self._calamity_other, prompt=prompt)
|
||||
story = StoryTeller.tell_story(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, self._calamity_other, prompt=prompt)
|
||||
events.append(Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True))
|
||||
return events
|
||||
|
||||
|
||||
@@ -478,7 +478,7 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
|
||||
base_event = Event(month_at_finish, event_text, related_avatars=related_avatars, is_major=True)
|
||||
|
||||
# 生成故事事件
|
||||
story = await StoryTeller.tell_from_actors_async(event_text, res_text, *actors_for_story, prompt=story_prompt)
|
||||
story = await StoryTeller.tell_story_async(event_text, res_text, *actors_for_story, prompt=story_prompt)
|
||||
story_event = Event(month_at_finish, story, related_avatars=related_avatars, is_story=True)
|
||||
|
||||
# 返回基础事件和故事事件
|
||||
|
||||
@@ -109,9 +109,9 @@ class DualCultivation(MutualAction):
|
||||
result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id], is_major=True)
|
||||
events.append(result_event)
|
||||
|
||||
# 生成恋爱/双修小故事:使用 StoryTeller 便捷方法
|
||||
# 生成恋爱/双修小故事
|
||||
start_text = self._start_event_content or result_event.content
|
||||
story = StoryTeller.tell_from_actors(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
|
||||
story = StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
|
||||
story_event = Event(self.world.month_stamp, story, related_avatars=[self.avatar.id, target.id], is_story=True)
|
||||
events.append(story_event)
|
||||
else:
|
||||
|
||||
@@ -98,7 +98,7 @@ class GiftSpiritStone(MutualAction):
|
||||
# 生成赠送小故事
|
||||
from src.classes.story_teller import StoryTeller
|
||||
start_text = self._start_event_content or result_event.content
|
||||
story = StoryTeller.tell_from_actors(
|
||||
story = StoryTeller.tell_story(
|
||||
start_text,
|
||||
result_text,
|
||||
self.avatar,
|
||||
|
||||
@@ -110,7 +110,7 @@ class Impart(MutualAction):
|
||||
# 生成师徒传道小故事
|
||||
from src.classes.story_teller import StoryTeller
|
||||
start_text = self._start_event_content or result_event.content
|
||||
story = StoryTeller.tell_from_actors(
|
||||
story = StoryTeller.tell_story(
|
||||
start_text,
|
||||
result_text,
|
||||
self.avatar,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, TYPE_CHECKING
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from src.utils.config import CONFIG
|
||||
@@ -30,26 +29,48 @@ class StoryTeller:
|
||||
"""
|
||||
故事生成器:基于模板与 LLM,将给定事件扩展为简短的小故事。
|
||||
"""
|
||||
|
||||
TEMPLATE_PATH = CONFIG.paths.templates / "story.txt"
|
||||
|
||||
@staticmethod
|
||||
def build_avatar_infos(*avatars: "Avatar") -> Dict[str, dict]:
|
||||
def _build_avatar_infos(*actors: "Avatar") -> Dict[str, dict]:
|
||||
"""
|
||||
将若干角色信息组织为 {name: info_dict} 映射,供故事模板使用。
|
||||
战斗/小故事使用详细信息(dict 版)。
|
||||
构建角色信息字典。
|
||||
- 双人故事:第一个角色使用 expanded_info(包含共同事件),第二个使用普通 info
|
||||
- 单人故事:使用 expanded_info
|
||||
"""
|
||||
infos: Dict[str, dict] = {}
|
||||
for av in avatars:
|
||||
if av is None:
|
||||
continue
|
||||
infos[av.name] = av.get_info(detailed=True)
|
||||
return infos
|
||||
non_null = [a for a in actors if a is not None]
|
||||
avatar_infos: Dict[str, dict] = {}
|
||||
|
||||
if len(non_null) >= 2:
|
||||
avatar_infos[non_null[0].name] = non_null[0].get_expanded_info(other_avatar=non_null[1], detailed=True)
|
||||
avatar_infos[non_null[1].name] = non_null[1].get_info(detailed=True)
|
||||
elif non_null:
|
||||
avatar_infos[non_null[0].name] = non_null[0].get_expanded_info(detailed=True)
|
||||
|
||||
return avatar_infos
|
||||
|
||||
@staticmethod
|
||||
def _build_template_data(event: str, res: str, avatar_infos: Dict[str, dict], prompt: str) -> dict:
|
||||
"""构建模板渲染所需的数据字典"""
|
||||
return {
|
||||
"avatar_infos": avatar_infos,
|
||||
"event": event,
|
||||
"res": res,
|
||||
"style": random.choice(story_styles),
|
||||
"story_prompt": prompt,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _make_fallback_story(event: str, res: str, style: str) -> str:
|
||||
"""生成降级文案"""
|
||||
return f"{event}。{res}。{style}"
|
||||
|
||||
@staticmethod
|
||||
def tell_story(event: str, res: str, *actors: "Avatar", prompt: str = "") -> str:
|
||||
"""
|
||||
基于 `static/templates/story.txt` 模板生成小故事。
|
||||
始终使用 fast 模式以提升速度。
|
||||
失败时返回降级版文案,避免中断流程。
|
||||
生成小故事(同步版本)。
|
||||
基于 `static/templates/story.txt` 模板,失败时返回降级文案。
|
||||
|
||||
Args:
|
||||
event: 事件描述
|
||||
@@ -57,41 +78,24 @@ class StoryTeller:
|
||||
*actors: 参与的角色(1-2个)
|
||||
prompt: 可选的故事提示词
|
||||
"""
|
||||
# 构建 avatar_infos,第一个 avatar 使用 expanded_info
|
||||
non_null = [a for a in actors if a is not None]
|
||||
avatar_infos: Dict[str, dict] = {}
|
||||
avatar_infos = StoryTeller._build_avatar_infos(*actors)
|
||||
infos = StoryTeller._build_template_data(event, res, avatar_infos, prompt)
|
||||
|
||||
if len(non_null) >= 2:
|
||||
# 双人故事:第一个用 expanded_info(包含共同事件),第二个用 detailed info
|
||||
avatar_infos[non_null[0].name] = non_null[0].get_expanded_info(other_avatar=non_null[1], detailed=True)
|
||||
avatar_infos[non_null[1].name] = non_null[1].get_info(detailed=True)
|
||||
elif non_null:
|
||||
# 单人故事:直接用 expanded_info
|
||||
avatar_infos[non_null[0].name] = non_null[0].get_expanded_info(detailed=True)
|
||||
|
||||
template_path = CONFIG.paths.templates / "story.txt"
|
||||
infos = {
|
||||
"avatar_infos": avatar_infos,
|
||||
"event": event,
|
||||
"res": res,
|
||||
"style": random.choice(story_styles),
|
||||
"story_prompt": prompt,
|
||||
}
|
||||
try:
|
||||
data = get_prompt_and_call_llm(template_path, infos, mode="fast")
|
||||
data = get_prompt_and_call_llm(StoryTeller.TEMPLATE_PATH, infos, mode="fast")
|
||||
story = data.get("story", "").strip()
|
||||
if story:
|
||||
return story
|
||||
except Exception:
|
||||
pass
|
||||
# 降级文案(不中断主流程)
|
||||
style = infos.get("style", "")
|
||||
return f"{event}。{res}。{style}"
|
||||
|
||||
return StoryTeller._make_fallback_story(event, res, infos["style"])
|
||||
|
||||
@staticmethod
|
||||
async def tell_story_async(event: str, res: str, *actors: "Avatar", prompt: str = "") -> str:
|
||||
"""
|
||||
异步版本:生成小故事,失败时返回降级文案。
|
||||
生成小故事(异步版本)。
|
||||
基于 `static/templates/story.txt` 模板,失败时返回降级文案。
|
||||
|
||||
Args:
|
||||
event: 事件描述
|
||||
@@ -99,56 +103,17 @@ class StoryTeller:
|
||||
*actors: 参与的角色(1-2个)
|
||||
prompt: 可选的故事提示词
|
||||
"""
|
||||
# 构建 avatar_infos,第一个 avatar 使用 expanded_info
|
||||
non_null = [a for a in actors if a is not None]
|
||||
avatar_infos: Dict[str, dict] = {}
|
||||
avatar_infos = StoryTeller._build_avatar_infos(*actors)
|
||||
infos = StoryTeller._build_template_data(event, res, avatar_infos, prompt)
|
||||
|
||||
if len(non_null) >= 2:
|
||||
# 双人故事:第一个用 expanded_info(包含共同事件),第二个用 detailed info
|
||||
avatar_infos[non_null[0].name] = non_null[0].get_expanded_info(other_avatar=non_null[1], detailed=True)
|
||||
avatar_infos[non_null[1].name] = non_null[1].get_info(detailed=True)
|
||||
elif non_null:
|
||||
# 单人故事:直接用 expanded_info
|
||||
avatar_infos[non_null[0].name] = non_null[0].get_expanded_info(detailed=True)
|
||||
|
||||
template_path = CONFIG.paths.templates / "story.txt"
|
||||
infos = {
|
||||
"avatar_infos": avatar_infos,
|
||||
"event": event,
|
||||
"res": res,
|
||||
"style": random.choice(story_styles),
|
||||
"story_prompt": prompt,
|
||||
}
|
||||
try:
|
||||
data = await get_prompt_and_call_llm_async(template_path, infos, mode="fast")
|
||||
data = await get_prompt_and_call_llm_async(StoryTeller.TEMPLATE_PATH, infos, mode="fast")
|
||||
story = str(data.get("story", "")).strip()
|
||||
if story:
|
||||
return story
|
||||
except Exception:
|
||||
pass
|
||||
style = infos.get("style", "")
|
||||
return f"{event}。{res}。{style}"
|
||||
|
||||
@staticmethod
|
||||
def tell_from_actors(event: str, res: str, *actors: "Avatar", prompt: str | None = None) -> str:
|
||||
"""
|
||||
便捷方法别名,保持向后兼容。直接调用 tell_story。
|
||||
"""
|
||||
return StoryTeller.tell_story(event, res, *actors, prompt=prompt or "")
|
||||
|
||||
@staticmethod
|
||||
async def tell_from_actors_async(event: str, res: str, *actors: "Avatar", prompt: str | None = None) -> str:
|
||||
"""
|
||||
便捷方法别名,保持向后兼容。直接调用 tell_story_async。
|
||||
"""
|
||||
return await StoryTeller.tell_story_async(event, res, *actors, prompt=prompt or "")
|
||||
|
||||
|
||||
__all__ = ["StoryTeller"]
|
||||
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# 仅用于类型检查,避免循环导入
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
return StoryTeller._make_fallback_story(event, res, infos["style"])
|
||||
|
||||
__all__ = ["StoryTeller"]
|
||||
Reference in New Issue
Block a user