tool_call.ipynb•66.3 kB
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page1.png\" alt=\"drawing\" width=\"1000\"/>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 前言 - 学习本项目你可以获得什么\n",
"- 理论学习:了解AIAgent的基础知识\n",
"- 上手实操:深入了解Agent中的FunctionCall运行流程\n",
"- 上手实操:入门百度智能云千帆AppBuilder,在十分钟内打造一个个性化AIAgent\n",
"- 上手实操:使用AppBuilder-SDK打造一个端云组件联动的进阶Agent"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 项目背景\n",
"\n",
"### 1.1、 什么是AppBuilder\n",
"[百度智能云千帆AppBuilder](https://appbuilder.cloud.baidu.com/)(以下简称AppBuilder)是基于大模型搭建AI原生应用的工作台,旨在降低AI原生应用的开发门槛,赋能开发者和企业快速实现应用搭建。\n",
"\n",
"平台提供了RAG(检索增强生成)、Agent(智能体)等应用框架,内置了文档问答、表格问答、多轮对话、生成创作等多种应用组件,还包括百度搜索和百度地图等特色组件,以及文本处理、图像处理和语音处理等传统AI组件,支持零代码、低代码、全代码三种开发方式,满足不同开发能力的开发者和企业的场景需求。\n",
"\n",
"### 1.2、 什么是AppBuilder-SDK\n",
"\n",
"[百度智能云千帆AppBuilder-SDK](https://github.com/baidubce/app-builder)(以下简称AB-SDK),百度智能云千帆AppBuilder-SDK是百度智能云千帆AppBuilder面向AI原生应用开发者提供的一站式开发平台的客户端SDK。\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page2.png\" alt=\"drawing\" width=\"1000\"/>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 2. 项目介绍 - 通过ToolCall实现端云组件联动的Agent\n",
"### 2.1、 什么是Agent\n",
"\n",
"AIAgent是能够感知环境,基于目标进行决策并执行动作的智能化应用。不同于传统人工智能应用(主要指以规则引擎、机器学习、深度学习等技术为核心)和RPA机器人,AIAgent能够基于目标和对现状能力的认知,在环境约束中,依赖特定资源和现有工具,找到行动规则并将行动拆解为必要的步骤,自主执行步骤,达成目标。\n",
"\n",
"AIAgent具备三个核心能力:独立思考、自主执行、持续迭代。\n",
"- 独立思考是指AlAgent能够根据给定任务目标和约束条件,进行任务规划和问题拆解,形成执行步骤(即工作流);\n",
"- 自主执行是指AlAgent能够调取各类组件和工具,按照执行步骤依次执行,实现任务目标;\n",
"- 持续选代是指AlAgent能够自动记录任务目标、工作流和执行结果,基于结果反馈,沉淀专家知识和案例。\n",
"\n",
"AICopilot、AIAgent、大模型等名词在各类文章上经常混淆,此处简要说明下三者的区别。大模型一般是指大模型技术,AlAgent和Al Copilot是基于大模型技术的智能化应用,AlAgent和AlCopilot在功能和场景上存在差别。\n",
"\n",
"自主性是AIAgent和AI Copilot之间最大的区别。AI Copilot是“副驾驶”,只是提供建议而非决策,AIAgent是“主驾驶”,需要真正做出决策并开展行动。\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page3.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"### 2.2、 什么是ToolCall\n",
"\n",
"解释该问题,需要了解以下的知识点:`Agent工具` -> `FunctionCall` - `ToolCall`\n",
"\n",
"AIAgent 有四大核心组件:记忆、规划、工具和执行。其中工具部分,与我们的开发关系最密切,在各类Agent开发平台/工具中,常被称为“组件”、\"插件\"、\"能力\"等.\n",
"\n",
"关于Agent的工具的定义与分类,如下图~\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page4.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"Agent使用工具的流程,一般称为`FunctionCall`,最早由OpenAI提出,并在[Assistant API](https://platform.openai.com/docs/assistants/overview)中广泛应用。\n",
"\n",
"\n",
"ToolCall,则是AppBuilder平台提出的一种进阶的FunctionCall,本质与OpenAI的FunctionCall一致,但具有以下两个特点:\n",
"\n",
"- **端云组件联动**: Agent 调用工具时,可以同时调用云端和本地组件。\n",
"\n",
"- **组件类型泛化**: AppBuilder在未来会支持多种类型组件,已经超出了Function的含义,例如数据库、记忆库、工作流等等\n",
"\n",
"\n",
"\n",
"### 2.3、 什么是端云组件联动,要解决什么问题\n",
"\n",
"我们首先从工具的执行位置出发展开~ 在使用如AppBuilder / Coze 等平台开发Agent时,我们可以使用很多平台组件广场中,官方提供的组件,这里组件开箱即用,非常方便。\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page5.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"但是存在一个问题,基于平台云端组件开发的应用,无法调用内网/局域网/私域的知识与能力,也无法与本地的工具进行联动,限制了Agent的灵活性。\n",
"\n",
"我们在解决实际业务问题时,常遇到需要访问内网链接API或本地/硬件功能的FunctionCall需求,AppBuilder ToolCall可以解决这个问题:\n",
"\n",
"* 1、用户可注册一个本地运行的组件到已发布的应用\n",
"* 2、由AppBuilder-Agent的云端思考模型进行规划和参数生成\n",
"* 3、用户基于生成的参数调用本地组件,并再上传运行结果\n",
"* 4、以此实现将本地组件能力嵌入到应用整体流程\n",
"\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page6.png\" alt=\"drawing\" width=\"1000\"/>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 3、ToolCall(FunctionCall)基础知识介绍\n",
"\n",
"### 3.1、Agent是如何调用Tool的\n",
"\n",
"我们可以将Agent的黑箱拆解为以下几个部分:\n",
"1. Agent的背景信息\n",
"2. Agent的输入信息\n",
"3. Agent的思考过程\n",
"4. Agent触发组件调用\n",
"5. Agent基于组件输出反思总结\n",
"\n",
"#### Agent的背景信息包含以下几个部分\n",
"- 角色定义描述(Prompt):定义Agent的角色\n",
"- 能力描述(Prompt):定义Agent可以干什么\n",
"- 工具描述(JsonSchema/Str):将工具的输入和输出,按照规范,定义为一段字符串,作为最终大模型Prompt的一部分\n",
"\n",
"#### Agent的输入信息包含以下几个部分\n",
"- 用户输入(Query/Prompt):用户输入的文本\n",
"- 对话相关的文件(File/Url):与本地对话相关的文件路径\n",
"\n",
"#### Agent的思考过程\n",
"AppBuilder-Agent会将背景信息与输入信息,拼接为最终的Prompt,然后调用大模型推理。\n",
"\n",
"Prompt的一个简单且直观的例子是:\n",
"\n",
"你是`{角色定义描述}`,你可以做以下事情:`{能力描述}`,你可以使用这些工具:`{工具描述-description}`,工具依赖的输入是:`{工具描述-paramters-properties-name}`,这些输入的格式分别是`{工具描述-paramters-properties-type}`。现在用户的问题是`{用户输入}`,与该问题相关的文件是`{对话相关的文件}`,请你解决用户的这个问题。\n",
"\n",
"#### Agent触发组件调用\n",
"\n",
"如果用户的query和组件能够解决的问题匹配,那么大模型就会尝试根据prompt里给出的工具的描述,从query中提炼出该次调用工具所需的参数,生成一个ToolCall命令,交给执行组件的模块去执行。\n",
"\n",
"例如,我们给出的组件能力是\"查找公司内指定人员的信息\",函数的参数名为\"name\"。当用户输入\"查找张三的信息\",大模型会从query中提炼出参数\"name=张三\"这个信息。\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page7.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"#### Agent基于组件输出反思总结\n",
"\n",
"组件运行模块执行组件后,会给出字符串形式的结果给到Agent,Agent会再次将结果拼接为Prompt,然后调用大模型推理。判断用户的需求是否已经解决。如果解决了,则经过一个对话模块,总结用户的需求,并生成一个对话记录。如果未解决,则继续调用大模型推理,尝试调用更多的工具,直到用户的需求被解决。\n",
"\n",
"### 3.2、开发者如何命令Agent调用本地Tool\n",
"\n",
"我们以AppBuilder-SDK中的AppBuilder-Client的基础代码为例,介绍开发者应该如何使用ToolCall功能\n",
"\n",
"\n",
"```python\n",
"import appbuilder\n",
"\n",
"# 实例化AppBuilderClient\n",
"app_client = appbuilder.AppBuilderClient(app_id)\n",
"conversation_id = app_client.create_conversation()\n",
"\n",
"# 第一次对话,输入原始的query 和 工具描述\n",
"message_1 = app_client.run(\n",
" conversation_id=conversation_id,\n",
" query=\"请问张三同学的生日是哪天?\",\n",
" tools=tools\n",
")\n",
"tool_call = message_1.content.events[-1].tool_calls[-1]\n",
"tool_call_id = tool_call.id\n",
"\n",
"# 第二次对话,在本地执行组件后,上传组件的运行结果\n",
"tool_call_result = \"张三同学的生日是2008年8月8日\"\n",
"message_2 = app_client.run(\n",
" conversation_id=conversation_id,\n",
" tool_outputs=[{\n",
" \"tool_call_id\": tool_call_id,\n",
" \"output\": tool_call_result\n",
" }]\n",
")\n",
"print(message_2.content)\n",
"```\n",
"\n",
"其中`AppBuilderClient`的`run`方法是核心,我们展开该函数的定义和参数介绍:\n",
"\n",
"`AppBuilderClient().run() -> Message`\n",
"\n",
"```python\n",
"def run(self, conversation_id: str,\n",
" query: str = \"\",\n",
" file_ids: list = [],\n",
" stream: bool = False,\n",
" tools: list[data_class.Tool] = None,\n",
" tool_outputs: list[data_class.ToolOutput] = None,\n",
" **kwargs\n",
" ) -> Message:\n",
" r\"\"\"\n",
" 参数:\n",
" query (str: 必须): query内容\n",
" conversation_id (str, 必须): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话\n",
" file_ids(list[str], 可选):\n",
" stream (bool, 可选): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回\n",
" tools(list[data_class.Tools], 可选): 一个Tools组成的列表,其中每个Tools对应一个工具的配置, 默认为None\n",
" tool_outputs(list[data_class.ToolOutput], 可选): 工具输出列表,格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果,以自然语言/json dump str描述,默认为None\n",
" 返回: message (obj: `Message`): 对话结果.\n",
" \"\"\"\n",
" pass\n",
"```\n",
"\n",
"\n",
"| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 |\n",
"| --------------- | ---------------- | -------- | ------------------------------------------------------------ | ----------------- |\n",
"| conversation_id | String | 是 | 会话ID | |\n",
"| query | String | 否 | query问题内容 | \"今天天气怎么样?\" |\n",
"| file_ids | list[String] | 否 | 对话可引用的文档ID | |\n",
"| stream | Bool | 否 | 为true时则流式返回,为false时则一次性返回所有内容, 推荐设为true,降低首token时延 | False |\n",
"| tools | List[Tool] | 否 | 一个列表,其中每个字典对应一个工具的配置 | |\n",
"| tools[0] | Tool | 否 | 工具配置 | |\n",
"| +type | String | 否 | 枚举:<br/>**file_retrieval**: 知识库检索工具能够理解文档内容,支持用户针对文档内容的问答。<br/>**code_interpreter**: 代码解释器, 代码解释器能够生成并执行代码,从而协助用户解决复杂问题,涵盖科学计算(包括普通数学计算题)、数据可视化、文件编辑处理(图片、PDF文档、视频、音频等)、文件格式转换(如WAV、MP3、text、SRT、PNG、jpg、MP4、GIF、MP3等)、数据分析&清洗&处理(文件以excel、csv格式为主)、机器学习&深度学习建模&自然语言处理等多个领域。<br/>**function**: 支持fucntion call模式调用工具 | |\n",
"| +function | Function | 否 | Function工具描述<br/>仅当**type为**`**function**` 时需要且必须填写 | |\n",
"| ++name | String | 否 | 函数名<br/>只允许数字、大小写字母和中划线和下划线,最大长度为64个字符。一次运行中唯一。 | |\n",
"| ++description | String | 否 | 工具描述 | |\n",
"| ++parameters | Dict | 否 | 工具参数, json_schema格式 | |\n",
"| tool_outputs | List[ToolOutput] | 否 | 内容为本地的工具执行结果,以自然语言/json dump str描述 | |\n",
"| tool_outputs[0] | ToolOutput | 否 | 工具执行结果 | |\n",
"| +tool_call_id | String | 否 | 工具调用ID | |\n",
"| +output | String | 否 | 工具输出 | |\n",
"\n",
"`Tool`与`Function`是本地组件的描述,类型为object,其定义如下:\n",
"\n",
"```python\n",
"class Tool(BaseModel):\n",
" type: str = \"function\"\n",
" function: Function = Field(..., description=\"工具信息\")\n",
"\n",
"class Function(BaseModel):\n",
" name: str = Field(..., description=\"工具名称\")\n",
" description: str = Field(..., description=\"工具描述\")\n",
" parameters: dict = Field(..., description=\"工具参数, json_schema格式\")\n",
"```\n",
"\n",
"`ToolOutput`是本地组件的执行结果,需要再次上传到Agent,参与思考,类型为object,其定义如下:\n",
"```python\n",
"class ToolOutput(BaseModel):\n",
" tool_call_id: str = Field(..., description=\"工具调用ID\")\n",
" output: str = Field(..., description=\"工具输出\")\n",
"\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 4、ToolCall的第一个例子\n",
"\n",
"我们继续以上文中提到的查找张三生日为例,看一下完整的流程是怎么样的\n",
"\n",
"### 前置工作,在AppBuilder平台上创建一个白板应用(可以跳过)\n",
"\n",
"网页链接:https://appbuilder.cloud.baidu.com/\n",
"\n",
"注册后,进入控制台:https://console.bce.baidu.com/ai_apaas/dialogHome\n",
"\n",
"点击左上角的【创建应用】-> 【AI自动配置】,我们输入以下Prompt,自动生成Agent:`你是智能问题解决者,自动集成多种工具组件,解决用户各类问题`\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page8.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"最终生成的Agent长这个样子:\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page9.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"而后点击【发布】,分别在控制台的左侧【个人空间】获取`app_id`,在【我的密钥】获取`APPBUILDER_TOEN`后,就可以开始后续的操作了。\n",
"\n",
"当然,下面的示例代码中,我们已经提供了可以直接运行的试用Token与App,你可以直接上手运行"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import appbuilder\n",
"\n",
"# AppBuilder Token,替换为您个人的Token\n",
"os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
"\n",
"# 应用为:智能问题解决者\n",
"app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
"app_client = appbuilder.AppBuilderClient(app_id)\n",
"conversation_id = app_client.create_conversation()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"首次提问一个问题,应用不具备该能力,通过回答可以印证\n",
"\n",
"- 由于并没有关于张三同学的信息,所以Agent无法实现查询"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Agent第一次回答: 很抱歉,由于个人隐私保护的原则,我无法直接查询并告知您本公司张三同学的生日。如果您需要了解这个信息,建议您通过合法且正当的途径,比如直接询问张三同学本人,或者查阅公司内部的员工档案,但前提是您需要确保有合适的权限和授权。尊重和保护个人隐私是我们每个人的责任。\n"
]
}
],
"source": [
"message_1 = app_client.run(\n",
" conversation_id=conversation_id,\n",
" query=\"请问本公司的张三同学的生日是哪天?\",\n",
")\n",
"print(\"Agent第一次回答: {}\".format(message_1.content.answer))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**output**\n",
"```\n",
"Agent第一次回答: 为了回答这个问题,我们首先需要明确几个关键点:\n",
"\n",
"1. **问题理解**:\n",
" - 需要确定的是“张三同学的生日”。\n",
"\n",
"2. **工具选择**:\n",
" - 由于问题涉及的是特定个人的信息(张三的生日),这通常不是通过工具或系统查询能得到的,而是需要通过公司内部的人事记录或直接询问张三本人来获取。\n",
"\n",
"3. **解决方案生成**:\n",
" - **步骤一**:首先,尝试访问公司的人事系统或员工档案,看是否有张三的生日信息记录。\n",
" - **步骤二**:如果人事系统或员工档案中没有相关信息,或者你不具备访问权限,那么可以考虑直接询问张三本人或其同事,看是否有人知道他的生日。\n",
" - **步骤三**:如果以上方法都不可行,还可以尝试联系公司的人力资源部门,看他们是否能提供相关信息。\n",
"\n",
"4. **注意事项**:\n",
" - 在尝试获取张三的生日信息时,要确保遵守公司的隐私政策和相关法律法规,不要侵犯张三的隐私权。\n",
" - 如果张三不愿意透露他的生日信息,应尊重他的选择,并停止进一步询问。\n",
"\n",
"5. **可能遇到的问题**:\n",
" - 人事系统或员工档案中可能没有张三的生日信息。\n",
" - 张三或其同事可能不愿意透露生日信息。\n",
" - 人力资源部门可能因隐私政策而无法提供相关信息。\n",
"\n",
"综上所述,要确定张三的生日,最直接且尊重隐私的方法是直接询问张三本人,或者通过公司正式渠道(如人力资源部门)在遵守隐私政策的前提下进行查询。\n",
"```\n",
"\n",
"\n",
"##### 赋予应用一个本地查询组件能力\n",
"\n",
"以下示例展示了三种方式来使用 ToolCall 进行调用,并演示了如何在 AppBuilder 环境中配置和执行会话调用。\n",
"\n",
"**方式1:使用 JSONSchema 格式直接描述 tools 调用**\n",
"\n",
"- 这里我们使用info_dict模拟一个数据库查询的返回结果"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"def get_person_infomation(name: str):\n",
" info_dict = {\n",
" \"张三\": \"1980年1月1日\",\n",
" \"李四\": \"1975年12月31日\",\n",
" \"刘伟\": \"1990年12月30日\"\n",
" }\n",
"\n",
" if name in info_dict:\n",
" return f\"您要查找的{name}的生日是:{info_dict[name]}\"\n",
" else:\n",
" return f\"您要查找的{name}的信息我们暂未收录,请联系管理员添加。\"\n",
" \n",
"# 创建工具的描述:json_schema格式\n",
"tools = [\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_person_infomation\",\n",
" \"description\": \"查找公司内指定人员的信息\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"name\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"人员名称,例如:张三、李四\",\n",
" },\n",
" },\n",
" \"required\": [\"name\"],\n",
" },\n",
" },\n",
" }\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 现在我们已经完成了本地tool组件的设计,接下来我们将tool的功能赋予Client应用"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Agent的中间思考过程:\n",
"{\n",
" \"code\": 0,\n",
" \"message\": \"\",\n",
" \"status\": \"interrupt\",\n",
" \"event_type\": \"Interrupt\",\n",
" \"content_type\": \"contexts\",\n",
" \"detail\": {\n",
" \"text\": {\n",
" \"function_call\": {\n",
" \"thought\": \"用户想要查询公司内指定人员张三的生日信息,这是一个具有明确目的和关键信息的需求。根据我们可用的工具,get_person_infomation 工具能够查找公司内指定人员的信息,包括生日等。因此,通过调用这个工具并传入张三作为参数,我们可以获取到张三的生日信息,从而满足用户的需求。\",\n",
" \"name\": \"get_person_infomation\",\n",
" \"arguments\": {\n",
" \"name\": \"张三\"\n",
" },\n",
" \"usage\": {\n",
" \"prompt_tokens\": 564,\n",
" \"completion_tokens\": 115,\n",
" \"total_tokens\": 679,\n",
" \"name\": \"ERNIE-4.0-8K\",\n",
" \"type\": \"plan\"\n",
" },\n",
" \"tool_call_id\": \"baf86c61-6627-4229-bc81-a17eda1bce36\"\n",
" },\n",
" \"used_tool\": []\n",
" }\n",
" },\n",
" \"usage\": null,\n",
" \"tool_calls\": [\n",
" {\n",
" \"id\": \"baf86c61-6627-4229-bc81-a17eda1bce36\",\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_person_infomation\",\n",
" \"arguments\": {\n",
" \"name\": \"张三\"\n",
" }\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"Agent思考结束,等待我们上传本地结果\n",
"\n"
]
}
],
"source": [
"message_2 = app_client.run(\n",
" conversation_id=conversation_id,\n",
" query=\"请问本公司的张三同学的生日是哪天?\",\n",
" tools=tools\n",
")\n",
"print(\"Agent的中间思考过程:\")\n",
"print(message_2.content.events[-1].model_dump_json(indent=4))\n",
"print(\"Agent思考结束,等待我们上传本地结果\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**output**\n",
"这部分输出为Client应用的思考过程\n",
"```\n",
"Agent的中间思考过程:\n",
"{\n",
" \"code\": 0,\n",
" \"message\": \"\",\n",
" \"status\": \"interrupt\",\n",
" \"event_type\": \"Interrupt\",\n",
" \"content_type\": \"contexts\",\n",
" \"detail\": {\n",
" \"text\": {\n",
" \"function_call\": {\n",
" \"thought\": \"用户想要查询公司内张三同学的生日信息,这个需求很明确,且背景信息也足够。我可以使用get_person_infomation工具来查找张三的生日信息。\",\n",
" \"name\": \"get_person_infomation\",\n",
" \"arguments\": {\n",
" \"name\": \"张三\"\n",
" },\n",
" \"usage\": {\n",
" \"prompt_tokens\": 697,\n",
" \"completion_tokens\": 87,\n",
" \"total_tokens\": 784,\n",
" \"name\": \"ERNIE-4.0-Turbo-8K\",\n",
" \"type\": \"plan\"\n",
" },\n",
" \"tool_call_id\": \"c23309f7-e24a-4476-85e2-3ef9cfd4f6ed\"\n",
" },\n",
" \"used_tool\": []\n",
"...\n",
" ]\n",
"}\n",
"Agent思考结束,等待我们上传本地结果\n",
"```\n",
"\n",
"- 大模型下发了调用本地函数的参数,我们使用这个参数调用本地函数"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"local_func_result: 您要查找的张三的生日是:1980年1月1日\n",
"\n"
]
}
],
"source": [
"tool_call = message_2.content.events[-1].tool_calls[-1]\n",
"tool_call_id = tool_call.id\n",
"tool_call_argument = tool_call.function.arguments\n",
"local_func_result = get_person_infomation(**tool_call_argument)\n",
"print(\"local_func_result: {}\\n\".format(local_func_result))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**output**\n",
"```\n",
"local_func_result: 您要查找的张三的生日是:1980年1月1日\n",
"```\n",
"\n",
"- 向应用返回本地运行的结果,完成本地函数toolcall调用"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Agent 拥有了本地函数调用能力后,回答是: 您要找的张三的生日是1980年1月1日。\n"
]
}
],
"source": [
"message_3 = app_client.run(\n",
" conversation_id=conversation_id,\n",
" tool_outputs=[{\n",
" \"tool_call_id\": tool_call_id,\n",
" \"output\": local_func_result\n",
" }]\n",
")\n",
"print(\"Agent 拥有了本地函数调用能力后,回答是: {}\".format(message_3.content.answer))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**output**\n",
"```\n",
"Agent的中间思考过程:\n",
"{\n",
" \"code\": 0,\n",
" \"message\": \"\",\n",
" \"status\": \"interrupt\",\n",
" \"event_type\": \"Interrupt\",\n",
" \"content_type\": \"contexts\",\n",
" \"detail\": {\n",
" \"text\": {\n",
" \"function_call\": {\n",
" \"thought\": \"用户想要查询公司内张三同学的生日信息,这个需求很明确,且背景信息也足够。我可以使用get_person_infomation工具来查找张三的生日信息。\",\n",
" \"name\": \"get_person_infomation\",\n",
" \"arguments\": {\n",
" \"name\": \"张三\"\n",
" },\n",
" \"usage\": {\n",
" \"prompt_tokens\": 697,\n",
" \"completion_tokens\": 87,\n",
" \"total_tokens\": 784,\n",
" \"name\": \"ERNIE-4.0-Turbo-8K\",\n",
" \"type\": \"plan\"\n",
" },\n",
" \"tool_call_id\": \"c23309f7-e24a-4476-85e2-3ef9cfd4f6ed\"\n",
" },\n",
" \"used_tool\": []\n",
"...\n",
" ]\n",
"}\n",
"Agent思考结束,等待我们上传本地结果\n",
"\n",
"Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...\n",
"\n",
"local_func_result: 您要查找的张三的生日是:1980年1月1日\n",
"\n",
"Agent 拥有了本地函数调用能力后,回答是: # 解决方案\n",
"\n",
"## 问题分析\n",
"\n",
"用户想要查询公司内张三同学的生日信息。这是一个明确且具体的需求,我们可以通过`get_person_infomation`工具来获取这一信息。\n",
"\n",
"## 工具运用\n",
"\n",
"1. **工具选择**:`get_person_infomation`\n",
"2. **参数设置**:\n",
"\n",
"\t* `name`:张三\n",
"\n",
"3. **执行结果**:张三的生日是1980年1月1日。\n",
"\n",
"## 解决方案步骤\n",
"\n",
"1. 使用`get_person_infomation`工具,并设置参数`name`为“张三”。\n",
"2. 等待工具执行,并获取张三的生日信息。\n",
"3. 将获取到的生日信息(1980年1月1日)告知用户。\n",
"\n",
"## 注意事项\n",
"\n",
"* 确保在使用`get_person_infomation`工具时,输入的姓名与公司内部记录的姓名完全一致,以避免查询错误。\n",
"* 如果工具返回“未找到”或类似结果,请检查姓名是否有误或联系公司人事部门确认信息。\n",
"\n",
"通过上述步骤,我们可以准确地回答用户的问题,并提供张三的生日信息。\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**方式2:使用 function_to_model 将函数对象传递为 ToolCall 的调用**\n",
"\n",
"- 前置步骤:设置环境变量和初始化操作"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import appbuilder\n",
"import os\n",
"import json\n",
"\n",
"# 请前往千帆AppBuilder官网创建密钥,流程详见:https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
"# 设置环境变量\n",
"# AppBuilder Token,替换为您个人的Token\n",
"os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
"\n",
"# 应用为:智能问题解决者\n",
"app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
"# 初始化智能体\n",
"client = appbuilder.AppBuilderClient(app_id)\n",
"# 创建会话\n",
"conversation_id = client.create_conversation()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 定义函数和函数列表,按照谷歌规范写好注释"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"#定义示例函数\n",
"def get_current_weather(location: str, unit: str) -> str:\n",
" \"\"\"获取指定中国城市的当前天气信息。\n",
"\n",
" 仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。\n",
"\n",
" Args:\n",
" location (str): 城市名,例如:\"北京\"。\n",
" unit (int): 温度单位,支持 \"celsius\" 或 \"fahrenheit\"。\n",
"\n",
" Returns:\n",
" str: 天气情况描述\n",
" \"\"\"\n",
" return \"北京今天25度\"\n",
" \n",
"#定义函数列表\n",
"functions = [get_current_weather]\n",
"function_map = {f.__name__: f for f in functions}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 查看一下function_to_model函数转化的结果"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_current_weather\",\n",
" \"description\": \"获取指定中国城市的当前天气信息。\\n\\n 仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。\\n\\n Args:\\n location (str): 城市名,例如:\\\"北京\\\"。\\n unit (int): 温度单位,支持 \\\"celsius\\\" 或 \\\"fahrenheit\\\"。\\n\\n Returns:\\n str: 天气情况描述\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"location\": {\n",
" \"name\": \"location\",\n",
" \"type\": \"str\",\n",
" \"description\": null,\n",
" \"required\": true\n",
" },\n",
" \"unit\": {\n",
" \"name\": \"unit\",\n",
" \"type\": \"str\",\n",
" \"description\": null,\n",
" \"required\": true\n",
" }\n",
" },\n",
" \"required\": [\n",
" \"location\",\n",
" \"unit\"\n",
" ]\n",
" },\n",
" \"returns\": {\n",
" \"type\": \"str\",\n",
" \"description\": null\n",
" }\n",
" }\n",
"}\n"
]
}
],
"source": [
"print(json.dumps(appbuilder.Manifest.from_function(get_current_weather), indent=4, ensure_ascii=False))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 调用大模型进行函数调用"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "BadRequestException",
"evalue": "request_id=928f30c6-d712-448b-a831-dc4cd15307b0 , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"928f30c6-d712-448b-a831-dc4cd15307b0\"}",
"output_type": "error",
"traceback": [
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[0;31mBadRequestException\u001B[0m Traceback (most recent call last)",
"Cell \u001B[0;32mIn[5], line 2\u001B[0m\n\u001B[1;32m 1\u001B[0m \u001B[38;5;66;03m#调用大模型\u001B[39;00m\n\u001B[0;32m----> 2\u001B[0m msg \u001B[38;5;241m=\u001B[39m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 3\u001B[0m \u001B[43m \u001B[49m\u001B[43mconversation_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mconversation_id\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 4\u001B[0m \u001B[43m \u001B[49m\u001B[43mquery\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43m今天北京的天气怎么样?\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m 5\u001B[0m \u001B[43m \u001B[49m\u001B[43mtools\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m \u001B[49m\u001B[43m[\u001B[49m\u001B[43mappbuilder\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mManifest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mfrom_function\u001B[49m\u001B[43m(\u001B[49m\u001B[43mf\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel_dump\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mfor\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mf\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;129;43;01min\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mfunctions\u001B[49m\u001B[43m]\u001B[49m\n\u001B[1;32m 6\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 7\u001B[0m \u001B[38;5;28mprint\u001B[39m(msg\u001B[38;5;241m.\u001B[39mmodel_dump_json(indent\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m4\u001B[39m))\n\u001B[1;32m 8\u001B[0m \u001B[38;5;66;03m# 获取最后的事件和工具调用信息\u001B[39;00m\n",
"File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/console/appbuilder_client/appbuilder_client.py:306\u001B[0m, in \u001B[0;36mAppBuilderClient.run\u001B[0;34m(self, conversation_id, query, file_ids, stream, tools, tool_outputs, tool_choice, end_user_id, action, **kwargs)\u001B[0m\n\u001B[1;32m 302\u001B[0m url \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mservice_url_v2(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m/app/conversation/runs\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 303\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39msession\u001B[38;5;241m.\u001B[39mpost(\n\u001B[1;32m 304\u001B[0m url, headers\u001B[38;5;241m=\u001B[39mheaders, json\u001B[38;5;241m=\u001B[39mreq\u001B[38;5;241m.\u001B[39mmodel_dump(), timeout\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, stream\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mTrue\u001B[39;00m\n\u001B[1;32m 305\u001B[0m )\n\u001B[0;32m--> 306\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mhttp_client\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcheck_response_header\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresponse\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 307\u001B[0m request_id \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mresponse_request_id(response)\n\u001B[1;32m 308\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m stream:\n",
"File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/_client.py:120\u001B[0m, in \u001B[0;36mHTTPClient.check_response_header\u001B[0;34m(response)\u001B[0m\n\u001B[1;32m 116\u001B[0m message \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrequest_id=\u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m , http status code is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m, body is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;241m.\u001B[39mformat(\n\u001B[1;32m 117\u001B[0m \u001B[38;5;18m__class__\u001B[39m\u001B[38;5;241m.\u001B[39mresponse_request_id(response), status_code, response\u001B[38;5;241m.\u001B[39mtext\n\u001B[1;32m 118\u001B[0m )\n\u001B[1;32m 119\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mbad_request:\n\u001B[0;32m--> 120\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m BadRequestException(message)\n\u001B[1;32m 121\u001B[0m \u001B[38;5;28;01melif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mforbidden:\n\u001B[1;32m 122\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m ForbiddenException(message)\n",
"\u001B[0;31mBadRequestException\u001B[0m: request_id=928f30c6-d712-448b-a831-dc4cd15307b0 , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"928f30c6-d712-448b-a831-dc4cd15307b0\"}"
]
}
],
"source": [
"#调用大模型\n",
"msg = client.run(\n",
" conversation_id=conversation_id,\n",
" query=\"今天北京的天气怎么样?\",\n",
" tools = [appbuilder.Manifest.from_function(f) for f in functions]\n",
" )\n",
"print(msg.model_dump_json(indent=4))\n",
"# 获取最后的事件和工具调用信息\n",
"event = msg.content.events[-1]\n",
"tool_call = event.tool_calls[-1]\n",
"\n",
"# 获取函数名称和参数\n",
"name = tool_call.function.name\n",
"args = tool_call.function.arguments\n",
"\n",
"# 将函数名称映射到具体的函数并执行\n",
"raw_result = function_map[name](**args)\n",
"\n",
"# 传递工具的输出\n",
"msg_2 = client.run(\n",
" conversation_id=conversation_id,\n",
" tool_outputs=[{\n",
" \"tool_call_id\": tool_call.id,\n",
" \"output\": str(raw_result)\n",
" }],\n",
")\n",
"print(msg_2.model_dump_json(indent=4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**方式3: 使用装饰器进行描述**\n",
"\n",
"- 前置步骤:设置环境变量和初始化操作"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import json\n",
"import appbuilder\n",
"from appbuilder import manifest, manifest_parameter\n",
"\n",
"# 请前往千帆AppBuilder官网创建密钥,流程详见:https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
"# 设置环境变量\n",
"# AppBuilder Token,替换为您个人的Token\n",
"#os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
"os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
"\n",
"# 应用为:智能问题解决者\n",
"#app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
"app_id = \"7cc4c21f-0e25-4a76-baf7-01a2b923a1a7\"\n",
"# 初始化智能体\n",
"client = appbuilder.AppBuilderClient(app_id)\n",
"# 创建会话\n",
"conversation_id = client.create_conversation()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 定义函数和函数列表,并用装饰器对函数进行进行描述."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"#使用function装饰描述函数,function_parameter装饰器描述参数,function_return装饰器描述函数返回值。\n",
"@manifest(description=\"获取指定中国城市的当前天气信息。仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。\")\n",
"@manifest_parameter(name=\"location\", description=\"城市名,例如:北京。\")\n",
"@manifest_parameter(name=\"unit\", description=\"温度单位,支持 'celsius' 或 'fahrenheit'\")\n",
"#定义示例函数\n",
"def get_current_weather(location: str, unit: str) -> str:\n",
" return \"北京今天25度\"\n",
"\n",
"#定义函数列表\n",
"functions = [get_current_weather]\n",
"function_map = {f.__name__: f for f in functions}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 查看一下装饰器的转化内容"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_current_weather\",\n",
" \"description\": \"获取指定中国城市的当前天气信息。仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"location\": {\n",
" \"name\": \"location\",\n",
" \"type\": \"str\",\n",
" \"description\": \"城市名,例如:北京。\",\n",
" \"required\": true\n",
" },\n",
" \"unit\": {\n",
" \"name\": \"unit\",\n",
" \"type\": \"str\",\n",
" \"description\": \"温度单位,支持 'celsius' 或 'fahrenheit'\",\n",
" \"required\": true\n",
" }\n",
" },\n",
" \"required\": [\n",
" \"location\",\n",
" \"unit\"\n",
" ]\n",
" }\n",
" }\n",
"}\n"
]
}
],
"source": [
"# 将 model_dump() 的输出进行格式化打印\n",
"print(\n",
" json.dumps(\n",
" appbuilder.Manifest.from_function(get_current_weather), indent=4, ensure_ascii=False\n",
" )\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "BadRequestException",
"evalue": "request_id=76224253-163f-46d3-a5eb-b22c6fcec2be , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"76224253-163f-46d3-a5eb-b22c6fcec2be\"}",
"output_type": "error",
"traceback": [
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[0;31mBadRequestException\u001B[0m Traceback (most recent call last)",
"Cell \u001B[0;32mIn[10], line 2\u001B[0m\n\u001B[1;32m 1\u001B[0m \u001B[38;5;66;03m#调用大模型\u001B[39;00m\n\u001B[0;32m----> 2\u001B[0m msg \u001B[38;5;241m=\u001B[39m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrun\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 3\u001B[0m \u001B[43m \u001B[49m\u001B[43mconversation_id\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mconversation_id\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 4\u001B[0m \u001B[43m \u001B[49m\u001B[43mquery\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43m今天北京的天气怎么样?\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m 5\u001B[0m \u001B[43m \u001B[49m\u001B[43mtools\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m \u001B[49m\u001B[43m[\u001B[49m\u001B[43mget_current_weather\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m__ab_manifest__\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel_dump\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m]\u001B[49m\n\u001B[1;32m 6\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 7\u001B[0m \u001B[38;5;28mprint\u001B[39m(msg\u001B[38;5;241m.\u001B[39mmodel_dump_json(indent\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m4\u001B[39m))\n\u001B[1;32m 8\u001B[0m \u001B[38;5;66;03m# 获取最后的事件和工具调用信息\u001B[39;00m\n",
"File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/console/appbuilder_client/appbuilder_client.py:306\u001B[0m, in \u001B[0;36mAppBuilderClient.run\u001B[0;34m(self, conversation_id, query, file_ids, stream, tools, tool_outputs, tool_choice, end_user_id, action, **kwargs)\u001B[0m\n\u001B[1;32m 302\u001B[0m url \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mservice_url_v2(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m/app/conversation/runs\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[1;32m 303\u001B[0m response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39msession\u001B[38;5;241m.\u001B[39mpost(\n\u001B[1;32m 304\u001B[0m url, headers\u001B[38;5;241m=\u001B[39mheaders, json\u001B[38;5;241m=\u001B[39mreq\u001B[38;5;241m.\u001B[39mmodel_dump(), timeout\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, stream\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mTrue\u001B[39;00m\n\u001B[1;32m 305\u001B[0m )\n\u001B[0;32m--> 306\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mhttp_client\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcheck_response_header\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresponse\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 307\u001B[0m request_id \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mhttp_client\u001B[38;5;241m.\u001B[39mresponse_request_id(response)\n\u001B[1;32m 308\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m stream:\n",
"File \u001B[0;32m/opt/anaconda3/envs/testenv/lib/python3.9/site-packages/appbuilder/core/_client.py:120\u001B[0m, in \u001B[0;36mHTTPClient.check_response_header\u001B[0;34m(response)\u001B[0m\n\u001B[1;32m 116\u001B[0m message \u001B[38;5;241m=\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrequest_id=\u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m , http status code is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m, body is \u001B[39m\u001B[38;5;132;01m{}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;241m.\u001B[39mformat(\n\u001B[1;32m 117\u001B[0m \u001B[38;5;18m__class__\u001B[39m\u001B[38;5;241m.\u001B[39mresponse_request_id(response), status_code, response\u001B[38;5;241m.\u001B[39mtext\n\u001B[1;32m 118\u001B[0m )\n\u001B[1;32m 119\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mbad_request:\n\u001B[0;32m--> 120\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m BadRequestException(message)\n\u001B[1;32m 121\u001B[0m \u001B[38;5;28;01melif\u001B[39;00m status_code \u001B[38;5;241m==\u001B[39m requests\u001B[38;5;241m.\u001B[39mcodes\u001B[38;5;241m.\u001B[39mforbidden:\n\u001B[1;32m 122\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m ForbiddenException(message)\n",
"\u001B[0;31mBadRequestException\u001B[0m: request_id=76224253-163f-46d3-a5eb-b22c6fcec2be , http status code is 400, body is {\"code\": \"QuotaLimitExceeded\", \"message\": \"quota\\u8d44\\u6e90\\u5df2\\u8fbe\\u4e0a\\u9650\", \"request_id\": \"76224253-163f-46d3-a5eb-b22c6fcec2be\"}"
]
}
],
"source": [
"# 调用大模型\n",
"msg = client.run(\n",
" conversation_id=conversation_id,\n",
" query=\"今天北京的天气怎么样?\",\n",
" tools=[appbuilder.Manifest.from_function(get_current_weather)],\n",
")\n",
"print(msg.model_dump_json(indent=4))\n",
"# 获取最后的事件和工具调用信息\n",
"event = msg.content.events[-1]\n",
"tool_call = event.tool_calls[-1]\n",
"\n",
"# 获取函数名称和参数\n",
"name = tool_call.function.name\n",
"args = tool_call.function.arguments\n",
"\n",
"# 将函数名称映射到具体的函数并执行\n",
"raw_result = function_map[name](**args)\n",
"\n",
"# 传递工具的输出\n",
"msg_2 = client.run(\n",
" conversation_id=conversation_id,\n",
" tool_outputs=[{\n",
" \"tool_call_id\": tool_call.id,\n",
" \"output\": str(raw_result)\n",
" }],\n",
")\n",
"print(msg_2.model_dump_json(indent=4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 5、ToolCal第二个例子-调用本地工具并且代码更简洁\n",
"\n",
"我们可以使用AppBuilderClient应用来执行tool_call操作,完成指定的命令,但是需要自己配置client的思考与运行流程,较为繁琐。SDK提供了使用AppBuilderEventHandler简化tool_call操作的功能\n",
"\n",
"##### 配置运行环境&导入Client应用"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import appbuilder\n",
"\n",
"\n",
"# AppBuilder Token,替换为您个人的Token\n",
"os.environ[\"APPBUILDER_TOKEN\"] = \"your api key\"\n",
"\n",
"# 应用为:智能问题解决者\n",
"app_id = \"b9473e78-754b-463a-916b-f0a9097a8e5f\"\n",
"app_client = appbuilder.AppBuilderClient(app_id)\n",
"conversation_id = app_client.create_conversation()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### 继承AppBuilderEventHandler类,并实现针对各类型event的处理方法"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from appbuilder.core.console.appbuilder_client.event_handler import AppBuilderEventHandler\n",
"class MyEventHandler(AppBuilderEventHandler):\n",
" def execute_local_command(self, cmd: str):\n",
" import subprocess\n",
" try:\n",
" result = subprocess.check_output(cmd, shell=True).decode(\"utf-8\")\n",
" if result.strip() == \"\":\n",
" return \"命令执行成功,无返回值\"\n",
" return result\n",
" except Exception as e:\n",
" return str(e)\n",
" \n",
" def interrupt(self, run_context, run_response):\n",
" thought = run_context.current_thought\n",
" # 绿色打印\n",
" print(\"\\033[1;32m\", \"-> Agent 中间思考: \", thought, \"\\033[0m\")\n",
"\n",
" tool_output = []\n",
" for tool_call in run_context.current_tool_calls:\n",
" tool_call_id = tool_call.id\n",
" tool_res = self.execute_local_command(\n",
" **tool_call.function.arguments)\n",
" # 蓝色打印\n",
" print(\"\\033[1;34m\", \"-> 本地ToolCall结果: \\n\", tool_res, \"\\033[0m\\n\")\n",
" tool_output.append(\n",
" {\n",
" \"tool_call_id\": tool_call_id,\n",
" \"output\": tool_res\n",
" }\n",
" )\n",
" return tool_output\n",
" \n",
" def success(self, run_context, run_response):\n",
" print(\"\\n\\033[1;31m\",\"-> Agent 非流式回答: \\n\", run_response.answer, \"\\033[0m\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### 定义本地的tools工具\n",
"\n",
"通过`subprocess.check_output`方法,可以在终端中执行命令,并返回执行结果"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"tools = [\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"execute_local_command\",\n",
" \"description\": \"可以在bash环境中,执行输入的指令,注意,一次只能执行一个原子命令。例如:ls\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"cmd\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"需要执行的指令\",\n",
" },\n",
" },\n",
" \"required\": [\"cmd\"],\n",
" },\n",
" },\n",
" }\n",
"]\n",
"\n",
"with app_client.run_with_handler(\n",
" conversation_id = conversation_id,\n",
" query = \"请问当前文件夹下有哪些文件?如果没有test.txt文件,请新建一个test.txt文件,内容为:Hello World!\",\n",
" tools = tools,\n",
" event_handler = MyEventHandler(),\n",
" ) as run:\n",
" run.until_done()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**output**\n",
"```\n",
" -> Agent 中间思考: 首先,我需要使用execute_local_command工具来执行'ls'命令,列出当前文件夹下的所有文件。然后,我需要检查输出中是否存在test.txt文件。如果不存在,我将再次使用execute_local_command工具来执行'echo \"Hello World\" > test.txt'命令,以创建并写入test.txt文件。 \n",
" -> 本地ToolCall结果: \n",
" multi_tool_call.ipynb\n",
"multi_tool_call.py\n",
"multi_tool_call_with_handler.ipynb\n",
"multi_tool_call_with_handler.py\n",
"sdk_ knowledgebase.ipynb\n",
"sdk_trace.ipynb\n",
"simple_tool_call.ipynb\n",
"simple_tool_call.py\n",
"tmp.log\n",
"黑神话(悟空).pdf\n",
" \n",
"\n",
" -> Agent 中间思考: 根据execute_local_command工具的返回结果,当前文件夹下并没有test.txt文件。因此,我需要使用execute_local_command工具来执行'echo \"Hello World\" > test.txt'命令,以创建并写入test.txt文件。 \n",
" -> 本地ToolCall结果: \n",
" 命令执行成功,无返回值 \n",
"\n",
"\n",
" -> Agent 非流式回答: \n",
" 当前文件夹下的文件包括:\n",
"\n",
"- multi_tool_call.ipynb\n",
"- multi_tool_call.py\n",
"- multi_tool_call_with_handler.ipynb\n",
"...\n",
"- tmp.log\n",
"- 黑神话(悟空).pdf\n",
"\n",
"经过检查,发现当前文件夹下**不存在**test.txt文件。因此,已经为您新建了一个test.txt文件,并写入了内容“Hello World!”。 \n",
"```\n",
"\n",
"- 使用AppBuilderEventHandler架构可以简化client的交互方式"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 6 使用异步调用优化toolcall并发执行效率\n",
"SDK提供了异步调用接口,可以大幅提升并发执行效率。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Copyright (c) 2024 Baidu, Inc. All Rights Reserved.\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# http://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License.\n",
"\n",
"import appbuilder\n",
"import asyncio\n",
"from appbuilder.core.console.appbuilder_client.async_event_handler import (\n",
" AsyncAppBuilderEventHandler,\n",
")\n",
"\n",
"\n",
"class MyEventHandler(AsyncAppBuilderEventHandler):\n",
" def get_current_weather(self, location=None, unit=\"摄氏度\"):\n",
" return \"{} 的温度是 {} {}\".format(location, 20, unit)\n",
"\n",
" async def interrupt(self, run_context, run_response):\n",
" thought = run_context.current_thought\n",
" # 绿色打印\n",
" print(\"\\033[1;32m\", \"-> Agent 中间思考: \", thought, \"\\033[0m\")\n",
"\n",
" tool_output = []\n",
" for tool_call in run_context.current_tool_calls:\n",
" tool_call_id = tool_call.id\n",
" tool_res = self.get_current_weather(**tool_call.function.arguments)\n",
" # 蓝色打印\n",
" print(\"\\033[1;34m\", \"-> 本地ToolCallId: \", tool_call_id, \"\\033[0m\")\n",
" print(\"\\033[1;34m\", \"-> ToolCall结果: \", tool_res, \"\\033[0m\\n\")\n",
" tool_output.append({\"tool_call_id\": tool_call_id, \"output\": tool_res})\n",
" return tool_output\n",
"\n",
" async def success(self, run_context, run_response):\n",
" print(\"\\n\\033[1;31m\", \"-> Agent 非流式回答: \", run_response.answer, \"\\033[0m\")\n",
"\n",
"\n",
"def main():\n",
" app_id = \"b2a972c5-e082-46e5-b313-acbf51792422\"\n",
" tools = [\n",
" {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"get_current_weather\",\n",
" \"description\": \"仅支持中国城市的天气查询,参数location为中国城市名称,其他国家城市不支持天气查询\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"location\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"城市名,举例:北京\",\n",
" },\n",
" \"unit\": {\"type\": \"string\", \"enum\": [\"摄氏度\", \"华氏度\"]},\n",
" },\n",
" \"required\": [\"location\", \"unit\"],\n",
" },\n",
" },\n",
" }\n",
" ]\n",
"\n",
" appbuilder.logger.setLoglevel(\"ERROR\")\n",
"\n",
" async def agent_run(client, query):\n",
" conversation_id = await client.create_conversation()\n",
" with await client.run_with_handler(\n",
" conversation_id=conversation_id,\n",
" query=query,\n",
" tools=tools,\n",
" event_handler=MyEventHandler(),\n",
" ) as run:\n",
" await run.until_done()\n",
"\n",
" async def agent_handle():\n",
" client = appbuilder.AsyncAppBuilderClient(app_id)\n",
" task1 = asyncio.create_task(agent_run(client, \"北京的天气怎么样\"))\n",
" task2 = asyncio.create_task(agent_run(client, \"上海的天气怎么样\"))\n",
" await asyncio.gather(task1, task2)\n",
" await client.http_client.session.close()\n",
"\n",
" loop = asyncio.get_event_loop()\n",
" loop.run_until_complete(agent_handle())\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" main()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 7、项目总结\n",
"\n",
"本项目通过多个知识点的学习,以及两个使用AppBuilder-SDK的实操,最终完成了一个支持ToolCall AIAgent的构建。\n",
"\n",
"- 理论学习:了解AIAgent的基础知识\n",
"- 上手实操:深入了解Agent中的FunctionCall运行流程\n",
"- 上手实操:入门百度智能云千帆AppBuilder,在十分钟内打造一个个性化AIAgent\n",
"- 上手实操:使用AppBuilder-SDK打造一个端云组件联动的进阶Agent\n",
"\n",
"\n",
"希望您可以不吝`Star`,给`AppBuilder-SDK`一些鼓励,期待您的`PR`,一起共建AIAgent生态。\n",
"\n",
"Github地址:https://github.com/baidubce/app-builder\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/page10.png\" alt=\"drawing\" width=\"1000\"/>\n",
"\n",
"最后,您也可以进入`AppBuilder-SDK`的WX交流群,和大家一起交流AppBuilder使用及开发心得。\n",
"\n",
"<img src=\"https://chengmo-dev1.bj.bcebos.com/wechat_group.png\" alt=\"drawing\" width=\"1000\"/>"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "testenv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.20"
}
},
"nbformat": 4,
"nbformat_minor": 2
}