Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

新增Console AgentBuilder对话能力 #203

Merged
merged 9 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions appbuilder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ def check_version(self):

from appbuilder.core.utils import get_model_list

from appbuilder.core.console.agent_builder.agent_builder import AgentBuilder

from .core._exception import (
BadRequestException,
ForbiddenException,
Expand Down Expand Up @@ -158,4 +160,5 @@ def check_version(self):
"HandwriteOCR",
"ImageUnderstand",
"MixCardOCR",
"AgentBuilder",
]
160 changes: 160 additions & 0 deletions appbuilder/core/console/agent_builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# AgentBuilder组件

## 简介

AgentBuilder组件支持调用在[百度智能云千帆AppBuilder](https://cloud.baidu.com/product/AppBuilder)平台上通过AgentBuilder构建并发布的智能体应用。

### 功能介绍

具体包括创建会话、上传文档、运行对话等

### 特色优势

与云端Console AgentBuilder能力打通,实现低代码会话

### 应用场景

快速、高效集成云端已发布智能体应用能力

## 基本用法

以下是使用SDK进行问答的示例代码

```python
import appbuilder
import os

# 请前往千帆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"] = '...'
app_id = '...' # 已发布AgentBuilder应用ID,可在console端查看
# 初始化智能体
agent = appbuilder.AgentBuilder(app_id)
# 创建会话
conversation_id = agent.create_conversation()
# 运行对话
out = agent.run("北京天气怎么样", conversation_id)
# 打印会话结果
print(out.content.answer)
```

## 参数说明

### 鉴权说明

使用组件之前,请首先申请并设置鉴权参数,可参考[使用流程](https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5)。

```python
# 设置环境中的TOKEN,以下示例略
os.environ["APPBUILDER_TOKEN"] = "bce-YOURTOKEN"
```

### 初始化参数

- `app_id`: 线上AgentBuilder应用ID,可在[百度智能云千帆AppBuilder](https://cloud.baidu.com/product/AppBuilder)我的应用中查看应用ID,示例如图

<img width="768" alt="image" src="./image/agentbuilder.png">

### 调用参数

| 参数名称 | 参数类型 | 是否必须 | 描述 | 示例值 |
|-----------------|--------------|------|------------|------------|
| conversation_id | String | 是 | 会话ID | |
| query | String | 否 | query问题内容 | "今天天气怎么样?" |
| file_ids | list[String] | 否 | 对话可引用的文档ID | False |
| stream | Bool | 否 | 是否流式返回 | False |

### 非流式返回

| 参数名称 | 参数类型 | 描述 | 示例值 |
|----------------|--------------------|------------------|------------------------------------------------------------------------|
| content | AgentBuilderAnswer | 对话返回结果 | |
| +code | Int | 错误码,0代码成功,非0表示失败 | 0 |
| +message | String | 错误具体消息 | |
| +answer | String | 智能体应用返回的回答 | |
| +events | List[Event] | 事件列表 | |
| +events[0] | Event | 具体事件内容 | |
| ++code | String | 错误码 | |
| ++message | String | 错误具体消息 | |
| ++status | String | 事件状态 | 状态描述,preparing(准备运行)running(运行中)error(执行错误) done(执行完成) |
| ++event_type | String | 事件类型 | |
| ++content_type | String | 内容类型 | 可选值包括:code text, image, status,image, function_call, rag, audio、video等 |
| ++detail | Dict | 事件输出详情 | 代码解释器、文生图、工具组件等的详细输出内容 |

### 流式返回

| 参数名称 | 参数类型 | 描述 | 示例值 |
|---------|------------------|--------------------------------|-----|
| content | Python Generator | 可迭代,每次迭代返回AgentBuilderAnswer类型 | 无 |

### 响应示例

```
Message(name=msg, content=code=0 message='' answer='模型识别结果为:\n类别: 黑松 置信度: 0.599807\n根据植物识别工具的识别结果,图中的植物很可能是黑松,置信度为0.599807。需要注意的是,置信度并不是特别高,因此这个结果仅供参考。如果你需要更准确的识别结果,可以尝试提供更多的图片信息或者使用更专业的植物识别工具。如果你还有其他问题或者需要进一步的帮助,请随时告诉我。' events=[Event(code=0, message='', status='done', event_type='function_call', content_type='function_call', detail={'text': {'thought': '', 'name': 'plant_rec', 'arguments': {'img_path': 'tree.png'}, 'component': 'PlantRecognition', 'name_cn': '植物识别'}}), Event(code=0, message='', status='preparing', event_type='PlantRecognition', content_type='status', detail={}), Event(code=0, message='', status='done', event_type='PlantRecognition', content_type='text', detail={'text': '模型识别结果为:\n类别: 黑松 置信度: 0.599807\n'}), Event(code=0, message='', status='success', event_type='PlantRecognition', content_type='status', detail={}), Event(code=0, message='', status='done', event_type='function_call', content_type='function_call', detail={'text': {'thought': '', 'name': 'chat_agent', 'arguments': {}, 'component': 'ChatAgent', 'name_cn': '聊天助手'}}), Event(code=0, message='', status='preparing', event_type='ChatAgent', content_type='status', detail={}), Event(code=0, message='', status='done', event_type='ChatAgent', content_type='text', detail={'text': '根据植物识别工具的识别结果,图中的植物很可能是黑松,置信度为0.599807。需要注意的是,置信度并不是特别高,因此这个结果仅供参考。如果你需要更准确的识别结果,可以尝试提供更多的图片信息或者使用更专业的植物识别工具。如果你还有其他问题或者需要进一步的帮助,请随时告诉我。'}), Event(code=0, message='', status='success', event_type='ChatAgent', content_type='status', detail={})], mtype=AgentBuilderAnswer)
```

## 高级用法

```python

import appbuilder
from appbuilder.core.console.agent_builder import data_class
import os

# 请前往千帆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"] = '...'
app_id = '...' # 已发布AgentBuilder应用的ID
# 初始化智能体
agent = appbuilder.AgentBuilder(app_id)
# 创建会话
conversation_id = agent.create_conversation()

# 上传一个介绍介绍北京旅游景点的文档
file_id = agent.upload_local_file(conversation_id, "/path/to/pdf/file")
# 开始对话,引用上传的文档
message = agent.run(conversation_id, "北京天气怎么样", file_ids=[file_id, ], stream=True)


answer = ""

# 每次迭代返回AgentBuilderAnswer结构,内可能包括多个事件内容
for content in message.content:
# stream=True时,将answer拼接起来才是完整的的对话结果
answer += content.answer
for event in content.events:
content_type = event.content_type
detail = event.detail
# 根据content类型对事件详情进行解析
if content_type == "code":
code_detail = data_class.CodeDetail(**detail)
print(code_detail.code)
elif content_type == "text":
text_detail = data_class.TextDetail(**detail)
print(text_detail.text)
elif content_type == "image":
image_detail = data_class.ImageDetail(**detail)
print(image_detail.url)
elif content_type == "rag":
rag_detail = data_class.RAGDetail(**detail)
print(rag_detail.references)
elif content_type == "function_call":
function_call_detail = data_class.FunctionCallDetail(**detail)
print(function_call_detail.video)
elif content_type == "audio":
audio_detail = data_class.AudioDetail(**detail)
print(audio_detail)
elif content_type == "video":
video_detail = data_class.VideoDetail(**detail)
print(video_detail)
elif content_type == "status":
data_class.StatusDetail(**detail)
else:
print(detail)

# 打印完整的answer结果
print(answer)
```

## 更新记录和贡献
* 集成Console AgentBuilder能力(2024-03)
13 changes: 13 additions & 0 deletions appbuilder/core/console/agent_builder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2024 Baidu, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
180 changes: 180 additions & 0 deletions appbuilder/core/console/agent_builder/agent_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Copyright (c) 2024 Baidu, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""AgentBuilder组件"""

import json
import os

from appbuilder.core.component import Message, Component
from appbuilder.core.console.agent_builder import data_class
from appbuilder.core._exception import AppBuilderServerException
from appbuilder.utils.sse_util import SSEClient


class AgentBuilder(Component):
r"""
AgentBuilder组件支持调用在[百度智能云千帆AppBuilder](https://cloud.baidu.com/product/AppBuilder)平台上通过AgentBuilder
构建并发布的智能体应用,具体包括创建会话、上传文档、运行对话等。
Examples:
... code-block:: python
import appbuilder
# 请前往千帆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"] = '...'
# 可在Console AgentBuilder应用页面获取
app_id = "app_id"
agent_builder = appbuilder.AgentBuilder("app_id")
conversation_id = agent_builder.create_conversation()
file_id = agent_builder.upload_local_file(conversation_id, "/path/to/file")
message = agent_builder.run(conversation_id, "今天你好吗?")
# 打印对话结果
print(message.content)
"""

def __init__(self, app_id: str, **kwargs):
r"""初始化智能体应用
参数:
app_id (str: 必须) : 应用唯一ID
返回:
response (obj: `AgentBuilder`): 智能体实例
"""
super().__init__(**kwargs)
if (not isinstance(app_id, str)) or len(app_id) == 0:
raise ValueError("app_id must be a str, and length is bigger then zero,"
"please go to official website which is 'https://cloud.baidu.com/product/AppBuilder'"
" to get a valid app_id after your application is published.")
self.app_id = app_id

def create_conversation(self) -> str:
r"""创建会话并返回会话ID,会话ID在服务端用于上下文管理、绑定会话文档等,如需开始新的会话,请创建并使用新的会话ID
参数:
返回:
response (str: ): 唯一会话ID
"""
headers = self.http_client.auth_header()
headers["Content-Type"] = "application/json"
url = self.http_client.service_url("/v1/ai_engine/agi_platform/v1/conversation/create", "/api")
response = self.http_client.session.post(url, headers=headers, json={"app_id": self.app_id}, timeout=None)
self.http_client.check_response_header(response)
request_id = self.http_client.response_request_id(response)
data = response.json()
self._check_console_response(request_id, data)
resp = data_class.CreateConversationResponse(**data)
return resp.result.conversation_id

def upload_local_file(self, conversation_id, local_file_path: str) -> str:
r"""上传文件并将文件与会话ID进行绑定,后续可使用该文件ID进行对话,目前仅支持上传xlsx、jsonl、pdf、png等文件格式
参数:
conversation_id (str: 必须) : 会话ID
local_file_path (str: 必须) : 本地文件路径
返回:
response (str: ): 唯一文件ID
"""

if len(conversation_id) == 0:
raise ValueError("conversation_id is empty")
multipart_form_data = {
'file': (os.path.basename(local_file_path), open(local_file_path, 'rb')),
'app_id': (None, self.app_id),
'conversation_id': (None, conversation_id),
'scenario': (None, "assistant")
}
headers = self.http_client.auth_header()
url = self.http_client.service_url("/v1/ai_engine/agi_platform/v1/instance/upload", "/api")
response = self.http_client.session.post(url, files=multipart_form_data, headers=headers)
self.http_client.check_response_header(response)
request_id = self.http_client.response_request_id(response)
data = response.json()
self._check_console_response(request_id, data)
resp = data_class.FileUploadResponse(**data)
return resp.result.id

def run(self, conversation_id: str,
query: str,
file_ids: list[str] = [],
stream: bool = False,
) -> Message:

r""" 动物识别
参数:
query (str: 必须): query内容
conversation_id (str, 必须): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话
file_ids(list[str], 可选):
stream (bool, 可选): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回
返回: message (obj: `Message`): 对话结果.
"""

if len(conversation_id) == 0:
raise ValueError("conversation_id is empty")

req = data_class.HTTPRequest(
app_id=self.app_id,
conversation_id=conversation_id,
query=query,
response_mode="streaming" if stream else "blocking",
file_ids=file_ids,
)

headers = self.http_client.auth_header()
headers["Content-Type"] = "application/json"
url = self.http_client.service_url("/v1/ai_engine/agi_platform/v1/instance/integrated", '/api')
response = self.http_client.session.post(url, headers=headers, json=req.model_dump(), timeout=None, stream=True)
self.http_client.check_response_header(response)
request_id = self.http_client.response_request_id(response)
if stream:
client = SSEClient(response)
return Message(content=self._iterate_events(request_id, client.events()))
else:
data = response.json()
self._check_console_response(request_id, data)
resp = data_class.HTTPResponse(**data)
out = data_class.AgentBuilderAnswer()
_transform(resp, out)
return Message(content=out)

def _iterate_events(self, request_id, events) -> data_class.AgentBuilderAnswer:
for event in events:
try:
data = event.data
if len(data) == 0:
data = event.raw
data = json.loads(data)
self._check_console_response(request_id, data)
except json.JSONDecodeError as e:
raise AppBuilderServerException(request_id=request_id, message="json decoder failed {}".format(str(e)))
inp = data_class.HTTPResponse(**data)
out = data_class.AgentBuilderAnswer()
_transform(inp, out)
yield out

@staticmethod
def _check_console_response(request_id: str, data):
if data["code"] != 0:
raise AppBuilderServerException(
request_id=request_id,
service_err_code=data["code"],
service_err_message="message={}".
format(data["message"])
)


def _transform(inp: data_class.HTTPResponse, out: data_class.AgentBuilderAnswer):
out.code = inp.code
out.message = inp.message
out.answer = inp.result.answer
for ev in inp.result.content:
out.events.append(data_class.Event(code=ev.event_code, message=ev.event_message,
status=ev.event_status, event_type=ev.event_type,
content_type=ev.content_type, detail=ev.outputs))
Loading
Loading