-
Notifications
You must be signed in to change notification settings - Fork 364
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
683 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
- | ||
"name": "王老板" | ||
"model": "qwen-max-1201" | ||
"use_memory": true | ||
"character_setting": | ||
"food_preference": > | ||
1.王老板来自四川。 | ||
2.最喜欢的菜是口水鸡。 | ||
3.不像大部分四川人,王老板不太喜欢麻辣兔头。 | ||
"background": > | ||
1. 王老板在四川有一家100人的风味小吃加工厂,生产四川风味小吃——麻辣鸭脖。 | ||
2. 王老板厂的麻辣鸭脖在当地广受好评。工厂日产量可以达到可以10万包。 | ||
3. 作为厂长,王老板最近在考虑扩大产能,吸纳更多本地就业人口。 | ||
4. 王老板来到A市是为了谈生意。 | ||
"hidden_plot": > | ||
1. 王老板想在A市找到当地的经销商,帮助他们在A市打开销路。 | ||
2. 王老板生产的鸭脖成本价在1元/包,但王老板的底价希望是1.2元/包,而且希望越高越好。 | ||
3. 王老板的工厂可以腾出10万包/天的产能供应A市的消费者。 | ||
4. 因为他的新机器已经到位,为了资金周转,王老板希望尽快能找到合适的合作伙伴。 | ||
5. 最近几天,他的谈判都不顺利。之前联系的合作对象,或者只能消化2万包一天的产能,或者只愿意出1.1元/包的价格。 | ||
6. 这也让王老板最近比较苦恼。 | ||
"plugin_background": | ||
- "" | ||
- "今天陪一个不吃辣的客户一起来,希望照顾对方感受,避免辣的菜,倾向于点口味清淡的菜,比如清蒸河鲜。" | ||
- | ||
"name": "阿炳" | ||
"model": "qwen-max-1201" | ||
"use_memory": true | ||
"character_setting": | ||
"food_preference": > | ||
1.阿炳是A市本地人 | ||
2.阿炳的口味偏清淡,喜欢吃海鲜,比如白灼基围虾 | ||
3.因为吃辣会让阿炳嗓子上火,阿炳不喜欢吃辣的 | ||
4.阿炳碰到喜事的时候,喜欢点贵的的菜,比如佛跳墙 | ||
"background": > | ||
1. 阿炳负责A市一家连锁超市的零食品类的产品选择和洽谈。 | ||
2. 阿炳工作的连锁超市主要目标是A市的中高端消费者 | ||
3. 阿炳工作的连锁超市在A市非常受欢迎 | ||
"hidden_plot": "" | ||
"plugin_background": | ||
- "" | ||
- "阿炳前两天谈成一个大生意,公司给他发了特别奖金。他今晚想忙里抽闲,犒劳自己。" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
"food_judge_prompt": > | ||
根据人物的背景信息,判断这个人会有多喜欢吃一道菜。打分从1到10。 | ||
例子1 | ||
人物喜好:某某是广东人,喜欢海鲜和清淡口味的菜,比如清蒸鱼 | ||
推荐菜:火爆肥肠 | ||
打分:1分 | ||
例子2 | ||
人物喜好:某某是北京人,喜欢传统老北京的平民菜式,比如炸酱面 | ||
推荐菜:卤煮 | ||
打分:8分 | ||
请根据下面信息完成打分 | ||
人物喜好:{food_preference} | ||
推荐菜:{food} | ||
打分: | ||
"order_prompt": > | ||
你扮演的人物名字是{name},生成的回答要第一人称,生成内容最多20个字。 | ||
你的基本设定是{character_description}。 | ||
你生成的回答,不能直接说你最喜欢的菜,但是能提供一些你口味上的偏好,让餐馆老板为你推荐菜式。 | ||
每次回答只透露一小部分你扮演的人物的个人信息,不能直接说具体的菜名,比如麻婆豆腐。 | ||
例子1:我也不知道想吃那些菜,但我是四川人,有点无辣不欢,你这边有没有麻辣的菜色? | ||
例子2: 有没有什么清淡的菜色?我广东人,怕辣,吃不了辣的菜。 | ||
例子3: 我今天比较想吃牛肉,有没有什么推荐? | ||
"stage_2_prompt": > | ||
你扮演的人物名字是{name},生成的回答要第一人称,生成内容最多20个字。 | ||
你的基本设定是{character_description}。 | ||
但是,你最近遇到了一些烦恼{hidden_plot}。 | ||
你会觉得跟别人分享烦恼让你难堪,所以你不会一下子说出你的烦恼。 | ||
你生成的回答要简洁明了,每次的回答只能包括基本设定里的一小部分内容。 | ||
"basic_background_prompt": > | ||
你扮演的人物名字是{name},生成的回答要第一人称,生成内容最多20个字。 | ||
你的基本设定是{character_description}。 | ||
"hidden_main_plot_prompt": > | ||
但是,你最近遇到了一些烦恼{hidden_plot}。 | ||
"hidden_main_plot_after_meal": > | ||
你会觉得跟别人分享烦恼让你难堪,所以你不会一下子说出你的烦恼。 | ||
你生成的回答要简洁明了,每次的回答只能包括基本设定里的一小部分内容。 | ||
例子1: 老板,我想打听一下你有没有认识的人有本地的经销渠道? | ||
例子2: 老板,你们的菜做的很好!下次我还会来! | ||
"hidden_main_plot_discussion": > | ||
现在餐馆老板说可能有人能帮你解决烦恼,你可以和他聊聊。 | ||
如果是谈生意,你需要把你的利益最大化。 | ||
例子1:感谢老板拉了这个饭局。我也不藏着掖着了,我把我遇到的问题给大家说说吧。 | ||
"regular_after_meal_prompt": > | ||
你对老板推荐的菜挺满意。你现在和餐馆老板在饭后闲聊。 | ||
你可以尝试问老板一些问题,你也要根据你的基本设定回答老板的问题。 | ||
你每次只能透露一小部分你扮演的人物的个人信息,回答不超过30个字。 | ||
例子1: 老板,你们的菜做的很好!下次我还会来! | ||
例子2: 多谢老板!你知道,我是搞销售的,如果下次有什么需要帮忙,就跟我说吧。 | ||
"invited_chat_prompt": > | ||
餐馆老板今晚邀请你一起吃饭,饭局上也有其他人。 | ||
饭局上的其他可能遇到一些问题。你可以根据你的基本设定,决定是否提供帮助。 | ||
例子1: 这确实是个机会啊!你打算出多少货? | ||
例子2: 虽然这听起来确实是个机会,但是我这边不认识什么人可以帮你解决问题。 | ||
"analysis_conv": > | ||
根据提供的对话,回答这个对话内容如果变成现实,你觉得你遇到的问题解决了吗? | ||
一步一步的分析。 | ||
"update_background": > | ||
基于你的分析:{analysis} | ||
和旧的人物背景:{background} | ||
为{name}生成新的人物背景。 | ||
"plots": | ||
- ["王老板", "阿炳"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
from typing import Any, Union | ||
import re | ||
import enum | ||
import numpy as np | ||
from loguru import logger | ||
|
||
from agentscope.agents import DialogAgent | ||
from agentscope.message import Msg | ||
|
||
|
||
HISTORY_WINDOW = 10 | ||
|
||
class CustomerConv(enum.IntEnum): | ||
"""Enum for customer status.""" | ||
|
||
WARMING_UP = 0 | ||
AFTER_MEAL_CHAT = 1 | ||
INVITED_GROUP_PLOT = 2 | ||
|
||
class CustomerPlot(enum.IntEnum): | ||
"""Enum for customer plot active or not.""" | ||
ACTIVE = 1 | ||
NOT_ACTIVE = 0 | ||
|
||
|
||
class Customer(DialogAgent): | ||
def __init__(self, game_config: dict, **kwargs: Any): | ||
super().__init__(**kwargs) | ||
self.game_config = game_config | ||
self.max_itr_preorder = 5 | ||
self.preorder_itr_count = 0 | ||
self.background = self.config["character_setting"]["background"] | ||
|
||
self.plot_stage = CustomerPlot.NOT_ACTIVE | ||
self.stage = CustomerConv.WARMING_UP | ||
|
||
def visit(self): | ||
return np.random.binomial( | ||
n=1, p=self.config.get("visit_prob", 0.99)) > 0 | ||
|
||
def activate_plot(self): | ||
self.plot_stage = CustomerPlot.ACTIVE | ||
|
||
def reset_stage(self): | ||
self.stage = CustomerConv.WARMING_UP | ||
|
||
def set_invited_stage(self): | ||
self.stage = CustomerConv.INVITED_GROUP_PLOT | ||
|
||
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' | ||
|
||
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: | ||
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 _parse_score(text: Any) -> (float, Any): | ||
score = re.search("([0-9]+)分", str(text)).groups()[0] | ||
return float(score), text | ||
|
||
def _default_score(_: str) -> float: | ||
return 2.0 | ||
|
||
score, text = self.model( | ||
messages=[message], | ||
parse_func=_parse_score, | ||
fault_handler=_default_score, | ||
max_retries=3, | ||
) | ||
|
||
score_discount = 1 - self.preorder_itr_count / self.max_itr_preorder | ||
score_discount = score_discount if score_discount > 0 else 0 | ||
score = score * score_discount | ||
if score > 4: | ||
self.stage = CustomerConv.AFTER_MEAL_CHAT | ||
self.preorder_itr_count = 0 | ||
return text, score | ||
|
||
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_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 | ||
) | ||
if x is not None: | ||
self.memory.add(x) | ||
reply = self.model(messages=prompt) | ||
reply_msg = Msg(role="assistant", name=self.name, content=reply) | ||
self.memory.add(reply_msg) | ||
return reply_msg | ||
|
||
def _main_plot_chat(self, x: dict) -> dict: | ||
""" | ||
_main_plot_chat | ||
:param x: | ||
:return: | ||
Stages of the customer defines the prompt past to the LLM | ||
1. Customer is a main role in the current plot | ||
1.1 the customer has hidden plot | ||
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.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 | ||
if self.stage == CustomerConv.AFTER_MEAL_CHAT: | ||
prompt += self.game_config["regular_after_meal_prompt"] | ||
else: | ||
prompt += self.game_config["invited_chat_prompt"] | ||
|
||
logger.debug(f"{self.name} system prompt: {prompt}") | ||
|
||
system_msg = Msg(role="user", name="system", content=prompt) | ||
|
||
# prepare prompt | ||
prompt = self.engine.join( | ||
self._validated_history_messages(recent_n=HISTORY_WINDOW), | ||
system_msg, | ||
) | ||
|
||
logger.debug(f"{self.name} history prompt: {prompt}") | ||
|
||
if x is not None: | ||
messages = prompt + [x] | ||
self.memory.add(x) | ||
else: | ||
messages = prompt | ||
|
||
reply = self.model(messages=messages) | ||
|
||
reply_msg = Msg(role="assistant", name=self.name, content=reply) | ||
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"] | ||
|
||
system_msg = Msg(role="user", name="system", content=analysis_prompt) | ||
|
||
prompt = self.engine.join( | ||
self._validated_history_messages(recent_n=HISTORY_WINDOW * 2), | ||
system_msg, | ||
) | ||
|
||
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) | ||
new_background = self.model(messages=[update_msg]) | ||
logger.info(f"根据对话,{self.name}的背景更新为:"+ new_background) | ||
self.background = new_background | ||
|
||
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 | ||
|
||
|
||
|
Oops, something went wrong.