Skip to main content
Glama

baidu-ai-search

Official
by baidubce
tool_call.ipynb66.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 }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/baidubce/app-builder'

If you have feedback or need assistance with the MCP directory API, please join our Discord server