Skip to content

Commit

Permalink
improve linear plot flow to support DAG style (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZiTao-Li authored Jan 17, 2024
1 parent ef181af commit 1d932ff
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 97 deletions.
48 changes: 45 additions & 3 deletions examples/game/config/customer_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,51 @@
4.阿炳碰到喜事的时候,喜欢点贵的的菜,比如佛跳墙
"background": >
1. 阿炳负责A市一家连锁超市的零食品类的产品选择和洽谈。
2. 阿炳工作的连锁超市主要目标是A市的中高端消费者
3. 阿炳工作的连锁超市在A市非常受欢迎
"hidden_plot": ""
2. 阿炳工作的连锁超市主要目标是A市的中高端消费者。
3. 阿炳工作的连锁超市在A市非常受欢迎。
"hidden_plot": >
1. 最近阿炳和一个客户达成了一个大单麻辣鸭脖的生意。
2. 在小范围内的试销售是,这款麻辣鸭脖的反响非常好,经常是一上架就在半天内卖光。
3. 但是这也给阿炳带来一个问题:超市自己的物流系统已经饱和了,如果铺开销售,这批麻辣鸭脖不知道能怎么从工厂样运送到各个门店。
4. 阿炳他们一共有大大小小30多门店,需要每天都动态补货。
"plugin_background":
- ""
- "阿炳前两天谈成一个大生意,公司给他发了特别奖金。他今晚想忙里抽闲,犒劳自己。"
-
"name": "老朱"
"model": "tongyi_model"
"use_memory": true
"character_setting":
"food_preference": >
1. 老朱来自内蒙古;
2. 老朱最喜欢的是羊肉,比如烤羊肉串和羊肉烧卖;
3. 老朱不习惯吃海鲜,对海鲜有轻微过敏。
"background": >
1. 老朱是一家物流公司的营业点的经理。
2. 老朱工作的营业点每天能处理5万件的包裹。
3. 营业点的效益不错,但是老朱也非常忙,时常要工作到很晚才能回家。
4. 老朱性格豪爽,喜欢喝酒,也喜欢交朋友。
"hidden_plot": >
1. 老朱家的孩子最近成绩下滑,其中成绩最不好的科目是数学。
2. 老朱因为工作繁忙,没办法经常陪孩子,和孩子的关系有点紧张。
3. 老朱也因为工作太忙,也没办法辅导孩子功课。
3. 老朱在考虑是否要为孩子请一名家教。
"plugin_background":
- ""
-
"name": "范老师"
"model": "tongyi_model"
"use_memory": true
"character_setting":
"food_preference": >
1. 范老师是山东人
2. 范老师喜欢吃鲁菜,比如九转大肠、葱烧海参
3. 范老师不喜欢食物有腥臊味,所以比较抗拒羊肉
"background": >
1. 范老师在一所中学当老师,主要负责学科是数学,这学期也兼任一个班级的班主任。
2. 范老师的教学水平不错,负责的班级数学成绩高于年级平均水平。
3. 范老师和班里的学生水平都很不错。学生们平时有什么生活烦心事或学习上的困难,都喜欢找他聊聊天。
"hidden_plot": ""
"plugin_background":
- ""

16 changes: 15 additions & 1 deletion examples/game/config/game_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"hidden_main_plot_prompt": >
但是,你最近遇到了一些烦恼{hidden_plot}。
"hidden_main_plot_after_meal": >
你对老板推荐的菜挺满意。你现在和餐馆老板在饭后闲聊。
你会觉得跟别人分享烦恼让你难堪,所以你不会一下子说出你的烦恼。
你生成的回答要简洁明了,每次的回答只能包括基本设定里的一小部分内容。
例子1: 老板,我想打听一下你有没有认识的人有本地的经销渠道?
Expand Down Expand Up @@ -78,4 +79,17 @@
生成故事:
"plots":
- ["王老板", "阿炳"]
- "predecessor_plots": null
"main_role": "王老板"
"supporting_roles":
- "阿炳"
- "predecessor_plots": null
"main_role": "老朱"
"supporting_roles":
- "范老师"
- "predecessor_plots":
- 0
- 1
"main_role": "阿炳"
"supporting_roles":
- "老朱"
100 changes: 58 additions & 42 deletions examples/game/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@


HISTORY_WINDOW = 10
# TODO: for debug, set the score bars to be lower
MIN_BAR_RECEIVED_CONST = 4
MIN_BAR_FRIENDSHIP_CONST = 80
MIN_BAR_FRIENDSHIP_CONST = 30


class CustomerConv(enum.IntEnum):
Expand Down Expand Up @@ -57,18 +58,24 @@ def __init__(self, game_config: dict, **kwargs: Any):
self.plot_stage = CustomerPlot.NOT_ACTIVE

def visit(self):
return (
np.random.binomial(
n=1,
p=min(self.friendship / 100, 1.0),
)
> 0
)
# 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=min(self.friendship / 100, 1.0),
# )
# > 0
# )

def activate_plot(self) -> None:
# Note: once activate, never deactivate
if self.friendship >= MIN_BAR_FRIENDSHIP_CONST:
self.plot_stage = CustomerPlot.ACTIVE
# when the customer is the main role in a plot, it will be activated
self.plot_stage = CustomerPlot.ACTIVE

def deactivate_plot(self) -> None:
# when the plot in which the customer is a main role is over, the
# customer will be deactivated
self.plot_stage = CustomerPlot.NOT_ACTIVE

def reply(self, x: dict = None) -> Union[dict, tuple]:
# TODO:
Expand Down Expand Up @@ -109,10 +116,6 @@ def _default_score(_: str) -> float:
score_discount = score_discount if score_discount > 0 else 0
score = score * score_discount

if score > MIN_BAR_RECEIVED_CONST and self.friendship > 60:
self.cur_state = CustomerConv.AFTER_MEAL_CHAT
self.preorder_itr_count = 0

change_in_friendship = score - MIN_BAR_RECEIVED_CONST
self.friendship += change_in_friendship
change_symbol = "+" if change_in_friendship >= 0 else ""
Expand All @@ -121,11 +124,15 @@ def _default_score(_: str) -> float:
f"当前好感度为 {self.friendship}",
)

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

