Skip to main content

· 8 min read
Ricky Loynd

可教学代理架构

简而言之:

  • 我们介绍了可教学代理(使用文本分析代理),以便用户可以教会他们的基于LLM的助手新的事实、偏好和技能。
  • 我们展示了可教学代理学习并在后续聊天中回忆事实、偏好和技能的示例。

引言

基于LLM的会话助手可以记住与用户的当前聊天,并且还可以在对话中学习用户的教导。但是一旦聊天结束,或者单个聊天对LLM来说太长而无法有效处理,助手的记忆和学习就会丢失。然后在后续的聊天中,用户被迫一遍又一遍地重复任何必要的指令。

可教学代理通过在长期记忆中持久化用户教导来解决这些限制,该记忆实现为向量数据库。记忆会在每次聊天结束时自动保存到磁盘,然后在下一次聊天开始时从磁盘加载。代替将所有记忆复制到上下文窗口中,这将占用宝贵的空间,需要时将单个记忆(称为备忘录)检索到上下文中。这允许用户只教一次常用的事实和技能给可教学代理,并在后续聊天中回忆它们。

为了有效决策备忘录的存储和检索,可教学代理调用文本分析代理的一个实例(另一个AutoGen代理)来识别和重新制定文本,以便记住事实、偏好和技能。请注意,这会增加额外的LLM调用,涉及相对较少的令牌数量,这可能会使用户等待每个响应的时间增加几秒钟。

自己运行

AutoGen包含三个使用可教学代理的代码示例。

  1. 运行chat_with_teachable_agent.py可教学代理对话。

  2. 使用Jupyter笔记本agentchat_teachability.ipynb逐步查看下面讨论的示例。

  3. 运行test_teachable_agent.py快速单元测试可教学代理

TeachableAgent的基本使用

  1. 安装依赖项

请在使用TeachableAgent之前安装带有[teachable]选项的pyautogen。

pip install "pyautogen[teachable]"
  1. 导入代理
from autogen import UserProxyAgent, config_list_from_json
from autogen.agentchat.contrib.teachable_agent import TeachableAgent
  1. 创建llm_config
# 从环境变量或文件加载LLM推理端点
# 参见 https://microsoft.github.io/autogen/docs/FAQ#set-your-api-endpoints
# 和 OAI_CONFIG_LIST_sample
filter_dict = {"model": ["gpt-4"]} # GPT-3.5比GPT-4在从用户反馈中学习时不那么可靠。
config_list = config_list_from_json(env_or_file="OAI_CONFIG_LIST", filter_dict=filter_dict)
llm_config={"config_list": config_list, "timeout": 120}
  1. 创建代理
teachable_agent = TeachableAgent(
name="teachableagent",
llm_config=llm_config,
teach_config={
"reset_db": False, # 使用True强制重置备忘录数据库,使用False使用现有数据库。
"path_to_db_dir": "./tmp/interactive/teachable_agent_db" # 可以是任何路径。
}
)

user = UserProxyAgent("user", human_input_mode="ALWAYS")
  1. 可教学代理聊天
# 当用户输入'exit'时,此函数将返回。
teachable_agent.initiate_chat(user, message="Hi, I'm a teachable user assistant! What's on your mind?")
  1. 更新磁盘上的数据库
# 在关闭应用程序之前,让可教学代理存储应该从此聊天中学习的内容。
teachable_agent.learn_from_user_feedback()
teachable_agent.close_db()

示例1 - 学习用户信息

用户可以教代理关于他们自己的事实。 (请注意,由于它们的微调,LLM可能不愿意承认它们知道个人信息。)

清除记忆
teachableagent(对用户):

嗨,我是一个可教学的用户助手!你在想什么?

--------------------------------------------------------------------------------
向teachableagent提供反馈。按Enter跳过并使用自动回复,或输入'exit'结束对话:我的名字是Ricky
用户(对teachableagent):

我的名字是Ricky

--------------------------------------------------------------------------------
teachableagent(对用户):

你好,Ricky!很高兴认识你。今天我能帮你什么?

--------------------------------------------------------------------------------
向teachableagent提供反馈。按Enter跳过并使用自动回复,或输入'exit'结束对话:exit

回顾聊天以记住用户教导的内容

将记忆保存到磁盘
位置 = ./tmp/interactive/te

achable_agent_db

示例2 - 学习用户偏好

用户可以教代理关于他们的偏好。

清除记忆
teachableagent(对用户):

嗨,我是一个可教学的用户助手!你在想什么?

--------------------------------------------------------------------------------
向teachableagent提供反馈。按Enter跳过并使用自动回复,或输入'exit'结束对话:我喜欢在周末去徒步旅行
用户(对teachableagent):

我喜欢在周末去徒步旅行

--------------------------------------------------------------------------------
teachableagent(对用户):

徒步旅行是一个很好的活动。你通常去哪里徒步?

--------------------------------------------------------------------------------
向teachableagent提供反馈。按Enter跳过并使用自动回复,或输入'exit'结束对话:exit

回顾聊天以记住用户教导的内容

将记忆保存到磁盘
位置 = ./tmp/interactive/teachable_agent_db

示例3 - 学习用户技能

