From 2fe46048aa1d58327abf5c4e071a6add18105eb7 Mon Sep 17 00:00:00 2001 From: "weirui.kwr@alibaba-inc.com" Date: Mon, 15 Jan 2024 15:10:03 +0800 Subject: [PATCH] update --- examples/game/config/customer_config.yaml | 2 - examples/game/config/game_config.yaml | 2 +- examples/game/customer.py | 127 +++++++++++++--------- examples/game/main.py | 122 +++++++++++++-------- examples/game/utils.py | 20 ++-- setup.py | 3 + src/agentscope/agents/user_agent.py | 7 +- src/agentscope/configs/model_config.py | 2 +- src/agentscope/models/tongyi_model.py | 29 ++--- 9 files changed, 192 insertions(+), 122 deletions(-) diff --git a/examples/game/config/customer_config.yaml b/examples/game/config/customer_config.yaml index 61df6cf7c..067c65e07 100644 --- a/examples/game/config/customer_config.yaml +++ b/examples/game/config/customer_config.yaml @@ -15,7 +15,6 @@ "hidden_plot": > 1. 王老板想在A市找到当地的经销商,帮助他们在A市打开销路。 2. 王老板生产的鸭脖成本价在1元/包,但王老板的底价希望是1.2元/包,而且希望越高越好。 - 3. 王老板的工厂可以腾出10万包/天的产能供应A市的消费者。 4. 因为他的新机器已经到位,为了资金周转,王老板希望尽快能找到合适的合作伙伴。 5. 最近几天,他的谈判都不顺利。之前联系的合作对象,或者只能消化2万包一天的产能,或者只愿意出1.1元/包的价格。 @@ -41,4 +40,3 @@ "plugin_background": - "" - "阿炳前两天谈成一个大生意,公司给他发了特别奖金。他今晚想忙里抽闲,犒劳自己。" - diff --git a/examples/game/config/game_config.yaml b/examples/game/config/game_config.yaml index 84cee50c4..9f117e161 100644 --- a/examples/game/config/game_config.yaml +++ b/examples/game/config/game_config.yaml @@ -46,7 +46,7 @@ 你每次只能透露一小部分你扮演的人物的个人信息,回答不超过30个字。 例子1: 老板,你们的菜做的很好!下次我还会来! 例子2: 多谢老板!你知道,我是搞销售的,如果下次有什么需要帮忙,就跟我说吧。 -"invited_chat_prompt": > +"invited_chat_prompt": > 餐馆老板今晚邀请你一起吃饭,饭局上也有其他人。 饭局上的其他可能遇到一些问题。你可以根据你的基本设定,决定是否提供帮助。 例子1: 这确实是个机会啊!你打算出多少货? diff --git a/examples/game/customer.py b/examples/game/customer.py index 1f175ee64..2318e7480 100644 --- a/examples/game/customer.py +++ b/examples/game/customer.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from typing import Any, Union import re import enum @@ -10,6 +11,7 @@ HISTORY_WINDOW = 10 + class CustomerConv(enum.IntEnum): """Enum for customer status.""" @@ -17,8 +19,10 @@ class CustomerConv(enum.IntEnum): AFTER_MEAL_CHAT = 1 INVITED_GROUP_PLOT = 2 + class CustomerPlot(enum.IntEnum): """Enum for customer plot active or not.""" + ACTIVE = 1 NOT_ACTIVE = 0 @@ -35,8 +39,13 @@ def __init__(self, game_config: dict, **kwargs: Any): self.stage = CustomerConv.WARMING_UP def visit(self): - return np.random.binomial( - n=1, p=self.config.get("visit_prob", 0.99)) > 0 + return ( + np.random.binomial( + n=1, + p=self.config.get("visit_prob", 0.99), + ) + > 0 + ) def activate_plot(self): self.plot_stage = CustomerPlot.ACTIVE @@ -51,26 +60,31 @@ def reply(self, x: dict = None) -> Union[dict, tuple]: # TODO: # not sure if it is some implicit requirement of the tongyi chat api, # the first/last message must have role 'user'. - x['role'] = 'user' + x["role"] = "user" - if self.stage == CustomerConv.WARMING_UP and "推荐" in x['content']: + if self.stage == CustomerConv.WARMING_UP and "推荐" in x["content"]: self.stage = CustomerConv.AFTER_MEAL_CHAT return self._recommendation_to_score(x) elif self.stage == CustomerConv.WARMING_UP: return self._pre_meal_chat(x) - elif self.stage == CustomerConv.AFTER_MEAL_CHAT \ - or self.stage == CustomerConv.INVITED_GROUP_PLOT: + elif ( + self.stage == CustomerConv.AFTER_MEAL_CHAT + or self.stage == CustomerConv.INVITED_GROUP_PLOT + ): return self._main_plot_chat(x) - def _recommendation_to_score(self, x:dict) -> dict: - food = x['content'] - food_judge_prompt = self.game_config['food_judge_prompt'] - food_judge_prompt = food_judge_prompt.format_map({ - "food_preference": self.config["character_setting"][ - "food_preference"], - "food": food - }) - message = Msg(name='user', content=food_judge_prompt, role='user') + def _recommendation_to_score(self, x: dict) -> dict: + food = x["content"] + food_judge_prompt = self.game_config["food_judge_prompt"] + food_judge_prompt = food_judge_prompt.format_map( + { + "food_preference": self.config["character_setting"][ + "food_preference" + ], + "food": food, + }, + ) + message = Msg(name="user", content=food_judge_prompt, role="user") def _parse_score(text: Any) -> (float, Any): score = re.search("([0-9]+)分", str(text)).groups()[0] @@ -96,18 +110,19 @@ def _default_score(_: str) -> float: def _pre_meal_chat(self, x: dict) -> dict: self.preorder_itr_count += 1 - system_prompt = self.game_config["order_prompt"].format_map({ - "name": self.config["name"], - "character_description": - self.background + - self.config["character_setting"]["food_preference"] - }) + system_prompt = self.game_config["order_prompt"].format_map( + { + "name": self.config["name"], + "character_description": self.background + + self.config["character_setting"]["food_preference"], + }, + ) system_msg = Msg(role="user", name="system", content=system_prompt) # prepare prompt prompt = self.engine.join( self._validated_history_messages(recent_n=HISTORY_WINDOW), system_msg, - x + x, ) if x is not None: self.memory.add(x) @@ -128,21 +143,27 @@ def _main_plot_chat(self, x: dict) -> dict: 2. Customer is not a main role in the current plot """ - prompt = self.game_config["basic_background_prompt"].format_map({ - "name": self.config["name"], - "character_description": self.background, - }) + prompt = self.game_config["basic_background_prompt"].format_map( + { + "name": self.config["name"], + "character_description": self.background, + }, + ) if self.plot_stage == CustomerPlot.ACTIVE: # -> prompt for the main role in the current plot - prompt += self.game_config["hidden_main_plot_prompt"].format_map({ - "hidden_plot": self.config["character_setting"]["hidden_plot"] - }) + prompt += self.game_config["hidden_main_plot_prompt"].format_map( + { + "hidden_plot": self.config["character_setting"][ + "hidden_plot" + ], + }, + ) if self.stage == CustomerConv.AFTER_MEAL_CHAT: prompt += self.game_config["hidden_main_plot_after_meal"] else: prompt += self.game_config["hidden_main_plot_discussion"] else: - # -> prompt for the helper or irrelvant roles in the current plot + # -> prompt for the helper or irrelvant roles in the current plot if self.stage == CustomerConv.AFTER_MEAL_CHAT: prompt += self.game_config["regular_after_meal_prompt"] else: @@ -172,16 +193,23 @@ def _main_plot_chat(self, x: dict) -> dict: self.memory.add(reply_msg) return reply_msg - def refine_background(self) -> None: - background_prompt = self.game_config["basic_background_prompt"].format_map({ - "name": self.config["name"], - "character_description": self.background, - }) - background_prompt += self.game_config["hidden_main_plot_prompt"].format_map({ - "hidden_plot": self.config["character_setting"]["hidden_plot"] - }) - analysis_prompt =background_prompt + self.game_config["analysis_conv"] + background_prompt = self.game_config[ + "basic_background_prompt" + ].format_map( + { + "name": self.config["name"], + "character_description": self.background, + }, + ) + background_prompt += self.game_config[ + "hidden_main_plot_prompt" + ].format_map( + { + "hidden_plot": self.config["character_setting"]["hidden_plot"], + }, + ) + analysis_prompt = background_prompt + self.game_config["analysis_conv"] system_msg = Msg(role="user", name="system", content=analysis_prompt) @@ -193,21 +221,20 @@ def refine_background(self) -> None: analysis = self.model(messages=prompt) logger.info(f"聊完之后,{self.name}在想:" + analysis) - update_promot = self.game_config["update_background"].format_map({ - "analysis": analysis, - "background": self.background, - "name": self.name, - }) - update_msg = Msg(role="user", name="system", content=update_promot) + update_prompt = self.game_config["update_background"].format_map( + { + "analysis": analysis, + "background": self.background, + "name": self.name, + }, + ) + update_msg = Msg(role="user", name="system", content=update_prompt) new_background = self.model(messages=[update_msg]) - logger.info(f"根据对话,{self.name}的背景更新为:"+ new_background) + logger.info(f"根据对话,{self.name}的背景更新为:" + new_background) self.background = new_background - def _validated_history_messages(self, recent_n: int=10): + def _validated_history_messages(self, recent_n: int = 10): hist_mem = self.memory.get_memory(recent_n=recent_n) if len(hist_mem) > 0: hist_mem[0]["role"], hist_mem[-1]["role"] = "user", "user" return hist_mem - - - diff --git a/examples/game/main.py b/examples/game/main.py index f95a13bc7..0b94675d6 100644 --- a/examples/game/main.py +++ b/examples/game/main.py @@ -1,4 +1,7 @@ -import os, yaml, sys +# -*- coding: utf-8 -*- +import os +import yaml +import sys import inquirer import random import argparse @@ -11,6 +14,8 @@ from agentscope.message import Msg from agentscope.msghub import msghub from customer import Customer + + from utils import ( StagePerNight, GameCheckpoint, @@ -19,6 +24,7 @@ speak_print, ) + def invited_group_chat(invited_customer, player, cur_plot): if len(invited_customer) == 0: return @@ -29,31 +35,32 @@ def invited_group_chat(invited_customer, player, cur_plot): with msghub(invited_customer + [player], announcement=annoucement): for i in range(10): questions = [ - inquirer.List('ans', - message="【系统】:你要发言吗?", - choices=['是','否','结束邀请对话'], - ), + inquirer.List( + "ans", + message="【系统】:你要发言吗?", + choices=["是", "否", "结束邀请对话"], + ), ] - answer = inquirer.prompt(questions)['ans'] + answer = inquirer.prompt(questions)["ans"] if answer == "是": msg = player(annoucement) - elif answer == '结束邀请对话': + elif answer == "结束邀请对话": break for c in invited_customer: msg = c(msg) speak_print(msg) - invited_names.sort() - correct_names = GAME_CONFIG['plots'][cur_plot] + correct_names = GAME_CONFIG["plots"][cur_plot] correct_names.sort() if invited_names == correct_names: print("===== successfully unlock a plot =======") - cur_plot += 1 # move to next plot + cur_plot += 1 # move to next plot for c in invited_customer: c.refine_background() return cur_plot + def one_on_one_loop(customers, player): visit_customers = [c for c in customers if c.visit()] random.shuffle(visit_customers) @@ -68,33 +75,36 @@ def one_on_one_loop(customers, player): print( f"【系统】{customer.name}(顾客)接受了你的推荐。\n" f"【系统】顾客对菜本身的评价:{msg[0]}\n" - f"【系统】{customer.name}(顾客)享用完之后,综合满意度为{msg[1]}" + f"【系统】{customer.name}(顾客)享用完之后,综合满意度为{msg[1]}", ) break speak_print(msg) print( - "【系统】如果想要最终推荐菜品,请说“推荐xxx”;否则请不要包含“推荐”关键词") + "【系统】如果想要最终推荐菜品,请说“推荐xxx”;否则请不要包含“推荐”关键词", + ) msg = player(msg) - if len(msg['content']) == 0 or "[TERMINATE]" in msg['content']: + if len(msg["content"]) == 0 or "[TERMINATE]" in msg["content"]: break - if (isinstance(msg, tuple) and msg[1] < 4) or \ - (isinstance(msg, dict) and len(msg['content']) == 0): + if (isinstance(msg, tuple) and msg[1] < 4) or ( + isinstance(msg, dict) and len(msg["content"]) == 0 + ): print(f"顾客{customer.name} 离开餐馆") continue questions = [ - inquirer.List('ans', - message="【系统】:接下来你会说些什么吗?", - choices=[ - '这里是赠送的果盘,请您享用。还有什么是我能为您做的呢?', - '感谢您的光顾。(结束与该顾客的当天对话)', - ], - ), + inquirer.List( + "ans", + message="【系统】:接下来你会说些什么吗?", + choices=[ + "这里是赠送的果盘,请您享用。还有什么是我能为您做的呢?", + "感谢您的光顾。(结束与该顾客的当天对话)", + ], + ), ] - answer = inquirer.prompt(questions)['ans'] - if answer == '感谢您的光顾。(结束与该顾客的当天对话)': + answer = inquirer.prompt(questions)["ans"] + if answer == "感谢您的光顾。(结束与该顾客的当天对话)": continue msg = Msg(role="user", name="餐馆老板", content=answer) player.observe(msg) @@ -102,21 +112,24 @@ def one_on_one_loop(customers, player): msg = customer(msg) # print(f"{customer_reply.name}(顾客):" + customer_reply.content) speak_print(msg) + print("【系统】输入`跳过`或者不输入终止对话。") msg = player(msg) - if len(msg['content']) == 0 or "跳过" in msg['content']: + if len(msg["content"]) == 0 or "跳过" in msg["content"]: break + def invite_customers(customers): available_customers = [c.name for c in customers] invited_customers = [] while len(available_customers) > 0: select_customer = [ - inquirer.List('invited', - message="系统:今天就没有更多顾客了,您明天有什么邀请计划吗?", - choices=available_customers + ['END'] - ), + inquirer.List( + "invited", + message="系统:今天就没有更多顾客了,您明天有什么邀请计划吗?", + choices=available_customers + ["END"], + ), ] - answer = inquirer.prompt(select_customer)['invited'] + answer = inquirer.prompt(select_customer)["invited"] if answer == "END": break else: @@ -124,16 +137,21 @@ def invite_customers(customers): available_customers.remove(answer) return invited_customers + def main(args): model = load_model_by_name("tongyi_model") - customer_configs = yaml.safe_load(open('config/customer_config.yaml')) + customer_configs = yaml.safe_load(open("config/customer_config.yaml")) customers = [ Customer( - name=cfg['name'], config=cfg, game_config=GAME_CONFIG, model=model, + name=cfg["name"], + config=cfg, + game_config=GAME_CONFIG, + model=model, use_memory=True, - ) for cfg in customer_configs + ) + for cfg in customer_configs ] player = UserAgent(name="餐馆老板") @@ -146,9 +164,13 @@ def main(args): checkpoint = load_game_checkpoint(args.load_checkpoint) customers = checkpoint.customers stage_per_night = checkpoint.stage_per_night - cur_plot = checkpoint.cur_plot, + cur_plot = (checkpoint.cur_plot,) invited_customers = checkpoint.invited_customers - print("load checkpoint", checkpoint.stage_per_night, checkpoint.cur_plot) + print( + "load checkpoint", + checkpoint.stage_per_night, + checkpoint.cur_plot, + ) else: checkpoint = GameCheckpoint( stage_per_night=stage_per_night, @@ -173,13 +195,19 @@ def main(args): c.set_invited_stage() # initial stage of the checkpoint.cur_plot = invited_group_chat( - checkpoint.invited_customers, player, checkpoint.cur_plot + checkpoint.invited_customers, + player, + checkpoint.cur_plot, ) checkpoint.stage_per_night = StagePerNight.CASUAL_CHAT_FOR_MEAL elif checkpoint.stage_per_night == StagePerNight.CASUAL_CHAT_FOR_MEAL: # ========== one-on-one loop ================= # the remaining not invited customers show up with probability - rest_customers = [c for c in customers if c.name not in checkpoint.invited_customers] + rest_customers = [ + c + for c in customers + if c.name not in checkpoint.invited_customers + ] one_on_one_loop(rest_customers, player) checkpoint.stage_per_night = StagePerNight.MAKING_INVITATION elif checkpoint.stage_per_night == StagePerNight.MAKING_INVITATION: @@ -198,20 +226,24 @@ def main(args): if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='Game init', description='',) - parser.add_argument('--load_checkpoint', type=str, default=None) - parser.add_argument('--save_checkpoint', type=str, default="./checkpoints/cp-") + parser = argparse.ArgumentParser(prog="Game init", description="") + parser.add_argument("--load_checkpoint", type=str, default=None) + parser.add_argument( + "--save_checkpoint", + type=str, + default="./checkpoints/cp-", + ) args = parser.parse_args() - GAME_CONFIG = yaml.safe_load(open('./config/game_config.yaml')) + GAME_CONFIG = yaml.safe_load(open("./config/game_config.yaml")) logger.add(sys.stderr, level="INFO") TONGYI_CONFIG = { "type": "tongyi", - 'name': 'tongyi_model', - 'model_name': 'qwen-max-1201', - 'api_key': os.environ.get('TONGYI_API_KEY') + "name": "tongyi_model", + "model_name": "qwen-max-1201", + "api_key": os.environ.get("TONGYI_API_KEY"), } read_model_configs(TONGYI_CONFIG) - main(args) \ No newline at end of file + main(args) diff --git a/examples/game/utils.py b/examples/game/utils.py index dc82a886a..b694c7c56 100644 --- a/examples/game/utils.py +++ b/examples/game/utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import enum import os import pickle @@ -6,6 +7,7 @@ from agentscope.message import Msg from colorist import BgBrightColor, Effect + class StagePerNight(enum.IntEnum): """Enum for customer status.""" @@ -16,11 +18,11 @@ class StagePerNight(enum.IntEnum): class GameCheckpoint: def __init__( - self, - stage_per_night: StagePerNight, - customers: list[Customer], - cur_plot: int, - invited_customers: list[Customer] + self, + stage_per_night: StagePerNight, + customers: list[Customer], + cur_plot: int, + invited_customers: list[Customer], ): self.stage_per_night = stage_per_night self.customers = customers @@ -29,10 +31,12 @@ def __init__( def save_game_checkpoint( - checkpoint: GameCheckpoint, - checkpoint_prefix: str) -> None: + checkpoint: GameCheckpoint, + checkpoint_prefix: str, +) -> None: time_str = datetime.now().strftime("%Y%m%d_%H%M%S") checkpoint_path = checkpoint_prefix + time_str + os.makedirs(os.path.dirname(checkpoint_path), exist_ok=True) with open(checkpoint_path, "wb") as f: pickle.dump(checkpoint, f) @@ -43,4 +47,4 @@ def load_game_checkpoint(checkpoint_path: str) -> GameCheckpoint: def speak_print(m: Msg): - print(f"{BgBrightColor.BLUE}{m.name}{BgBrightColor.OFF}: {m.content}") \ No newline at end of file + print(f"{BgBrightColor.BLUE}{m.name}{BgBrightColor.OFF}: {m.content}") diff --git a/setup.py b/setup.py index efb9c9d50..4d0aaa6c5 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,8 @@ test_requires = ["pytest", "pytest-cov", "pre-commit"] +game_requires = ["inquirer", "colorist", "dashscope"] + # released requires minimal_requires = [ "loguru", @@ -65,6 +67,7 @@ packages=setuptools.find_packages("src"), install_requires=minimal_requires, extras_require={ + "game": game_requires, "distribute": distribute_requires, "dev": dev_requires, "full": full_requires, diff --git a/src/agentscope/agents/user_agent.py b/src/agentscope/agents/user_agent.py index 00bbb242c..3076682b5 100644 --- a/src/agentscope/agents/user_agent.py +++ b/src/agentscope/agents/user_agent.py @@ -3,6 +3,7 @@ import time from typing import Union from typing import Optional +from loguru import logger from agentscope.agents import AgentBase from agentscope.message import Msg @@ -62,7 +63,11 @@ def reply( # TODO: To avoid order confusion, because `input` print much quicker # than logger.chat time.sleep(0.5) - content = input(f"{self.name}: ") + try: + content = input(f"{self.name}: ") + except Exception as e: + logger.warning(f"Input invalid: {e}. Please retry.") + content = input(f"{self.name}: ") kwargs = {} if required_keys is not None: diff --git a/src/agentscope/configs/model_config.py b/src/agentscope/configs/model_config.py index 5ceef39d5..118caee60 100644 --- a/src/agentscope/configs/model_config.py +++ b/src/agentscope/configs/model_config.py @@ -144,4 +144,4 @@ class TongyiCfg(CfgBase): generate_args: dict = None """The arguments used in openai api generation, e.g. `temperature`, - `seed`.""" \ No newline at end of file + `seed`.""" diff --git a/src/agentscope/models/tongyi_model.py b/src/agentscope/models/tongyi_model.py index 6a68a09d0..0d0a05fbc 100644 --- a/src/agentscope/models/tongyi_model.py +++ b/src/agentscope/models/tongyi_model.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from typing import Any, Union from http import HTTPStatus @@ -13,21 +14,21 @@ class TongyiWrapper(ModelWrapperBase): """The model wrapper for dashscope API (with Qwen models).""" def __init__( - self, - name: str, - model_name: str = None, - api_key: str = None, - organization: str = None, - client_args: dict = None, - generate_args: dict = None, - **kwargs + self, + name: str, + model_name: str = None, + api_key: str = None, + organization: str = None, + client_args: dict = None, + generate_args: dict = None, + **kwargs, ) -> None: """Initialize the openai client.""" super().__init__(name) if dashscope is None: raise ImportError( - "Cannot find openai package in current python environment.", + "Cannot find dashscope package in current python environment.", ) self.model_name = model_name or name @@ -40,15 +41,15 @@ def __init__( class TongyiChatModel(TongyiWrapper): def __call__( - self, - messages: list, - return_raw: bool = False, - **kwargs: Any, + self, + messages: list, + return_raw: bool = False, + **kwargs: Any, ) -> Union[str, dict]: response = dashscope.Generation.call( model=self.model_name, messages=messages, - result_format='message', # set the result to be "message" format. + result_format="message", # set the result to be "message" format. ) if response.status_code == HTTPStatus.OK: