callback(回调函数)
回调函数是将函数A作为参数传递给另一个函数B,然后在函数B内部执行函数A。当函数B完成某些操作后,会调用函数A(即“回调”)。这种编程模式常见于处理异步操作,如事件监听、定时任务或网络请求等。
在编程中,异步通常是指代码不必等待某个操作完成(如I/O操作、网络请求、数据库查询等)就可以继续执行的能力。异步机制的实现涉及事件循环、任务队列和其他复杂的底层机制。这与同步编程形成对比,在同步编程中,操作必须按照它们出现的顺序完成。
下面是回调函数的一个简单示例:
def compute(x, y, callback):
result = x + y
callback(result)
def print_result(value):
print(f"The result is: {value}")
def square_result(value):
print(f"The squared result is: {value**2}")
# 使用print_result作为回调
compute(3, 4, print_result) # 输出: The result is: 7
# 使用square_result作为回调
compute(3, 4, square_result) # 输出: The squared result is: 49
上面的代码并没有体现出异步操作。虽然回调常见于异步操作,但是回调函数本身并不代表异步。回调只是一种编程模式,允许在某个操作完成时执行某些代码。
下面是异步操作中回调函数的示例:
import asyncio
async def compute(x, y, callback):
print("Starting compute...") #第二个输出
await asyncio.sleep(0.5) # 模拟异步操作 等待0.5秒,此时转入task2
result = x + y
# callback(result)
print("Finished compute...") # 第四个输出
def print_result(value):
print(f"The result is: {value}")
async def another_task():
print("Starting another task...") # 第三个输出
await asyncio.sleep(1) # 等待1秒,此时又转回task1
print("Finished another task...") # 第五个输出
async def main():
print("Main starts...") # 首先输出
task1 = asyncio.create_task(compute(3, 4, print_result))
task2 = asyncio.create_task(another_task())
await task1 # main函数的执行暂停,等待task1执行
await task2 # 等待task2执行
print("Main ends...") # 最后输出
asyncio.run(main())
运行结果如下:
Main starts...
Starting compute...
Starting another task...
Finished compute...
Finished another task...
Main ends...
Langchain中的Callback
LangChain 的 Callback 机制允许你在应用程序的不同阶段进行自定义操作,如日志记录、监控和数据流处理,这个机制通过 CallbackHandler(回调处理器)来实现。回调处理器是LangChain中实现 CallbackHandler 接口的对象,为每类可监控的事件提供一个方法。当该事件被触发时,CallbackManager 会在这些处理器上调用适当的方法。
BaseCallbackHandler是最基本的回调处理器,你可以继承它来创建自己的回调处理器。它包含了多种方法,如on_llm_start/on_chat(当 LLM 开始运行时调用)和on_llm_error(当 LLM 出现错误时调用)等。
LangChain 也提供了一些内置的处理器,例如 StdOutCallbackHandler,它会将所有事件记录到标准输出。还有FileCallbackHandler,会将所有的日志记录到一个指定的文件中。
在 LangChain 的各个组件,如 Chains、Models、Tools、Agents 等,都提供了两种类型的回调设置方法:构造函数回调和请求回调。你可以在初始化 LangChain 时将回调处理器传入,或者在单独的请求中使用回调。例如,当你想要在整个链的所有请求中进行日志记录时,可以在初始化时传入处理器;而当你只想在某个特定请求中使用回调时,可以在请求时传入。
下面的代码结合langchain使用了回调,将相关事件同时输出到标准输出和output.log
from loguru import logger
from langchain.callbacks import FileCallbackHandler
from langchain.chains import LLMChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
logfile = "output.log"
logger.add(logfile, colorize=True, enqueue=True)
handler = FileCallbackHandler(logfile)
llm = OpenAI()
prompt = PromptTemplate.from_template("1 + {number} = ")
# this chain will both print to stdout (because verbose=True) and write to 'output.log'
# if verbose=False, the FileCallbackHandler will still write to 'output.log'
chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler], verbose=True)
answer = chain.run(number=2)
logger.info(answer)
其中,初始化LLMChain时指定的 verbose 参数,就等同于将一个输出到控制台的回调处理器添加到你的对象中。
自定义回调函数
我们也可以通过BaseCallbackHandler和AsyncCallbackHandler来自定义回调函数。
import asyncio
from typing import Any, Dict, List
from langchain.chat_models import ChatOpenAI
from langchain.schema import LLMResult, HumanMessage
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
# 创建同步回调处理器
class MyFlowerShopSyncHandler(BaseCallbackHandler):
def on_llm_new_token(self, token: str, **kwargs) -> None: #箭头符号表示返回的类型
print(f"获取花卉数据: token: {token}")
# 创建异步回调处理器
class MyFlowerShopAsyncHandler(AsyncCallbackHandler):
async def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> None:
print("正在获取花卉数据...")
await asyncio.sleep(0.5) # 模拟异步操作
print("花卉数据获取完毕。提供建议...")
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
print("整理花卉建议...")
await asyncio.sleep(0.5) # 模拟异步操作
print("祝你今天愉快!")
# 主要的异步函数
async def main():
flower_shop_chat = ChatOpenAI(
max_tokens=100,
streaming=True,
callbacks=[MyFlowerShopSyncHandler(), MyFlowerShopAsyncHandler()],
)
# 异步生成聊天回复
await flower_shop_chat.agenerate([[HumanMessage(content="哪种花卉最适合生日?只简单说3种,不超过50字")]])
# 运行主异步函数
asyncio.run(main())
MyFlowerShopSyncHandler是一个同步回调,每当新的Token生成时,它就简单地打印出正在获取的鲜花数据。
而MyFlowerShopAsyncHandler则是异步的,当客服开始提供鲜花建议时,它会模拟数据的异步获取。在建议完成后,它还会模拟一个结束的操作,如向客户发出感谢。
这里的异步体现在这样几个方面。
- 模拟延时操作:在MyFlowerShopAsyncHandler中,我们使用了await asyncio.sleep(0.5)来模拟其他请求异步获取花卉信息的过程。当执行到这个await语句时,当前的on_llm_start函数会“暂停”,释放控制权回到事件循环。这意味着,在这个sleep期间,其他异步任务(如其他客户的请求)可以被处理。
- 回调机制:当ChatOpenAI在处理每个新Token时,它会调用on_llm_new_token方法。因为这是一个同步回调,所以它会立即输出。但是,开始和结束的异步回调on_llm_start和on_llm_end在开始和结束时都有一个小的延时操作,这是通过await asyncio.sleep(0.5)模拟的。
- 事件循环:Python的syncio库提供了一个事件循环,允许多个异步任务并发运行。在我们的例子中,虽然看起来所有的操作都是按顺序发生的,但由于我们使用了异步操作和回调,如果有其他并发任务,它们可以在await暂停期间运行。
get_openai_callback
get_openai_callback用于构造令牌计数器,这个计数功能对于监控大模型的会话消耗以及成本控制十分重要。
在Python中,一个上下文管理器通常用于管理资源,如文件或网络连接,这些资源在使用前需要设置,在使用后需要清理。上下文管理器经常与with语句一起使用,以确保资源正确地设置和清理。
get_openai_callback被设计用来监控与OpenAI交互的Token数量。当你进入该上下文时,它会通过监听器跟踪Token的使用。当你退出上下文时,它会清理监听器并提供一个Token的总数。通过这种方式,它充当了一个回调机制,允许你在特定事件发生时执行特定的操作或收集特定的信息。
from langchain import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory
from langchain.callbacks import get_openai_callback
# 初始化大语言模型
llm = OpenAI(temperature=0.5, model_name="gpt-3.5-turbo-instruct")
# 初始化对话链
conversation = ConversationChain(
llm=llm,
memory=ConversationBufferMemory()
)
# 使用context manager进行token counting
with get_openai_callback() as cb:
# 第一天的对话
# 回合1
conversation("我姐姐明天要过生日,我需要一束生日花束。")
print("第一次对话后的记忆:", conversation.memory.buffer)
# 回合2
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("第二次对话后的记忆:", conversation.memory.buffer)
# 回合3 (第二天的对话)
conversation("我又来了,还记得我昨天为什么要来买花吗?")
print("/n第三次对话后时提示:/n",conversation.prompt.template)
print("/n第三次对话后的记忆:/n", conversation.memory.buffer)
# 输出使用的tokens
print("\n总计使用的tokens:", cb.total_tokens)
输出:
总计使用的tokens: 966
此外还可以再添加一个additinoal_interactions异步函数,用于演示如何在多个并发交互中计算tokens。
当我们讨论异步交互时,指的是我们可以启动多个任务,它们可以并发(而不是并行)地运行,并且不会阻塞主线程。在Python中,这是通过asyncio库实现的,它使用事件循环来管理并发的异步任务。
import asyncio
# 进行更多的异步交互和token计数
async def additional_interactions():
with get_openai_callback() as cb:
await asyncio.gather(
*[llm.agenerate(["我姐姐喜欢什么颜色的花?"]) for _ in range(3)]
)
print("\n另外的交互中使用的tokens:", cb.total_tokens)
# 运行异步函数
asyncio.run(additional_interactions())
0. async def:这表示additional_interactions是一个异步函数。它可以使用await关键字在其中挂起执行,允许其他异步任务继续。
await asyncio.gather(...):这是asyncio库提供的一个非常有用的方法,用于并发地运行多个异步任务。它会等待所有任务完成,然后继续执行。*[llm.agenerate(["我姐姐喜欢什么颜色的花?"]) for _ in range(3)]:这实际上是一个Python列表解析,它生成了3个llm.agenerate(...)的异步调用。asyncio.gather将并发地运行这3个调用。
由于这3个llm.agenerate调用是并发的,所以它们不会按顺序执行,而是几乎同时启动,并在各自完成时返回。这意味着,即使其中一个调用由于某种原因需要更长时间,其他调用也不会被阻塞,它们会继续并完成。
CAMEL
CAMEL框架来自于论文CAMEL: Communicative Agents for “Mind” Exploration of Large Scale Language Model Society,其中C、A、M、E、L分别代表购同、代理、心智、探索和LLM。
CAMEL框架旨在通过角色扮演来促进交流代理之间的自主合作,并为其“认知”过程提供洞察。这种方法涉及使用启示式提示来指导聊天代理完成任务,同时保持与人类意图的一致性。这个框架为研究多代理系统的合作行为和能力提供了一种可扩展的方法。
- 交流式代理 Communicative Agents,是一种可以与人类或其他代理进行交流的计算机程序。这些代理可以是聊天机器人、智能助手或任何其他需要与人类交流的软件。为了使这些代理能够更好地与人类交流,研究人员一直在寻找方法来提高它们的交流能力。
- 角色扮演 role-playing,则是这篇论文提出的主要思路,它允许交流代理扮演不同的角色,以更好地与人类或其他代理交流。这意味着代理可以模仿人类的行为,理解人类的意图,并据此做出反应。
- 启示式提示 inception prompting,是一种指导代理完成任务的方法。通过给代理提供一系列的提示或指示,代理可以更好地理解它应该如何行动。这种方法可以帮助代理更好地与人类交流,并完成与人类的合作任务。
这里的核心创新点是,通过角色扮演和启示式提示的框架来引导代理的交流过程。
下面是论文中提到的一个使用场景:
之所以引入任务指定代理,是因为对话代理通常需要一个具体的任务提示来实现任务,对于非领域专家来说,创建这样一个具体的任务提示可能是具有挑战性或耗时的。
那么,参与此任务的 AI 角色就包括:
- 一个以Python程序员为身份的 AI 助手代理
- 一个以股票交易员为身份的 AI 用户代理
在CAMEL这个角色扮演框架中,Prompt Engineering非常关键。与其他对话语言模型技术有所不同,这种提示工程只在角色扮演的初始阶段进行,主要用于明确任务和分配角色。当会话开始后,AI助手和AI用户会自动地相互给出提示,直到对话结束。这种方法被称为 “Inception Prompting”。
Prompt Engineering
在CAMEL这个角色扮演框架中,Prompt Engineering非常关键。与其他对话语言模型技术有所不同,这种提示工程只在角色扮演的初始阶段进行,主要用于明确任务和分配角色。当会话开始后,AI助手和AI用户会自动地相互给出提示,直到对话结束。这种方法被称为 “Inception Prompting”。
Inception Prompting 包括三种类型的提示:任务明确提示、AI助手提示和AI用户提示。
这个提示模板为AI助手和AI用户提供了一个明确的框架,确保它们在对话中的行为是有序、一致和有效的。
实战
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your Key'
# 导入所需的库
from typing import List
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage,
BaseMessage,
)
定义CAMELAgent类,该类用于管理与语言模型的交互。包含初始化消息、更新消息和与模型进行交互的方法。
# 定义CAMELAgent类,用于管理与语言模型的交互
class CAMELAgent:
def __init__(
self,
system_message: SystemMessage,
model: ChatOpenAI,
) -> None:
self.system_message = system_message
self.model = model
self.init_messages()
def reset(self) -> None:
"""重置对话消息"""
self.init_messages()
return self.stored_messages
def init_messages(self) -> None:
"""初始化对话消息"""
self.stored_messages = [self.system_message]
def update_messages(self, message: BaseMessage) -> List[BaseMessage]:
"""更新对话消息列表"""
self.stored_messages.append(message)
return self.stored_messages
def step(self, input_message: HumanMessage) -> AIMessage:
"""进行一步交互,并获取模型的响应"""
messages = self.update_messages(input_message)
output_message = self.model(messages)
self.update_messages(output_message)
return output_message
预设角色和任务提示,这部分定义了AI助手和用户的角色名称、任务描述以及每次讨论的字数限制。
# 设置一些预设的角色和任务提示
assistant_role_name = "花店营销专员"
user_role_name = "花店老板"
task = "整理出一个夏季玫瑰之夜的营销活动的策略"
word_limit = 50 # 每次讨论的字数限制
assistant_role_name = "花店营销专员":这是定义助手的角色。在此设定中,助手被视为一名花店营销专员,主要职责是为花店老板(即用户)提供关于营销活动的建议和策略。user_role_name = "花店老板":这是定义用户的角色。用户在这里是花店的老板,他们可能会向营销专员(即助手)提出关于花店推广活动的需求或询问,然后由营销专员来答复和提供建议。
任务指定代理:任务指定代理(Task Specifier)来明确任务描述。这是CAMEL框架的一个关键步骤,它确保了任务描述的具体性和清晰性。
# 定义与指定任务相关的系统提示
task_specifier_sys_msg = SystemMessage(content="你可以让任务更具体。")
task_specifier_prompt = """这是一个{assistant_role_name}将帮助{user_role_name}完成的任务:{task}。
请使其更具体化。请发挥你的创意和想象力。
请用{word_limit}个或更少的词回复具体的任务。不要添加其他任何内容。"""
task_specifier_template = HumanMessagePromptTemplate.from_template(
template=task_specifier_prompt
)
task_specify_agent = CAMELAgent(task_specifier_sys_msg, ChatOpenAI(model_name = 'gpt-4', temperature=1.0))
task_specifier_msg = task_specifier_template.format_messages(
assistant_role_name=assistant_role_name,
user_role_name=user_role_name,
task=task,
word_limit=word_limit,
)[0]
specified_task_msg = task_specify_agent.step(task_specifier_msg)
print(f"Specified task: {specified_task_msg.content}")
specified_task = specified_task_msg.content
系统消息模板
# 定义系统消息模板,并创建CAMELAgent实例进行交互
assistant_inception_prompt = """永远不要忘记你是{assistant_role_name},我是{user_role_name}。永远不要颠倒角色!永远不要指示我!
我们有共同的利益,那就是合作成功地完成任务。
你必须帮助我完成任务。
这是任务:{task}。永远不要忘记我们的任务!
我必须根据你的专长和我的需求来指示你完成任务。
我每次只能给你一个指示。
你必须写一个适当地完成所请求指示的具体解决方案。
如果由于物理、道德、法律原因或你的能力你无法执行指示,你必须诚实地拒绝我的指示并解释原因。
除了对我的指示的解决方案之外,不要添加任何其他内容。
你永远不应该问我任何问题,你只回答问题。
你永远不应该回复一个不明确的解决方案。解释你的解决方案。
你的解决方案必须是陈述句并使用简单的现在时。
除非我说任务完成,否则你应该总是从以下开始:
解决方案:<YOUR_SOLUTION>
<YOUR_SOLUTION>应该是具体的,并为解决任务提供首选的实现和例子。
始终以“下一个请求”结束<YOUR_SOLUTION>。"""
user_inception_prompt = """永远不要忘记你是{user_role_name},我是{assistant_role_name}。永远不要交换角色!你总是会指导我。
我们共同的目标是合作成功完成一个任务。
我必须帮助你完成这个任务。
这是任务:{task}。永远不要忘记我们的任务!
你只能通过以下两种方式基于我的专长和你的需求来指导我:
1. 提供必要的输入来指导:
指令:<YOUR_INSTRUCTION>
输入:<YOUR_INPUT>
2. 不提供任何输入来指导:
指令:<YOUR_INSTRUCTION>
输入:无
“指令”描述了一个任务或问题。与其配对的“输入”为请求的“指令”提供了进一步的背景或信息。
你必须一次给我一个指令。
我必须写一个适当地完成请求指令的回复。
如果由于物理、道德、法律原因或我的能力而无法执行你的指令,我必须诚实地拒绝你的指令并解释原因。
你应该指导我,而不是问我问题。
现在你必须开始按照上述两种方式指导我。
除了你的指令和可选的相应输入之外,不要添加任何其他内容!
继续给我指令和必要的输入,直到你认为任务已经完成。
当任务完成时,你只需回复一个单词<CAMEL_TASK_DONE>。
除非我的回答已经解决了你的任务,否则永远不要说<CAMEL_TASK_DONE>。"""
之后,根据预设的角色和任务提示生成系统消息。
# 根据预设的角色和任务提示生成系统消息
def get_sys_msgs(assistant_role_name: str, user_role_name: str, task: str):
assistant_sys_template = SystemMessagePromptTemplate.from_template(
template=assistant_inception_prompt
)
assistant_sys_msg = assistant_sys_template.format_messages(
assistant_role_name=assistant_role_name,
user_role_name=user_role_name,
task=task,
)[0]
user_sys_template = SystemMessagePromptTemplate.from_template(
template=user_inception_prompt
)
user_sys_msg = user_sys_template.format_messages(
assistant_role_name=assistant_role_name,
user_role_name=user_role_name,
task=task,
)[0]
return assistant_sys_msg, user_sys_msg
assistant_sys_msg, user_sys_msg = get_sys_msgs(
assistant_role_name, user_role_name, specified_task
)
创建Agent实例
# 创建助手和用户的CAMELAgent实例
assistant_agent = CAMELAgent(assistant_sys_msg, ChatOpenAI(temperature=0.2))
user_agent = CAMELAgent(user_sys_msg, ChatOpenAI(temperature=0.2))
# 重置两个agent
assistant_agent.reset()
user_agent.reset()
# 初始化对话互动
assistant_msg = HumanMessage(
content=(
f"{user_sys_msg.content}。"
"现在开始逐一给我介绍。"
"只回复指令和输入。"
)
)
user_msg = HumanMessage(content=f"{assistant_sys_msg.content}")
user_msg = assistant_agent.step(user_msg)
print(f"Original task prompt:\n{task}\n")
print(f"Specified task prompt:\n{specified_task}\n")
这里,assistant_inception_prompt 和 user_inception_prompt 是两个关键的提示,用于引导聊天代理的行为和交流方式。
- assistant_inception_prompt: 这个提示是为了引导助手(即营销专员)如何响应用户(即花店老板)的指示。它明确指出助手的角色和职责,强调了在完成任务的过程中需要遵循的一些基本规则和原则。例如,助手需要针对用户的每一个指示提供一个明确的解决方案,而且这个解决方案必须是具体、易于理解的,并且只有在遇到物理、道德、法律的限制或自身能力的限制时,才能拒绝用户的指示。这个提示的设计目标是引导助手在一次有目标的对话中,有效地对用户的指示做出响应。
- user_inception_prompt: 这个提示是为了引导用户(即花店老板)如何给助手(即营销专员)下达指示。它明确指出了用户的角色和职责,强调了在提出任务指示时需要遵循的一些基本规则和原则。例如,用户需要一次只给出一个指示,并且必须清楚地提供相关的输入(如果有的话)。而且用户在给出指示的同时,不能向助手提问。这个提示的设计目标是引导用户在一次有目标的对话中,有效地给出指示,以便助手能够更好地理解和完成任务。
接下来,模拟助手和用户之间的多轮对话,直到达到对话轮次上限或任务完成。
# 模拟对话交互,直到达到对话轮次上限或任务完成
chat_turn_limit, n = 30, 0
while n < chat_turn_limit:
n += 1
user_ai_msg = user_agent.step(assistant_msg)
user_msg = HumanMessage(content=user_ai_msg.content)
print(f"AI User ({user_role_name}):\n\n{user_msg.content}\n\n")
assistant_ai_msg = assistant_agent.step(user_msg)
assistant_msg = HumanMessage(content=assistant_ai_msg.content)
print(f"AI Assistant ({assistant_role_name}):\n\n{assistant_msg.content}\n\n")
if "<CAMEL_TASK_DONE>" in user_msg.content:
break
BabyAGI
随着ChatGPT的崭露头角,我们迎来了一种新型的代理——Autonomous Agents(自治代理或自主代理)。这些代理的设计初衷就是能够独立地执行任务,并持续地追求长期目标。在LangChain的代理、工具和记忆这些组件的支持下,它们能够在无需外部干预的情况下自主运行,这在真实世界的应用中具有巨大的价值。
AutoGPT
Auto-GPT 是由Toran Bruce Richards创建的一个开源的自主AI代理,基于OpenAI的GPT-4语言模型。它的主要功能是自动链接多个任务,以实现用户设定的大目标。与传统的聊天机器人(如ChatGPT)不同,用户只需提供一个提示或一组自然语言指令,Auto-GPT 就会通过自动化多步提示过程,将目标分解为子任务,以达到其目标。
Auto-GPT可以用于各种任务,例如生成文本、执行特定操作和进行网络搜索等。它还可以作为研究助手,帮助用户进行科学研究、市场研究、内容创建、销售线索生成、业务计划创建、产品评论、播客大纲制定等。
它的缺陷是没有长期记忆
BabyAGI
它可以根据设定的目标生成、组织、确定优先级以及执行任务。它也使用OpenAI的GPT-4语言模型来理解和创建任务,利用Pinecone向量搜索来存储和检索特定任务的结果,提供执行任务的上下文,并采用LangChain框架进行决策。BabyAGI尝试使用预定义的目标进行自我驱动,自动化个人任务管理。它不仅可以自动生成和执行任务,而且还可以根据完成的任务结果生成新任务,并且可以实时确定任务的优先级。
在BabyAGI中,你向系统提出一个目标之后,它将不断优先考虑需要实现或完成的任务,以实现该目标。具体来说,系统将形成任务列表,从任务列表中拉出优先级最高的第一个任务,使用 OpenAI API 根据上下文将任务发送到执行代理并完成任务,一旦这些任务完成,它们就会被存储在内存(或者Pinecone这类向量数据库)中,然后,根据目标和上一个任务的结果创建新任务并确定优先级。
在这个过程中,驱动任务的是三个不同作用的代理。分别是执行代理execution_agent,任务创建代理task_creation_agent,以及优先级设置代理prioritization_agent。
- 执行代理,是系统的核心,利用OpenAI的API来处理任务。这个代理的实现函数有两个参数,目标和任务,用于向 OpenAI 的 API 发送提示,并以字符串形式返回任务结果。
- 任务创建代理,通过OpenAI的API根据当前对象和先前任务的结果创建新任务。这个代理的实现函数有四个参数,目标、上一个任务的结果、任务描述和当前任务列表。这个代理会向 OpenAI 的 API 发送一条提示,该 API 将以字符串形式返回新任务列表。然后,该函数将以字典列表的形式返回这些新任务,其中每个字典都包含任务的名称。
- 优先级设置代理,负责任务列表的排序和优先级,仍然是通过调用OpenAI 的 API 来重新确定任务列表的优先级。这个代理的实现函数有一个参数,即当前任务的 ID。这个代理会向 OpenAI 的 API 发送提示,并返回已重新优先排序为编号列表的新任务列表。
HuggingGPT
HuggingGPT的工作流程包括四个阶段。
- 任务规划:LLM(例如ChatGPT)解析用户请求,生成任务列表,并确定任务之间的执行顺序和资源依赖关系。
- 模型选择:LLM根据Hugging Face上的专家模型描述,为任务分配适当的模型。
- 任务执行:整合各个任务端点上的专家模型,执行分配的任务。
- 响应生成:LLM整合专家的推断结果,生成工作流摘要,并给用户提供最终的响应。
HuggingGPT的设计,使其能够根据用户请求自动生成计划,并使用外部模型,从而整合多模态感知能力,并处理多个复杂的AI任务。此外,这种流程还允许HuggingGPT持续从任务特定的专家模型中吸收能力,从而实现可增长和可扩展的AI能力。
实战
这里实现自动创建、优先级排序和执行任务。
# 设置API Key
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI API Key
# 导入所需的库和模块
from collections import deque
from typing import Dict, List, Optional, Any
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import BaseLLM, OpenAI
from langchain.vectorstores.base import VectorStore
from pydantic import BaseModel, Field
from langchain.chains.base import Chain
from langchain.vectorstores import FAISS
import faiss
from langchain.docstore import InMemoryDocstore
初始化OpenAIEmbedding作为嵌入模型,并使用Faiss作为向量数据库存储任务信息。
# 定义嵌入模型
embeddings_model = OpenAIEmbeddings()
# 初始化向量存储
embedding_size = 1536
index = faiss.IndexFlatL2(embedding_size)
vectorstore = FAISS(embeddings_model.embed_query, index, InMemoryDocstore({}), {})
定义任务生成链,基于给定的条件,这个链可以创建新任务。
# 任务生成链
class TaskCreationChain(LLMChain):
"""负责生成任务的链"""
@classmethod
def from_llm(cls, llm: BaseLLM, verbose: bool = True) -> LLMChain:
"""从LLM获取响应解析器"""
task_creation_template = (
"You are a task creation AI that uses the result of an execution agent"
" to create new tasks with the following objective: {objective},"
" The last completed task has the result: {result}."
" This result was based on this task description: {task_description}."
" These are incomplete tasks: {incomplete_tasks}."
" Based on the result, create new tasks to be completed"
" by the AI system that do not overlap with incomplete tasks."
" Return the tasks as an array."
)
prompt = PromptTemplate(
template=task_creation_template,
input_variables=[
"result",
"task_description",
"incomplete_tasks",
"objective",
],
)
return cls(prompt=prompt, llm=llm, verbose=verbose)
定义任务优先级链,这个链负责重新排序任务的优先级。给定一个任务列表,它会返回一个新的按优先级排序的任务列表。
# 任务优先级链
class TaskPrioritizationChain(LLMChain):
"""负责任务优先级排序的链"""
@classmethod
def from_llm(cls, llm: BaseLLM, verbose: bool = True) -> LLMChain:
"""从LLM获取响应解析器"""
task_prioritization_template = (
"You are a task prioritization AI tasked with cleaning the formatting of and reprioritizing"
" the following tasks: {task_names}."
" Consider the ultimate objective of your team: {objective}."
" Do not remove any tasks. Return the result as a numbered list, like:"
" #. First task"
" #. Second task"
" Start the task list with number {next_task_id}."
)
prompt = PromptTemplate(
template=task_prioritization_template,
input_variables=["task_names", "next_task_id", "objective"],
)
return cls(prompt=prompt, llm=llm, verbose=verbose)
定义任务执行链,这个链负责执行具体的任务,并返回结果。
# 任务执行链
class ExecutionChain(LLMChain):
"""负责执行任务的链"""
@classmethod
def from_llm(cls, llm: BaseLLM, verbose: bool = True) -> LLMChain:
"""从LLM获取响应解析器"""
execution_template = (
"You are an AI who performs one task based on the following objective: {objective}."
" Take into account these previously completed tasks: {context}."
" Your task: {task}."
" Response:"
)
prompt = PromptTemplate(
template=execution_template,
input_variables=["objective", "context", "task"],
)
return cls(prompt=prompt, llm=llm, verbose=verbose)
定义一系列功能函数,实现 get_next_task、prioritize_tasks、_get_top_tasks 以及 execute_task 等具体功能。
def get_next_task(
task_creation_chain: LLMChain,
result: Dict,
task_description: str,
task_list: List[str],
objective: str,
) -> List[Dict]:
"""Get the next task."""
incomplete_tasks = ", ".join(task_list)
response = task_creation_chain.run(
result=result,
task_description=task_description,
incomplete_tasks=incomplete_tasks,
objective=objective,
)
new_tasks = response.split("\n")
return [{"task_name": task_name} for task_name in new_tasks if task_name.strip()]
def prioritize_tasks(
task_prioritization_chain: LLMChain,
this_task_id: int,
task_list: List[Dict],
objective: str,
) -> List[Dict]:
"""Prioritize tasks."""
task_names = [t["task_name"] for t in task_list]
next_task_id = int(this_task_id) + 1
response = task_prioritization_chain.run(
task_names=task_names, next_task_id=next_task_id, objective=objective
)
new_tasks = response.split("\n")
prioritized_task_list = []
for task_string in new_tasks:
if not task_string.strip():
continue
task_parts = task_string.strip().split(".", 1)
if len(task_parts) == 2:
task_id = task_parts[0].strip()
task_name = task_parts[1].strip()
prioritized_task_list.append({"task_id": task_id, "task_name": task_name})
return prioritized_task_list
def _get_top_tasks(vectorstore, query: str, k: int) -> List[str]:
"""Get the top k tasks based on the query."""
results = vectorstore.similarity_search_with_score(query, k=k)
if not results:
return []
sorted_results, _ = zip(*sorted(results, key=lambda x: x[1], reverse=True))
return [str(item.metadata["task"]) for item in sorted_results]
def execute_task(
vectorstore, execution_chain: LLMChain, objective: str, task: str, k: int = 5
) -> str:
"""Execute a task."""
context = _get_top_tasks(vectorstore, query=objective, k=k)
return execution_chain.run(objective=objective, context=context, task=task)
定义BabyAGI,这是主类,它控制整个系统的运行流程,包括添加任务、输出任务列表、执行任务等。
# BabyAGI 主类
class BabyAGI(Chain, BaseModel):
"""BabyAGI代理的控制器模型"""
task_list: deque = Field(default_factory=deque)
task_creation_chain: TaskCreationChain = Field(...)
task_prioritization_chain: TaskPrioritizationChain = Field(...)
execution_chain: ExecutionChain = Field(...)
task_id_counter: int = Field(1)
vectorstore: VectorStore = Field(init=False)
max_iterations: Optional[int] = None
class Config:
"""Configuration for this pydantic object."""
arbitrary_types_allowed = True
def add_task(self, task: Dict):
self.task_list.append(task)
def print_task_list(self):
print("\033[95m\033[1m" + "\n*****TASK LIST*****\n" + "\033[0m\033[0m")
for t in self.task_list:
print(str(t["task_id"]) + ": " + t["task_name"])
def print_next_task(self, task: Dict):
print("\033[92m\033[1m" + "\n*****NEXT TASK*****\n" + "\033[0m\033[0m")
print(str(task["task_id"]) + ": " + task["task_name"])
def print_task_result(self, result: str):
print("\033[93m\033[1m" + "\n*****TASK RESULT*****\n" + "\033[0m\033[0m")
print(result)
@property
def input_keys(self) -> List[str]:
return ["objective"]
@property
def output_keys(self) -> List[str]:
return []
def _call(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
"""Run the agent."""
objective = inputs["objective"]
first_task = inputs.get("first_task", "Make a todo list")
self.add_task({"task_id": 1, "task_name": first_task})
num_iters = 0
while True:
if self.task_list:
self.print_task_list()
# Step 1: Pull the first task
task = self.task_list.popleft()
self.print_next_task(task)
# Step 2: Execute the task
result = execute_task(
self.vectorstore, self.execution_chain, objective, task["task_name"]
)
this_task_id = int(task["task_id"])
self.print_task_result(result)
# Step 3: Store the result in Pinecone
result_id = f"result_{task['task_id']}_{num_iters}"
self.vectorstore.add_texts(
texts=[result],
metadatas=[{"task": task["task_name"]}],
ids=[result_id],
)
# Step 4: Create new tasks and reprioritize task list
new_tasks = get_next_task(
self.task_creation_chain,
result,
task["task_name"],
[t["task_name"] for t in self.task_list],
objective,
)
for new_task in new_tasks:
self.task_id_counter += 1
new_task.update({"task_id": self.task_id_counter})
self.add_task(new_task)
self.task_list = deque(
prioritize_tasks(
self.task_prioritization_chain,
this_task_id,
list(self.task_list),
objective,
)
)
num_iters += 1
if self.max_iterations is not None and num_iters == self.max_iterations:
print(
"\033[91m\033[1m" + "\n*****TASK ENDING*****\n" + "\033[0m\033[0m"
)
break
return {}
@classmethod
def from_llm(
cls, llm: BaseLLM, vectorstore: VectorStore, verbose: bool = False, **kwargs
) -> "BabyAGI":
"""Initialize the BabyAGI Controller."""
task_creation_chain = TaskCreationChain.from_llm(llm, verbose=verbose)
task_prioritization_chain = TaskPrioritizationChain.from_llm(
llm, verbose=verbose
)
execution_chain = ExecutionChain.from_llm(llm, verbose=verbose)
return cls(
task_creation_chain=task_creation_chain,
task_prioritization_chain=task_prioritization_chain,
execution_chain=execution_chain,
vectorstore=vectorstore,
**kwargs,
)
主执行部分,这是代码的入口点,定义了一个目标(分析北京市今天的气候情况,并提出鲜花储存策略),然后初始化并运行BabyAGI。
# 主执行部分
if __name__ == "__main__":
OBJECTIVE = "分析一下北京市今天的气候情况,写出鲜花储存策略。"
llm = OpenAI(temperature=0)
verbose = False
max_iterations: Optional[int] = 6
baby_agi = BabyAGI.from_llm(llm=llm, vectorstore=vectorstore,
verbose=verbose,
max_iterations=max_iterations)
baby_agi({"objective": OBJECTIVE})