diff --git a/cookbooks/end2end_application/agent/chatflow.ipynb b/cookbooks/end2end_application/agent/chatflow.ipynb new file mode 100644 index 000000000..c0d022d3d --- /dev/null +++ b/cookbooks/end2end_application/agent/chatflow.ipynb @@ -0,0 +1,356 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 前言 - 学习本项目你可以获得什么\n", + "- 入门百度智能云千帆AppBuilder,搭建一个工作流Agent应用\n", + "- 使用AppBuilder-SDK进行工作流Agent对话\n", + "- 了解如何使用AppBuilder-SDK简化多轮对话的开发" + ] + }, + { + "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", + "\"drawing\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. 创建工作流Agent应用并对话\n", + "\n", + "## 2.1 什么是工作流Agent?\n", + "通过工作流编排的形式还原业务流程,每轮对话均严格按照工作流执行,提高了AI应用的可控性,并可编排出复杂业务流程,适用于客服、营销、生成、办公等高可控及高复杂度等场景。\n", + "\n", + "**工作流Agent无需设置角色人设,通过工作流编排的形式实现应用功能。**\n", + "\n", + "用户的所有对话均会触发此工作流处理,适用于严格按照流程执行的任务,例如:\n", + "\n", + "* 客服对话智能体,判断终端用户的意图后严格按照任务分支自动执行,无需大模型思考选择。\n", + "* MBTI小助手,根据开发者编排的问题逐个提问,严格按照任务分支和结果执行后续的流程。\n", + "\n", + "工作流Agent支持用户配置工作流完成整个应用的对话过程,通过开始节点的Rawquery传入首轮对话,利用信息收集节点可以进行一组工作流中的多轮对话,最后以结束节点作为整个工作流结束的标志。开始节点和结束节点之前的完整流程构成一组工作流,每个信息收集节点都可以支持终端用户和应用的一次交互。\n", + "\n", + "## 2.2 创建工作流Agent应用-飞行客服小助手\n", + "\n", + "参考产品文档[飞行客服小助手](https://cloud.baidu.com/doc/AppBuilder/s/cm38k8nqr)创建示例应用。通过阅读本篇最佳实践,读者可以深度理解问答节点、信息处理节点、全局跳转节点的功能点和使用方式,搭建自己的工作流agent。对话效果如下:\n", + "\n", + "

\n", + " \n", + " \n", + " \n", + "