用户可以教代理关于他们的技能。

清除记忆
teachableagent(对用户):

嗨,我是一个可教学的用户助手!你在想什么?

--------------------------------------------------------------------------------
向teachableagent提供反馈。按Enter跳过并使用自动回复,或输入'exit'结束对话:我会弹吉他
用户(对teachableagent):

我会弹吉他

--------------------------------------------------------------------------------
teachableagent(对用户):

那太酷了!你弹了多久了?

--------------------------------------------------------------------------------
向teachableagent提供反馈。按Enter跳过并使用自动回复,或输入'exit'结束对话:exit

回顾聊天以记住用户教导的内容

将记忆保存到磁盘
位置 = ./tmp/interactive/teachable_agent_db

结论

可教学代理提供了一种方法,允许基于LLM的助手记住用户的教导,并在后续聊天中使用这些信息。这种方法通过使用备忘录数据库和文本分析代理来持久化记忆,从而提高了用户体验,并减少了重复指令的需要。尽管这可能会增加响应时间,但它为用户提供了更个性化和连贯的交互体验。

参考文献

版权声明

本文档由AutoGen团队编写,并遵循MIT许可协议。所有代码示例均可自由使用、修改和分发。


如果您对可教学代理的概念或实现有任何疑问,请随时联系我们。我们期待您的反馈和贡献!

感谢您的阅读!

AutoGen团队

返回顶部

· 13 min read
Li Jiang

RAG 架构

简而言之:

  • 我们介绍了 AutoGen 的 RAG 代理 RetrieveUserProxyAgentRetrieveAssistantAgent,它们允许检索增强生成及其基本用法。
  • 我们展示了 RAG 代理的自定义,例如自定义嵌入函数、文本分割函数和向量数据库。
  • 我们还展示了 RAG 代理的两种高级用法,包括与群聊集成和使用 Gradio 构建聊天应用程序。

介绍

检索增强已经成为一种实用且有效的方法,通过结合外部文档来缓解 LLM 的内在限制。在这篇博客文章中,我们介绍了允许检索增强生成的 AutoGen 的 RAG 代理。该系统由两个代理组成:一个检索增强的用户代理,称为 RetrieveUserProxyAgent,和一个检索增强的助手代理,称为 RetrieveAssistantAgent,两者都是从 AutoGen 的内置代理扩展而来。RAG 代理的整体架构如上图所示。

要使用检索增强聊天,需要初始化两个代理,包括检索增强的用户代理和检索增强的助手代理。初始化检索增强的用户代理需要指定文档集合的路径。随后,检索增强的用户代理可以下载文档,将它们分割成特定大小的块,计算嵌入,并将它们存储在向量数据库中。一旦开始聊天,代理就会根据下面概述的程序合作进行代码生成或问答:

  1. 检索增强的用户代理根据嵌入相似性检索文档块,并将它们连同问题一起发送给检索增强的助手代理。
  2. 检索增强的助手代理使用 LLM 根据提供的问题和上下文生成代码或文本作为答案。如果 LLM 无法产生满意的回应,它会被指示回复“更新上下文”给检索增强的用户代理。
  3. 如果回应包含代码块,检索增强的用户代理执行代码并将输出作为反馈发送。如果没有代码块或指示更新上下文的指令,它终止对话。否则,它更新上下文并将问题连同新上下文一起转发给检索增强的助手代理。请注意,如果启用了人工输入请求,个人可以主动发送任何反馈,包括“更新上下文”,给检索增强的助手代理。
  4. 如果检索增强的助手代理收到“更新上下文”,它会从检索增强的用户代理请求下一组最相似的文档块作为新上下文。否则,它基于反馈和聊天历史生成新的代码或文本。如果 LLM 未能生成答案,它会再次回复“更新上下文”。这个过程可以重复多次。如果没有更多的文档可用于上下文,对话就终止。

RAG 代理的基本用法

  1. 安装依赖

请在使用 RAG 代理之前安装 pyautogen 和 [retrievechat] 选项。

pip install "pyautogen[retrievechat]"
  1. 导入代理
from autogen
from autogen.agentchat.contrib.retrieve_assistant_agent import RetrieveAssistantAgent
from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent
  1. 创建一个名为 "assistant" 的 'RetrieveAssistantAgent' 实例和一个名为 "ragproxyagent" 的 'RetrieveUserProxyAgent' 实例
assistant = RetrieveAssistantAgent(
name="assistant",
system_message="You are a helpful assistant.",
llm_config=llm_config,
)

ragproxyagent = RetrieveUserProxyAgent(
name="ragproxyagent",
retrieve_config={
"task": "qa",
"docs_path": "https://raw.githubusercontent.com/microsoft/autogen/main/README.md",
},
)
  1. 初始化聊天并提问
assistant.reset()
ragproxyagent.initiate_chat(assistant, problem="What is autogen?")

输出如下:

--------------------------------------------------------------------------------
assistant (to ragproxyagent):

AutoGen 是一个框架,它允许使用多个可以相互对话以解决任务的代理来开发大型语言模型(LLM)应用程序。这些代理是可定制的、可对话的,并允许人类参与。他们可以在使用 LLM、人类输入和工具的组合模式下操作。

