通过 Function Calling 构建自主 AI Agents

2024-04-19 00:52

本文主要是介绍通过 Function Calling 构建自主 AI Agents,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文地址:Build Autonomous AI Agents with Function Calling

将聊天机器人转变为可以与外部 API 交互的代理

2024 年 4 月 2 日

Function Call(函数调用)并不是什么新鲜事。2023 年 7 月,OpenAI 为其 GPT 模型引入了函数调用,该功能现在已被竞争对手采用。Google 的 Gemini API 最近支持它,Anthropic 正在将其集成到 Claude 中。函数调用对于大型语言模型 (LLMs )来说变得至关重要,从而增强了它们的功能。

考虑到这一点,我的目标是编写一个全面的教程,涵盖基本介绍之外的函数调用(已经有很多教程了)。重点将放在实际实施上,构建一个完全自主的人工智能代理,并将其与 Streamlit 集成,以获得类似 ChatGPT 的界面。虽然 OpenAI 用于演示,但本教程可以很容易地适用于其他LLMs支持函数调用,例如 Gemini。

Function Calling是干什么用的?

Function Calling(函数调用)使开发人员能够描述函数(也称为工具,您可以将其视为模型要执行的操作,例如执行计算或下订单),并让模型智能地选择输出包含参数的 JSON 对象来调用这些函数。简单来说,它允许:

  • Autonomous decision making(自主决策):模型可以智能地选择工具来回答问题。
  • Reliable parsing(可靠的解析): 响应采用 JSON 格式,而不是更典型的类似对话的响应。乍一看似乎并不多,但这就是允许LLM连接到外部系统的原因,例如通过具有结构化输入的 API。

它开辟了许多可能性:

  • Autonomous AI assistants(自主人工智能助手): 机器人可以与内部系统交互,完成客户订单和退货等任务,而不仅仅是提供查询的答案
  • Personal research assistants(个人研究助理): 假设您正在计划旅行,助理可以在 Excel 中搜索 Web、抓取内容、比较选项和汇总结果。
  • IoT voice commands(IoT 语音命令): 模型可以控制设备或根据检测到的意图建议操作,例如调整交流温度。

Function Calling的结构

借用 Gemini 的函数调用文档,函数调用具有以下结构,在 OpenAI 中的工作原理相同

img

图片来自 Gemini 的函数调用文档

  1. 用户像应用程序提出问题
  2. 应用程序传递用户提供的prompt和函数声明,这是对模型可以使用的工具的描述
  3. 根据函数声明,模型建议要使用的工具以及相关的请求参数。请注意,模型仅输出建议的工具和参数,而不实际调用函数
  4. 4 & 5 根据响应,应用程序调用相关 API
  5. 6 & 7 来自 API 的响应再次输入模型,模型再输出人类可读的响应
  6. 应用程序将最终响应返回给用户,然后从 1 开始重复。

这似乎有些混乱,但我们将通过示例详细说明这个概念

架构

在深入研究代码之前,先介绍一下Demo应用程序的架构。

解决方案

在这里,我们为参观酒店的游客建立了一个助手。助手可以访问以下工具,这些工具允许助手访问外部应用程序。

  • get_items , purchase_item : 通过API连接到存储在数据库中的产品目录,分别用于检索项目列表和进行购买
  • rag_pipeline_func :使用检索增强生成 (RAG) 连接到文档存储,以从非结构化文本(例如酒店手册)中获取信息

img

技术栈

  • Embedding model: all-MiniLM-L6-v2
  • Vector Database: Haystack 的 InMemoryDocumentStore
  • LLM: 通过 OpenRouter 访问的 GPT-4 Turbo。使用 OpenRouter,您可以在没有 VPN 的情况下从香港访问不同的 LLM API。该流程可以适应使用其他LLMs流程,只需稍作代码更改,只要它们支持函数调用,Gemini
  • LLM Framework: Haystack 的易用性、出色的文档和管道建设的透明度。本教程实际上是他们针对同一主题的精彩教程的扩展

现在让我们开始吧!

示例应用程序

准备工作