\n", + "\n", + "## 2.3 使用AppBuilder-SDK进行对话\n", + "### 2.3.1 方式一:使用SDK直接对话(不推荐)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import appbuilder\n", + "import os\n", + "from appbuilder.core.console.appbuilder_client import data_class\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", + "os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n", + "appbuilder.logger.setLoglevel(\"ERROR\")\n", + "\n", + "# 飞行客服小助手的应用id\n", + "app_id = \"...\"\n", + "# 初始化智能体\n", + "client = appbuilder.AppBuilderClient(app_id)\n", + "# 创建会话\n", + "conversation_id = client.create_conversation()\n", + "interrupt_ids = []\n", + "\n", + "msg = client.run(conversation_id, \"查天气\", stream=True)\n", + "interrupt_event_id = None\n", + "for ans in msg.content:\n", + " for event in ans.events:\n", + " if event.content_type == \"publish_message\":\n", + " print(event.detail.get(\"message\"))\n", + " if event.content_type == \"chatflow_interrupt\":\n", + " interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n", + " break\n", + "interrupt_ids.append(interrupt_event_id)\n", + "\n", + "msg2 = client.run(\n", + " conversation_id,\n", + " \"查航班\",\n", + " stream=True,\n", + " action=data_class.Action.create_resume_action(interrupt_event_id),\n", + ")\n", + "interrupt_event_id = None\n", + "for ans in msg2.content:\n", + " for event in ans.events:\n", + " if event.content_type == \"publish_message\":\n", + " print(event.detail.get(\"message\"))\n", + " if event.content_type == \"chatflow_interrupt\":\n", + " interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n", + " break\n", + " interrupt_ids.append(interrupt_event_id)\n", + "\n", + "msg3 = client.run(\n", + " conversation_id=conversation_id,\n", + " query=\"CA1234\",\n", + " stream=True,\n", + " action=data_class.Action.create_resume_action(interrupt_ids.pop()),\n", + ")\n", + "interrupt_event_id = None\n", + "for ans in msg3.content:\n", + " for event in ans.events:\n", + " if event.content_type == \"text\":\n", + " print(event.detail.get(\"text\"))\n", + " if event.content_type == \"chatflow_interrupt\":\n", + " interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n", + " break\n", + "interrupt_ids.append(interrupt_event_id)\n", + "\n", + "msg4 = client.run(\n", + " conversation_id=conversation_id,\n", + " query=\"北京的\",\n", + " stream=True,\n", + " action=data_class.Action.create_resume_action(interrupt_ids.pop()),\n", + ")\n", + "has_multiple_dialog_event = False\n", + "for ans in msg4.content:\n", + " for event in ans.events:\n", + " if event.content_type == \"text\":\n", + " print(event.detail.get(\"text\"))\n", + " if event.content_type == \"multiple_dialog_event\":\n", + " has_multiple_dialog_event = True\n", + " break" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3.2 方式二 实现自己的EventHandler,更方便地进行对话(推荐)\n", + "直接调用处理较为繁琐。SDK提供了使用AppBuilderEventHandler简化tool_call操作的功能。**继承AppBuilderEventHandler类,并实现针对各类型event的处理方法。**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import appbuilder\n", + "from appbuilder.core.console.appbuilder_client.event_handler import (\n", + " AppBuilderEventHandler,\n", + ")\n", + "\n", + "\n", + "class MyEventHandler(AppBuilderEventHandler):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.interrupt_ids = []\n", + "\n", + " def handle_content_type(self, run_context, run_response):\n", + " interrupt_event_id = None\n", + " event = run_response.events[-1]\n", + " if event.content_type == \"chatflow_interrupt\":\n", + " interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n", + " if interrupt_event_id is not None:\n", + " self.interrupt_ids.append(interrupt_event_id)\n", + "\n", + " def _create_action(self):\n", + " if len(self.interrupt_ids) == 0:\n", + " return None\n", + " event_id = self.interrupt_ids.pop()\n", + " return {\n", + " \"action_type\": \"resume\",\n", + " \"parameters\": {\"interrupt_event\": {\"id\": event_id, \"type\": \"chat\"}},\n", + " }\n", + "\n", + " def run(self, query=None):\n", + " super().new_dialog(\n", + " query=query,\n", + " action=self._create_action(),\n", + " )\n", + "\n", + "\n", + "def main():\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", + " os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n", + " appbuilder.logger.setLoglevel(\"DEBUG\")\n", + "\n", + " # 飞行客服小助手的应用id\n", + " app_id = \"...\"\n", + " # 初始化智能体\n", + " client = appbuilder.AppBuilderClient(app_id)\n", + " conversation_id = client.create_conversation()\n", + "\n", + " event_handler = MyEventHandler()\n", + " event_handler.init(\n", + " appbuilder_client=client,\n", + " conversation_id=conversation_id,\n", + " stream=True,\n", + " query=\"查天气\",\n", + " )\n", + " for data in event_handler:\n", + " pass\n", + " event_handler.run(\n", + " query=\"查航班\",\n", + " )\n", + " for data in event_handler:\n", + " pass\n", + " event_handler.run(\n", + " query=\"CA1234\",\n", + " )\n", + " for data in event_handler:\n", + " pass\n", + " event_handler.run(\n", + " query=\"北京的\",\n", + " )\n", + " for data in event_handler:\n", + " pass\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3.3 方式三、实现自己的EventHandler,更方便地进行多轮对话(推荐)\n", + "SDK提供了run_multiple_dialog_with_handler方法来进行多轮对话,按需传入iterable对象(queries、actions等参数),即可一次调用完成多轮对话。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import appbuilder\n", + "from appbuilder.core.console.appbuilder_client.event_handler import (\n", + " AppBuilderEventHandler,\n", + ")\n", + "\n", + "\n", + "class MyEventHandler(AppBuilderEventHandler):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.interrupt_ids = []\n", + "\n", + " def handle_content_type(self, run_context, run_response):\n", + " interrupt_event_id = None\n", + " event = run_response.events[-1]\n", + " if event.content_type == \"chatflow_interrupt\":\n", + " interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n", + " if interrupt_event_id is not None:\n", + " self.interrupt_ids.append(interrupt_event_id)\n", + "\n", + " def _create_action(self):\n", + " if len(self.interrupt_ids) == 0:\n", + " return None\n", + " event_id = self.interrupt_ids.pop()\n", + " return {\n", + " \"action_type\": \"resume\",\n", + " \"parameters\": {\"interrupt_event\": {\"id\": event_id, \"type\": \"chat\"}},\n", + " }\n", + "\n", + " def gen_action(self):\n", + " while True:\n", + " yield self._create_action()\n", + "\n", + "\n", + "def main():\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", + " os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n", + " appbuilder.logger.setLoglevel(\"DEBUG\")\n", + "\n", + " # 飞行客服小助手的应用id\n", + " app_id = \"...\"\n", + " # 初始化智能体\n", + " client = appbuilder.AppBuilderClient(app_id)\n", + " conversation_id = client.create_conversation()\n", + "\n", + " queries = [\"查天气\", \"查航班\", \"CA1234\", \"北京的\"]\n", + " event_handler = MyEventHandler()\n", + " event_handler = client.run_multiple_dialog_with_handler(\n", + " conversation_id=conversation_id,\n", + " queries=queries,\n", + " event_handler=event_handler,\n", + " stream=True,\n", + " actions=event_handler.gen_action(),\n", + " )\n", + " for data in event_handler:\n", + " for ans in data:\n", + " pass\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3、项目总结\n", + "\n", + "本项目最终完成了一个工作流Agent应用的创建,并使用Appbuilder-SDK进行对话功能。\n", + "\n", + "希望您可以不吝`Star`,给`AppBuilder-SDK`一些鼓励,期待您的`PR`,一起共建AIAgent生态。\n", + "\n", + "Github地址:https://github.com/baidubce/app-builder\n", + "\n", + "\"drawing\"\n", + "\n", + "最后,您也可以进入`AppBuilder-SDK`的WX交流群,和大家一起交流AppBuilder使用及开发心得。\n", + "\n", + "\"drawing\"" + ] + } + ], + "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 +} diff --git a/docs/BasisModule/Platform/Application/appbuilder_client.md b/docs/BasisModule/Platform/Application/appbuilder_client.md index 08dcc44ca..cf48bff57 100644 --- a/docs/BasisModule/Platform/Application/appbuilder_client.md +++ b/docs/BasisModule/Platform/Application/appbuilder_client.md @@ -60,29 +60,32 @@ AppBuilderClient组件支持调用在[百度智能云千帆AppBuilder](https://c #### 方法参数 -| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 | -| --------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| conversation_id | String | 是 | 会话ID | | -| query | String | 否 | query问题内容 | "今天天气怎么样?" | -| file_ids | list[String] | 否 | 对话可引用的文档ID | | -| stream | Bool | 否 | 为true时则流式返回,为false时则一次性返回所有内容, 推荐设为true,降低首token时延 | False | -| end_user_id | String | 否 | 终端用户ID,限制6 - 64字符 | | -| tools | List[Tool] | 否 | 一个列表,其中每个字典对应一个工具的配置 | | -| tools[0] | Tool | 否 | 工具配置 | | +| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 | +| --------------- | ------------------ | -------- | ------------------------------------------------------------ | ----------------- | +| conversation_id | String | 是 | 会话ID | | +| query | String | 否 | query问题内容 | "今天天气怎么样?" | +| file_ids | list[String] | 否 | 对话可引用的文档ID | | +| stream | Bool | 否 | 为true时则流式返回,为false时则一次性返回所有内容, 推荐设为true,降低首token时延 | False | +| end_user_id | String | 否 | 终端用户ID,限制6 - 64字符 | | +| tools | List[Tool] | 否 | 一个列表,其中每个字典对应一个工具的配置 | | +| tools[0] | Tool | 否 | 工具配置 | | | +type | String | 否 | 枚举:
**file_retrieval**: 知识库检索工具能够理解文档内容,支持用户针对文档内容的问答。
**code_interpreter**: 代码解释器, 代码解释器能够生成并执行代码,从而协助用户解决复杂问题,涵盖科学计算(包括普通数学计算题)、数据可视化、文件编辑处理(图片、PDF文档、视频、音频等)、文件格式转换(如WAV、MP3、text、SRT、PNG、jpg、MP4、GIF、MP3等)、数据分析&清洗&处理(文件以excel、csv格式为主)、机器学习&深度学习建模&自然语言处理等多个领域。
**function**: 支持fucntion call模式调用工具 | | -| +function | Function | 否 | Function工具描述
仅当**type为**`**function**` 时需要且必须填写 | | -| ++name | String | 否 | 函数名
只允许数字、大小写字母和中划线和下划线,最大长度为64个字符。一次运行中唯一。 | | -| ++description | String | 否 | 工具描述 | | -| ++parameters | Dict | 否 | 工具参数, json_schema格式 | | -| tool_outputs | List[ToolOutput] | 否 | 内容为本地的工具执行结果,以自然语言/json dump str描述 | | -| tool_outputs[0] | ToolOutput | 否 | 工具执行结果 | | -| +tool_call_id | String | 否 | 工具调用ID | | -| +output | String | 否 | 工具输出 | | -| tool_choice | ToolChoice | 否 | 控制大模型使用组件的方式,仅对自主规划Agent生效。 | | -| +type | String | 否 | auto/function,auto表示由LLM自动判断调什么组件;function表示由用户指定调用哪个组件。 | | -| +function | ToolChoiceFunction | 否 | 组件对象,包括组件的英文名称和入参 | | -| ++name | String | 否 | 组件的英文名称(唯一标识) | | -| ++input | String | 否 | 组件入参,当组件没有入参时填入空对象{} | | +| +function | Function | 否 | Function工具描述
仅当**type为**`**function**` 时需要且必须填写 | | +| ++name | String | 否 | 函数名
只允许数字、大小写字母和中划线和下划线,最大长度为64个字符。一次运行中唯一。 | | +| ++description | String | 否 | 工具描述 | | +| ++parameters | Dict | 否 | 工具参数, json_schema格式 | | +| tool_outputs | List[ToolOutput] | 否 | 内容为本地的工具执行结果,以自然语言/json dump str描述 | | +| tool_outputs[0] | ToolOutput | 否 | 工具执行结果 | | +| +tool_call_id | String | 否 | 工具调用ID | | +| +output | String | 否 | 工具输出 | | +| tool_choice | ToolChoice | 否 | 控制大模型使用组件的方式,仅对自主规划Agent生效。 | | +| +type | String | 否 | auto/function,auto表示由LLM自动判断调什么组件;function表示由用户指定调用哪个组件。 | | +| +function | ToolChoiceFunction | 否 | 组件对象,包括组件的英文名称和入参 | | +| ++name | String | 否 | 组件的英文名称(唯一标识) | | +| ++input | String | 否 | 组件入参,当组件没有入参时填入空对象{} | | +| action | Action | 否 | 对话时要进行的特殊操作。如回复工作流agent中“信息收集节点“的消息 | | +| +action_type | String | 是 | 要执行的操作。
可选值为:
resume:回复“信息收集节点” 的消息 | | +| +parameters | Object | 是 | 执行操作时所需的参数 | | #### Run方法非流式返回值 @@ -443,6 +446,244 @@ answer = app_builder_client.run( ) ``` + + +#### Run方法回复工作流Agent “信息收集节点”使用示例: + +使用[“飞行客服小助手”](https://cloud.baidu.com/doc/AppBuilder/s/cm38k8nqr)作为工作流Agent的示例应用。 + +**方式1:** SDK直接进行对话(不推荐) + +```python +import appbuilder +import os +from appbuilder.core.console.appbuilder_client import data_class + +# 请前往千帆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 +# 设置环境变量 +os.environ["APPBUILDER_TOKEN"] = ( + "..." +) +appbuilder.logger.setLoglevel("ERROR") + +# 飞行客服小助手的应用id +app_id = "..." +# 初始化智能体 +client = appbuilder.AppBuilderClient(app_id) +# 创建会话 +conversation_id = client.create_conversation() +interrupt_ids = [] + +msg = client.run(conversation_id, "查天气", stream=True) +interrupt_event_id = None +for ans in msg.content: + for event in ans.events: + if event.content_type == "publish_message": + print(event.detail.get("message")) + if event.content_type == "chatflow_interrupt": + interrupt_event_id = event.detail.get("interrupt_event_id") + break +interrupt_ids.append(interrupt_event_id) + +msg2 = client.run( + conversation_id, + "查航班", + stream=True, + action=data_class.Action.create_resume_action(interrupt_event_id), +) +interrupt_event_id = None +for ans in msg2.content: + for event in ans.events: + if event.content_type == "publish_message": + print(event.detail.get("message")) + if event.content_type == "chatflow_interrupt": + interrupt_event_id = event.detail.get("interrupt_event_id") + break + interrupt_ids.append(interrupt_event_id) + +msg3 = client.run( + conversation_id=conversation_id, + query="CA1234", + stream=True, + action=data_class.Action.create_resume_action(interrupt_ids.pop()), +) +interrupt_event_id = None +for ans in msg3.content: + for event in ans.events: + if event.content_type == "text": + print(event.detail.get("text")) + if event.content_type == "chatflow_interrupt": + interrupt_event_id = event.detail.get("interrupt_event_id") + break +interrupt_ids.append(interrupt_event_id) + +msg4 = client.run( + conversation_id=conversation_id, + query="北京的", + stream=True, + action=data_class.Action.create_resume_action(interrupt_ids.pop()), +) +has_multiple_dialog_event = False +for ans in msg4.content: + for event in ans.events: + if event.content_type == "text": + print(event.detail.get("text")) + if event.content_type == "multiple_dialog_event": + has_multiple_dialog_event = True + break + +``` + +**方式2:** 实现自己的EventHandler,更方便地进行对话(推荐) + +```python +import os +import appbuilder +from appbuilder.core.console.appbuilder_client.event_handler import ( + AppBuilderEventHandler, +) + + +class MyEventHandler(AppBuilderEventHandler): + def __init__(self): + super().__init__() + self.interrupt_ids = [] + + def handle_content_type(self, run_context, run_response): + interrupt_event_id = None + event = run_response.events[-1] + if event.content_type == "chatflow_interrupt": + interrupt_event_id = event.detail.get("interrupt_event_id") + if interrupt_event_id is not None: + self.interrupt_ids.append(interrupt_event_id) + + def _create_action(self): + if len(self.interrupt_ids) == 0: + return None + event_id = self.interrupt_ids.pop() + return { + "action_type": "resume", + "parameters": {"interrupt_event": {"id": event_id, "type": "chat"}}, + } + + def run(self, query=None): + super().new_dialog( + query=query, + action=self._create_action(), + ) + + +def main(): + # 请前往千帆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 + # 设置环境变量 + os.environ["APPBUILDER_TOKEN"] = "..." + appbuilder.logger.setLoglevel("DEBUG") + + # 飞行客服小助手的应用id + app_id = "..." + # 初始化智能体 + client = appbuilder.AppBuilderClient(app_id) + conversation_id = client.create_conversation() + + event_handler = MyEventHandler() + event_handler.init( + appbuilder_client=client, + conversation_id=conversation_id, + stream=True, + query="查天气", + ) + for data in event_handler: + pass + event_handler.run( + query="查航班", + ) + for data in event_handler: + pass + event_handler.run( + query="CA1234", + ) + for data in event_handler: + pass + event_handler.run( + query="北京的", + ) + for data in event_handler: + pass + + +if __name__ == "__main__": + main() +``` + +**方式3:** 实现自己的EventHandler,更方便地进行多轮对话(推荐) + +```python +import os +import appbuilder +from appbuilder.core.console.appbuilder_client.event_handler import ( + AppBuilderEventHandler, +) + + +class MyEventHandler(AppBuilderEventHandler): + def __init__(self): + super().__init__() + self.interrupt_ids = [] + + def handle_content_type(self, run_context, run_response): + interrupt_event_id = None + event = run_response.events[-1] + if event.content_type == "chatflow_interrupt": + interrupt_event_id = event.detail.get("interrupt_event_id") + if interrupt_event_id is not None: + self.interrupt_ids.append(interrupt_event_id) + + def _create_action(self): + if len(self.interrupt_ids) == 0: + return None + event_id = self.interrupt_ids.pop() + return { + "action_type": "resume", + "parameters": {"interrupt_event": {"id": event_id, "type": "chat"}}, + } + + def gen_action(self): + while True: + yield self._create_action() + + +def main(): + # 请前往千帆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 + # 设置环境变量 + os.environ["APPBUILDER_TOKEN"] = "..." + appbuilder.logger.setLoglevel("DEBUG") + + # 飞行客服小助手的应用id + app_id = "..." + # 初始化智能体 + client = appbuilder.AppBuilderClient(app_id) + conversation_id = client.create_conversation() + + queries = ["查天气", "查航班", "CA1234", "北京的"] + event_handler = MyEventHandler() + event_handler = client.run_multiple_dialog_with_handler( + conversation_id=conversation_id, + queries=queries, + event_handler=event_handler, + stream=True, + actions=event_handler.gen_action(), + ) + for data in event_handler: + for ans in data: + pass + + +if __name__ == "__main__": + main() +``` + + + ## Java基本用法 ### ```new AppBuilderClient(appId)``` @@ -473,27 +714,30 @@ answer = app_builder_client.run( #### Run方法入参`AppBuilderCientRunRequest` -| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 | -| --------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| query | String | 是 | query内容 | "汽车性能参数怎么样" | -| conversationId | String | 是 | 对话id,可以通过createConversation()获取 | | -| stream | boolean | 是 | 为true时则流式返回,为false时则一次性返回所有内容, 推荐设为true,降低首token时延 | | -| tools | List[Tool] | 否 | 一个列表,其中每个字典对应一个工具的配置 | | -| tools[0] | Tool | 否 | 工具配置 | | +| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 | +| --------------- | ------------------ | -------- | ------------------------------------------------------------ | -------------------- | +| query | String | 是 | query内容 | "汽车性能参数怎么样" | +| conversationId | String | 是 | 对话id,可以通过createConversation()获取 | | +| stream | boolean | 是 | 为true时则流式返回,为false时则一次性返回所有内容, 推荐设为true,降低首token时延 | | +| tools | List[Tool] | 否 | 一个列表,其中每个字典对应一个工具的配置 | | +| tools[0] | Tool | 否 | 工具配置 | | | +type | String | 否 | 枚举:
**file_retrieval**: 知识库检索工具能够理解文档内容,支持用户针对文档内容的问答。
**code_interpreter**: 代码解释器, 代码解释器能够生成并执行代码,从而协助用户解决复杂问题,涵盖科学计算(包括普通数学计算题)、数据可视化、文件编辑处理(图片、PDF文档、视频、音频等)、文件格式转换(如WAV、MP3、text、SRT、PNG、jpg、MP4、GIF、MP3等)、数据分析&清洗&处理(文件以excel、csv格式为主)、机器学习&深度学习建模&自然语言处理等多个领域。
**function**: 支持fucntion call模式调用工具 | | -| +function | Function | 否 | Function工具描述
仅当**type为**`**function**` 时需要且必须填写 | | -| ++name | String | 否 | 函数名
只允许数字、大小写字母和中划线和下划线,最大长度为64个字符。一次运行中唯一。 | | -| ++description | String | 否 | 工具描述 | | -| ++parameters | Dict | 否 | 工具参数, json_schema格式 | | -| tool_outputs | List[ToolOutput] | 否 | 内容为本地的工具执行结果,以自然语言/json dump str描述 | | -| tool_outputs[0] | ToolOutput | 否 | 工具执行结果 | | -| +tool_call_id | String | 否 | 工具调用ID | | -| +output | String | 否 | 工具输出 | | -| tool_choice | ToolChoice | 否 | 控制大模型使用组件的方式,仅对自主规划Agent生效。 | | -| +type | String | 否 | auto/function,auto表示由LLM自动判断调什么组件;function表示由用户指定调用哪个组件。 | | -| +function | ToolChoiceFunction | 否 | 组件对象,包括组件的英文名称和入参 | | -| ++name | String | 否 | 组件的英文名称(唯一标识) | | -| ++input | String | 否 | 组件入参,当组件没有入参时填入空对象{} | | +| +function | Function | 否 | Function工具描述
仅当**type为**`**function**` 时需要且必须填写 | | +| ++name | String | 否 | 函数名
只允许数字、大小写字母和中划线和下划线,最大长度为64个字符。一次运行中唯一。 | | +| ++description | String | 否 | 工具描述 | | +| ++parameters | Dict | 否 | 工具参数, json_schema格式 | | +| tool_outputs | List[ToolOutput] | 否 | 内容为本地的工具执行结果,以自然语言/json dump str描述 | | +| tool_outputs[0] | ToolOutput | 否 | 工具执行结果 | | +| +tool_call_id | String | 否 | 工具调用ID | | +| +output | String | 否 | 工具输出 | | +| tool_choice | ToolChoice | 否 | 控制大模型使用组件的方式,仅对自主规划Agent生效。 | | +| +type | String | 否 | auto/function,auto表示由LLM自动判断调什么组件;function表示由用户指定调用哪个组件。 | | +| +function | ToolChoiceFunction | 否 | 组件对象,包括组件的英文名称和入参 | | +| ++name | String | 否 | 组件的英文名称(唯一标识) | | +| ++input | String | 否 | 组件入参,当组件没有入参时填入空对象{} | | +| action | Action | 否 | 对话时要进行的特殊操作。如回复工作流agent中“信息收集节点“的消息 | | +| +action_type | String | 是 | 要执行的操作。
可选值为:
resume:回复“信息收集节点” 的消息 | | +| +parameters | Object | 是 | 执行操作时所需的参数 | | #### Run方法出参 | 参数名称 | 参数类型 | 描述 | 示例值 | @@ -832,6 +1076,116 @@ class AppBuilderClientDemo { ``` +#### Run方法回复工作流Agent “信息收集节点”使用示例: + +使用[“飞行客服小助手”](https://cloud.baidu.com/doc/AppBuilder/s/cm38k8nqr)作为工作流Agent的示例应用 + +```java +package org.example; + +import java.io.IOException; +import java.util.*; + +import com.google.gson.annotations.SerializedName; + +import com.baidubce.appbuilder.base.exception.AppBuilderServerException; +import com.baidubce.appbuilder.console.appbuilderclient.AppBuilderClient; +import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientIterator; +import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientResult; +import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientRunRequest; +import com.baidubce.appbuilder.model.appbuilderclient.Event; +import com.baidubce.appbuilder.base.utils.json.JsonUtils; + +class AppBuilderClientDemo { + + public static void main(String[] args) throws IOException, AppBuilderServerException { + System.setProperty("APPBUILDER_TOKEN", "请设置正确的应用密钥"); + String chatflowAppId = "请设置正确的应用ID"; + AppBuilderClient builder = new AppBuilderClient(chatflowAppId); + String conversationId = builder.createConversation(); + + AppBuilderClientRunRequest request = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "查天气", true); + Stack interruptStack = new Stack(); + AppBuilderClientIterator itor = builder.run(request); + String interruptEventId = ""; + while (itor.hasNext()) { + AppBuilderClientResult result = itor.next(); + for (Event event : result.getEvents()) { + if (event.getContentType().equals(EventContent.PublishMessageContentType)) { + String message = event.getDetail().get("message").toString(); + System.out.println(message); + } + if (event.getContentType().equals(EventContent.ChatflowInterruptContentType)) { + interruptEventId = event.getDetail().get("interrupt_event_id").toString(); + interruptStack.push(interruptEventId); + break; + } + } + } + + interruptEventId = ""; + AppBuilderClientRunRequest request2 = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "我先查个航班动态", true); + request2.setAction(AppBuilderClientRunRequest.Action.createAction(interruptStack.pop())); + AppBuilderClientIterator itor2 = builder.run(request2); + while (itor2.hasNext()) { + AppBuilderClientResult result2 = itor2.next(); + for (Event event : result2.getEvents()) { + if (event.getContentType().equals(EventContent.PublishMessageContentType)) { + String message = event.getDetail().get("message").toString(); + System.out.println(message); + } + if (event.getContentType().equals(EventContent.ChatflowInterruptContentType)) { + interruptEventId = event.getDetail().get("interrupt_event_id").toString(); + interruptStack.push(interruptEventId); + break; + } + } + } + + interruptEventId = ""; + AppBuilderClientRunRequest request3 = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "CA1234", true); + request3.setAction(AppBuilderClientRunRequest.Action.createAction(interruptStack.pop())); + AppBuilderClientIterator itor3 = builder.run(request3); + while (itor3.hasNext()) { + AppBuilderClientResult result3 = itor3.next(); + for (Event event : result3.getEvents()) { + if (event.getContentType().equals(EventContent.TextContentType)) { + String text = event.getDetail().get("text").toString(); + System.out.println(text); + } + if (event.getContentType().equals(EventContent.ChatflowInterruptContentType)) { + interruptEventId = event.getDetail().get("interrupt_event_id").toString(); + interruptStack.push(interruptEventId); + break; + } + } + } + + boolean hasMultipleContentType = false; + AppBuilderClientRunRequest request4 = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "北京的", + true); + request4.setAction(AppBuilderClientRunRequest.Action.createAction(interruptStack.pop())); + AppBuilderClientIterator itor4 = builder.run(request4); + while (itor4.hasNext()) { + AppBuilderClientResult result4 = itor4.next(); + for (Event event : result4.getEvents()) { + if (event.getContentType().equals(EventContent.TextContentType)) { + String text = event.getDetail().get("text").toString(); + System.out.println(text); + } + if (event.getContentType().equals(EventContent.MultipleDialogEventContentType)) { + hasMultipleContentType = true; + break; + } + } + } + } +} + +``` + + + ## Go基本用法 ### ```NewAppBuilderClient()``` @@ -1170,3 +1524,169 @@ func main() { } } ``` + +#### Run方法回复工作流Agent “信息收集节点”使用示例: + +使用[“飞行客服小助手”](https://cloud.baidu.com/doc/AppBuilder/s/cm38k8nqr)作为工作流Agent的示例应用 + +```go +package main + +import ( + "fmt" + "os" + + "github.com/baidubce/app-builder/go/appbuilder" +) + +func main() { + // 设置APPBUILDER_TOKEN、GATEWAY_URL_V2环境变量 + os.Setenv("APPBUILDER_TOKEN", "请设置正确的应用密钥") + // 默认可不填,默认值是 https://qianfan.baidubce.com + os.Setenv("GATEWAY_URL_V2", "") + config, err := appbuilder.NewSDKConfig("", "") + if err != nil { + fmt.Println("new config failed: ", err) + return + } + // 初始化实例 + appID := "请填写正确的应用ID" + client, err := appbuilder.NewAppBuilderClient(appID, config) + if err != nil { + fmt.Println("new agent builder failed: ", err) + return + } + // 创建对话ID + conversationID, err := client.CreateConversation() + if err != nil { + fmt.Println("create conversation failed: ", err) + return + } + i, err := client.Run(appbuilder.AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "查天气", + Stream: true, + }) + + if err != nil { + fmt.Println("run failed: ", err) + return + } + + var interruptId string + interruptStack := make([]string, 0) + for answer, err := i.Next(); err == nil; answer, err = i.Next() { + for _, ev := range answer.Events { + if ev.ContentType == appbuilder.PublishMessageContentType { + detail := ev.Detail.(appbuilder.PublishMessageDetail) + message := detail.Message + fmt.Println(message) + break + } + if ev.ContentType == appbuilder.ChatflowInterruptContentType { + deatil := ev.Detail.(appbuilder.ChatflowInterruptDetail) + interruptId = deatil.InterruptEventID + interruptStack = append(interruptStack, interruptId) + break + } + } + } + if len(interruptId) == 0 { + fmt.Println("interrupt id is empty") + return + } + + interruptId = "" + i2, err := client.Run(appbuilder.AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "我先查个航班动态", + Stream: true, + Action: appbuilder.NewResumeAction(interruptStack[len(interruptStack)-1]), + }) + if err != nil { + fmt.Println("run failed:", err) + return + } + interruptStack = interruptStack[:len(interruptStack)-1] + for answer, err := i2.Next(); err == nil; answer, err = i2.Next() { + for _, ev := range answer.Events { + if ev.ContentType == appbuilder.PublishMessageContentType { + detail := ev.Detail.(appbuilder.PublishMessageDetail) + message := detail.Message + fmt.Println(message) + break + } + if ev.ContentType == appbuilder.ChatflowInterruptContentType { + deatil := ev.Detail.(appbuilder.ChatflowInterruptDetail) + interruptId = deatil.InterruptEventID + interruptStack = append(interruptStack, interruptId) + break + } + } + } + if len(interruptId) == 0 { + fmt.Println("interrupt id is empty") + return + } + + interruptId = "" + i3, err := client.Run(appbuilder.AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "CA1234", + Stream: true, + Action: appbuilder.NewResumeAction(interruptStack[len(interruptStack)-1]), + }) + if err != nil { + fmt.Println("run failed:", err) + return + } + interruptStack = interruptStack[:len(interruptStack)-1] + for answer, err := i3.Next(); err == nil; answer, err = i3.Next() { + for _, ev := range answer.Events { + if ev.ContentType == appbuilder.TextContentType { + detail := ev.Detail.(appbuilder.TextDetail) + text := detail.Text + fmt.Println(text) + break + } + if ev.ContentType == appbuilder.ChatflowInterruptContentType { + deatil := ev.Detail.(appbuilder.ChatflowInterruptDetail) + interruptId = deatil.InterruptEventID + interruptStack = append(interruptStack, interruptId) + break + } + } + } + if len(interruptId) == 0 { + fmt.Println("interrupt id is empty") + return + } + + i4, err := client.Run(appbuilder.AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "北京的", + Stream: true, + Action: appbuilder.NewResumeAction(interruptStack[len(interruptStack)-1]), + }) + if err != nil { + fmt.Println("run failed:", err) + return + } + for answer, err := i4.Next(); err == nil; answer, err = i4.Next() { + for _, ev := range answer.Events { + if ev.ContentType == appbuilder.TextContentType { + detail := ev.Detail.(appbuilder.TextDetail) + text := detail.Text + fmt.Println(text) + break + } + } + } +} + +``` + diff --git a/go/appbuilder/app_builder_client_test.go b/go/appbuilder/app_builder_client_test.go index e6b817566..28141b9e1 100644 --- a/go/appbuilder/app_builder_client_test.go +++ b/go/appbuilder/app_builder_client_test.go @@ -648,58 +648,117 @@ func TestAppBuilderClientRunChatflow(t *testing.T) { } var interruptId string + interruptStack := make([]string, 0) for answer, err := i.Next(); err == nil; answer, err = i.Next() { for _, ev := range answer.Events { + if ev.ContentType == PublishMessageContentType { + detail := ev.Detail.(PublishMessageDetail) + message := detail.Message + fmt.Println(message) + break + } if ev.ContentType == ChatflowInterruptContentType { - if ev.EventType != ChatflowEventType { - t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") - t.Fatalf("event type error:%v", err) - } - deatil := ev.Detail.(ChatflowInterruptDetail) interruptId = deatil.InterruptEventID + interruptStack = append(interruptStack, interruptId) break } } } - if len(interruptId) == 0 { t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") t.Fatalf("interrupt id is empty") } + interruptId = "" i2, err := client.Run(AppBuilderClientRunRequest{ ConversationID: conversationID, AppID: appID, Query: "我先查个航班动态", Stream: true, - Action: NewResumeAction(interruptId), + Action: NewResumeAction(interruptStack[len(interruptStack)-1]), }) if err != nil { t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") t.Fatalf("run failed:%v", err) } - - var message string + interruptStack = interruptStack[:len(interruptStack)-1] for answer, err := i2.Next(); err == nil; answer, err = i2.Next() { for _, ev := range answer.Events { if ev.ContentType == PublishMessageContentType { - if ev.EventType != ChatflowEventType { - t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") - t.Fatalf("event type error:%v", err) - } - detail := ev.Detail.(PublishMessageDetail) - message = detail.Message + message := detail.Message + fmt.Println(message) + break + } + if ev.ContentType == ChatflowInterruptContentType { + deatil := ev.Detail.(ChatflowInterruptDetail) + interruptId = deatil.InterruptEventID + interruptStack = append(interruptStack, interruptId) + break + } + } + } + if len(interruptId) == 0 { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("interrupt id is empty") + } + + interruptId = "" + i3, err := client.Run(AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "CA1234", + Stream: true, + Action: NewResumeAction(interruptStack[len(interruptStack)-1]), + }) + if err != nil { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("run failed:%v", err) + } + interruptStack = interruptStack[:len(interruptStack)-1] + for answer, err := i3.Next(); err == nil; answer, err = i3.Next() { + for _, ev := range answer.Events { + if ev.ContentType == TextContentType { + detail := ev.Detail.(TextDetail) + text := detail.Text + fmt.Println(text) + break + } + if ev.ContentType == ChatflowInterruptContentType { + deatil := ev.Detail.(ChatflowInterruptDetail) + interruptId = deatil.InterruptEventID + interruptStack = append(interruptStack, interruptId) break } } } - if len(message) == 0 { + if len(interruptId) == 0 { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("interrupt id is empty") + } + + i4, err := client.Run(AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "北京的", + Stream: true, + Action: NewResumeAction(interruptStack[len(interruptStack)-1]), + }) + if err != nil { t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") - t.Fatalf("message is empty") + t.Fatalf("run failed:%v", err) + } + for answer, err := i4.Next(); err == nil; answer, err = i4.Next() { + for _, ev := range answer.Events { + if ev.ContentType == TextContentType { + detail := ev.Detail.(TextDetail) + text := detail.Text + fmt.Println(text) + break + } + } } - fmt.Println(message) // 如果测试失败,则输出缓冲区中的日志 if t.Failed() { diff --git a/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/EventContent.java b/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/EventContent.java index 4c6a672af..1d0879177 100644 --- a/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/EventContent.java +++ b/java/src/main/java/com/baidubce/appbuilder/model/appbuilderclient/EventContent.java @@ -15,6 +15,7 @@ public class EventContent { public static final String StatusContentType = "status"; public static final String ChatflowInterruptContentType = "chatflow_interrupt"; public static final String PublishMessageContentType = "publish_message"; + public static final String MultipleDialogEventContentType = "multiple_dialog_event"; @SerializedName("event_code") private String eventCode; diff --git a/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java b/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java index cb33dc655..61376b2b3 100644 --- a/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java +++ b/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java @@ -9,6 +9,8 @@ import java.nio.file.Files; import java.util.HashMap; import java.util.Map; +import java.util.Stack; + import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientIterator; import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientResult; import com.baidubce.appbuilder.model.appbuilderclient.AppListRequest; @@ -133,33 +135,91 @@ public void AppBuilderClientRunChatflowTest() throws IOException, AppBuilderServ AppBuilderClientRunRequest request = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "查天气", true); AppBuilderClientIterator itor = builder.run(request); assertTrue(itor.hasNext()); + Stack interruptStack = new Stack(); String interruptEventId = ""; while (itor.hasNext()) { AppBuilderClientResult result = itor.next(); for (Event event : result.getEvents()) { - System.out.println(event.getContentType()); + if (event.getContentType().equals(EventContent.PublishMessageContentType)) { + String message = event.getDetail().get("message").toString(); + System.out.println(message); + } if (event.getContentType().equals(EventContent.ChatflowInterruptContentType)) { assertEquals(event.getEventType(), Event.ChatflowEventType); interruptEventId = event.getDetail().get("interrupt_event_id").toString(); + interruptStack.push(interruptEventId); + break; } } } - assert interruptEventId != null && !interruptEventId.isEmpty(); + + interruptEventId = ""; AppBuilderClientRunRequest request2 = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "我先查个航班动态", true); - request2.setAction(AppBuilderClientRunRequest.Action.createAction(interruptEventId)); + request2.setAction(AppBuilderClientRunRequest.Action.createAction(interruptStack.pop())); AppBuilderClientIterator itor2 = builder.run(request2); assertTrue(itor2.hasNext()); - String message = ""; while (itor2.hasNext()) { AppBuilderClientResult result2 = itor2.next(); for (Event event : result2.getEvents()) { if (event.getContentType().equals(EventContent.PublishMessageContentType)) { - message = event.getDetail().get("message").toString(); + String message = event.getDetail().get("message").toString(); + System.out.println(message); + } + if (event.getContentType().equals(EventContent.ChatflowInterruptContentType)) { + assertEquals(event.getEventType(), Event.ChatflowEventType); + interruptEventId = event.getDetail().get("interrupt_event_id").toString(); + interruptStack.push(interruptEventId); + break; + } + } + } + assert interruptEventId != null && !interruptEventId.isEmpty(); + + interruptEventId = ""; + AppBuilderClientRunRequest request3 = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "CA1234", + true); + request3.setAction(AppBuilderClientRunRequest.Action.createAction(interruptStack.pop())); + AppBuilderClientIterator itor3 = builder.run(request3); + assertTrue(itor3.hasNext()); + while (itor3.hasNext()) { + AppBuilderClientResult result3 = itor3.next(); + for (Event event : result3.getEvents()) { + if (event.getContentType().equals(EventContent.TextContentType)) { + String text = event.getDetail().get("text").toString(); + System.out.println(text); + } + if (event.getContentType().equals(EventContent.ChatflowInterruptContentType)) { + assertEquals(event.getEventType(), Event.ChatflowEventType); + interruptEventId = event.getDetail().get("interrupt_event_id").toString(); + interruptStack.push(interruptEventId); + break; + } + } + } + assert interruptEventId != null && !interruptEventId.isEmpty(); + + boolean hasMultipleContentType = false; + AppBuilderClientRunRequest request4 = new AppBuilderClientRunRequest(chatflowAppId, conversationId, "北京的", + true); + request4.setAction(AppBuilderClientRunRequest.Action.createAction(interruptStack.pop())); + AppBuilderClientIterator itor4 = builder.run(request4); + assertTrue(itor4.hasNext()); + while (itor4.hasNext()) { + AppBuilderClientResult result4 = itor4.next(); + for (Event event : result4.getEvents()) { + if (event.getContentType().equals(EventContent.TextContentType)) { + String text = event.getDetail().get("text").toString(); + System.out.println(text); + } + if (event.getContentType().equals(EventContent.MultipleDialogEventContentType)) { + assertEquals(event.getEventType(), Event.ChatflowEventType); + hasMultipleContentType = true; + break; } } } - assert message != null && !message.isEmpty(); + assertTrue(hasMultipleContentType); } }