--------------------------------------------------------------------------------
  1. 创建一个 UserProxyAgent 并提出相同的问题
assistant.reset()
userproxyagent = autogen.UserProxyAgent(name="userproxyagent")
userproxyagent.initiate_chat(assistant, message="What is autogen?")

输出如下:

--------------------------------------------------------------------------------
assistant (to userproxyagent):

在计算机软件中,autogen 是一个自动生成程序代码的工具,无需手动编码。它通常用于软件工程、游戏开发和网页开发等领域,以加快开发过程并减少错误。Autogen 工具通常使用预编程的规则、模板和数据为重复任务生成代码,例如生成用户界面、数据库架构和数据模型。一些流行的 autogen 工具包括 Visual Studio 的代码生成器和 Unity 的资产商店。

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

您可以看到 UserProxyAgent 的输出与我们的 autogen 无关,因为最新的 `aut

ogen 信息不在 ChatGPT 的训练数据中。RetrieveUserProxyAgent` 的输出是正确的,因为它可以根据给定的文档文件执行检索增强生成。

自定义 RAG 代理

RetrieveUserProxyAgent 可以通过 retrieve_config 进行自定义。根据不同的用例,有几个参数可以配置。在本节中,我们将展示如何自定义嵌入函数、文本分割函数和向量数据库。

自定义嵌入函数

默认情况下,将使用 Sentence Transformers 及其预训练模型来计算嵌入。您可能想要使用 OpenAI、Cohere、HuggingFace 或其他嵌入函数。

  • OpenAI
from chromadb.utils import embedding_functions

openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key="YOUR_API_KEY",
model_name="text-embedding-ada-002"
)

ragproxyagent = RetrieveUserProxyAgent(
name="ragproxyagent",
retrieve_config={
"task": "qa",
"docs_path": "https://raw.githubusercontent.com/microsoft/autogen/main/README.md",
"embedding_function": openai_ef,
},
)
  • HuggingFace
huggingface_ef = embedding_functions.HuggingFaceEmbeddingFunction(
api_key="YOUR_API_KEY",
model_name="sentence-transformers/all-MiniLM-L6-v2"
)

更多示例可以在这里找到。

自定义文本分割函数

在我们将文档存储到向量数据库之前,我们需要将文本分割成块。尽管我们在 autogen 中实现了一个灵活的文本分割器,您可能仍然想要使用不同的文本分割器。还有一些现有的文本分割工具可以重用。

例如,您可以使用 langchain 中的所有文本分割器。

from langchain.text_splitter import RecursiveCharacterTextSplitter

recur_spliter = RecursiveCharacterTextSplitter(separators=["\n", "\r", "\t"])

ragproxyagent = RetrieveUserProxyAgent(
name="ragproxyagent",
retrieve_config={
"task": "qa",
"docs_path": "https://raw.githubusercontent.com/microsoft/autogen/main/README.md",
"custom_text_split_function": recur_spliter.split_text,
},
)

自定义向量数据库

我们使用 chromadb 作为默认的向量数据库,您也可以通过简单地覆盖 RetrieveUserProxyAgentretrieve_docs 函数来替换它。

例如,您可以使用 Qdrant 如下:

# 创建 qdrant 客户端
from qdrant_client import QdrantClient

client = QdrantClient(url="***", api_key="***")

# 包装 RetrieveUserProxyAgent
from litellm import embedding as test_embedding
from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent
from qdrant_client.models import SearchRequest, Filter, FieldCondition, MatchText

class QdrantRetrieveUserProxyAgent(RetrieveUserProxyAgent):
def query_vector_db(
self,
query_texts: List[str],
n_results: int = 10,
search_string: str = "",
**kwargs,
) -> Dict[str, Union[List[str], List[List[str]]]]:
# 在这里定义您自己的查询函数
embed_response = test_embedding('text-embedding-ada-002', input=query_texts)

all_embeddings: List[List[float]] = []

for item in embed_response['data']:
all_embeddings.append(item['embedding'])

search_queries: List[SearchRequest] = []

for embedding in all_embeddings:
search_queries.append(
SearchRequest(
vector=embedding,
filter=Filter(
must=[
FieldCondition(
key="page_content",
match=MatchText(
text=search_string,
)
)
]
),
limit=n_results,
with_payload=True,
)
)

search_response = client.search_batch(
collection_name="{your collection name}",
requests=search_queries,
)

return {
"ids": [[scored_point.id for scored_point in batch] for batch in search_response],
"documents": [[scored_point.payload.get('page_content', '') for scored_point in batch] for batch in search_response],
"metadatas": [[scored_point.payload.get('metadata', {}) for scored_point in batch] for batch in search_response]
}

def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str = "", **kwargs):
results = self.query_vector_db(
query_texts=[problem],
n_results=n_results,
search_string=search_string,
**kwargs,
)

self._results = results


# 使用 QdrantRetrieveUserProxyAgent
qdrantragagent = QdrantRetrieveUserProxyAgent(
name="ragproxyagent",
human_input_mode="NEVER",
max_consecutive_auto_reply=2,
retrieve_config={
"task": "qa",
},
)