前往 Github 克隆我的代码。以下内容可以在笔记本中找到 function_calling_demo 。

还请创建并激活虚拟环境,然后 pip install -r requirements.txt 安装所需的软件包

初始化

我们首先连接到 OpenRouter。或者,只要您有 OpenAI API 密钥,使用原始 OpenAIChatGenerator 文件而不覆盖 也可以 api_base_url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os
from dotenv import load_dotenv
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
from haystack.components.generators.utils import print_streaming_chunk# Set your API key as environment variable before executing this
load_dotenv()
OPENROUTER_API_KEY = os.environ.get('OPENROUTER_API_KEY')chat_generator = OpenAIChatGenerator(api_key=Secret.from_env_var("OPENROUTER_API_KEY"),api_base_url="https://openrouter.ai/api/v1",model="openai/gpt-4-turbo-preview",streaming_callback=print_streaming_chunk)

然后我们测试是否可以 chat_generator 成功调用

1
2
3
chat_generator.run(messages=[ChatMessage.from_user("Return this text: 'test'")])
---------- The response should look like this ----------
{'replies': [ChatMessage(content="'test'", role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'stop', 'usage': {}})]}

Step 1: 建立数据存储

在这里,我们在应用程序和两个数据源之间建立连接:非结构化文本的文档存储和通过 API 的应用程序数据库

使用管道索引文档

我们 documents 为模型提供了示例文本,以执行 Retrival Augmented Generation (RAG)。文本被转换为嵌入并存储在内存中的文档存储中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from haystack import Pipeline, Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack.components.embedders import SentenceTransformersDocumentEmbedder# Sample documents
documents = [Document(content="Coffee shop opens at 9am and closes at 5pm."),Document(content="Gym room opens at 6am and closes at 10pm.")
]# Create the document store
document_store = InMemoryDocumentStore()# Create a pipeline to turn the texts into embeddings and store them in the document store
indexing_pipeline = Pipeline()
indexing_pipeline.add_component("doc_embedder", SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
)
indexing_pipeline.add_component("doc_writer", DocumentWriter(document_store=document_store))indexing_pipeline.connect("doc_embedder.documents", "doc_writer.documents")indexing_pipeline.run({"doc_embedder": {"documents": documents}})

它应该输出这个,对应 documents 于我们创建的样本

1
{'doc_writer': {'documents_written': 2}}

启动 API 服务器

使用 Flask 创建的 API 服务器在 db_api.py 下创建,以连接到 SQLite。请通过在您的终端中运行 python db_api.py 来启动它

This would be shown in the terminal, if successfully executed

如果成功执行,这将显示在终端中

另请注意,一些初始数据已添加到 db_api.py

img

数据库中的示例数据

Step 2: 定义函数

在这里,我们为模型准备实际函数以调用 AFTER 函数调用(步骤 4-5,如函数调用结构中所述)

RAG 函数

即 rag_pipeline_func .这是为了让模型通过搜索存储在文档存储中的文本来提供答案。我们首先将 RAG 检索定义为 Haystack 管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGeneratortemplate = """
Answer the questions based on the given context.Context:
{% for document in documents %}{{ document.content }}
{% endfor %}
Question: {{ question }}
Answer:
"""
rag_pipe = Pipeline()
rag_pipe.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
rag_pipe.add_component("retriever", InMemoryEmbeddingRetriever(document_store=document_store))
rag_pipe.add_component("prompt_builder", PromptBuilder(template=template))
# Note to llm: We are using OpenAIGenerator, not the OpenAIChatGenerator, because the latter only accepts List[str] as input and cannot accept prompt_builder's str output
rag_pipe.add_component("llm", OpenAIGenerator(api_key=Secret.from_env_var("OPENROUTER_API_KEY"),api_base_url="https://openrouter.ai/api/v1",model="openai/gpt-4-turbo-preview"))rag_pipe.connect("embedder.embedding", "retriever.query_embedding")
rag_pipe.connect("retriever", "prompt_builder.documents")
rag_pipe.connect("prompt_builder", "llm")

测试该函数是否有效

1
2
query = “When does the coffee shop open?”
rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}})

