diff --git a/go/appbuilder/app_builder_client.go b/go/appbuilder/app_builder_client.go index 1040c4227..46e4e54e8 100644 --- a/go/appbuilder/app_builder_client.go +++ b/go/appbuilder/app_builder_client.go @@ -295,6 +295,43 @@ func (t *AppBuilderClient) Run(conversationID string, query string, fileIDS []st return &AppBuilderClientOnceIterator{body: resp.Body}, nil } +func (t *AppBuilderClient) RunV2(req AppBuilderClientRunRequest) (AppBuilderClientIterator, error) { + if len(req.ConversationID) == 0 { + return nil, errors.New("conversationID mustn't be empty") + } + + request := http.Request{} + + serviceURL, err := t.sdkConfig.ServiceURLV2("/app/conversation/runs") + if err != nil { + return nil, err + } + + header := t.sdkConfig.AuthHeaderV2() + request.URL = serviceURL + request.Method = "POST" + header.Set("Content-Type", "application/json") + request.Header = header + data, _ := json.Marshal(req) + request.Body = NopCloser(bytes.NewReader(data)) + request.ContentLength = int64(len(data)) + + t.sdkConfig.BuildCurlCommand(&request) + resp, err := t.client.Do(&request) + if err != nil { + return nil, err + } + requestID, err := checkHTTPResponse(resp) + if err != nil { + return nil, fmt.Errorf("requestID=%s, err=%v", requestID, err) + } + r := NewSSEReader(1024*1024, bufio.NewReader(resp.Body)) + if req.Stream { + return &AppBuilderClientStreamIterator{requestID: requestID, r: r, body: resp.Body}, nil + } + return &AppBuilderClientOnceIterator{body: resp.Body}, nil +} + func (t *AppBuilderClient) RunWithToolCall(req AppBuilderClientRunRequest) (AppBuilderClientIterator, error) { if len(req.ConversationID) == 0 { return nil, errors.New("conversationID mustn't be empty") diff --git a/go/appbuilder/app_builder_client_data.go b/go/appbuilder/app_builder_client_data.go index 288c24805..ca8887b28 100644 --- a/go/appbuilder/app_builder_client_data.go +++ b/go/appbuilder/app_builder_client_data.go @@ -23,25 +23,33 @@ import ( ) const ( - CodeContentType = "code" - TextContentType = "text" - ImageContentType = "image" - RAGContentType = "rag" - FunctionCallContentType = "function_call" - AudioContentType = "audio" - VideoContentType = "video" - StatusContentType = "status" + CodeContentType = "code" + TextContentType = "text" + ImageContentType = "image" + RAGContentType = "rag" + FunctionCallContentType = "function_call" + AudioContentType = "audio" + VideoContentType = "video" + StatusContentType = "status" + ChatflowInterruptContentType = "chatflow_interrupt" + PublishMessageContentType = "publish_message" +) + +const ( + ChatflowEventType = "chatflow" ) var TypeToStruct = map[string]reflect.Type{ - CodeContentType: reflect.TypeOf(CodeDetail{}), - TextContentType: reflect.TypeOf(TextDetail{}), - ImageContentType: reflect.TypeOf(ImageDetail{}), - RAGContentType: reflect.TypeOf(RAGDetail{}), - FunctionCallContentType: reflect.TypeOf(FunctionCallDetail{}), - AudioContentType: reflect.TypeOf(AudioDetail{}), - VideoContentType: reflect.TypeOf(VideoDetail{}), - StatusContentType: reflect.TypeOf(StatusDetail{}), + CodeContentType: reflect.TypeOf(CodeDetail{}), + TextContentType: reflect.TypeOf(TextDetail{}), + ImageContentType: reflect.TypeOf(ImageDetail{}), + RAGContentType: reflect.TypeOf(RAGDetail{}), + FunctionCallContentType: reflect.TypeOf(FunctionCallDetail{}), + AudioContentType: reflect.TypeOf(AudioDetail{}), + VideoContentType: reflect.TypeOf(VideoDetail{}), + StatusContentType: reflect.TypeOf(StatusDetail{}), + ChatflowInterruptContentType: reflect.TypeOf(ChatflowInterruptDetail{}), + PublishMessageContentType: reflect.TypeOf(PublishMessageDetail{}), } type AppBuilderClientRunRequest struct { @@ -53,6 +61,7 @@ type AppBuilderClientRunRequest struct { Tools []Tool `json:"tools"` ToolOutputs []ToolOutput `json:"tool_outputs"` ToolChoice *ToolChoice `json:"tool_choice"` + Action *Action `json:"action"` } type Tool struct { @@ -81,6 +90,36 @@ type ToolChoiceFunction struct { Input map[string]interface{} `json:"input"` } +type Action struct { + ActionType string `json:"action_type"` + Paramters *ActionParamters `json:"parameters"` +} + +type ActionParamters struct { + InterruptEvent *ActionInterruptEvent `json:"interrupt_event"` +} + +type ActionInterruptEvent struct { + ID string `json:"id"` + Type string `json:"type"` +} + +func NewResumeAction(eventId string) *Action { + return NewAction("resume", eventId, "chat") +} + +func NewAction(actionType string, eventId string, eventType string) *Action { + return &Action{ + ActionType: actionType, + Paramters: &ActionParamters{ + InterruptEvent: &ActionInterruptEvent{ + ID: eventId, + Type: eventType, + }, + }, + } +} + type AgentBuilderRawResponse struct { RequestID string `json:"request_id"` Date string `json:"date"` @@ -122,7 +161,7 @@ type Event struct { EventType string ContentType string Usage Usage - Detail any // 将any替换为interface{} + Detail any ToolCalls []ToolCall } @@ -185,6 +224,16 @@ type VideoDetail struct { type StatusDetail struct{} +type ChatflowInterruptDetail struct { + InterruptEventID string `json:"interrupt_event_id"` + InterruptEventType string `json:"interrupt_event_type"` +} + +type PublishMessageDetail struct { + Message string `json:"message"` + MessageID string `json:"message_id"` +} + type DefaultDetail struct { URLS []string `json:"urls"` Files []string `json:"files"` diff --git a/go/appbuilder/app_builder_client_test.go b/go/appbuilder/app_builder_client_test.go index 000ba85aa..8cd46daf1 100644 --- a/go/appbuilder/app_builder_client_test.go +++ b/go/appbuilder/app_builder_client_test.go @@ -574,7 +574,7 @@ func TestAppBuilderClientRunToolChoice(t *testing.T) { input := make(map[string]any) input["city"] = "北京" end_user_id := "go_user_id_0" - i, err := client.RunWithToolCall(AppBuilderClientRunRequest{ + i, err := client.RunV2(AppBuilderClientRunRequest{ ConversationID: conversationID, AppID: appID, Query: "你能干什么", @@ -610,3 +610,103 @@ func TestAppBuilderClientRunToolChoice(t *testing.T) { t.Logf("%s========== OK: %s ==========%s", "\033[32m", t.Name(), "\033[0m") } } + +func TestAppBuilderClientRunChatflow(t *testing.T) { + var logBuffer bytes.Buffer + + os.Setenv("APPBUILDER_LOGLEVEL", "DEBUG") + + 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 := "4403205e-fb83-4fac-96d8-943bdb63796f" + client, err := NewAppBuilderClient(appID, config) + if err != nil { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("new AgentBuidler 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) + } + + i, err := client.RunV2(AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "查天气", + Stream: true, + }) + + if err != nil { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("run failed:%v", err) + } + + var interruptId string + for answer, err := i.Next(); err == nil; answer, err = i.Next() { + for _, ev := range answer.Events { + 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 + break + } + } + } + + if len(interruptId) == 0 { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("interrupt id is empty") + } + + i2, err := client.RunV2(AppBuilderClientRunRequest{ + ConversationID: conversationID, + AppID: appID, + Query: "我先查个航班动态", + Stream: true, + Action: NewResumeAction(interruptId), + }) + if err != nil { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("run failed:%v", err) + } + + var message string + 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 + break + } + } + } + if len(message) == 0 { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + t.Fatalf("message is empty") + } + fmt.Println(message) + + // 如果测试失败,则输出缓冲区中的日志 + if t.Failed() { + t.Logf("%s========== FAIL: %s ==========%s", "\033[31m", t.Name(), "\033[0m") + fmt.Println(logBuffer.String()) + } else { // else 紧跟在右大括号后面 + // 测试通过,打印文件名和测试函数名 + t.Logf("%s========== OK: %s ==========%s", "\033[32m", t.Name(), "\033[0m") + } +} diff --git a/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java b/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java index bca258a2b..dc51cb18e 100644 --- a/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java +++ b/java/src/test/java/com/baidubce/appbuilder/AppBuilderClientTest.java @@ -138,8 +138,6 @@ public void AppBuilderClientRunChatflowTest() throws IOException, AppBuilderServ for (Event event : result.getEvents()) { System.out.println(event.getContentType()); if (event.getContentType().equals("chatflow_interrupt")) { - System.out.println("11111"); - System.out.println(event.getDetail()); assertEquals(event.getEventType(), "chatflow"); interruptEventId = event.getDetail().get("interrupt_event_id").toString(); }