qdrantragagent.retrieve_docs("What is Autogen?", n_results=10, search_string="autogen")

RAG代理的高级使用方法

在群聊中与其他代理集成

在群聊中使用RetrieveUserProxyAgent与在两个代理的聊天中使用它几乎相同。唯一的区别是你需要用RetrieveUserProxyAgent初始化聊天。在群聊中不需要RetrieveAssistantAgent

然而,在某些情况下,你可能想要用另一个代理初始化聊天。为了充分利用RetrieveUserProxyAgent的优势,你需要从一个函数中调用它。

llm_config = {
"functions": [
{
"name": "retrieve_content",
"description": "retrieve content for code generation and question answering.",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Refined message which keeps the original meaning and can be used to retrieve content for code generation and question answering.",
}
},
"required": ["message"],
},
},
],
"config_list": config_list,
"timeout": 60,
"seed": 42,
}

boss = autogen.UserProxyAgent(
name="Boss",
is_termination_msg=termination_msg,
human_input_mode="TERMINATE",
system_message="The boss who ask questions and give tasks.",
)

boss_aid = RetrieveUserProxyAgent(
name="Boss_Assistant",
is_termination_msg=termination_msg,
system_message="Assistant who has extra content retrieval power for solving difficult problems.",
human_input_mode="NEVER",
max_consecutive_auto_reply=3,
retrieve_config={
"task": "qa",
},
code_execution_config=False, # we don't want to execute code in this case.
)

coder = AssistantAgent(
name="Senior_Python_Engineer",
is_termination_msg=termination_msg,
system_message="You are a senior python engineer. Reply `TERMINATE` in the end when everything is done.",
llm_config=llm_config,
)

pm = autogen.AssistantAgent(
name="Product_Manager",
is_termination_msg=termination_msg,
system_message="You are a product manager. Reply `TERMINATE` in the end when everything is done.",
llm_config=llm_config,
)

reviewer = autogen.AssistantAgent(
name="Code_Reviewer",
is_termination_msg=termination_msg,
system_message="You are a code reviewer. Reply `TERMINATE` in the end when everything is done.",
llm_config=llm_config,
)

def retrieve_content(message, n_results=3):
boss_aid.n_results = n_results # Set the number of results to be retrieved.
# Check if we need to update the context.
update_context_case1, update_context_case2 = boss_aid._check_update_context(message)
if (update_context_case1 or update_context_case2) and boss_aid.update_context:
boss_aid.problem = message if not hasattr(boss_aid, "problem") else boss_aid.problem
_, ret_msg = boss_aid._generate_retrieve_user_reply(message)
else:
ret_msg = boss_aid.generate_init_message(message, n_results=n_results)
return ret_msg if ret_msg else message

for agent in [boss, coder, pm, reviewer]:
# register functions for all agents.
agent.register_function(
function_map={
"retrieve_content": retrieve_content,
}
)

groupchat = autogen.GroupChat(
agents=[boss, coder, pm, reviewer], messages=[], max_round=12
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)

# Start chatting with boss as this is the user proxy agent.
boss.initiate_chat(
manager,
message="How to use spark for parallel training in FLAML? Give me sample code.",
)

使用Gradio构建聊天应用

现在,让我们总结一下并用AutoGen和Gradio制作一个聊天应用。

RAG ChatBot with AutoGen

# 初始化代理
def initialize_agents(config_list, docs_path=None):
...
return assistant, ragproxyagent

# 初始化聊天
def initiate_chat(config_list, problem, queue, n_results=3):
...
assistant.reset()
try:
ragproxyagent.a_initiate_chat(
assistant, problem=problem, silent=False, n_results=n_results
)
messages = ragproxyagent.chat_messages
messages = [messages[k] for k in messages.keys()][0]
messages = [m["content"] for m in messages if m["role"] == "user"]
print("messages: ", messages)
except Exception as e:
messages = [str(e)]
queue.put(messages)

# 将AutoGen部分包装成一个函数
def chatbot_reply(input_text):
"""通过终端与代理聊天。"""
queue = mp.Queue()
process = mp.Process(
target=initiate_chat,
args=(config_list, input_text, queue),
)
process.start()
try:
messages = queue.get(timeout=TIMEOUT)
except Exception as e:
messages = [str(e) if len(str(e)) > 0 else "Invalid Request to OpenAI, please check your API keys."]
finally:
try:
process.terminate()
except:
pass
return messages

...

# 使用Gradio设置UI
with gr.Blocks() as demo:
...
assistant, ragproxyagent = initialize_agents(config_list)

chatbot = gr.Chatbot(
[],
elem_id="chatbot",
bubble_full_width=False,
avatar_images=(None, (os.path.join(os.path.dirname(__file__), "autogen.png"))),
# height=600,
)

txt_input = gr.Textbox(
scale=4,
show_label=False,
placeholder="Enter text and press enter",
container=False,
)

with gr.Row():
txt_model = gr.Dropdown(
label="Model",
choices=[
"gpt-4",
"gpt-35-turbo",
"gpt-3.5-turbo",
],
allow_custom_value=True,
value="gpt-35-turbo",
container=True,
)
txt_oai_key = gr.Textbox(
label="OpenAI API Key",
placeholder="Enter key and press enter",
max_lines=1,
show_label=True,
value=os.environ.get("OPENAI_API_KEY", ""),
container=True,
type="password",
)
...