这应该会产生以下输出。请注意, replies 该模型给出的样本来自我们之前提供的示例文档

1
2
3
4
5
6
7
8
{'llm': {'replies': ['The coffee shop opens at 9am.'],'meta': [{'model': 'openai/gpt-4-turbo-preview','index': 0,'finish_reason': 'stop','usage': {'completion_tokens': 9,'prompt_tokens': 60,'total_tokens': 69,'total_cost': 0.00087}}]}}

然后,我们可以将其 rag_pipe 转换为一个函数,该函数在不添加其他细节的情况下提供 replies 唯一

1
2
3
4
def rag_pipeline_func(query: str):result = rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}})return {"reply": result["llm"]["replies"][0]}

API 调用

我们定义了用于与数据库交互的 get_items and purchase_item 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Flask's default local URL, change it if necessary
db_base_url = 'http://127.0.0.1:5000'# Use requests to get the data from the database
import requests
import json# get_categories is supplied as part of the prompt, it is not used as a tool
def get_categories():response = requests.get(f'{db_base_url}/category')data = response.json()return datadef get_items(ids=None,categories=None):params = {'id': ids,'category': categories,}response = requests.get(f'{db_base_url}/item', params=params)data = response.json()return datadef purchase_item(id,quantity):headers = {'Content-type':'application/json', 'Accept':'application/json'}data = {'id': id,'quantity': quantity,}response = requests.post(f'{db_base_url}/item/purchase', json=data, headers=headers)return response.json()

定义工具列表

现在我们已经定义了函数,我们需要让模型识别这些函数,并通过为它们提供描述来指导它们如何使用它们。

由于我们在这里使用的是 OpenAI, tools 因此按照 Open AI 要求的格式格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
tools = [{"type": "function","function": {"name": "get_items","description": "Get a list of items from the database","parameters": {"type": "object","properties": {"ids": {"type": "string","description": "Comma separated list of item ids to fetch",},"categories": {"type": "string","description": "Comma separated list of item categories to fetch",},},"required": [],},}},{"type": "function","function": {"name": "purchase_item","description": "Purchase a particular item","parameters": {"type": "object","properties": {"id": {"type": "string","description": "The given product ID, product name is not accepted here. Please obtain the product ID from the database first.",},"quantity": {"type": "integer","description": "Number of items to purchase",},},"required": [],},}},{"type": "function","function": {"name": "rag_pipeline_func","description": "Get information from hotel brochure","parameters": {"type": "object","properties": {"query": {"type": "string","description": "The query to use in the search. Infer this from the user's message. It should be a question or a statement",}},"required": ["query"],},},}
]

Step 3: 将它们放在一起

现在,我们有了测试函数调用的必要输入!在这里,我们做一些事情:

  1. 向模型提供初始提示,为其提供一些上下文
  2. 提供用户生成的消息示例
  3. 最重要的是,我们将工具列表传递给聊天 tools 生成器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. Initial prompt
context = f"""You are an assistant to tourists visiting a hotel.
You have access to a database of items (which includes {get_categories()}) that tourists can buy, you also have access to the hotel's brochure.
If the tourist's question cannot be answered from the database, you can refer to the brochure.
If the tourist's question cannot be answered from the brochure, you can ask the tourist to ask the hotel staff.
"""
messages = [ChatMessage.from_system(context),# 2. Sample message from userChatMessage.from_user("Can I buy a coffee?"),]# 3. Passing the tools list and invoke the chat generator
response = chat_generator.run(messages=messages, generation_kwargs= {"tools": tools})
response
---------- Response ----------
{'replies': [ChatMessage(content='[{"index": 0, "id": "call_AkTWoiJzx5uJSgKW0WAI1yBB", "function": {"arguments": "{\\"categories\\":\\"Food and beverages\\"}", "name": "get_items"}, "type": "function"}]', role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'tool_calls', 'usage': {}})]}

现在让我们检查一下响应。请注意,函数调用如何返回模型选择的函数,以及用于调用所选函数的参数。

