Skip to main content

从爸爸笑话到悲伤笑话:使用 GPTAssistantAgent 进行函数调用

在 Colab 中打开 在 GitHub 上打开

Autogen 允许 GPTAssistantAgent 通过“工具”进行增强,这些工具是预定义的函数或功能,扩展了它处理特定任务的能力,类似于人们在 OpenAI Assistant 的 API 中使用工具的方式。

在这个笔记本中,我们使用 Autogen 的 GPTAssistantAgent 创建了一个基本的多智能体系统,将特定主题的爸爸笑话转换为悲伤笑话。它由一个“爸爸”智能体和一个“悲伤小丑”智能体组成。爸爸智能体具有搜索 Dad Joke API 的能力,而悲伤小丑智能体则将爸爸笑话转换为悲伤笑话。然后,悲伤小丑将悲伤笑话写入一个文本文件。

在这个过程中,我们演示了如何调用工具并对 GPTAssistantAgent 进行函数调用。

要求

AutoGen 需要 Python 3.8 或更高版本。请在此笔记本中安装 pyautogen

pip install pyautogen
已满足要求:pyautogen 在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 0.2.8
已满足要求:pyautogen 对 openai 的版本要求为 1.3 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 1.6.1
已满足要求:pyautogen 对 diskcache 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 5.6.3
已满足要求:pyautogen 对 termcolor 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2.4.0
已满足要求:pyautogen 对 flaml 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2.1.1
已满足要求:pyautogen 对 python-dotenv 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 1.0.0
已满足要求:pyautogen 对 tiktoken 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 0.5.2
已满足要求:pyautogen 对 pydantic 的版本要求为 1.10 及以上,但小于 3,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2.5.3
已满足要求:pyautogen 对 docker 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 7.0.0
已满足要求:openai 对 anyio 的版本要求为 3.5.0 及以上,但小于 5,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 4.2.0
已满足要求:openai 对 distro 的版本要求为 1.7.0 及以上,但小于 2,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 1.8.0
已满足要求:openai 对 httpx 的版本要求为 0.23.0 及以上,但小于 1,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 0.26.0
已满足要求:openai 对 sniffio 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 1.3.0
已满足要求:openai 对 tqdm 的版本要求为 4 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 4.66.1
已满足要求:openai 对 typing-extensions 的版本要求为 4.7 及以上,但小于 5,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 4.9.0
已满足要求:pydantic 对 annotated-types 的版本要求为 0.4.0 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 0.6.0
已满足要求:pydantic 对 pydantic-core 的版本要求为 2.14.6,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2.14.6
已满足要求:docker 对 packaging 的版本要求为 14.0 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 23.2
已满足要求:docker 对 requests 的版本要求为 2.26.0 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2.31.0
已满足要求:docker 对 urllib3 的版本要求为 1.26.0 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2.1.0
已满足要求:flaml 对 NumPy 的版本要求为 1.17.0rc1 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 1.26.2
已满足要求:tiktoken 对 regex 的版本要求为 2022.1.18 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2023.10.3
已满足要求:anyio 对 idna 的版本要求为 2.8 及以上,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 3.6
已满足要求:httpx 对 certifi 的版本要求为任意,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 2023.11.17
已满足要求:httpcore 对 httpx 的版本要求为 1.*,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 1.0.2
已满足要求:httpcore 对 h11 的版本要求为 0.13 及以上,但小于 0.15,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 0.14.0
已满足要求:requests 对 charset-normalizer 的版本要求为 2 及以上,但小于 4,在 /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages 中的版本为 3.3.2

[注意] pip 有新版本可用:23.3.2 -> 24.0
[注意] 若要更新,请运行:pip install --upgrade pip
注意:您可能需要重新启动内核以使用更新的软件包。

导入依赖项

from typing import Annotated, Literal

import requests

import autogen
from autogen import UserProxyAgent
from autogen.agentchat.contrib.gpt_assistant_agent import GPTAssistantAgent
from autogen.function_utils import get_function_schema

config_list = autogen.config_list_from_json(
env_or_file="OAI_CONFIG_LIST",
)

创建函数

我们需要为我们的代理创建函数。

这个函数使用代理创建一个搜索词,并调用 Dad Joke API 返回一个爸爸笑话的列表。