clear = gr.ClearButton([txt_input, chatbot])

...

if __name__ == "__main__":
demo.launch(share=True)

在线应用和源代码托管在[HuggingFace](https://huggingface.co/spaces/think

all/autogen-demos)。欢迎尝试!

阅读更多

你可以查看更多RAG使用案例的示例笔记本:

· 3 min read
Jiale Liu

简要总结: 我们展示了如何使用autogen进行本地LLM应用。作为示例,我们将启动一个使用FastChat的端点,并在ChatGLMv2-6b上执行推理。

准备工作

克隆FastChat

FastChat为其支持的模型提供了与OpenAI兼容的API,因此您可以将FastChat作为OpenAI API的本地替代品使用。然而,其代码需要进行小的修改才能正常工作。

git clone https://github.com/lm-sys/FastChat.git
cd FastChat

下载检查点

ChatGLM-6B是一个基于通用语言模型(GLM)框架的开放双语语言模型,拥有62亿参数。ChatGLM2-6B是其第二代版本。

在从HuggingFace Hub下载之前,您需要安装Git LFS 安装

git clone https://huggingface.co/THUDM/chatglm2-6b

启动服务器

首先,启动控制器

python -m fastchat.serve.controller

然后,启动模型工作器

python -m fastchat.serve.model_worker --model-path chatglm2-6b

最后,启动RESTful API服务器

python -m fastchat.serve.openai_api_server --host localhost --port 8000

通常这样做就可以了。然而,如果您遇到像这样的错误,注释掉fastchat/protocol/api_protocal.pyfastchat/protocol/openai_api_protocol.py中所有包含finish_reason的行,将解决问题。修改后的代码看起来像这样:

class CompletionResponseChoice(BaseModel):
index: int
text: str
logprobs: Optional[int] = None
# finish_reason: Optional[Literal["stop", "length"]]

class CompletionResponseStreamChoice(BaseModel):
index: int
text: str
logprobs: Optional[float] = None
# finish_reason: Optional[Literal["stop", "length"]] = None

使用oai.Completion与模型交互(要求openai<1)

现在可以通过openai-python库以及autogen.oai.Completionautogen.oai.ChatCompletion直接访问模型。

from autogen import oai

# 创建一个文本完成请求
response = oai.Completion.create(
config_list=[
{
"model": "chatglm2-6b",
"base_url": "http://localhost:8000/v1",
"api_type": "open_ai",
"api_key": "NULL", # 只是一个占位符
}
],
prompt="Hi",
)
print(response)

# 创建一个聊天完成请求
response = oai.ChatCompletion.create(
config_list=[
{
"model": "chatglm2-6b",
"base_url": "http://localhost:8000/v1",
"api_type": "open_ai",
"api_key": "NULL",
}
],
messages=[{"role": "user", "content": "Hi"}]
)
print(response)

如果您想切换到不同的模型,请下载它们的检查点,并在启动模型工作器时指定模型路径。

与多个本地LLM交互

如果您想在本地机器上与多个LLM交互,请用多模型变体替换上面的model_worker步骤:

python -m fastchat.serve.multi_model_worker \
--model-path lmsys/vicuna-7b-v1.3 \
--model-names vicuna-7b-v1.3 \
--model-path chatglm2-6b \
--model-names chatglm2-6b

推理代码将是:

from autogen import oai

# 创建一个聊天完成请求
response = oai.ChatCompletion.create(
config_list=[
{
"model": "chatglm2-6b",
"base_url": "http://localhost:8000/v1",
"api_type": "open_ai",
"api_key": "NULL",
},
{
"model": "vicuna-7b-v1.3",
"base_url": "http://localhost:8000/v1",
"api_type": "open_ai",
"api_key": "NULL",
}
],
messages=[{"role": "user", "content": "Hi"}]
)
print(response)

进一步阅读

· 12 min read
Yiran Wu

MathChat 工作流程 简要总结:

  • 我们介绍了MathChat,一个利用大型语言模型(LLMs),特别是GPT-4,来解决高级数学问题的对话框架。
  • MathChat提高了LLM在挑战性数学问题解决方面的性能,比基本提示和其他策略高出约6%。在代数类别中,准确性特别提高了15%。
  • 尽管有所进步,GPT-4在解决非常具有挑战性的数学问题上仍然存在困难,即使采用有效的提示策略。还需要进一步改进,例如开发更具体的助手模型或集成新工具和提示。

最近的大型语言模型(LLMs),如GTP-3.5和GPT-4,在各种任务上展示了惊人的能力,例如文本生成、问题回答和代码生成。此外,这些模型可以通过对话与人类交流并记住之前的上下文,使人类更容易与它们互动。这些模型在我们日常生活中扮演着越来越重要的角色,帮助人们完成不同的任务,如写电子邮件、总结文件和编写代码。

在这篇博客文章中,我们探讨了LLMs的问题解决能力。具体来说,我们对它们解决高级数学问题的能力感兴趣,这可能代表了一类需要精确推理并且有确定性解决方案的更广泛问题。