1
2
3
4
5
6
7
8
function_call = json.loads(response["replies"][0].content)[0]
function_name = function_call["function"]["name"]
function_args = json.loads(function_call["function"]["arguments"])
print("Function Name:", function_name)
print("Function Arguments:", function_args)
---------- Response ----------
Function Name: get_items
Function Arguments: {‘categories’: ‘Food and beverages’}

当出现另一个问题时,模型将使用另一个更相关的工具

1
2
3
4
5
6
7
8
9
10
11
12
13
# Another question
messages.append(ChatMessage.from_user("Where's the coffee shop?"))# Invoke the chat generator, and passing the tools list
response = chat_generator.run(messages=messages, generation_kwargs= {"tools": tools})
function_call = json.loads(response["replies"][0].content)[0]
function_name = function_call["function"]["name"]
function_args = json.loads(function_call["function"]["arguments"])
print("Function Name:", function_name)
print("Function Arguments:", function_args)
---------- Response ----------
Function Name: rag_pipeline_func
Function Arguments: {'query': "Where's the coffee shop?"}

同样,请注意,这里没有调用任何实际函数,这就是我们接下来要做的!

调用函数

然后,我们可以将参数输入到所选函数中

1
2
3
4
5
6
7
## Find the correspoding function and call it with the given arguments
available_functions = {"get_items": get_items, "purchase_item": purchase_item,"rag_pipeline_func": rag_pipeline_func}
function_to_call = available_functions[function_name]
function_response = function_to_call(**function_args)
print("Function Response:", function_response)
---------- Response ----------
Function Response: {'reply': 'The provided context does not specify a physical location for the coffee shop, only its operating hours. Therefore, I cannot determine where the coffee shop is located based on the given information.'}

然后,将来自 rag_pipeline_func 的响应作为上下文传递到聊天中,方法是将其附加到 messages ,以便模型提供最终答案

1
2
3
4
5
6
7
messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))
response = chat_generator.run(messages=messages)
response_msg = response["replies"][0]print(response_msg.content)
---------- Response ----------
For the location of the coffee shop within the hotel, I recommend asking the hotel staff directly. They will be able to guide you to it accurately.

我们现在已经完成了聊天周期!

Step 4: 转为互动聊天

上面的代码显示了如何完成函数调用,但我们希望更进一步,将其转换为交互式聊天

在这里,我展示了两种方法,从将对话打印到笔记本本身的更原始 input() 的方法,到通过 Streamlit 渲染它以提供类似 ChatGPT 的 UI

input() loop

代码是从 Haystack 的教程中复制而来的,它允许我们快速测试模型。注意:此应用程序的创建是为了演示函数调用的想法,并不意味着完全健壮,例如同时支持多个项目的顺序,没有幻觉等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import json
from haystack.dataclasses import ChatMessage, ChatRoleresponse = None
messages = [ChatMessage.from_system(context)
]while True:# if OpenAI response is a tool callif response and response["replies"][0].meta["finish_reason"] == "tool_calls":function_calls = json.loads(response["replies"][0].content)for function_call in function_calls:## Parse function calling informationfunction_name = function_call["function"]["name"]function_args = json.loads(function_call["function"]["arguments"])## Find the correspoding function and call it with the given argumentsfunction_to_call = available_functions[function_name]function_response = function_to_call(**function_args)## Append function response to the messages list using `ChatMessage.from_function`messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))# Regular Conversationelse:# Append assistant messages to the messages listif not messages[-1].is_from(ChatRole.SYSTEM):messages.append(response["replies"][0])user_input = input("ENTER YOUR MESSAGE .. INFO: Type 'exit' or 'quit' to stop\n")if user_input.lower() == "exit" or user_input.lower() == "quit":breakelse:messages.append(ChatMessage.from_user(user_input))response = chat_generator.run(messages=messages, generation_kwargs={"tools": tools})

img

在 IDE 中运行交互式聊天

当它有效时,我们可能想要一些看起来更好的东西。

Streamlit界面

Streamlit 将数据脚本转换为可共享的 Web 应用程序,为我们的应用程序提供了一个简洁的 UI。上面显示的代码被改编成我的存储库 streamlit 文件夹下的 Streamlit 应用程序