def get_dad_jokes(search_term: str, page: int = 1, limit: int = 10) -> str:
"""
根据搜索词获取爸爸笑话的列表。

参数:
- search_term: 要搜索的词语。
- page: 要获取的结果页数(默认为 1)。
- limit: 每页返回的结果数量(默认为 20,最大为 30)。

返回:
爸爸笑话的列表。
"""
url = "https://icanhazdadjoke.com/search"
headers = {"Accept": "application/json"}
params = {"term": search_term, "page": page, "limit": limit}

response = requests.get(url, headers=headers, params=params)

if response.status_code == 200:
data = response.json()
jokes = [joke["joke"] for joke in data["results"]]
return jokes
else:
return f"获取笑话失败,状态码: {response.status_code}"
# 示例:使用爸爸笑话函数
jokes = get_dad_jokes("猫")
print(jokes)
['猫在哪里写笔记?\r\n抓痕纸!', '前几天下着猫狗大雨。我差点踩到一只贵宾犬。', '你怎么称呼一群杂乱无章的猫?猫灾。', '昨晚我不小心吃了我家猫的药。别问我喵。', '你怎么称呼一堆猫?喵山。', '动物事实 #25:大多数山猫的名字都不叫山。']

这个函数允许代理将内容写入文本文件。

def write_to_txt(content: str, filename: str = "dad_jokes.txt"):
"""
将格式化的字符串写入文本文件。

参数:
- content: 要写入的格式化字符串。
- filename: 要写入的文件名。默认为 "output.txt"。
"""
with open(filename, "w") as file:
file.write(content)
# 示例:使用写入文本文件函数
content = "\n".join(jokes) # 格式化上面示例中的笑话
write_to_txt(content)

创建函数模式

为了在 GPTAssistantAgents 中使用这些函数,我们需要生成函数模式。可以使用 get_function_schema 来完成。

# get_dad_jokes 的助手 API 工具模式
get_dad_jokes_schema = get_function_schema(
get_dad_jokes,
name="get_dad_jokes",
description="根据搜索词获取爸爸笑话的列表。可以使用 page 和 limit 参数进行分页。",
)
# write_to_txt 的 Assistant API 工具模式
write_to_txt_schema = get_function_schema(
write_to_txt,
name="write_to_txt",
description="将格式化的字符串写入文本文件。如果文件不存在,将创建新文件。如果文件已存在,将覆盖原有内容。",
)
函数 'write_to_txt' 的返回类型未注释。虽然注释是可选的,但该函数应返回字符串或 'pydantic.BaseModel' 的子类。

创建代理

在本节中,我们创建并配置了 Dad 和 Sad Joker 代理。

设置用户代理

user_proxy = UserProxyAgent(
name="user_proxy",
is_termination_msg=lambda msg: "TERMINATE" in msg["content"],
human_input_mode="NEVER",
max_consecutive_auto_reply=1,
)

Dad 代理

我们使用 GPTAssistantAgent 创建 Dad 代理,为了使 Dad 能够使用 get_dad_jokes 函数,我们需要在 llm_config 中提供函数的规范。

我们将 llm_config 中的 tools 格式化为与 OpenAI Assistant tools 文档 中提供的格式相同。