我们介绍了MathChat,一个为了与LLMs一起解决挑战性数学问题而设计的对话框架。这个框架利用了最先进LLMs的聊天优化特性,其中用户代理和LLM助手共同努力解决数学问题。我们还测试了之前的提示技术进行比较。

MathChat框架

MathChat模拟了LLM助手和用户代理之间的对话。顾名思义,用户代理充当用户的代理,负责与LLM助手通信并以期望的方式继续对话。

代理首先向LLM助手提出一个数学问题,由一个初始提示框架出来,指导助手如何协作解决问题。我们在初始提示中融入了现有的提示方法,如CoT(思维链)和使用工具的提示。提示的关键组成部分包括:

  • 使用工具的提示: 这指导LLM助手使用正确格式的Python代码来解决问题,允许用户代理解析代码并提供适当的结果。

  • 问题解决策略选择提示: 助手被指示选择三种潜在问题解决策略之一,包括:

    1. 编写Python程序直接解决问题。
    2. 不使用Python直接解决问题,这锻炼了LLMs的内在推理能力。
    3. 使用Python逐步解决问题。当前两种策略不适用时应使用此策略。
  • 最终答案封装提示: 这部分指导助手将最终答案放在 \boxed{} 中。

MathChat的提示设计灵活,可以轻松地细化以包括其他工具,如Wolfram Alpha。

让我们看一个例子,介绍用户代理LLM助手(GPT-4)之间的对话。对话集中在如何使用Python解决不等式。 (对话已修改以便阅读。)

参与者对话
用户代理(初始提示: 让我们用Python解决一个数学问题...) 问题:找到所有满足不等式 (2x+10)(x+3) < (3x+9)(x+8) 的x。用区间表示法表达你的答案。
LLM助手解决这个问题的关键思路是找到临界点,这些是不等式相等的点。然后测试临界点之间的区间,找出不等式成立的区间。
LLM助手(继续)这是找到临界点的Python代码:from sympy import symbols, Eq, solve; x = symbols("x"); lhs = (2*x + 10)*(x + 3); rhs = (3*x + 9)*(x + 8); equation = Eq(lhs, rhs); critical_points = solve(equation, x); critical_points
用户代理临界点是:[-14, -3]
LLM助手我们发现临界点是x = -14和x = -3。现在我们将测试这些临界点之间的区间,以及临界点之前和之后的点。区间是:x < -14, -14 < x < -3, x > -3。
LLM助手(继续)这是测试每个区间不等式的Python代码:from sympy import symbols; x = symbols("x"); inequality = (2*x + 10)*(x + 3) < (3*x + 9)*(x + 8); test_points = [-15, -5, 0]; intervals = []; for point in test_points: if inequality.subs(x, point): intervals.append(point); intervals
用户代理不等式成立的区间是测试点:[-15, 0]

| LLM

助手 | 我们已经测试了区间并发现不等式对于:1. x < -14 (测试点:-15) 2. x > -3 (测试点:0) 是成立的。因此,不等式 (2x+10)(x+3) < (3x+9)(x+8) 的解是 x ∈ (-∞, -14) ∪ (-3, ∞) |

实验设置

我们评估了MathChat带来的改进。

对于实验,我们专注于MATH数据集中的5级问题,这些问题由高中竞赛问题组成。这些问题包括定理的应用和复杂方程的推导,即使对于本科生来说也是具有挑战性的。我们评估了数据集中的6个类别中的7个(几何除外):初等代数、代数、数论、计数与概率、中级代数和预备微积分。

我们评估了GPT-4并使用了OpenAI API的默认配置。为了访问最终性能,我们手动将最终答案与正确答案进行比较。对于普通提示、程序合成和MathChat,我们让GPT-4将最终答案放在 \boxed{} 中,并将PoT函数的返回作为最终答案。

我们还评估了以下方法进行比较:

  1. 普通提示: 评估GPT-4的直接问题解决能力。使用的提示是:"仔细解决问题。将最终答案放在 \boxed{} 中"

  2. 思维程序(PoT): 使用零次射击PoT提示,要求模型创建一个Solver函数来解决问题并返回最终答案。

  3. 程序合成(PS)提示: 像PoT一样,它提示模型编写一个程序来解决问题。使用的提示是:"编写一个程序来回答以下问题:{问题}"

实验结果

下面显示了使用不同方法从MATH数据集的不同类别中难度为5级的所有问题的准确性:

结果

我们发现,与展示GPT-4内在能力的基本提示相比,将Python用于PoT或PS策略的上下文中提高了大约10%的总体准确性。这种增加主要出现在涉及更多数字操作的类别,如计数与概率和数论,以及更复杂的类别,如中级代数和预备微积分。

对于代数和初等代数等类别,PoT和PS显示出的改进很小,在某些情况下,甚至导致准确性下降。然而,MathChat能够相比PoT和PS提高总体准确性约6%,在所有类别中显示出竞争性能。值得注意的是,MathChat在代数类别中的准确性提高了约15%,超过了其他方法。注意,像中级代数和预备微积分这样的类别对所有方法来说仍然是挑战,只有大约20%的问题被准确解决。

