Skip to content
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
24 changes: 20 additions & 4 deletions docs/BasisModule/Platform/Application/appbuilder_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,32 @@ AppBuilderClient组件支持调用在[百度智能云千帆AppBuilder](https://c
| conversation_id | string | 会话的ID | "80c5bbee-931d-4ed9-a4ff-63e1971bd071" |


### `AppBuilderClient().upload_local_file(file_path: str)-> str`
### `AppBuilderClient().upload_local_file(conversation_id, local_file_path: str)-> str`
#### 方法参数
| 参数名称 | 参数类型 | 描述 | 示例值 |
| --------- | -------- | -------- | ---------------- |
| file_path | string | 文件路径 | "正确的文件路径" |
| 参数名称 | 参数类型 | 描述 | 示例值 |
| --------------- | -------- | -------- | ---------------- |
| conversation_Id | string | 会话ID | |
| file_path | string | 文件路径 | "正确的文件路径" |
#### 方法返回值
| 参数名称 | 参数类型 | 描述 | 示例值 |
| -------- | -------- | ------ | ---------------------------------- |
| file_id | string | 文件ID | "80c5bbee-931d-4ed9-a4ff-63e1971bd |

### `AppBuilderClient().upload_file(conversation_id, local_file_path: str=None, file_url: str=None)-> str`

#### 方法参数

| 参数名称 | 参数类型 | 描述 | 示例值 |
| --------------- | -------- | -------- | ---------------- |
| conversation_Id | string | 会话ID | |
| file_path | string | 文件路径 | "正确的文件路径" |
| file_url | string | 文件url | |

#### 方法返回值

| 参数名称 | 参数类型 | 描述 | 示例值 |
| -------- | -------- | ------ | ---------------------------------- |
| file_id | string | 文件ID | "80c5bbee-931d-4ed9-a4ff-63e1971bd |

### `AppBuilderClient().run() -> Message`

Expand Down
71 changes: 71 additions & 0 deletions go/appbuilder/app_builder_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,77 @@ func (t *AppBuilderClient) UploadLocalFile(conversationID string, filePath strin
return val.(string), nil
}

func (t *AppBuilderClient) UploadFile(req *AppBuilderClientUploadFileRequest) (string, error) {
var appID string
if req.AppID != "" {
appID = req.AppID
} else {
appID = t.appID
}
if appID == "" {
return "", errors.New("appID is required")
}
if req.FilePath == "" && req.FileURL == "" {
return "", errors.New("either FilePath or FileURL is required")
}

var data bytes.Buffer
w := multipart.NewWriter(&data)
appIDPart, _ := w.CreateFormField("app_id")
appIDPart.Write([]byte(appID))
conversationIDPart, _ := w.CreateFormField("conversation_id")
conversationIDPart.Write([]byte(req.ConversationID))
if req.FilePath != "" {
file, err := os.Open(req.FilePath)
if err != nil {
return "", err
}
defer file.Close()
filePart, _ := w.CreateFormFile("file", filepath.Base(req.FilePath))
if _, err := io.Copy(filePart, file); err != nil {
return "", err
}
} else {
fileURLPart, _ := w.CreateFormField("file_url")
fileURLPart.Write([]byte(req.FileURL))
}

w.Close()
request := http.Request{}
serviceURL, err := t.sdkConfig.ServiceURLV2("/app/conversation/file/upload")
if err != nil {
return "", err
}
request.URL = serviceURL
request.Method = "POST"
header := t.sdkConfig.AuthHeaderV2()
header.Set("Content-Type", w.FormDataContentType())
request.Header = header
request.Body = NopCloser(bytes.NewReader(data.Bytes()))
resp, err := t.client.Do(&request)
if err != nil {
return "", err
}
defer resp.Body.Close()
requestID, err := checkHTTPResponse(resp)
if err != nil {
return "", fmt.Errorf("requestID=%s, err=%v", requestID, err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("requestID=%s, err=%v", requestID, err)
}
rsp := make(map[string]any)
if err := json.Unmarshal(body, &rsp); err != nil {
return "", fmt.Errorf("requestID=%s, err=%v", requestID, err)
}
val, ok := rsp["id"]
if !ok {
return "", fmt.Errorf("requestID=%s, body=%s", requestID, string(body))
}
return val.(string), nil
}

func (t *AppBuilderClient) Run(param ...interface{}) (AppBuilderClientIterator, error) {
if len(param) == 0 {
return nil, errors.New("no arguments provided")
Expand Down
7 changes: 7 additions & 0 deletions go/appbuilder/app_builder_client_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ type AppBuilderClientRunRequest struct {
Action *Action `json:"action"`
}

type AppBuilderClientUploadFileRequest struct {
AppID string `json:"app_id"`
ConversationID string `json:"conversation_id"`
FilePath string `json:"file_path"`
FileURL string `json:"file_url"`
}

type AppBuilderClientFeedbackRequest struct {
AppID string `json:"app_id"`
ConversationID string `json:"conversation_id"`
Expand Down
55 changes: 55 additions & 0 deletions go/appbuilder/app_builder_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,3 +838,58 @@ func TestAppbuilderClientFeedback(t *testing.T) {
t.Logf("%s========== OK: %s ==========%s", "\033[32m", t.Name(), "\033[0m")
}
}

func TestAppBuilderClientUploadFile(t *testing.T) {
var logBuffer bytes.Buffer

// 设置环境变量
os.Setenv("APPBUILDER_LOGLEVEL", "DEBUG")
os.Setenv("APPBUILDER_LOGFILE", "")

// 测试逻辑
config, err := NewSDKConfig("", "")
if err != nil {
t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m")
t.Fatalf("new http client config failed: %v", err)
}
appID := "fb64d96b-f828-4385-ba1d-835298d635a9"
client, err := NewAppBuilderClient(appID, config)
if err != nil {
t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m")
t.Fatalf("new AppBuilderClient instance failed")
}

conversationID, err := client.CreateConversation()
if err != nil {
t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m")
t.Fatalf("create conversation failed: %v", err)
}

req := AppBuilderClientUploadFileRequest{
ConversationID: conversationID,
FilePath: "./files/test.pdf",
}
_, err = client.UploadFile(&req)
if err != nil {
t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m")
t.Fatalf("upload local file failed: %v", err)
}

req = AppBuilderClientUploadFileRequest{
ConversationID: conversationID,
FileURL: "https://bj.bcebos.com/v1/appbuilder/animal_recognize_test.png?authorization=bce-auth-v1%2FALTAKGa8m4qCUasgoljdEDAzLm%2F2024-01-24T12%3A19%3A16Z%2F-1%2Fhost%2F411bad53034fa8f9c6edbe5c4909d76ecf6fad6862cf937c03f8c5260d51c6ae",
}
_, err = client.UploadFile(&req)
if err != nil {
t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m")
t.Fatalf("upload url file failed: %v", err)
}

// 如果测试失败,则输出缓冲区中的日志
if t.Failed() {
fmt.Println(logBuffer.String())
} else { // else 紧跟在右大括号后面
// 测试通过,打印文件名和测试函数名
t.Logf("%s========== OK: %s ==========%s", "\033[32m", t.Name(), "\033[0m")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,28 @@ private String innerCreateConversation() throws IOException, AppBuilderServerExc
*/
public String uploadLocalFile(String conversationId, String filePath)
throws IOException, AppBuilderServerException {
return innerUploadLocalFile(conversationId, filePath);
return innerUploadLocalFile(conversationId, filePath, "");
}

private String innerUploadLocalFile(String conversationId, String filePath)
public String uploadFile(String conversationId, String filePath, String fileUrl)
throws IOException, AppBuilderServerException {
return innerUploadLocalFile(conversationId, filePath, fileUrl);
}

private String innerUploadLocalFile(String conversationId, String filePath, String fileUrl)
throws IOException, AppBuilderServerException {
String url = AppBuilderConfig.UPLOAD_FILE_URL;
if (this.appID == null || this.appID.isEmpty()) {
throw new RuntimeException("Param 'appID' is required!");
}
MultipartEntityBuilder builder = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.LEGACY).setCharset(StandardCharsets.UTF_8);
builder.addBinaryBody("file", new File(filePath));

if(filePath != null && !filePath.isEmpty()) {
builder.addBinaryBody("file", new File(filePath));
} else if (fileUrl != null && !fileUrl.isEmpty()) {
builder.addTextBody("file_url", fileUrl);
}
builder.addTextBody("app_id", this.appID);
builder.addTextBody("conversation_id", conversationId);
builder.addTextBody("scenario", "assistant");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public void AppBuilderClientRunTest() throws IOException, AppBuilderServerExcept
AppBuilderClient builder = new AppBuilderClient(followupqueryId);
String conversationId = builder.createConversation();
assertNotNull(conversationId);
builder.uploadFile(conversationId, "src/test/java/com/baidubce/appbuilder/files/test.pdf", "");
builder.uploadFile(conversationId, "",
"https://bj.bcebos.com/v1/appbuilder/animal_recognize_test.png?authorization=bce-auth-v1%2FALTAKGa8m4qCUasgoljdEDAzLm%2F2024-01-24T12%3A19%3A16Z%2F-1%2Fhost%2F411bad53034fa8f9c6edbe5c4909d76ecf6fad6862cf937c03f8c5260d51c6ae");
String fileId = builder.uploadLocalFile(conversationId,
"src/test/java/com/baidubce/appbuilder/files/test.pdf");
assertNotNull(fileId);
Expand Down
41 changes: 35 additions & 6 deletions python/core/console/appbuilder_client/appbuilder_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,22 @@ def upload_local_file(self, conversation_id, local_file_path: str) -> str:
conversation_id (str) : 会话ID
local_file_path (str) : 本地文件路径

Returns:
response (str): 唯一文件ID
"""
return self.upload_file(conversation_id, local_file_path=local_file_path)

@client_tool_trace
def upload_file(self, conversation_id, local_file_path: str=None, file_url: str=None) -> str:
r"""上传文件并将文件与会话ID进行绑定,后续可使用该文件ID进行对话,目前仅支持上传xlsx、jsonl、pdf、png等文件格式

该接口用于在对话中上传文件供大模型处理,文件的有效期为7天并且不超过对话的有效期。一次只能上传一个文件。

Args:
conversation_id (str) : 会话ID
local_file_path (str) : 本地文件路径
file_url(str): 待上传的文件url

Returns:
response (str): 唯一文件ID

Expand All @@ -225,20 +241,33 @@ def upload_local_file(self, conversation_id, local_file_path: str) -> str:
"conversation_id is empty, you can run self.create_conversation to get a conversation_id"
)

filepath = os.path.abspath(local_file_path)
if not os.path.exists(filepath):
raise FileNotFoundError(f"{filepath} does not exist")
if local_file_path is None and file_url is None:
raise ValueError(
"local_file_path and file_url cannot both be empty"
)
if local_file_path:
filepath = os.path.abspath(local_file_path)
if not os.path.exists(filepath):
raise FileNotFoundError(f"{filepath} and {file_url} does not exist")

headers = self.http_client.auth_header_v2()
url = self.http_client.service_url_v2("/app/conversation/file/upload")

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),
}
headers = self.http_client.auth_header_v2()
url = self.http_client.service_url_v2("/app/conversation/file/upload")
if local_file_path:
multipart_form_data["file"] = (
os.path.basename(local_file_path),
open(local_file_path, "rb"),
)
else:
multipart_form_data["file_url"] = (None, file_url)
response = self.http_client.session.post(
url, files=multipart_form_data, headers=headers
)

self.http_client.check_response_header(response)
data = response.json()
resp = data_class.FileUploadResponse(**data)
Expand Down
42 changes: 34 additions & 8 deletions python/core/console/appbuilder_client/async_appbuilder_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,25 +146,51 @@ async def upload_local_file(self, conversation_id, local_file_path: str) -> str:
Returns:
response (str): 唯一文件ID

"""
return await self.upload_local_file(conversation_id, local_file_path)

async def upload_file(self, conversation_id, local_file_path: str=None, file_url: str=None) -> str:
r"""异步运行,上传文件并将文件与会话ID进行绑定,后续可使用该文件ID进行对话,目前仅支持上传xlsx、jsonl、pdf、png等文件格式

该接口用于在对话中上传文件供大模型处理,文件的有效期为7天并且不超过对话的有效期。一次只能上传一个文件。

Args:
conversation_id (str) : 会话ID
local_file_path (str) : 本地文件路径
file_url (str) : 文件URL

Returns:
response (str): 唯一文件ID

"""
if len(conversation_id) == 0:
raise ValueError(
"conversation_id is empty, you can run self.create_conversation to get a conversation_id"
)

filepath = os.path.abspath(local_file_path)
if not os.path.exists(filepath):
raise FileNotFoundError(f"{filepath} does not exist")
if local_file_path is None and file_url is None:
raise ValueError(
"local_file_path and file_url cannot both be empty"
)
if local_file_path:
filepath = os.path.abspath(local_file_path)
if not os.path.exists(filepath):
raise FileNotFoundError(f"{filepath} does not exist")

multipart_form_data = FormData()
multipart_form_data.add_field(
name="file",
value=open(local_file_path, "rb"),
filename=os.path.basename(local_file_path),
)
multipart_form_data.add_field(name="app_id", value=self.app_id)
multipart_form_data.add_field(
name="conversation_id", value=conversation_id)

if local_file_path:
multipart_form_data.add_field(
name="file",
value=open(local_file_path, "rb"),
filename=os.path.basename(local_file_path),
)
else:
multipart_form_data.add_field(name="file_url", value=file_url)

headers = self.http_client.auth_header_v2()
url = self.http_client.service_url_v2("/app/conversation/file/upload")
response = await self.http_client.session.post(
Expand Down
6 changes: 6 additions & 0 deletions python/tests/test_appbuilder_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def test_upload_local_file_raise(self):
conversation_id = builder.create_conversation()
with self.assertRaises(FileNotFoundError):
builder.upload_local_file(conversation_id=conversation_id, local_file_path='not_exist')

def test_upload_file_url(self):
file_url = "https://bj.bcebos.com/v1/appbuilder/animal_recognize_test.png?authorization=bce-auth-v1%2FALTAKGa8m4qCUasgoljdEDAzLm%2F2024-01-24T12%3A19%3A16Z%2F-1%2Fhost%2F411bad53034fa8f9c6edbe5c4909d76ecf6fad6862cf937c03f8c5260d51c6ae"
builder = appbuilder.AppBuilderClient(self.app_id)
conversation_id = builder.create_conversation()
builder.upload_file(conversation_id=conversation_id, file_url=file_url)


if __name__ == '__main__':
Expand Down
6 changes: 5 additions & 1 deletion python/tests/test_async_appbuilder_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ async def agent_run(client, conversation_id, text):
async def agent_handle():
client = appbuilder.AsyncAppBuilderClient(self.app_id)
conversation_id = await client.create_conversation()
await client.upload_local_file(conversation_id, "./data/qa_appbuilder_client_demo.pdf")
await client.upload_file(conversation_id, "./data/qa_appbuilder_client_demo.pdf")
await client.upload_file(
conversation_id=conversation_id,
file_url="https://bj.bcebos.com/v1/appbuilder/animal_recognize_test.png?authorization=bce-auth-v1%2FALTAKGa8m4qCUasgoljdEDAzLm%2F2024-01-24T12%3A19%3A16Z%2F-1%2Fhost%2F411bad53034fa8f9c6edbe5c4909d76ecf6fad6862cf937c03f8c5260d51c6ae",
)
task1 = asyncio.create_task(
agent_run(client, conversation_id, "最早的邮展"))
task2 = asyncio.create_task(
Expand Down