104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
from typing import Any, Dict, List, TYPE_CHECKING
|
||
from src.utils.llm import call_llm_with_task_name
|
||
from src.utils.config import CONFIG
|
||
import json
|
||
|
||
if TYPE_CHECKING:
|
||
from src.classes.avatar import Avatar
|
||
|
||
async def make_decision(
|
||
avatar: "Avatar",
|
||
context_desc: str,
|
||
options: List[Dict[str, Any]]
|
||
) -> str:
|
||
"""
|
||
让角色在多个选项中做出单选决策。
|
||
|
||
Args:
|
||
avatar: 做出决策的角色
|
||
context_desc: 决策背景描述
|
||
options: 选项列表,每个选项是一个字典,必须包含 'key' 和 'desc' 字段
|
||
例如: [{'key': 'A', 'desc': '...'}, {'key': 'B', 'desc': '...'}]
|
||
|
||
Returns:
|
||
str: AI 选择的选项 Key (如 'A' 或 'B')
|
||
"""
|
||
# 1. 获取角色信息 (详细模式)
|
||
avatar_infos = str(avatar.get_info(detailed=True))
|
||
|
||
# 2. 格式化选项字符串
|
||
choices_list = [f"{opt.get('key', '')}: {opt.get('desc', '')}" for opt in options]
|
||
choices_str = "\n".join(choices_list)
|
||
full_choices_str = f"【当前情境】:{context_desc}\n\n{choices_str}"
|
||
|
||
# 3. 调用 AI
|
||
template_path = CONFIG.paths.templates / "single_choice.txt"
|
||
world_info = avatar.world.static_info
|
||
result = await call_llm_with_task_name(
|
||
"single_choice",
|
||
template_path,
|
||
infos={
|
||
"world_info": world_info,
|
||
"avatar_infos": avatar_infos,
|
||
"choices": full_choices_str
|
||
}
|
||
)
|
||
|
||
# 4. 解析结果
|
||
choice = ""
|
||
if isinstance(result, dict):
|
||
choice = result.get("choice", "").strip()
|
||
elif isinstance(result, str):
|
||
clean_result = result.strip()
|
||
# 尝试解析可能包含 markdown 的 json
|
||
if "{" in clean_result and "}" in clean_result:
|
||
try:
|
||
# 提取可能的 json 部分
|
||
start = clean_result.find("{")
|
||
end = clean_result.rfind("}") + 1
|
||
json_str = clean_result[start:end]
|
||
data = json.loads(json_str)
|
||
choice = data.get("choice", "").strip()
|
||
except (json.JSONDecodeError, ValueError):
|
||
# 如果 JSON 解析失败,直接看字符串内容是否就是选项 key
|
||
choice = clean_result
|
||
# 有时候 llm 会输出 "choice: A",这里做个兼容
|
||
else:
|
||
choice = clean_result
|
||
|
||
# 验证 choice 是否在 options key 中
|
||
valid_keys = {opt["key"] for opt in options}
|
||
# 简单的容错:如果返回的是 "A." 或者 "A "
|
||
if choice not in valid_keys:
|
||
for k in valid_keys:
|
||
if k in choice:
|
||
choice = k
|
||
break
|
||
|
||
if choice not in valid_keys:
|
||
# 兜底:默认选第一个
|
||
choice = options[0]["key"]
|
||
|
||
return choice
|
||
|
||
|
||
def format_swap_choice_desc(new_item: Any, old_item: Any | None, item_type_name: str) -> str:
|
||
"""
|
||
生成替换装备/功法时的决策描述文本。
|
||
|
||
Args:
|
||
new_item: 新获得的物品对象(必须实现 get_info(detailed=True))
|
||
old_item: 当前持有的物品对象(可能为 None)
|
||
item_type_name: 物品类型名称(如"兵器"、"功法")
|
||
"""
|
||
new_info = new_item.get_info(detailed=True)
|
||
|
||
if old_item:
|
||
old_info = old_item.get_info(detailed=True)
|
||
return (
|
||
f"现有{item_type_name}:{old_info}\n"
|
||
f"新{item_type_name}:{new_info}\n"
|
||
f"(选择替换将卖出旧{item_type_name})"
|
||
)
|
||
else:
|
||
return f"新{item_type_name}:{new_info}" |