您可以通过以下方式运行它:

  1. 如果您尚未这样做,请使用 python db_api.py
  2. 将OPENROUTER_API_KEY设置为环境变量,例如假设 export OPENROUTER_API_KEY = ‘@REPLACE WITH YOUR API KEY’ 您在 Linux 上/使用 git bash 执行
  3. 导航到终端 cd streamlit 中的 streamlit 文件夹
  4. 使用 streamlit run app.py .应在运行应用程序的浏览器中自动创建一个新选项卡

基本上就是这样!我希望你喜欢这篇文章。

img

这篇关于通过 Function Calling 构建自主 AI Agents的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/916227

相关文章

揭秘未来艺术:AI绘画工具全面介绍

📑前言 随着科技的飞速发展,人工智能(AI)已经逐渐渗透到我们生活的方方面面。在艺术创作领域,AI技术同样展现出了其独特的魅力。今天,我们就来一起探索这个神秘而引人入胜的领域,深入了解AI绘画工具的奥秘及其为艺术创作带来的革命性变革。 一、AI绘画工具的崛起 1.1 颠覆传统绘画模式 在过去,绘画是艺术家们通过手中的画笔,蘸取颜料,在画布上自由挥洒的创造性过程。然而,随着AI绘画工

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

AI儿童绘本创作

之前分享过AI儿童绘画的项目,但是主要问题是角色一致要花费很长的时间! 今天发现了这款,非常奈斯! 只需输入故事主题、风格、模板,软件就会自动创作故事内容,自动生成插画配图,自动根据模板生成成品,测试效果如下图。 变现方式:生成儿童绘本发布到各平台,吸引宝妈群体进私域。  百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

智能客服到个人助理,国内AI大模型如何改变我们的生活?

引言 随着人工智能(AI)技术的高速发展,AI大模型越来越多地出现在我们的日常生活和工作中。国内的AI大模型在过去几年里取得了显著的进展,不少独创的技术点和实际应用令人瞩目。 那么,国内的AI大模型有哪些独创的技术点?它们在实际应用中又有哪些出色表现呢?此外,普通人又该如何利用这些大模型提升工作和生活的质量和效率呢?本文将为你一一解析。 一、国内AI大模型的独创技术点 多模态学习 多

【新闻】AI程序员要来了吗?阿里云官宣

内容提要 6 月 21 日,在阿里云上海 AI 峰会上,阿里云宣布推出首个AI 程序员。 据介绍,这个AI程序员具备架构师、开发工程师、测试工程师等多种岗位的技能,能一站式自主完成任务分解、代码编写、测试、问题修复、代码提交整个过程,最快分钟级即可完成应用开发,大幅提升研发效率。 近段时间以来,有关AI的实践应用突破不断,全球开发者加速研发步伐。有业内人士坦言,随着大模型性能逐渐提升,AI应

AI元宇宙

随着科技的迅猛发展,人工智能(AI)迎来了一个宇宙大爆发的时代。特别是以GPT为代表的生成式大模型的诞生和不断进步,彻底改变了人们的工作和生活方式。程序员与AI协同工作写代码已成为常态,大模型不仅提高了工作效率,还为人类带来了无限的可能性。 AI元宇宙http://ai.toolxq.com/#/如同生物进化出眼睛打开了三维世界的元宇宙之后,GPT打开了人+AI工作模式的新时代,程序员的人生被划

AI学习指南机器学习篇-朴素贝叶斯处理连续特征和离散特征

AI学习指南机器学习篇-朴素贝叶斯处理连续特征和离散特征 在机器学习领域,朴素贝叶斯是一种常用的分类算法,它的简单性和高效性使得它在实际应用中得到了广泛的应用。然而,在使用朴素贝叶斯算法进行分类时,我们通常会面临一个重要的问题,就是如何处理连续特征和离散特征。因为朴素贝叶斯算法基于特征的条件独立性假设,所以对于不同类型的特征,我们需要采取不同的处理方式。 在本篇博客中,我们将探讨如何有效地处理