return Msg(role="assistant", name=self.name, content=text, score=score)

def _pre_meal_chat(self, x: dict) -> dict:
if "推荐" in x["content"]:
self.transition(CustomerConv.AFTER_MEAL_CHAT)
return self._recommendation_to_score(x)

self.preorder_itr_count += 1
Expand All @@ -143,6 +150,7 @@ def _pre_meal_chat(self, x: dict) -> dict:
system_msg,
x,
)
logger.debug(system_prompt)
if x is not None:
self.memory.add(x)
reply = self.model(messages=prompt)
Expand All @@ -161,32 +169,7 @@ def _main_plot_chat(self, x: dict) -> dict:
1.2 the customer has no hidden plot (help with background)
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,
},
)
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"
],
},
)
if self.cur_state == 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
if self.cur_state == CustomerConv.AFTER_MEAL_CHAT:
prompt += self.game_config["regular_after_meal_prompt"]
else:
prompt += self.game_config["invited_chat_prompt"]
prompt = self._gen_plot_related_prompt()

logger.debug(f"{self.name} system prompt: {prompt}")

Expand Down Expand Up @@ -282,3 +265,36 @@ def generate_pov_story(self, recent_n: int = 20):
print("*" * 20)
logger.info(pov_story)
print("*" * 20)

def _gen_plot_related_prompt(self) -> str:
"""
generate prompot depending on the state and friendship of the customer
"""
prompt = self.game_config["basic_background_prompt"].format_map(
{
"name": self.config["name"],
"character_description": self.background,
},
)
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(
{
"hidden_plot": self.config["character_setting"][
"hidden_plot"
],
},
)
if self.cur_state == 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
if self.cur_state == CustomerConv.AFTER_MEAL_CHAT:
prompt += self.game_config["regular_after_meal_prompt"]
else:
prompt += self.game_config["invited_chat_prompt"]

return prompt
101 changes: 59 additions & 42 deletions examples/game/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
load_game_checkpoint,
save_game_checkpoint,
speak_print,
check_active_plot,
)


def invited_group_chat(invited_customer, player, cur_plot):
def invited_group_chat(invited_customer, player, cur_plots_indices):
logger.debug("\n---active_plots:" + str(cur_plots_indices))
if len(invited_customer) == 0:
return cur_plot
return cur_plots_indices
invited_names = [c.name for c in invited_customer]
print("===== invited group chat ====")
print(f"老板今天邀请了{invited_names},大家一起聊聊")
Expand All @@ -50,27 +52,31 @@ def invited_group_chat(invited_customer, player, cur_plot):
speak_print(msg)

invited_names.sort()
correct_names = GAME_CONFIG["plots"][cur_plot]
correct_names.sort()

