Skip to content

Commit

Permalink
Merge pull request #203 from ide-rea/master
Browse files Browse the repository at this point in the history
新增Console AgentBuilder对话能力
  • Loading branch information
guru4elephant authored Mar 20, 2024
2 parents ae0a536 + 3031cd0 commit 25d1a1e
Show file tree
Hide file tree
Showing 16 changed files with 796 additions and 1 deletion.
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

0 comments on commit 25d1a1e

Please sign in to comment.