diff --git a/examples/game/config/ingredients.yaml b/examples/game/config/ingredients.yaml new file mode 100644 index 000000000..ea4a3d48a --- /dev/null +++ b/examples/game/config/ingredients.yaml @@ -0,0 +1,56 @@ +蔬菜: + - 土豆 + - 西红柿 + - 洋葱 + - 胡萝卜 + - 青椒 + - 黄瓜 + - 茄子 + - 蒜头 + - 生姜 + - 菠菜 + - 西兰花 + - 香菜 + - 莴苣 + - 冬瓜 + - 南瓜 + - 芹菜 + - 玉米 + - 大白菜 + - 芦笋 + - 蘑菇 +肉类: + - 猪肉 + - 牛肉 + - 鸡肉 + - 羊肉 + - 鸭肉 + - 火腿 + - 培根 +海鲜: + - 虾 + - 鲈鱼 + - 三文鱼 + - 扇贝 + - 蟹肉 + - 鲜贝 +调味品: + - 盐 + - 白糖 + - 酱油 + - 醋 + - 料酒 + - 蚝油 + - 花椒 + - 辣椒粉 + - 五香粉 + - 黑胡椒粉 + - 味精 + - 鸡精 + - 芝麻油 + - 橄榄油 +其他辅料: + - 大米 + - 面粉 + - 鸡蛋 + - 豆腐 \ No newline at end of file diff --git a/examples/game/config/user.yaml b/examples/game/config/user.yaml index 0fbb057ad..dce40b237 100644 --- a/examples/game/config/user.yaml +++ b/examples/game/config/user.yaml @@ -21,4 +21,10 @@ 或{{"allowed": "false", "reason": "为什么不应该在游戏中允许这样做"}}。 以下是用户作为餐厅老板的输入: - {content} \ No newline at end of file + {content} +"ingredients_dict": {} +"cook_prompt": > + 你是一个魔法锅,我给你食材{ingredient}。 + 你帮我生成一道菜,请只回复我菜名,菜名要简短、优雅、让人有食欲、有创意。 + 菜名中不要包含不存在的食材。 + 如果食材只包含配料,那么允许使用额外的食材米饭: \ No newline at end of file diff --git a/examples/game/customer.py b/examples/game/customer.py index 39b1fa47a..d5ed139ec 100644 --- a/examples/game/customer.py +++ b/examples/game/customer.py @@ -59,7 +59,7 @@ def __init__(self, game_config: dict, **kwargs: Any): def visit(self): # TODO: for debug, set the visit prob to be 0.9 - return np.random.binomial(n=1, p=0.9,) > 0 + return np.random.binomial(n=1, p=0.9) > 0 # return ( # np.random.binomial( # n=1, @@ -124,7 +124,10 @@ def _default_score(_: str) -> float: f"当前好感度为 {self.friendship}", ) - if score > MIN_BAR_RECEIVED_CONST and self.friendship > MIN_BAR_FRIENDSHIP_CONST: + if ( + score > MIN_BAR_RECEIVED_CONST + and self.friendship > MIN_BAR_FRIENDSHIP_CONST + ): self.transition(CustomerConv.AFTER_MEAL_CHAT) print("---", self.cur_state) self.preorder_itr_count = 0 @@ -132,7 +135,7 @@ def _default_score(_: str) -> float: return Msg(role="assistant", name=self.name, content=text, score=score) def _pre_meal_chat(self, x: dict) -> dict: - if "推荐" in x["content"]: + if "food" in x: return self._recommendation_to_score(x) self.preorder_itr_count += 1 @@ -276,8 +279,10 @@ def _gen_plot_related_prompt(self) -> str: "character_description": self.background, }, ) - if self.plot_stage == CustomerPlot.ACTIVE \ - and self.friendship > MIN_BAR_FRIENDSHIP_CONST: + if ( + self.plot_stage == CustomerPlot.ACTIVE + and self.friendship > MIN_BAR_FRIENDSHIP_CONST + ): # -> prompt for the main role in the current plot prompt += self.game_config["hidden_main_plot_prompt"].format_map( { diff --git a/examples/game/main.py b/examples/game/main.py index 77b29554a..06a050e99 100644 --- a/examples/game/main.py +++ b/examples/game/main.py @@ -54,8 +54,9 @@ def invited_group_chat(invited_customer, player, cur_plots_indices): invited_names.sort() print(cur_plots_indices) for idx in cur_plots_indices: - correct_names = [GAME_CONFIG["plots"][idx]["main_role"]] + \ - GAME_CONFIG["plots"][idx]["supporting_roles"] + correct_names = [GAME_CONFIG["plots"][idx]["main_role"]] + GAME_CONFIG[ + "plots" + ][idx]["supporting_roles"] correct_names.sort() print("current names", correct_names) @@ -83,6 +84,28 @@ def one_on_one_loop(customers, player): visit_customers = [c for c in customers if c.visit()] random.shuffle(visit_customers) + with open( + "config/ingredients.yaml", + "r", + encoding="utf-8", + ) as ingredients_file: + ingredients = yaml.safe_load(ingredients_file) + + ingredient_today = {} + for category, items in ingredients.items(): + ingredient_today[category] = ( + random.sample(items, 3) + if len( + items, + ) + >= 3 + and category not in ["调味品", "其他辅料"] + else items + ) + print(f"今天拥有的食材是:{ingredient_today}") + + player.set_ingredients(ingredient_today) + if not visit_customers: print("今天没有出现客人,请增加与客人的好感度以增大出现概率") else: @@ -96,7 +119,7 @@ def one_on_one_loop(customers, player): msg = customer(msg) if "score" in msg: print( - f"【系统】{customer.name}(顾客)接受了你的推荐。\n" + f"【系统】{customer.name}(顾客)接受了你的菜。\n" f"【系统】顾客对菜本身的评价:{msg['content']}\n" f"【系统】{customer.name}(顾客)享用完之后," f"综合满意度为{msg['score']}\n", @@ -104,8 +127,7 @@ def one_on_one_loop(customers, player): break speak_print(msg) print( - "【系统】如果想要最终推荐菜品,请说“推荐xxx” 否则请不要包含“推荐”关键词。" - " (对话轮次过多会使得顾客综合满意度下降。)", + "【提示】请输入“做菜”启动魔法锅,它会按所选定食材产生菜品。" " (对话轮次过多会使得顾客综合满意度下降。)", ) msg = player(msg) if len(msg["content"]) == 0 or "[TERMINATE]" in msg["content"]: @@ -197,7 +219,8 @@ def main() -> None: if args.load_checkpoint is not None: checkpoint = load_game_checkpoint(args.load_checkpoint) logger.debug( - "load checkpoint\n" + str(checkpoint.stage_per_night) + "load checkpoint\n" + + str(checkpoint.stage_per_night) + str(checkpoint.cur_plots), ) else: @@ -240,7 +263,10 @@ def main() -> None: checkpoint.cur_plots, ) if done_plot_idx is not None: - next_active_roles, active_plots = check_active_plot(plots, done_plot_idx) + next_active_roles, active_plots = check_active_plot( + plots, + done_plot_idx, + ) logger.debug("---active_plots:", active_plots) checkpoint.cur_plots = active_plots checkpoint.done_plots += [done_plot_idx] @@ -296,8 +322,8 @@ def main() -> None: agentscope.init(model_configs=[TONGYI_CONFIG], logger_level="DEBUG") game_description = """ 这是一款模拟餐馆经营的文字冒险游戏,玩家扮演餐馆老板,通过与顾客互动来经营餐馆并解锁剧情。 - 游戏分为三个阶段:随意聊天,一对一互动以及邀请对话。 - 玩家需要根据顾客的喜好和需求来推荐菜品,通过顾客对用餐的满意度来获取好感度并解锁剧情。 + 游戏分为四个阶段:选择食材做菜,随意聊天,一对一互动以及邀请对话。 + 玩家需要根据顾客的喜好和需求来挑选食材做菜,通过顾客对用餐的满意度来获取好感度并解锁剧情。 在游戏中,玩家需要经营餐厅、与顾客互动并决定邀请哪些顾客参与对话,以推动故事剧情的发展。 通过与顾客的互动,玩家可以解锁剧情并发展餐馆的故事,体验不同的情节和结局。 """ diff --git a/examples/game/ruled_user.py b/examples/game/ruled_user.py index f64a33ec6..82c1c4551 100644 --- a/examples/game/ruled_user.py +++ b/examples/game/ruled_user.py @@ -1,13 +1,37 @@ # -*- coding: utf-8 -*- import time import json +import random +from enum import Enum from typing import Optional, Union, Any, Callable + +import inquirer from loguru import logger from agentscope.agents import AgentBase from agentscope.message import Msg +class FoodQuality(Enum): + DELICIOUS = "美味的" + BURNT = "烧焦的" + FRESH = "新鲜的" + STALE = "不新鲜的" + SPICY = "辣的" + BLAND = "清淡的" + SWEET = "甜的" + SOUR = "酸的" + BITTER = "苦的" + SALTY = "咸的" + SAVORY = "鲜美的" + CRISPY = "脆的" + TENDER = "嫩的" + JUICY = "多汁的" + RICH = "浓郁的" + FRAGRANT = "香的" + SMOKY = "烟熏味的" + + class RuledUser(AgentBase): """User agent under rules""" @@ -16,10 +40,14 @@ def __init__( name: str = "User", model: Optional[Union[Callable[..., Any], str]] = None, sys_prompt: Optional[str] = None, + ingredients_dict: Optional[dict] = None, + cook_prompt: Optional[str] = None, ) -> None: """Initialize a RuledUser object.""" super().__init__(name=name, model=model, sys_prompt=sys_prompt) self.retry_time = 10 + self.ingredients_dict = ingredients_dict + self.cook_prompt = cook_prompt def reply( self, @@ -46,12 +74,12 @@ def reply( ruler_res = self.is_content_valid(content) if ruler_res.get("allowed") == "true": break - else: - logger.warning( - f"Input is not allowed:" - f" {ruler_res.get('reason', 'Unknown reason')} " - f"【Please retry】", - ) + + logger.warning( + f"Input is not allowed:" + f" {ruler_res.get('reason', 'Unknown reason')} " + f"【Please retry】", + ) except Exception as e: logger.warning(f"Input invalid: {e}. Please try again.") @@ -63,6 +91,10 @@ def reply( for key in required_keys: kwargs[key] = input(f"{key}: ") + if content == "做菜": + content = self.cook() + kwargs["food"] = content + # Add additional keys msg = Msg( self.name, @@ -85,3 +117,54 @@ def is_content_valid(self, content): max_retries=self.retry_time, ) return ruler_res + + def set_ingredients(self, ingredients_dict): + self.ingredients_dict = ingredients_dict + + def cook(self): + ingredients_list = [ + item + for sublist in self.ingredients_dict.values() + for item in sublist + ] + + ingredients_list = ["*清空*", "*结束*"] + ingredients_list + cook_list = [] + questions = [ + inquirer.List( + "ingredient", + message="以下是今天拥有的食材是,请选择您需要使用的食材,完成后按回车键", + choices=ingredients_list, + ), + ] + while True: + print(f"【系统】当前已选择的食材是{cook_list}。") + sel_ingr = inquirer.prompt(questions)["ingredient"] + + if sel_ingr == "*结束*": + if len(cook_list) > 0: + break + print("【系统】你的魔法锅空空如也。") + elif sel_ingr == "*清空*": + cook_list.clear() + else: + cook_list.append(sel_ingr) + + prompt = self.cook_prompt.format_map( + {"ingredient": "+".join(cook_list)}, + ) + message = Msg(name="user", content=prompt, role="user") + food = self.model( + messages=[message], + parse_func=json.loads, + max_retries=self.retry_time, + ) + # random_quality = random.choice(list(FoodQuality)).value + # food = random_quality + food + + print( + f"【系统】魔法锅周围光芒四射,你听到了轻微的咔哒声。" + f"当一切平静下来,一道《{food}》出现在你眼前。", + ) + + return food