the_dad = GPTAssistantAgent(
name="the_dad",
instructions="""
作为 'The Dad',您的主要任务是通过获取爸爸笑话来娱乐,而悲伤的小丑将根据给定的主题将其转化为 '悲伤的笑话'。当提供一个主题,比如 '植物' 或 '动物',您的任务如下:

1. 使用 'get_dad_jokes' 函数通过提供与主题相关的搜索词来搜索与主题相关的爸爸笑话。获取与主题相关的笑话列表。
2. 以清晰易读的格式将这些笑话呈现给悲伤的小丑,为其转化做准备。

请记住,团队的目标是在保持忠于用户提供的主题的同时,创造性地调整每个爸爸笑话的精髓,以适应 '悲伤的笑话' 的格式。
""",
overwrite_instructions=True, # 使用提供的指示覆盖任何现有的指示
overwrite_tools=True, # 使用提供的工具覆盖任何现有的工具
llm_config={
"config_list": config_list,
"tools": [get_dad_jokes_schema],
},
)
GPTAssistantAgent(the_dad)的OpenAI客户端配置 - 模型:gpt-4-1106-preview
找到匹配的助手,使用第一个匹配的助手:{'id': 'asst_BLBUwYPugb1UR2jQMGAA7RtU', 'created_at': 1714660644, 'description': None, 'file_ids': [], 'instructions': "\n 作为'The Dad',您的主要角色是通过获取爸爸笑话来娱乐,而悲伤的笑话人将根据给定的主题将其转化为'悲伤的笑话'。当提供一个主题,比如'植物'或'动物'时,您的任务如下:\n\n 1. 使用'get_dad_jokes'函数通过提供与主题相关的搜索词来搜索与主题相关的爸爸笑话。获取与主题相关的笑话列表。\n 2. 以清晰易读的格式将这些笑话呈现给悲伤的笑话人,为他们的转化做好准备。\n\n 请记住,团队的目标是在保持用户提供的主题的同时,创造性地将每个爸爸笑话的精髓适应'悲伤的笑话'的格式。
", 'metadata': {}, 'model': 'gpt-4-1106-preview', 'name': 'the_dad', 'object': 'assistant', 'tools': [ToolFunction(function=FunctionDefinition(name='get_dad_jokes', description='基于搜索词获取爸爸笑话列表。可以使用page和limit参数进行分页。', parameters={'type': 'object', 'properties': {'search_term': {'type': 'string', 'description': '搜索词'}, 'page': {'type': 'integer', 'default': 1, 'description': '页码'}, 'limit': {'type': 'integer', 'default': 10, 'description': '每页数量'}}, 'required': ['search_term']}), type='function')]}

接下来,我们将 get_dad_jokes 函数注册到 Dad 的 GPTAssistantAgent 中。

# 将 get_dad_jokes 函数注册到 the_dad GPTAssistantAgent
the_dad.register_function(
function_map={
"get_dad_jokes": get_dad_jokes,
},
)

悲伤小丑 Agent

然后,我们以与上述 Dad agent 类似的方式创建和配置悲伤小丑 agent。

the_sad_joker = GPTAssistantAgent(
name="the_sad_joker",
instructions="""
作为“悲伤小丑”,你的独特角色是将爸爸笑话创造性地转化为“悲伤笑话”。当你收到一系列以“植物”或“动物”为主题的爸爸笑话时,你应该:

1. 仔细阅读每个爸爸笑话,理解它的主题和笑点。
2. 创造性地改变笑话的情绪,将其从幽默变为忧郁或悲伤。这可能涉及到修改笑点,修改设置,甚至完全重新构想笑话,但要保持与原始主题的相关性。
3. 确保你的改编与原始爸爸笑话之间有明确的联系,并且可以理解为爸爸笑话的改编。
4. 使用 'write_to_txt' 函数将你改编后的悲伤笑话写入文本文件中。除非有特定的文件名要求,否则请使用反映主题或笑话性质的有意义的文件名。

你的目标不仅仅是改变笑话的情绪,而是以一种创造性、深思熟虑的方式,尊重原始幽默的本质进行改编。请记住,尽管主题可能是轻松愉快的,但你的改编应该提供一种使它们独特的“悲伤笑话”的忧郁转折。
""",
overwrite_instructions=True, # 使用提供的指令覆盖任何现有的指令
overwrite_tools=True, # 使用提供的工具覆盖任何现有的工具
llm_config={
"config_list": config_list,
"tools": [write_to_txt_schema],
},
)
GPTAssistantAgent(the_sad_joker)的OpenAI客户端配置 - 模型:gpt-4-1106-preview
找到匹配的助手,使用第一个匹配的助手:{'id': 'asst_HzB75gkobafXZhkuIAmiBiai', 'created_at': 1714660668, 'description': None, 'file_ids': [], 'instructions': "\n 作为'The Sad Joker',您的独特角色是将爸爸笑话创造性地转化为'悲伤笑话'。当您收到一系列以'植物'或'动物'为主题的爸爸笑话时,您应该:\n\n 1. 仔细阅读每个爸爸笑话,理解其主题和笑点。\n 2. 创造性地改变笑话的情绪,使其从幽默变为忧郁或悲伤。这可能涉及调整笑点,修改设置,甚至完全重新构想笑话,同时保持与原始主题的相关性。\n 3. 确保您的改编与原始爸爸笑话之间保持明确的联系,并且可以理解为爸爸笑话的改编。\n 4. 使用'write_to_txt'函数将您转化后的悲伤笑话写入文本文件。除非有特定的文件名要求,否则请使用反映主题或笑话性质的有意义的文件名。\n\n 您的目标不仅是改变笑话的情绪,而且要以创造性、深思熟虑的方式尊重原始幽默的本质。请记住,尽管主题可能是轻松愉快的,但您的改编应该提供一种使它们独特的'悲伤笑话'的忧郁转折。\n ", 'metadata': {}, 'model': 'gpt-4-1106-preview', 'name': 'the_sad_joker', 'object': 'assistant', 'tools': [ToolFunction(function=FunctionDefinition(name='write_to_txt', description='将格式化的字符串写入文本文件。如果文件不存在,将创建该文件。如果文件存在,将覆盖该文件。', parameters={'type': 'object', 'properties': {'content': {'type': 'string', 'description': '内容'}, 'filename': {'type': 'string', 'default': 'dad_jokes.txt', 'description': '文件名'}}, 'required': ['content']}), type='function')]}

使用 Sad Joker 的 GPTAssistantAgent 注册 write_to_txt 函数。

# 使用 the_sad_joker 的 GPTAssistantAgent 注册 write_to_txt 函数
the_sad_joker.register_function(
function_map={
"write_to_txt": write_to_txt,
},
)

创建群聊并开始对话

创建群聊

groupchat = autogen.GroupChat(agents=[user_proxy, the_dad, the_sad_joker], messages=[], max_round=15)
group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config={"config_list": config_list})

