refactor(workflow): Calculate chat history rounds during schema convertion (#1990)
Co-authored-by: zhuangjie.1125 <zhuangjie.1125@bytedance.com>main
parent
4416127d47
commit
4bfce5a8cb
@ -0,0 +1,604 @@ |
||||
/* |
||||
* Copyright 2025 coze-dev Authors |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package workflow |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/cloudwego/eino/schema" |
||||
"github.com/stretchr/testify/assert" |
||||
"go.uber.org/mock/gomock" |
||||
|
||||
messageentity "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" |
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow" |
||||
crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun" |
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun/agentrunmock" |
||||
crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload" |
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload/uploadmock" |
||||
agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" |
||||
uploadentity "github.com/coze-dev/coze-studio/backend/domain/upload/entity" |
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/service" |
||||
) |
||||
|
||||
func TestApplicationService_makeChatFlowUserInput(t *testing.T) { |
||||
ctx := context.Background() |
||||
ctrl := gomock.NewController(t) |
||||
defer ctrl.Finish() |
||||
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl) |
||||
crossupload.SetDefaultSVC(mockUpload) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
message *workflow.EnterMessage |
||||
setupMock func() |
||||
expected string |
||||
expectErr bool |
||||
}{ |
||||
{ |
||||
name: "content type text", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "text", |
||||
Content: "hello", |
||||
}, |
||||
setupMock: func() {}, |
||||
expected: "hello", |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with text", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "text", "text": "hello world"}]`, |
||||
}, |
||||
setupMock: func() {}, |
||||
expected: "hello world", |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with file", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{ |
||||
File: &uploadentity.File{Url: "https://example.com/file"}, |
||||
}, nil) |
||||
}, |
||||
expected: "https://example.com/file", |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with text and file", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "text", "text": "see this file"}, {"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{ |
||||
File: &uploadentity.File{Url: "https://example.com/file"}, |
||||
}, nil) |
||||
}, |
||||
expected: "see this file,https://example.com/file", |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "get file error", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error")) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "file not found", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{ |
||||
File: nil, |
||||
}, nil) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid content type", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "invalid", |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid json", |
||||
message: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `invalid-json`, |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
} |
||||
|
||||
w := &ApplicationService{} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
tt.setupMock() |
||||
result, err := w.makeChatFlowUserInput(ctx, tt.message) |
||||
if tt.expectErr { |
||||
assert.Error(t, err) |
||||
} else { |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, tt.expected, result) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_toConversationMessage(t *testing.T) { |
||||
ctx := context.Background() |
||||
ctrl := gomock.NewController(t) |
||||
defer ctrl.Finish() |
||||
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl) |
||||
crossupload.SetDefaultSVC(mockUpload) |
||||
|
||||
bizID, cid, userID, roundID, sectionID := int64(2), int64(1), int64(4), int64(3), int64(5) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
msg *workflow.EnterMessage |
||||
messageType messageentity.MessageType |
||||
setupMock func() |
||||
expected *messageentity.Message |
||||
expectErr bool |
||||
}{ |
||||
{ |
||||
name: "content type text", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "text", |
||||
Content: "hello", |
||||
}, |
||||
messageType: messageentity.MessageTypeQuestion, |
||||
setupMock: func() {}, |
||||
expected: &messageentity.Message{ |
||||
Role: schema.User, |
||||
ConversationID: cid, |
||||
AgentID: bizID, |
||||
RunID: roundID, |
||||
Content: "hello", |
||||
ContentType: messageentity.ContentTypeText, |
||||
MessageType: messageentity.MessageTypeQuestion, |
||||
UserID: strconv.FormatInt(userID, 10), |
||||
SectionID: sectionID, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with text", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "text", "text": "hello"}]`, |
||||
}, |
||||
messageType: messageentity.MessageTypeQuestion, |
||||
setupMock: func() {}, |
||||
expected: &messageentity.Message{ |
||||
Role: schema.User, |
||||
MessageType: messageentity.MessageTypeQuestion, |
||||
ConversationID: cid, |
||||
AgentID: bizID, |
||||
UserID: strconv.FormatInt(userID, 10), |
||||
RunID: roundID, |
||||
ContentType: messageentity.ContentTypeMix, |
||||
MultiContent: []*messageentity.InputMetaData{ |
||||
{Type: messageentity.InputTypeText, Text: "hello"}, |
||||
}, |
||||
SectionID: sectionID, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with file", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
messageType: messageentity.MessageTypeQuestion, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{ |
||||
File: &uploadentity.File{Url: "https://example.com/file", TosURI: "tos://uri", Name: "file.txt"}, |
||||
}, nil) |
||||
}, |
||||
expected: &messageentity.Message{ |
||||
Role: schema.User, |
||||
MessageType: messageentity.MessageTypeQuestion, |
||||
ConversationID: cid, |
||||
AgentID: bizID, |
||||
UserID: strconv.FormatInt(userID, 10), |
||||
RunID: roundID, |
||||
ContentType: messageentity.ContentTypeMix, |
||||
MultiContent: []*messageentity.InputMetaData{ |
||||
{ |
||||
Type: "file", |
||||
FileData: []*messageentity.FileData{ |
||||
{Url: "https://example.com/file", URI: "tos://uri", Name: "file.txt"}, |
||||
}, |
||||
}, |
||||
}, |
||||
SectionID: sectionID, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "get file error", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error")) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "file not found", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{}, nil) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid content type", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "invalid", |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid json", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: "invalid-json", |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid input type", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "invalid"}]`, |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
tt.setupMock() |
||||
result, err := toConversationMessage(ctx, bizID, cid, userID, roundID, sectionID, tt.messageType, tt.msg) |
||||
if tt.expectErr { |
||||
assert.Error(t, err) |
||||
} else { |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, tt.expected, result) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_toSchemaMessage(t *testing.T) { |
||||
ctx := context.Background() |
||||
ctrl := gomock.NewController(t) |
||||
defer ctrl.Finish() |
||||
|
||||
mockUpload := uploadmock.NewMockUploader(ctrl) |
||||
crossupload.SetDefaultSVC(mockUpload) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
msg *workflow.EnterMessage |
||||
setupMock func() |
||||
expected *schema.Message |
||||
expectErr bool |
||||
}{ |
||||
{ |
||||
name: "content type text", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "text", |
||||
Content: "hello", |
||||
}, |
||||
setupMock: func() {}, |
||||
expected: &schema.Message{ |
||||
Role: schema.User, |
||||
Content: "hello", |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with text", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "text", "text": "hello"}]`, |
||||
}, |
||||
setupMock: func() {}, |
||||
expected: &schema.Message{ |
||||
Role: schema.User, |
||||
MultiContent: []schema.ChatMessagePart{ |
||||
{Type: schema.ChatMessagePartTypeText, Text: "hello"}, |
||||
}, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with image", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "image", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{ |
||||
File: &uploadentity.File{Url: "https://example.com/image.png"}, |
||||
}, nil) |
||||
}, |
||||
expected: &schema.Message{ |
||||
Role: schema.User, |
||||
MultiContent: []schema.ChatMessagePart{ |
||||
{ |
||||
Type: schema.ChatMessagePartTypeImageURL, |
||||
ImageURL: &schema.ChatMessageImageURL{URL: "https://example.com/image.png"}, |
||||
}, |
||||
}, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "content type object_string with various file types", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "1"}, {"type": "audio", "file_id": "2"}, {"type": "video", "file_id": "3"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 1}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/file"}}, nil) |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 2}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/audio"}}, nil) |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 3}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/video"}}, nil) |
||||
}, |
||||
expected: &schema.Message{ |
||||
Role: schema.User, |
||||
MultiContent: []schema.ChatMessagePart{ |
||||
{Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URL: "https://example.com/file"}}, |
||||
{Type: schema.ChatMessagePartTypeAudioURL, AudioURL: &schema.ChatMessageAudioURL{URL: "https://example.com/audio"}}, |
||||
{Type: schema.ChatMessagePartTypeVideoURL, VideoURL: &schema.ChatMessageVideoURL{URL: "https://example.com/video"}}, |
||||
}, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "get file error", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error")) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "file not found", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "file", "file_id": "123"}]`, |
||||
}, |
||||
setupMock: func() { |
||||
mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{}, nil) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid content type", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "invalid", |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid json", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: "invalid-json", |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid input type", |
||||
msg: &workflow.EnterMessage{ |
||||
ContentType: "object_string", |
||||
Content: `[{"type": "invalid"}]`, |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
tt.setupMock() |
||||
result, err := toSchemaMessage(ctx, tt.msg) |
||||
if tt.expectErr { |
||||
assert.Error(t, err) |
||||
} else { |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, tt.expected, result) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_makeChatFlowHistoryMessages(t *testing.T) { |
||||
ctx := context.Background() |
||||
ctrl := gomock.NewController(t) |
||||
defer ctrl.Finish() |
||||
|
||||
mockAgentRun := agentrunmock.NewMockAgentRun(ctrl) |
||||
crossagentrun.SetDefaultSVC(mockAgentRun) |
||||
mockUpload := uploadmock.NewMockUploader(ctrl) |
||||
crossupload.SetDefaultSVC(mockUpload) |
||||
|
||||
bizID, conversationID, userID, sectionID, connectorID := int64(2), int64(1), int64(3), int64(4), int64(5) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
messages []*workflow.EnterMessage |
||||
setupMock func() |
||||
expected []*messageentity.Message |
||||
expectErr bool |
||||
}{ |
||||
{ |
||||
name: "empty messages", |
||||
messages: []*workflow.EnterMessage{}, |
||||
setupMock: func() {}, |
||||
expected: []*messageentity.Message{}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "one user message", |
||||
messages: []*workflow.EnterMessage{ |
||||
{Role: "user", ContentType: "text", Content: "hello"}, |
||||
}, |
||||
setupMock: func() { |
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil).Times(1) |
||||
}, |
||||
expected: []*messageentity.Message{ |
||||
{ |
||||
Role: schema.User, |
||||
ConversationID: conversationID, |
||||
AgentID: bizID, |
||||
RunID: 100, |
||||
Content: "hello", |
||||
ContentType: messageentity.ContentTypeText, |
||||
MessageType: messageentity.MessageTypeQuestion, |
||||
UserID: strconv.FormatInt(userID, 10), |
||||
SectionID: sectionID, |
||||
}, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "user and assistant message", |
||||
messages: []*workflow.EnterMessage{ |
||||
{Role: "user", ContentType: "text", Content: "hello"}, |
||||
{Role: "assistant", ContentType: "text", Content: "hi"}, |
||||
}, |
||||
setupMock: func() { |
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil).Times(1) |
||||
}, |
||||
expected: []*messageentity.Message{ |
||||
{ |
||||
Role: schema.User, |
||||
ConversationID: conversationID, |
||||
AgentID: bizID, |
||||
RunID: 100, |
||||
Content: "hello", |
||||
ContentType: messageentity.ContentTypeText, |
||||
MessageType: messageentity.MessageTypeQuestion, |
||||
UserID: strconv.FormatInt(userID, 10), |
||||
SectionID: sectionID, |
||||
}, |
||||
{ |
||||
Role: schema.User, |
||||
ConversationID: conversationID, |
||||
AgentID: bizID, |
||||
RunID: 100, |
||||
Content: "hi", |
||||
ContentType: messageentity.ContentTypeText, |
||||
MessageType: messageentity.MessageTypeAnswer, |
||||
UserID: strconv.FormatInt(userID, 10), |
||||
SectionID: sectionID, |
||||
}, |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "only assistant message", |
||||
messages: []*workflow.EnterMessage{ |
||||
{Role: "assistant", ContentType: "text", Content: "hi"}, |
||||
}, |
||||
setupMock: func() {}, |
||||
expected: []*messageentity.Message{}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "create run record error", |
||||
messages: []*workflow.EnterMessage{ |
||||
{Role: "user", ContentType: "text", Content: "hello"}, |
||||
}, |
||||
setupMock: func() { |
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil, errors.New("db error")) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "invalid role", |
||||
messages: []*workflow.EnterMessage{ |
||||
{Role: "system", ContentType: "text", Content: "hello"}, |
||||
}, |
||||
setupMock: func() {}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "toConversationMessage error", |
||||
messages: []*workflow.EnterMessage{ |
||||
{Role: "user", ContentType: "invalid", Content: "hello"}, |
||||
}, |
||||
setupMock: func() { |
||||
mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
tt.setupMock() |
||||
result, err := makeChatFlowHistoryMessages(ctx, bizID, conversationID, userID, sectionID, connectorID, tt.messages) |
||||
if tt.expectErr { |
||||
assert.Error(t, err) |
||||
} else { |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, tt.expected, result) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,73 @@ |
||||
/* |
||||
* Copyright 2025 coze-dev Authors |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: upload.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination uploadmock/upload_mock.go --package uploadmock -source upload.go
|
||||
//
|
||||
|
||||
// Package uploadmock is a generated GoMock package.
|
||||
package uploadmock |
||||
|
||||
import ( |
||||
context "context" |
||||
reflect "reflect" |
||||
|
||||
service "github.com/coze-dev/coze-studio/backend/domain/upload/service" |
||||
gomock "go.uber.org/mock/gomock" |
||||
) |
||||
|
||||
// MockUploader is a mock of Uploader interface.
|
||||
type MockUploader struct { |
||||
ctrl *gomock.Controller |
||||
recorder *MockUploaderMockRecorder |
||||
isgomock struct{} |
||||
} |
||||
|
||||
// MockUploaderMockRecorder is the mock recorder for MockUploader.
|
||||
type MockUploaderMockRecorder struct { |
||||
mock *MockUploader |
||||
} |
||||
|
||||
// NewMockUploader creates a new mock instance.
|
||||
func NewMockUploader(ctrl *gomock.Controller) *MockUploader { |
||||
mock := &MockUploader{ctrl: ctrl} |
||||
mock.recorder = &MockUploaderMockRecorder{mock} |
||||
return mock |
||||
} |
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockUploader) EXPECT() *MockUploaderMockRecorder { |
||||
return m.recorder |
||||
} |
||||
|
||||
// GetFile mocks base method.
|
||||
func (m *MockUploader) GetFile(ctx context.Context, req *service.GetFileRequest) (*service.GetFileResponse, error) { |
||||
m.ctrl.T.Helper() |
||||
ret := m.ctrl.Call(m, "GetFile", ctx, req) |
||||
ret0, _ := ret[0].(*service.GetFileResponse) |
||||
ret1, _ := ret[1].(error) |
||||
return ret0, ret1 |
||||
} |
||||
|
||||
// GetFile indicates an expected call of GetFile.
|
||||
func (mr *MockUploaderMockRecorder) GetFile(ctx, req any) *gomock.Call { |
||||
mr.mock.ctrl.T.Helper() |
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFile", reflect.TypeOf((*MockUploader)(nil).GetFile), ctx, req) |
||||
} |
@ -0,0 +1,275 @@ |
||||
{ |
||||
"nodes": [ |
||||
{ |
||||
"id": "100001", |
||||
"type": "1", |
||||
"meta": { |
||||
"position": { |
||||
"x": 180, |
||||
"y": 79.2 |
||||
} |
||||
}, |
||||
"data": { |
||||
"outputs": [ |
||||
{ |
||||
"type": "string", |
||||
"name": "USER_INPUT", |
||||
"required": false |
||||
}, |
||||
{ |
||||
"type": "string", |
||||
"name": "CONVERSATION_NAME", |
||||
"required": false, |
||||
"description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", |
||||
"defaultValue": "dhl" |
||||
} |
||||
], |
||||
"nodeMeta": { |
||||
"title": "开始", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", |
||||
"description": "工作流的起始节点,用于设定启动工作流需要的信息", |
||||
"subTitle": "" |
||||
}, |
||||
"trigger_parameters": [] |
||||
} |
||||
}, |
||||
{ |
||||
"id": "900001", |
||||
"type": "2", |
||||
"meta": { |
||||
"position": { |
||||
"x": 2020, |
||||
"y": 66.2 |
||||
} |
||||
}, |
||||
"data": { |
||||
"nodeMeta": { |
||||
"title": "结束", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", |
||||
"description": "工作流的最终节点,用于返回工作流运行后的结果信息", |
||||
"subTitle": "" |
||||
}, |
||||
"inputs": { |
||||
"terminatePlan": "useAnswerContent", |
||||
"streamingOutput": true, |
||||
"inputParameters": [ |
||||
{ |
||||
"name": "output", |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"type": "ref", |
||||
"content": { |
||||
"source": "block-output", |
||||
"blockID": "142077", |
||||
"name": "optionContent" |
||||
}, |
||||
"rawMeta": { |
||||
"type": 1 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"content": { |
||||
"type": "string", |
||||
"value": { |
||||
"type": "literal", |
||||
"content": "{{output}}" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
{ |
||||
"id": "190196", |
||||
"type": "30", |
||||
"meta": { |
||||
"position": { |
||||
"x": 640, |
||||
"y": 78.5 |
||||
} |
||||
}, |
||||
"data": { |
||||
"outputs": [ |
||||
{ |
||||
"type": "string", |
||||
"name": "input", |
||||
"required": true |
||||
} |
||||
], |
||||
"nodeMeta": { |
||||
"title": "输入", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Input-v2.jpg", |
||||
"description": "支持中间过程的信息输入", |
||||
"mainColor": "#5C62FF", |
||||
"subTitle": "输入" |
||||
}, |
||||
"inputs": { |
||||
"outputSchema": "[{\"type\":\"string\",\"name\":\"input\",\"required\":true}]" |
||||
} |
||||
} |
||||
}, |
||||
{ |
||||
"id": "133775", |
||||
"type": "18", |
||||
"meta": { |
||||
"position": { |
||||
"x": 1100, |
||||
"y": 39 |
||||
} |
||||
}, |
||||
"data": { |
||||
"inputs": { |
||||
"llmParam": { |
||||
"modelType": 1001, |
||||
"modelName": "Doubao-Seed-1.6", |
||||
"generationDiversity": "balance", |
||||
"temperature": 0.8, |
||||
"maxTokens": 4096, |
||||
"topP": 0.7, |
||||
"responseFormat": 2, |
||||
"systemPrompt": "" |
||||
}, |
||||
"inputParameters": [], |
||||
"extra_output": false, |
||||
"answer_type": "text", |
||||
"option_type": "static", |
||||
"dynamic_option": { |
||||
"type": "string", |
||||
"value": { |
||||
"type": "ref", |
||||
"content": { |
||||
"source": "block-output", |
||||
"blockID": "", |
||||
"name": "" |
||||
} |
||||
} |
||||
}, |
||||
"question": "你好", |
||||
"options": [ |
||||
{ |
||||
"name": "" |
||||
}, |
||||
{ |
||||
"name": "" |
||||
} |
||||
], |
||||
"limit": 3 |
||||
}, |
||||
"nodeMeta": { |
||||
"title": "问答", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Direct-Question-v2.jpg", |
||||
"description": "支持中间向用户提问问题,支持预置选项提问和开放式问题提问两种方式", |
||||
"mainColor": "#3071F2", |
||||
"subTitle": "问答" |
||||
}, |
||||
"outputs": [ |
||||
{ |
||||
"type": "string", |
||||
"name": "USER_RESPONSE", |
||||
"required": true, |
||||
"description": "用户本轮对话输入内容" |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
{ |
||||
"id": "142077", |
||||
"type": "18", |
||||
"meta": { |
||||
"position": { |
||||
"x": 1560, |
||||
"y": 0 |
||||
} |
||||
}, |
||||
"data": { |
||||
"inputs": { |
||||
"llmParam": { |
||||
"modelType": 1001, |
||||
"modelName": "Doubao-Seed-1.6", |
||||
"generationDiversity": "balance", |
||||
"temperature": 0.8, |
||||
"maxTokens": 4096, |
||||
"topP": 0.7, |
||||
"responseFormat": 2, |
||||
"systemPrompt": "" |
||||
}, |
||||
"inputParameters": [], |
||||
"extra_output": false, |
||||
"answer_type": "option", |
||||
"option_type": "static", |
||||
"dynamic_option": { |
||||
"type": "string", |
||||
"value": { |
||||
"type": "ref", |
||||
"content": { |
||||
"source": "block-output", |
||||
"blockID": "", |
||||
"name": "" |
||||
} |
||||
} |
||||
}, |
||||
"question": "请选择", |
||||
"options": [ |
||||
{ |
||||
"name": "A" |
||||
}, |
||||
{ |
||||
"name": "B" |
||||
} |
||||
], |
||||
"limit": 3 |
||||
}, |
||||
"nodeMeta": { |
||||
"title": "问答_1", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Direct-Question-v2.jpg", |
||||
"description": "支持中间向用户提问问题,支持预置选项提问和开放式问题提问两种方式", |
||||
"mainColor": "#3071F2", |
||||
"subTitle": "问答" |
||||
}, |
||||
"outputs": [ |
||||
{ |
||||
"type": "string", |
||||
"name": "optionId", |
||||
"required": false |
||||
}, |
||||
{ |
||||
"type": "string", |
||||
"name": "optionContent", |
||||
"required": false |
||||
} |
||||
] |
||||
} |
||||
} |
||||
], |
||||
"edges": [ |
||||
{ |
||||
"sourceNodeID": "100001", |
||||
"targetNodeID": "190196" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "142077", |
||||
"targetNodeID": "900001", |
||||
"sourcePortID": "branch_0" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "142077", |
||||
"targetNodeID": "900001", |
||||
"sourcePortID": "branch_1" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "142077", |
||||
"targetNodeID": "900001", |
||||
"sourcePortID": "default" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "190196", |
||||
"targetNodeID": "133775" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "133775", |
||||
"targetNodeID": "142077" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,397 @@ |
||||
{ |
||||
"nodes": [ |
||||
{ |
||||
"blocks": [], |
||||
"data": { |
||||
"nodeMeta": { |
||||
"description": "工作流的起始节点,用于设定启动工作流需要的信息", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", |
||||
"subTitle": "", |
||||
"title": "开始" |
||||
}, |
||||
"outputs": [ |
||||
{ |
||||
"name": "USER_INPUT", |
||||
"required": false, |
||||
"type": "string" |
||||
}, |
||||
{ |
||||
"defaultValue": "Default", |
||||
"description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", |
||||
"name": "CONVERSATION_NAME", |
||||
"required": false, |
||||
"type": "string" |
||||
} |
||||
], |
||||
"trigger_parameters": [] |
||||
}, |
||||
"edges": null, |
||||
"id": "100001", |
||||
"meta": { |
||||
"position": { |
||||
"x": 0, |
||||
"y": 0 |
||||
} |
||||
}, |
||||
"type": "1" |
||||
}, |
||||
{ |
||||
"blocks": [], |
||||
"data": { |
||||
"inputs": { |
||||
"content": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "{{output}}", |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"inputParameters": [ |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": { |
||||
"blockID": "123887", |
||||
"name": "output", |
||||
"source": "block-output" |
||||
}, |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "ref" |
||||
} |
||||
}, |
||||
"name": "output" |
||||
} |
||||
], |
||||
"streamingOutput": true, |
||||
"terminatePlan": "useAnswerContent" |
||||
}, |
||||
"nodeMeta": { |
||||
"description": "工作流的最终节点,用于返回工作流运行后的结果信息", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", |
||||
"subTitle": "", |
||||
"title": "结束" |
||||
} |
||||
}, |
||||
"edges": null, |
||||
"id": "900001", |
||||
"meta": { |
||||
"position": { |
||||
"x": 926, |
||||
"y": -13 |
||||
} |
||||
}, |
||||
"type": "2" |
||||
}, |
||||
{ |
||||
"blocks": [], |
||||
"data": { |
||||
"inputs": { |
||||
"fcParamVar": { |
||||
"knowledgeFCParam": {} |
||||
}, |
||||
"inputParameters": [ |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": { |
||||
"blockID": "100001", |
||||
"name": "USER_INPUT", |
||||
"source": "block-output" |
||||
}, |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "ref" |
||||
} |
||||
}, |
||||
"name": "input" |
||||
} |
||||
], |
||||
"llmParam": [ |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "1737521813", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "modelType" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "豆包·1.5·Pro·32k", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "modleName" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "balance", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "generationDiversity" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "float", |
||||
"value": { |
||||
"content": "0.8", |
||||
"rawMeta": { |
||||
"type": 4 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "temperature" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "4096", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "maxTokens" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "spCurrentTime" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "spAntiLeak" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "prefixCache" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "2", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "responseFormat" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "{{input}}", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "prompt" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "enableChatHistory" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "3", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "chatHistoryRound" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "systemPrompt" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "stableSystemPrompt" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "canContinue" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "loopPromptVersion" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "loopPromptName" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "loopPromptId" |
||||
} |
||||
], |
||||
"settingOnError": { |
||||
"processType": 1, |
||||
"retryTimes": 0, |
||||
"timeoutMs": 180000 |
||||
} |
||||
}, |
||||
"nodeMeta": { |
||||
"description": "调用大语言模型,使用变量和提示词生成回复", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-LLM-v2.jpg", |
||||
"mainColor": "#5C62FF", |
||||
"subTitle": "大模型", |
||||
"title": "大模型" |
||||
}, |
||||
"outputs": [ |
||||
{ |
||||
"name": "output", |
||||
"type": "string" |
||||
} |
||||
], |
||||
"version": "3" |
||||
}, |
||||
"edges": null, |
||||
"id": "123887", |
||||
"meta": { |
||||
"position": { |
||||
"x": 463, |
||||
"y": -39 |
||||
} |
||||
}, |
||||
"type": "3" |
||||
} |
||||
], |
||||
"edges": [ |
||||
{ |
||||
"sourceNodeID": "100001", |
||||
"targetNodeID": "123887", |
||||
"sourcePortID": "" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "123887", |
||||
"targetNodeID": "900001", |
||||
"sourcePortID": "" |
||||
} |
||||
], |
||||
"versions": { |
||||
"loop": "v2" |
||||
} |
||||
} |
@ -0,0 +1,397 @@ |
||||
{ |
||||
"nodes": [ |
||||
{ |
||||
"blocks": [], |
||||
"data": { |
||||
"nodeMeta": { |
||||
"description": "工作流的起始节点,用于设定启动工作流需要的信息", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", |
||||
"subTitle": "", |
||||
"title": "开始" |
||||
}, |
||||
"outputs": [ |
||||
{ |
||||
"name": "USER_INPUT", |
||||
"required": false, |
||||
"type": "string" |
||||
}, |
||||
{ |
||||
"defaultValue": "Default", |
||||
"description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", |
||||
"name": "CONVERSATION_NAME", |
||||
"required": false, |
||||
"type": "string" |
||||
} |
||||
], |
||||
"trigger_parameters": [] |
||||
}, |
||||
"edges": null, |
||||
"id": "100001", |
||||
"meta": { |
||||
"position": { |
||||
"x": 0, |
||||
"y": 0 |
||||
} |
||||
}, |
||||
"type": "1" |
||||
}, |
||||
{ |
||||
"blocks": [], |
||||
"data": { |
||||
"inputs": { |
||||
"content": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "{{output}}", |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"inputParameters": [ |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": { |
||||
"blockID": "123887", |
||||
"name": "output", |
||||
"source": "block-output" |
||||
}, |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "ref" |
||||
} |
||||
}, |
||||
"name": "output" |
||||
} |
||||
], |
||||
"streamingOutput": true, |
||||
"terminatePlan": "useAnswerContent" |
||||
}, |
||||
"nodeMeta": { |
||||
"description": "工作流的最终节点,用于返回工作流运行后的结果信息", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", |
||||
"subTitle": "", |
||||
"title": "结束" |
||||
} |
||||
}, |
||||
"edges": null, |
||||
"id": "900001", |
||||
"meta": { |
||||
"position": { |
||||
"x": 926, |
||||
"y": -13 |
||||
} |
||||
}, |
||||
"type": "2" |
||||
}, |
||||
{ |
||||
"blocks": [], |
||||
"data": { |
||||
"inputs": { |
||||
"fcParamVar": { |
||||
"knowledgeFCParam": {} |
||||
}, |
||||
"inputParameters": [ |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": { |
||||
"blockID": "100001", |
||||
"name": "USER_INPUT", |
||||
"source": "block-output" |
||||
}, |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "ref" |
||||
} |
||||
}, |
||||
"name": "input" |
||||
} |
||||
], |
||||
"llmParam": [ |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "1737521813", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "modelType" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "豆包·1.5·Pro·32k", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "modleName" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "balance", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "generationDiversity" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "float", |
||||
"value": { |
||||
"content": "0.8", |
||||
"rawMeta": { |
||||
"type": 4 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "temperature" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "4096", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "maxTokens" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "spCurrentTime" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "spAntiLeak" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "prefixCache" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "2", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "responseFormat" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "{{input}}", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "prompt" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": true, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "enableChatHistory" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "integer", |
||||
"value": { |
||||
"content": "3", |
||||
"rawMeta": { |
||||
"type": 2 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "chatHistoryRound" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "systemPrompt" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "stableSystemPrompt" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "boolean", |
||||
"value": { |
||||
"content": false, |
||||
"rawMeta": { |
||||
"type": 3 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "canContinue" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "loopPromptVersion" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "loopPromptName" |
||||
}, |
||||
{ |
||||
"input": { |
||||
"type": "string", |
||||
"value": { |
||||
"content": "", |
||||
"rawMeta": { |
||||
"type": 1 |
||||
}, |
||||
"type": "literal" |
||||
} |
||||
}, |
||||
"name": "loopPromptId" |
||||
} |
||||
], |
||||
"settingOnError": { |
||||
"processType": 1, |
||||
"retryTimes": 0, |
||||
"timeoutMs": 180000 |
||||
} |
||||
}, |
||||
"nodeMeta": { |
||||
"description": "调用大语言模型,使用变量和提示词生成回复", |
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-LLM-v2.jpg", |
||||
"mainColor": "#5C62FF", |
||||
"subTitle": "大模型", |
||||
"title": "大模型" |
||||
}, |
||||
"outputs": [ |
||||
{ |
||||
"name": "output", |
||||
"type": "string" |
||||
} |
||||
], |
||||
"version": "3" |
||||
}, |
||||
"edges": null, |
||||
"id": "123887", |
||||
"meta": { |
||||
"position": { |
||||
"x": 463, |
||||
"y": -39 |
||||
} |
||||
}, |
||||
"type": "3" |
||||
} |
||||
], |
||||
"edges": [ |
||||
{ |
||||
"sourceNodeID": "100001", |
||||
"targetNodeID": "123887", |
||||
"sourcePortID": "" |
||||
}, |
||||
{ |
||||
"sourceNodeID": "123887", |
||||
"targetNodeID": "900001", |
||||
"sourcePortID": "" |
||||
} |
||||
], |
||||
"versions": { |
||||
"loop": "v2" |
||||
} |
||||
} |
@ -0,0 +1,286 @@ |
||||
/* |
||||
* Copyright 2025 coze-dev Authors |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
package service |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"testing" |
||||
|
||||
"github.com/cloudwego/eino/schema" |
||||
"github.com/stretchr/testify/assert" |
||||
"go.uber.org/mock/gomock" |
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" |
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" |
||||
messagemock "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message/messagemock" |
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity" |
||||
mock_workflow "github.com/coze-dev/coze-studio/backend/internal/mock/domain/workflow" |
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" |
||||
) |
||||
|
||||
func TestImpl_handleHistory(t *testing.T) { |
||||
ctx := context.Background() |
||||
ctrl := gomock.NewController(t, gomock.WithOverridableExpectations()) |
||||
defer ctrl.Finish() |
||||
|
||||
// Setup for cross-domain service mock
|
||||
mockMessage := messagemock.NewMockMessage(ctrl) |
||||
crossmessage.SetDefaultSVC(mockMessage) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
setupMock func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) |
||||
config *workflowModel.ExecuteConfig |
||||
input map[string]any |
||||
historyRounds int64 |
||||
shouldFetch bool |
||||
expectErr bool |
||||
expectedHistory []*crossmessage.WfMessage |
||||
expectedSchemaHistory []*schema.Message |
||||
}{ |
||||
{ |
||||
name: "historyRounds is zero", |
||||
historyRounds: 0, |
||||
shouldFetch: true, |
||||
config: &workflowModel.ExecuteConfig{}, |
||||
setupMock: func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) { |
||||
}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "shouldFetch is false", |
||||
historyRounds: 5, |
||||
shouldFetch: false, |
||||
config: &workflowModel.ExecuteConfig{ |
||||
AppID: ptr.Of(int64(1)), |
||||
ConversationID: ptr.Of(int64(100)), |
||||
SectionID: ptr.Of(int64(101)), |
||||
}, |
||||
setupMock: func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) { |
||||
msgSvc.EXPECT().GetLatestRunIDs(gomock.Any(), gomock.Any()).Return([]int64{1, 2}, nil).AnyTimes() |
||||
msgSvc.EXPECT().GetMessagesByRunIDs(gomock.Any(), gomock.Any()).Return(&crossmessage.GetMessagesByRunIDsResponse{ |
||||
Messages: []*crossmessage.WfMessage{{ID: 1}}, |
||||
SchemaMessages: []*schema.Message{{ |
||||
Role: schema.User, |
||||
Content: "123", |
||||
}}, |
||||
}, nil).AnyTimes() |
||||
}, |
||||
expectErr: false, |
||||
expectedHistory: []*crossmessage.WfMessage{{ID: 1}}, |
||||
expectedSchemaHistory: []*schema.Message{{ |
||||
Role: schema.User, |
||||
Content: "123", |
||||
}}, |
||||
}, |
||||
{ |
||||
name: "fetch conversation by name - conversation exists", |
||||
historyRounds: 3, |
||||
shouldFetch: true, |
||||
config: &workflowModel.ExecuteConfig{AppID: ptr.Of(int64(1))}, |
||||
input: map[string]any{"CONVERSATION_NAME": "test-conv"}, |
||||
setupMock: func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) { |
||||
service.EXPECT().GetOrCreateConversation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "test-conv").Return(int64(200), int64(201), nil).AnyTimes() |
||||
msgSvc.EXPECT().GetLatestRunIDs(gomock.Any(), gomock.Any()).Return([]int64{3, 4}, nil).AnyTimes() |
||||
msgSvc.EXPECT().GetMessagesByRunIDs(gomock.Any(), gomock.Any()).Return(&crossmessage.GetMessagesByRunIDsResponse{ |
||||
Messages: []*crossmessage.WfMessage{{ID: 2}}, |
||||
SchemaMessages: []*schema.Message{{ |
||||
Role: schema.Assistant, |
||||
Content: "123", |
||||
}}, |
||||
}, nil).AnyTimes() |
||||
repo.EXPECT().GetConversationTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(&entity.ConversationTemplate{ |
||||
TemplateID: int64(202), |
||||
SpaceID: int64(203), |
||||
AppID: int64(204), |
||||
}, true, nil).AnyTimes() |
||||
repo.EXPECT().GetOrCreateStaticConversation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(int64(205), int64(206), true, nil).AnyTimes() |
||||
}, |
||||
expectErr: false, |
||||
expectedHistory: []*crossmessage.WfMessage{{ID: 2}}, |
||||
expectedSchemaHistory: []*schema.Message{{ |
||||
Role: schema.Assistant, |
||||
Content: "123", |
||||
}}, |
||||
}, |
||||
{ |
||||
name: "fetch conversation by name - conversation not exists", |
||||
historyRounds: 3, |
||||
shouldFetch: true, |
||||
config: &workflowModel.ExecuteConfig{AgentID: ptr.Of(int64(2))}, |
||||
input: map[string]any{"CONVERSATION_NAME": "new-conv"}, |
||||
setupMock: func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) { |
||||
service.EXPECT().GetOrCreateConversation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "new-conv").Return(int64(300), int64(301), nil).AnyTimes() |
||||
msgSvc.EXPECT().GetLatestRunIDs(gomock.Any(), gomock.Any()).Return([]int64{5, 6}, nil).AnyTimes() |
||||
msgSvc.EXPECT().GetMessagesByRunIDs(gomock.Any(), gomock.Any()).Return(&crossmessage.GetMessagesByRunIDsResponse{ |
||||
Messages: []*crossmessage.WfMessage{{ID: 3}}, |
||||
}, nil).AnyTimes() |
||||
repo.EXPECT().GetConversationTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(&entity.ConversationTemplate{ |
||||
TemplateID: int64(202), |
||||
SpaceID: int64(203), |
||||
AppID: int64(204), |
||||
}, false, nil).AnyTimes() |
||||
repo.EXPECT().GetOrCreateDynamicConversation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(int64(205), int64(206), true, nil).AnyTimes() |
||||
}, |
||||
expectErr: false, |
||||
expectedHistory: []*crossmessage.WfMessage{{ID: 3}}, |
||||
}, |
||||
{ |
||||
name: "input with wrong type for conversation name", |
||||
historyRounds: 5, |
||||
shouldFetch: true, |
||||
config: &workflowModel.ExecuteConfig{AppID: ptr.Of(int64(1))}, |
||||
input: map[string]any{"CONVERSATION_NAME": 12345}, |
||||
setupMock: func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) { |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "GetOrCreateConversation returns error", |
||||
historyRounds: 5, |
||||
shouldFetch: true, |
||||
config: &workflowModel.ExecuteConfig{AppID: ptr.Of(int64(1))}, |
||||
input: map[string]any{"CONVERSATION_NAME": "fail-conv"}, |
||||
setupMock: func(service *mock_workflow.MockService, msgSvc *messagemock.MockMessage, repo *mock_workflow.MockRepository) { |
||||
service.EXPECT().GetOrCreateConversation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), "fail-conv").Return(int64(0), int64(0), errors.New("db error")).AnyTimes() |
||||
repo.EXPECT().GetConversationTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(&entity.ConversationTemplate{ |
||||
TemplateID: int64(202), |
||||
SpaceID: int64(203), |
||||
AppID: int64(204), |
||||
}, false, nil).AnyTimes() |
||||
repo.EXPECT().GetOrCreateDynamicConversation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(int64(205), int64(206), true, errors.New("db error")).AnyTimes() |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
mockService := mock_workflow.NewMockService(ctrl) |
||||
mockRepo := mock_workflow.NewMockRepository(ctrl) |
||||
testImpl := &impl{repo: mockRepo, conversationImpl: &conversationImpl{repo: mockRepo}} |
||||
|
||||
tt.setupMock(mockService, mockMessage, mockRepo) |
||||
|
||||
err := testImpl.handleHistory(ctx, tt.config, tt.input, tt.historyRounds, tt.shouldFetch) |
||||
|
||||
if tt.expectErr { |
||||
assert.Error(t, err) |
||||
} else { |
||||
assert.NoError(t, err) |
||||
if tt.expectedHistory != nil { |
||||
assert.Equal(t, tt.expectedHistory, tt.config.ConversationHistory) |
||||
} else if tt.historyRounds == 0 { |
||||
assert.Nil(t, tt.config.ConversationHistory) |
||||
} else if tt.expectedSchemaHistory != nil { |
||||
assert.Equal(t, tt.expectedSchemaHistory, tt.config.ConversationHistorySchemaMessages) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestImpl_prefetchChatHistory(t *testing.T) { |
||||
ctx := context.Background() |
||||
ctrl := gomock.NewController(t, gomock.WithOverridableExpectations()) |
||||
defer ctrl.Finish() |
||||
|
||||
mockMessage := messagemock.NewMockMessage(ctrl) |
||||
crossmessage.SetDefaultSVC(mockMessage) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
setupMock func(msgSvc *messagemock.MockMessage) |
||||
config workflowModel.ExecuteConfig |
||||
historyRounds int64 |
||||
expectErr bool |
||||
}{ |
||||
{ |
||||
name: "SectionID is nil", |
||||
config: workflowModel.ExecuteConfig{ |
||||
ConversationID: ptr.Of(int64(100)), |
||||
AppID: ptr.Of(int64(1)), |
||||
}, |
||||
historyRounds: 5, |
||||
setupMock: func(msgSvc *messagemock.MockMessage) {}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "ConversationID is nil", |
||||
config: workflowModel.ExecuteConfig{ |
||||
SectionID: ptr.Of(int64(101)), |
||||
AppID: ptr.Of(int64(1)), |
||||
}, |
||||
historyRounds: 5, |
||||
setupMock: func(msgSvc *messagemock.MockMessage) {}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "AppID and AgentID are both nil", |
||||
config: workflowModel.ExecuteConfig{ |
||||
ConversationID: ptr.Of(int64(100)), |
||||
SectionID: ptr.Of(int64(101)), |
||||
}, |
||||
historyRounds: 5, |
||||
setupMock: func(msgSvc *messagemock.MockMessage) {}, |
||||
expectErr: false, |
||||
}, |
||||
{ |
||||
name: "GetLatestRunIDs returns error", |
||||
config: workflowModel.ExecuteConfig{ |
||||
AppID: ptr.Of(int64(1)), |
||||
ConversationID: ptr.Of(int64(100)), |
||||
SectionID: ptr.Of(int64(101)), |
||||
}, |
||||
historyRounds: 5, |
||||
setupMock: func(msgSvc *messagemock.MockMessage) { |
||||
msgSvc.EXPECT().GetLatestRunIDs(gomock.Any(), gomock.Any()).Return(nil, errors.New("db error")) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
{ |
||||
name: "GetMessagesByRunIDs returns error", |
||||
config: workflowModel.ExecuteConfig{ |
||||
AppID: ptr.Of(int64(1)), |
||||
ConversationID: ptr.Of(int64(100)), |
||||
SectionID: ptr.Of(int64(101)), |
||||
}, |
||||
historyRounds: 5, |
||||
setupMock: func(msgSvc *messagemock.MockMessage) { |
||||
msgSvc.EXPECT().GetLatestRunIDs(gomock.Any(), gomock.Any()).Return([]int64{1, 2, 3}, nil) |
||||
msgSvc.EXPECT().GetMessagesByRunIDs(gomock.Any(), gomock.Any()).Return(nil, errors.New("db error")) |
||||
}, |
||||
expectErr: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
testImpl := &impl{} |
||||
tt.setupMock(mockMessage) |
||||
|
||||
_, _, err := testImpl.prefetchChatHistory(ctx, tt.config, tt.historyRounds) |
||||
|
||||
if tt.expectErr { |
||||
assert.Error(t, err) |
||||
} else { |
||||
assert.NoError(t, err) |
||||
} |
||||
}) |
||||
} |
||||
} |
Loading…
Reference in new issue