# TODO: decided by multi factor: chat history of msghub, correct_names
if invited_names == correct_names:
print("===== successfully unlock a plot =======")
questions = [
inquirer.List(
"ans",
message="【系统】:需要以哪位角色的视角生成一段完整故事吗?",
choices=invited_names + ["跳过"],
),
]
answer = inquirer.prompt(questions)["ans"]
for c in invited_customer:
if c.name == answer:
c.generate_pov_story()
cur_plot += 1 # move to next plot
for c in invited_customer:
c.refine_background()
return cur_plot
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.sort()
print("current names", correct_names)

# TODO: decided by multi factor: chat history of msghub, correct_names
if invited_names == correct_names:
print("===== successfully unlock a plot =======")
questions = [
inquirer.List(
"ans",
message="【系统】:需要以哪位角色的视角生成一段完整故事吗?",
choices=invited_names + ["跳过"],
),
]
answer = inquirer.prompt(questions)["ans"]
for c in invited_customer:
if c.name == answer:
c.generate_pov_story()
for c in invited_customer:
c.refine_background()
return idx
return None


def one_on_one_loop(customers, player):
Expand Down Expand Up @@ -115,7 +121,7 @@ def one_on_one_loop(customers, player):
"ans",
message="【系统】:接下来你会说些什么吗?",
choices=[
"这里是赠送的果盘,请您享用。还有什么是我能为您做的呢?",
"感谢您的今天来我们这里消费。这里是赠送的果盘,请您享用。还有什么是我能为您做的呢?",
"感谢您的光顾。(结束与该顾客的当天对话)",
],
),
Expand Down Expand Up @@ -188,33 +194,35 @@ def main() -> None:

player = RuledUser(**user_configs)

invited_customers = []
stage_per_night = StagePerNight.CASUAL_CHAT_FOR_MEAL
cur_plot = 0

if args.load_checkpoint is not None:
checkpoint = load_game_checkpoint(args.load_checkpoint)
customers = checkpoint.customers
stage_per_night = checkpoint.stage_per_night
cur_plot = (checkpoint.cur_plot,)
invited_customers = checkpoint.invited_customers
print(
"load checkpoint",
checkpoint.stage_per_night,
checkpoint.cur_plot,
logger.debug(
"load checkpoint\n" + str(checkpoint.stage_per_night)
+ str(checkpoint.cur_plots),
)
else:
invited_customers = []
stage_per_night = StagePerNight.CASUAL_CHAT_FOR_MEAL
cur_plots, done_plots = [], []
checkpoint = GameCheckpoint(
stage_per_night=stage_per_night,
cur_plot=cur_plot,
cur_plots=cur_plots,
done_plots=done_plots,
customers=customers,
invited_customers=invited_customers,
)

# set current plot and done plots
# initialize main role of current plot cur_state
main_role = GAME_CONFIG["plots"][checkpoint.cur_plot][0]
plots = GAME_CONFIG["plots"]
for i in checkpoint.done_plots:
plots[i]["state"] = "done"
to_activate_customers, active_plots = check_active_plot(plots, None)
checkpoint.cur_plots = active_plots
logger.debug(str(to_activate_customers) + str(active_plots))
to_activate_customers = set(to_activate_customers)
for c in customers:
if c.name == main_role:
if c.name in to_activate_customers:
c.activate_plot()

while True:
Expand All @@ -226,11 +234,20 @@ def main() -> None:
# set customer to invited discussion cur_state
c.transition(CustomerConv.INVITED_GROUP_PLOT)
# initial cur_state of the
checkpoint.cur_plot = invited_group_chat(
done_plot_idx = invited_group_chat(
checkpoint.invited_customers,
player,
checkpoint.cur_plot,
checkpoint.cur_plots,
)
if done_plot_idx is not None:
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]
next_active_roles = set(next_active_roles)
for c in checkpoint.customers:
if c.name in next_active_roles:
c.activate_plot()
checkpoint.stage_per_night = StagePerNight.CASUAL_CHAT_FOR_MEAL
elif checkpoint.stage_per_night == StagePerNight.CASUAL_CHAT_FOR_MEAL:
# ========== one-on-one loop =================
Expand Down Expand Up @@ -276,7 +293,7 @@ def main() -> None:
"api_key": os.environ.get("TONGYI_API_KEY"),
}

agentscope.init(model_configs=[TONGYI_CONFIG], logger_level="INFO")
agentscope.init(model_configs=[TONGYI_CONFIG], logger_level="DEBUG")
game_description = """
这是一款模拟餐馆经营的文字冒险游戏,玩家扮演餐馆老板,通过与顾客互动来经营餐馆并解锁剧情。
游戏分为三个阶段:随意聊天,一对一互动以及邀请对话。
Expand Down
Loading

0 comments on commit 1d932ff

Please sign in to comment.