开始对话

user_proxy.initiate_chat(group_chat_manager, message="关于猫的笑话")
user_proxy (发送给 chat_manager):

关于猫的笑话

--------------------------------------------------------------------------------

>>>>>>>> 执行函数 get_dad_jokes...
the_dad (发送给 chat_manager):

以下是一些关于猫的爸爸笑话,供悲伤小丑转化:

1. 猫在哪里写笔记?抓纸!
2. 前几天下着猫狗大雨,我差点踩到一只贵宾犬。
3. 你怎么称呼一群杂乱无章的猫?猫灾。
4. 昨晚我不小心吃了我家猫的药。别问我为什么。
5. 你怎么称呼一堆猫?猫山。
6. 动物事实 #25:大多数山猫都不叫 Bob。


--------------------------------------------------------------------------------

>>>>>>>> 执行函数 write_to_txt...
the_sad_joker (发送给 chat_manager):

猫主题的悲伤笑话已经转化并保存到名为 "sad_cat_jokes.txt" 的文本文件中。


--------------------------------------------------------------------------------
user_proxy (发送给 chat_manager):



--------------------------------------------------------------------------------
ChatResult(chat_id=None, chat_history=[{'content': '关于猫的笑话', 'role': 'assistant'}, {'content': '以下是一些关于猫的爸爸笑话,供悲伤小丑转化:\n\n1. 猫在哪里写笔记?抓纸!\n2. 前几天下着猫狗大雨,我差点踩到一只贵宾犬。\n3. 你怎么称呼一群杂乱无章的猫?猫灾。\n4. 昨晚我不小心吃了我家猫的药。别问我为什么。\n5. 你怎么称呼一堆猫?猫山。\n6. 动物事实 #25:大多数山猫都不叫 Bob。\n', 'name': 'the_dad', 'role': 'user'}, {'content': '猫主题的悲伤笑话已经转化并保存到名为 "sad_cat_jokes.txt" 的文本文件中。\n', 'name': 'the_sad_joker', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0.0278, 'gpt-4-1106-preview': {'cost': 0.0278, 'prompt_tokens': 2744, 'completion_tokens': 12, 'total_tokens': 2756}}, {'total_cost': 0.02194, 'gpt-4-1106-preview': {'cost': 0.02194, 'prompt_tokens': 2167, 'completion_tokens': 9, 'total_tokens': 2176}}), human_input=[])