实验的代码可以在这个仓库找到。 我们现在提供了使用AutoGen中的交互式代理的MathChat实现。参见这个笔记本以了解示例用法。

未来方向

尽管MathChat在先前方法上有所改进,但结果显示,即使在外部工具的帮助下,复杂的数学问题对于最近强大的LLMs,如GPT-4,仍然是一个挑战。

可以进行进一步的工作来增强这个框架或一般的数学问题解决:

  • 虽然使模型能够使用像Python这样的工具可以减少计算错误,但LLMs仍然容易出现逻辑错误。像自我一致性(采样多个解决方案并对最终答案进行多数投票)或自我验证(使用另一个LLM实例检查答案是否正确)的方法可能会提高性能。
  • 有时,LLM能否解决问题取决于它使用的计划。一些计划需要较少的计算和逻辑推理,从而减少了错误的空间。
  • MathChat有潜力被改编成一个副驾驶系统,这可以帮助用户解决数学问题。这个系统可以允许用户更多地参与解决问题的过程,可能增强学习。

进一步阅读

您是否正在从事涉及数学问题解决的应用?您是否需要在LLM-based代理用于数学问题解决的应用上额外的研究或支持?请加入我们的Discord服务器进行讨论。

· 12 min read
Chi Wang

在编码成功率和推理成本上,使用GPT-3.5和GPT-4的自适应方式优于单独使用GPT-4

简而言之:

  • 使用HumanEval基准测试案例研究表明,通过自适应使用多个GPT模型,可以在编码方面实现更高的准确性(从68%提高到90%)和更低的推理成本(降低18%),而不是单独使用GPT-4。

GPT-4是基础模型能力的一个重大升级,例如在代码和数学方面,伴随着使用代价的大幅提升(超过10倍)相比于GPT-3.5-Turbo。在OpenAI开发的代码完成基准测试HumanEval上,GPT-4可以成功解决68%的任务,而GPT-3.5-Turbo则为46%。通过生成多个响应或进行多次调用,可以进一步提高GPT-4的成功率。然而,这将进一步增加成本,而且API调用率限制更为严格,成本已经近乎使用GPT-3.5-Turbo的20倍。我们能否用更少的资源做到更多?

在这篇博客文章中,我们将探索一种创造性的自适应使用GPT模型的方法,这将带来一个巨大的飞跃。

观察

  • GPT-3.5-Turbo已经可以解决40%-50%的任务。对于这些任务,如果我们从不使用GPT-4,我们可以节省近40-50%的成本。
  • 如果我们使用节省下来的成本在剩余未解决的任务上用GPT-4生成更多响应,有可能在保持平均成本降低的同时解决更多任务。

利用这些观察的障碍是,我们事先不知道哪些任务可以由更便宜的模型解决,哪些任务需要更昂贵的模型解决,以及哪些任务需要支付更多给昂贵的模型。

为了克服这个障碍,人们可能想要预测哪个任务需要哪个模型来解决,以及每个任务需要多少响应。让我们看一个代码完成任务的例子:

def vowels_count(s):
"""Write a function vowels_count which takes a string representing
a word as input and returns the number of vowels in the string.
Vowels in this case are 'a', 'e', 'i', 'o', 'u'. Here, 'y' is also a
vowel, but only when it is at the end of the given word.

Example:
>>> vowels_count("abcde")
2
>>> vowels_count("ACEDY")
3
"""

我们能预测GPT-3.5-Turbo是否能解决这个任务,还是我们需要使用GPT-4吗?我的第一反应是GPT-3.5-Turbo可以做对,因为指令相当直接。然而,事实证明,如果我们只给它一次机会,GPT-3.5-Turbo并不总是能做对。如何在不实际尝试的情况下预测性能是一个有趣的研究问题。

我们还能做什么?我们注意到: 验证给定解决方案比从头开始找到正确解决方案“更容易”。

文档字符串中提供了一些简单的示例测试用例。如果我们已经有了一个模型生成的响应,我们可以使用这些测试用例来过滤错误的实现,并使用更强大的模型或生成更多响应,直到结果通过示例测试用例。此外,这一步骤可以通过要求GPT-3.5-Turbo从文档字符串中给出的示例生成断言语句(一个我们可以下注的更简单的任务)并执行代码来自动化。

解决方案

结合这些观察,我们可以设计一个包含两个直观想法的解决方案:

  • 利用自动生成的反馈,即代码执行结果,来过滤响应。
  • 逐一尝试推理配置,直到一个响应能通过过滤。

设计

这个解决方案在不知道或预测哪个任务适合哪种配置的情况下自适应工作。它简单地逐一尝试多种配置,从最便宜的配置开始。注意,一个配置可以生成多个响应(通过将推理参数n设置得大于1)。不同的配置可以使用相同的模型和不同的推理参数,例如n和温度。每个任务返回并评估一个响应。

  1. GPT-3.5-Turbo, n=1, temperature=0
  2. GPT-3.5-Turbo, n=7, temperature=1, stop=["\nclass", "\ndef", "\nif", "\nprint"]
  3. GPT-4, n=1, temperature=0
  4. GPT-4, n=2, temperature=1, stop=["\nclass", "\ndef", "\nif", "\nprint"]
  5. GPT-4, n=1, temperature=1, stop=["\nclass", "\ndef", "\nif", "\nprint"]

实验结果

本博客文章的第一张图显示了与默认GPT-4相比,自适应解决方案的成功率和平均推理成本。推理成本包括在我们解决方案中生成断言的成本。生成的断言并不总是正确的,通过/未通过生成断言的程序并不总是正确/错误的。尽管如此,自适应解决方案可以将成功率(文献中称为pass@1)从68%提高到90%,同时降低成本18%。

以下是一些由不同配置在投资组合中解决的函数定义示例。

  1. 由GPT-3.5-Turbo, n=1, temperature=0解决的:
def compare(game,guess):
"""I think we all remember that feeling when the result of some long-awaited
event is finally known. The feelings and thoughts you have at that moment are
definitely worth noting down and comparing.
Your task is to determine if a person correctly guessed the results of a number of matches.
You are given two arrays of scores and guesses of equal length, where each index shows a match.
Return an array of the same length denoting how far off each guess was. If they have guessed correctly,
the value is 0, and if not, the value is the absolute difference between the guess and the score.


example:

compare([1,2,3,4,5,1],[1,2,3,4,2,-2]) -> [0,0,0,0,3,3]
compare([0,5,0,0,0,4],[4,1,1,0,0,-2]) -> [4,4,1,0,0,6]
"""
  1. 由GPT-3.5-Turbo, n=7, temperature=1, stop=["\nclass", "\ndef", "\nif", "\nprint"]解决的:前面提到的vowels_count函数。
  2. 由GPT-4, n=1, temperature=0解决的:
def string_xor(a: str, b: str) -> str:
""" Input are two strings a and b consisting only of 1s and 0s.
Perform binary XOR on these inputs and return result also as a string.
>>> string_xor('010', '110')
'100'
"""
  1. 由GPT-4, n=2, temperature=1, stop=["\nclass", "\ndef", "\nif", "\nprint"]解决的:
def is_palindrome(string: str) -> bool:
""" Test if given string is a palindrome """
return string == string[::-1]


def make_palindrome(string: str) -> str:
""" Find the shortest palindrome that begins with a supplied string.
Algorithm idea is simple:
- Find the longest postfix of supplied string that is a palindrome.
- Append to the end of the string reverse of a string prefix that comes before the palindromic suffix.
>>> make_palindrome('')
''
>>> make_palindrome('cat')
'catac'
>>> make_palindrome('cata')
'catac'
"""
  1. 由GPT-4, n=1, temperature=1, stop=["\nclass", "\ndef", "\nif", "\nprint"]解决的:
def sort_array(arr):
"""
In this Kata, you have to sort an array of non-negative integers according to
number of ones in their binary representation in ascending order.
For similar number of ones, sort based on decimal value.

It must be implemented like this:
>>> sort_array([1, 5, 2, 3, 4]) == [1, 2, 3, 4, 5]
>>> sort_array([-2, -3, -4, -5, -6]) == [-6, -5, -4, -3, -2]
>>> sort_array([1, 0, 2, 3, 4]) [0, 1, 2, 3, 4]
"""

最后一个问题是原始定义中有错误示例测试用例的一个例子。它误导了自适应解决方案,因为一个正确的实现被认为是错误的,并且进行了更多尝试。最后一个配置返回了正确的实现,即使它没有通过自动生成的断言。这个例子表明:

  • 我们的自适应解决方案具有一定程度的容错能力。
  • 如果使用正确的示例测试用例,自适应解决方案的成功率和推理成本可以进一步提高。

值得注意的是,降低的推理成本是在所有任务上的摊销成本。对于每个单独的任务,成本可能比直接使用GPT-4要大或小。这是自适应解决方案的本质:对于困难任务的成本通常比简单任务的成本要高。

一个运行此实验的示例笔记本可以在以下地址找到:https://github.com/microsoft/FLAML/blob/v1.2.1/notebook/research/autogen_code.ipynb。实验是在AutoGen作为FLAML子包时进行的。

讨论

我们的解决方案使用autogen中提供的通用接口实现起来非常简单,但结果却非常鼓舞人心。

虽然生成断言的具体方法是特定于应用的,但主要思想在LLM操作中是通用的:

  • 生成多个响应以供选择 - 特别是当选择一个好的响应比一次性生成一个好的响应更容易时。
  • 考虑多种配置来生成响应 - 特别是当:
    • 模型和其他推理参数的选择影响效用成本权衡;或者
    • 不同配置有互补效应。

之前的博客文章提供了这些想法在解决数学问题中也是相关的证据。 autogen使用一种技术EcoOptiGen来支持推理参数调整和模型选择。

在研究和开发中有许多扩展方向:

  • 概括提供反馈的方式。
  • 自动化优化配置的过程。
  • 为不同的应用构建自适应代理。

您觉得这种方法适用于您的用例吗?您是否有其他关于LLM应用的挑战要分享?您是否希望看到更多关于LLM优化或自动化的支持或研究?请加入我们的Discord服务器进行讨论。

进一步阅读