Compare commits
18 Commits
feat/addit
...
fix/coderu
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e42cf3b2f | |||
| 315a93f8c0 | |||
| 091e7a69c4 | |||
| c7e027e065 | |||
| 62f12d6b57 | |||
| c950318a94 | |||
| 61e8331a54 | |||
| ffec252d9e | |||
| 6790abe8de | |||
| b7d8e0cd4f | |||
| 764ffc2213 | |||
| 1d9fa80972 | |||
| 9a0b45eccf | |||
| 0fbc34e5c5 | |||
| 4bfce5a8cb | |||
| 4416127d47 | |||
| 878a7ef15b | |||
| 77ebc297f9 |
1
.gitignore
vendored
@ -60,3 +60,4 @@ values-dev.yaml
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.coda/
|
||||
|
||||
@ -228,6 +228,7 @@ func newWfTestRunner(t *testing.T) *wfTestRunner {
|
||||
h.POST("/api/workflow_api/chat_flow_role/delete", DeleteChatFlowRole)
|
||||
h.POST("/api/workflow_api/chat_flow_role/create", CreateChatFlowRole)
|
||||
h.GET("/api/workflow_api/chat_flow_role/get", GetChatFlowRole)
|
||||
h.POST("/v1/workflows/chat", OpenAPIChatFlowRun)
|
||||
|
||||
ctrl := gomock.NewController(t, gomock.WithOverridableExpectations())
|
||||
mockIDGen := mock.NewMockIDGenerator(ctrl)
|
||||
@ -1082,6 +1083,46 @@ func (r *wfTestRunner) openapiResume(id string, eventID string, resumeData strin
|
||||
return re
|
||||
}
|
||||
|
||||
func (r *wfTestRunner) openapiChatFlowRun(wfID string, cID, appID, botID *string, input any, additionalMessage []*workflow.EnterMessage) *sse.Reader {
|
||||
inputStr, _ := sonic.MarshalString(input)
|
||||
|
||||
req := &workflow.ChatFlowRunRequest{
|
||||
WorkflowID: wfID,
|
||||
Parameters: ptr.Of(inputStr),
|
||||
AdditionalMessages: additionalMessage,
|
||||
}
|
||||
if cID != nil {
|
||||
req.ConversationID = cID
|
||||
}
|
||||
if appID != nil {
|
||||
req.AppID = appID
|
||||
}
|
||||
if botID != nil {
|
||||
req.BotID = botID
|
||||
}
|
||||
|
||||
m, err := sonic.Marshal(req)
|
||||
assert.NoError(r.t, err)
|
||||
|
||||
c, _ := client.NewClient()
|
||||
hReq, hResp := protocol.AcquireRequest(), protocol.AcquireResponse()
|
||||
hReq.SetRequestURI("http://localhost:8888" + "/v1/workflows/chat")
|
||||
hReq.SetMethod("POST")
|
||||
hReq.SetBody(m)
|
||||
hReq.SetHeader("Content-Type", "application/json")
|
||||
err = c.Do(context.Background(), hReq, hResp)
|
||||
assert.NoError(r.t, err)
|
||||
|
||||
if hResp.StatusCode() != http.StatusOK {
|
||||
r.t.Errorf("unexpected status code: %d, body: %s", hResp.StatusCode(), string(hResp.Body()))
|
||||
}
|
||||
|
||||
re, err := sse.NewReader(hResp)
|
||||
assert.NoError(r.t, err)
|
||||
|
||||
return re
|
||||
}
|
||||
|
||||
func (r *wfTestRunner) runServer() func() {
|
||||
go func() {
|
||||
_ = r.h.Run()
|
||||
@ -2454,7 +2495,7 @@ func TestStartNodeDefaultValues(t *testing.T) {
|
||||
result, _ := r.openapiSyncRun(idStr, input)
|
||||
assert.Equal(t, result, map[string]any{
|
||||
"ts": "2025-07-09 21:43:34",
|
||||
"files": "http://imagex.fanlv.fun/tos-cn-i-1heqlfnr21/e81acc11277f421390770618e24e01ce.jpeg~tplv-1heqlfnr21-image.image?x-wf-file_name=20250317-154742.jpeg",
|
||||
"files": "http://imagex.fanlv.fun/tos-cn-i-1heqlfnr21/e81acc11277f421390770618e24e01ce.jpeg~tplv-1heqlfnr21-image.image",
|
||||
"str": "str",
|
||||
"object": map[string]any{
|
||||
"a": "1",
|
||||
@ -2478,7 +2519,7 @@ func TestStartNodeDefaultValues(t *testing.T) {
|
||||
result, _ := r.openapiSyncRun(idStr, input)
|
||||
assert.Equal(t, result, map[string]any{
|
||||
"ts": "2025-07-09 21:43:34",
|
||||
"files": "http://imagex.fanlv.fun/tos-cn-i-1heqlfnr21/e81acc11277f421390770618e24e01ce.jpeg~tplv-1heqlfnr21-image.image?x-wf-file_name=20250317-154742.jpeg",
|
||||
"files": "http://imagex.fanlv.fun/tos-cn-i-1heqlfnr21/e81acc11277f421390770618e24e01ce.jpeg~tplv-1heqlfnr21-image.image",
|
||||
"str": "str",
|
||||
"object": map[string]any{
|
||||
"a": "1",
|
||||
@ -2503,7 +2544,7 @@ func TestStartNodeDefaultValues(t *testing.T) {
|
||||
result, _ := r.openapiSyncRun(idStr, input)
|
||||
assert.Equal(t, result, map[string]any{
|
||||
"ts": "2025-07-09 21:43:34",
|
||||
"files": "http://imagex.fanlv.fun/tos-cn-i-1heqlfnr21/e81acc11277f421390770618e24e01ce.jpeg~tplv-1heqlfnr21-image.image?x-wf-file_name=20250317-154742.jpeg",
|
||||
"files": "http://imagex.fanlv.fun/tos-cn-i-1heqlfnr21/e81acc11277f421390770618e24e01ce.jpeg~tplv-1heqlfnr21-image.image",
|
||||
"str": "value",
|
||||
"object": map[string]any{
|
||||
"a": "1",
|
||||
@ -5491,7 +5532,7 @@ func TestConversationOfChatFlow(t *testing.T) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Name == "CONVERSATION_NAME" {
|
||||
if v.Name == vo.ConversationNameKey {
|
||||
v.DefaultValue = cName
|
||||
}
|
||||
startNode.Data.Outputs[idx] = v
|
||||
@ -5522,7 +5563,7 @@ func TestConversationOfChatFlow(t *testing.T) {
|
||||
for _, vAny := range node.Data.Outputs {
|
||||
v, err := vo.ParseVariable(vAny)
|
||||
assert.NoError(t, err)
|
||||
if v.Name == "CONVERSATION_NAME" {
|
||||
if v.Name == vo.ConversationNameKey {
|
||||
assert.Equal(t, v.DefaultValue, updateName)
|
||||
}
|
||||
}
|
||||
@ -5569,7 +5610,7 @@ func TestConversationOfChatFlow(t *testing.T) {
|
||||
for _, vAny := range node.Data.Outputs {
|
||||
v, err := vo.ParseVariable(vAny)
|
||||
assert.NoError(t, err)
|
||||
if v.Name == "CONVERSATION_NAME" {
|
||||
if v.Name == vo.ConversationNameKey {
|
||||
assert.Equal(t, v.DefaultValue, cName+"copy")
|
||||
}
|
||||
}
|
||||
@ -5988,3 +6029,266 @@ func TestConversationHistoryNodes(t *testing.T) {
|
||||
assert.Equal(t, []any{}, outputMap["history_list"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkflowRunWithFiles(t *testing.T) {
|
||||
mockey.PatchConvey("workflow run with files", t, func() {
|
||||
r := newWfTestRunner(t)
|
||||
defer r.closeFn()
|
||||
|
||||
r.knowledge.EXPECT().Store(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, document *knowledge.CreateDocumentRequest) (*knowledge.CreateDocumentResponse, error) {
|
||||
|
||||
assert.Equal(t, "北京旅游景点.txt", document.FileName)
|
||||
return &knowledge.CreateDocumentResponse{
|
||||
DocumentID: 1,
|
||||
FileURL: document.FileURL,
|
||||
FileName: document.FileName,
|
||||
}, nil
|
||||
}).AnyTimes()
|
||||
|
||||
runner := mockcode.NewMockRunner(r.ctrl)
|
||||
runner.EXPECT().Run(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, request *coderunner.RunRequest) (*coderunner.RunResponse, error) {
|
||||
|
||||
return &coderunner.RunResponse{
|
||||
Result: request.Params,
|
||||
}, nil
|
||||
}).AnyTimes()
|
||||
|
||||
mockey.Mock(code.GetCodeRunner).Return(runner).Build()
|
||||
|
||||
idStr := r.load("workflow_wf_file_name.json")
|
||||
r.publish(idStr, "v0.1.1", true)
|
||||
m, execID := r.openapiSyncRun(idStr, map[string]string{
|
||||
"f": "http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/27b01dd5-b0f5-4dbd-a075-a48c14162d23.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T074412Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=2f3051a0645c9ed260f7cb6c93954147ceb347a61366c9f70b98d43c299a7732&x-wf-file_name=%E5%8C%97%E4%BA%AC%E6%97%85%E6%B8%B8%E6%99%AF%E7%82%B9.txt",
|
||||
"fs": "[\"http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/85056c12-ea40-4588-a2a2-5eab56b94e4c.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T074404Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=59f3a7b6774a33de127e42878e4821635ce74e1fc29237ba03b13d67a068fedf&x-wf-file_name=%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_2025-07-02_154139_105.jpg\",\"http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/5ec9856d-0db0-44a1-9b82-43628221a928.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T074410Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=3887b0583084b0294b91e93e307c61ce3b910531d0e33a08e7c7d57de24c71ec&x-wf-file_name=20250317-154742.jpeg\"]",
|
||||
})
|
||||
assert.NotNil(t, execID)
|
||||
|
||||
assert.Equal(t, m["output"], []any{
|
||||
"http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/85056c12-ea40-4588-a2a2-5eab56b94e4c.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T074404Z&X-Amz-Expires=604800&X-Amz-Signature=59f3a7b6774a33de127e42878e4821635ce74e1fc29237ba03b13d67a068fedf&X-Amz-SignedHeaders=host",
|
||||
"http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/5ec9856d-0db0-44a1-9b82-43628221a928.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T074410Z&X-Amz-Expires=604800&X-Amz-Signature=3887b0583084b0294b91e93e307c61ce3b910531d0e33a08e7c7d57de24c71ec&X-Amz-SignedHeaders=host"})
|
||||
assert.Equal(t, m["filename"], "北京旅游景点.txt")
|
||||
fmt.Println(m, execID)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestChatFlowRun(t *testing.T) {
|
||||
mockey.PatchConvey("chat flow run", t, func() {
|
||||
r := newWfTestRunner(t)
|
||||
appworkflow.SVC.IDGenerator = r.idGen
|
||||
defer r.closeFn()
|
||||
defer r.runServer()()
|
||||
|
||||
chatModel1 := &testutil.UTChatModel{
|
||||
StreamResultProvider: func(_ int, in []*schema.Message) (*schema.StreamReader[*schema.Message], error) {
|
||||
sr := schema.StreamReaderFromArray([]*schema.Message{
|
||||
{
|
||||
Role: schema.Assistant,
|
||||
Content: "I ",
|
||||
},
|
||||
{
|
||||
Role: schema.Assistant,
|
||||
Content: "don't know.",
|
||||
},
|
||||
})
|
||||
return sr, nil
|
||||
},
|
||||
}
|
||||
r.modelManage.EXPECT().GetModel(gomock.Any(), gomock.Any()).Return(chatModel1, nil, nil).AnyTimes()
|
||||
|
||||
id := r.load("chatflow/llm_chat.json", withMode(workflow.WorkflowMode_ChatFlow))
|
||||
r.publish(id, "v0.0.1", true)
|
||||
cID := time.Now().UnixNano()
|
||||
cIDStr := strconv.FormatInt(cID, 10)
|
||||
appID := time.Now().UnixNano()
|
||||
appIDStr := strconv.FormatInt(appID, 10)
|
||||
|
||||
// Create conversation first
|
||||
r.conversation.EXPECT().CreateConversation(gomock.Any(), gomock.Any()).Return(&conventity.Conversation{
|
||||
ID: cID,
|
||||
}, nil).AnyTimes()
|
||||
idStr := r.load("conversation_manager/update_dynamic_conversation.json")
|
||||
r.publish(idStr, "v0.0.1", true)
|
||||
ret, _ := r.openapiSyncRun(idStr, map[string]string{
|
||||
"input": "v1",
|
||||
"new_name": "v2",
|
||||
}, withRunProjectID(appID))
|
||||
assert.Equal(t, map[string]any{"conversationId": strconv.FormatInt(cID, 10), "isExisted": false, "isSuccess": true}, ret["obj"])
|
||||
|
||||
msg := []*workflow.EnterMessage{
|
||||
{
|
||||
Role: "user",
|
||||
ContentType: "text",
|
||||
Content: "你好",
|
||||
},
|
||||
}
|
||||
sID := time.Now().UnixNano()
|
||||
r.conversation.EXPECT().GetByID(gomock.Any(), gomock.Any()).Return(&conventity.Conversation{
|
||||
ID: cID,
|
||||
SectionID: sID,
|
||||
}, nil).AnyTimes()
|
||||
rID := time.Now().UnixNano()
|
||||
r.agentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{
|
||||
ID: rID,
|
||||
}, nil).AnyTimes()
|
||||
mID := time.Now().Unix()
|
||||
r.message.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&message.Message{
|
||||
ID: mID,
|
||||
}, nil).AnyTimes()
|
||||
|
||||
t.Run("chat flow run in app", func(t *testing.T) {
|
||||
sseReader := r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, msg)
|
||||
err := sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("chat flow run in bot", func(t *testing.T) {
|
||||
botID := time.Now().UnixNano()
|
||||
botIDStr := strconv.FormatInt(botID, 10)
|
||||
sseReader := r.openapiChatFlowRun(id, ptr.Of(cIDStr), nil, ptr.Of(botIDStr), map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, msg)
|
||||
err := sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("chat flow run without cID", func(t *testing.T) {
|
||||
sseReader := r.openapiChatFlowRun(id, nil, ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, msg)
|
||||
err := sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("chat flow run with additional messages", func(t *testing.T) {
|
||||
additionalMsg := []*workflow.EnterMessage{
|
||||
{
|
||||
Role: "user",
|
||||
ContentType: "text",
|
||||
Content: "你好, 我叫小明",
|
||||
},
|
||||
{
|
||||
Role: "assistant",
|
||||
ContentType: "text",
|
||||
Content: "你好小明, 很高兴认识你",
|
||||
},
|
||||
{
|
||||
Role: "user",
|
||||
ContentType: "text",
|
||||
Content: "你好",
|
||||
},
|
||||
}
|
||||
sseReader := r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, additionalMsg)
|
||||
err := sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("chat flow run with history messages", func(t *testing.T) {
|
||||
id := r.load("chatflow/llm_chat_with_history.json", withMode(workflow.WorkflowMode_ChatFlow))
|
||||
r.publish(id, "v0.0.1", true)
|
||||
r.message.EXPECT().GetLatestRunIDs(gomock.Any(), gomock.Any()).Return([]int64{rID}, nil).AnyTimes()
|
||||
r.message.EXPECT().GetMessagesByRunIDs(gomock.Any(), gomock.Any()).Return(&message0.GetMessagesByRunIDsResponse{
|
||||
Messages: []*message0.WfMessage{
|
||||
{
|
||||
ID: mID,
|
||||
Role: schema.User,
|
||||
Text: ptr.Of("你好"),
|
||||
},
|
||||
},
|
||||
}, nil).AnyTimes()
|
||||
sseReader := r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, msg)
|
||||
err := sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("chat flow run with interrupt nodes ", func(t *testing.T) {
|
||||
// 生成一个携带 input, 问答文本 问答选项的三个中断节点 做测试
|
||||
id := r.load("chatflow/chat_run_with_interrupt.json", withMode(workflow.WorkflowMode_ChatFlow))
|
||||
r.publish(id, "v0.0.1", true)
|
||||
sseReader := r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, msg)
|
||||
|
||||
err := sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
if e.ID == "3" {
|
||||
assert.Equal(t, e.Type, "conversation.message.completed")
|
||||
assert.Contains(t, string(e.Data), "7383997384420262000")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
sseReader = r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, []*workflow.EnterMessage{
|
||||
{Role: string(schema.User), Content: "input:1", ContentType: "text"},
|
||||
})
|
||||
|
||||
err = sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
if e.ID == "4" {
|
||||
assert.Equal(t, e.Type, "conversation.message.completed")
|
||||
assert.Contains(t, string(e.Data), "你好")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
sseReader = r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, []*workflow.EnterMessage{
|
||||
{Role: string(schema.User), Content: "hello", ContentType: "text"},
|
||||
})
|
||||
|
||||
err = sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
if e.ID == "3" {
|
||||
assert.Equal(t, e.Type, "conversation.message.completed")
|
||||
assert.Contains(t, string(e.Data), "question_card_data", "请选择")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
sseReader = r.openapiChatFlowRun(id, ptr.Of(cIDStr), ptr.Of(appIDStr), nil, map[string]any{
|
||||
vo.ConversationNameKey: "Default",
|
||||
}, []*workflow.EnterMessage{
|
||||
{Role: string(schema.User), Content: "A", ContentType: "text"},
|
||||
})
|
||||
err = sseReader.ForEach(t.Context(), func(e *sse.Event) error {
|
||||
t.Logf("sse id: %s, type: %s, data: %s", e.ID, e.Type, string(e.Data))
|
||||
|
||||
if e.ID == "4" {
|
||||
assert.Equal(t, e.Type, "conversation.message.completed")
|
||||
assert.Contains(t, string(e.Data), "answer", "A")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ type ExecuteConfig struct {
|
||||
ConversationHistorySchemaMessages []*schema.Message
|
||||
SectionID *int64
|
||||
MaxHistoryRounds *int32
|
||||
InputFileFields map[string]*FileInfo
|
||||
}
|
||||
|
||||
type ExecuteMode string
|
||||
@ -91,3 +92,9 @@ const (
|
||||
BizTypeAgent BizType = "agent"
|
||||
BizTypeWorkflow BizType = "workflow"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
FileURL string `json:"file_url"`
|
||||
FileName string `json:"file_name"`
|
||||
FileExtension string `json:"file_extension"`
|
||||
}
|
||||
|
||||
@ -51,7 +51,6 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/messages2query"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/modelmgr"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/cache/redis"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/coderunner/direct"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/coderunner/sandbox"
|
||||
builtinNL2SQL "github.com/coze-dev/coze-studio/backend/infra/impl/document/nl2sql/builtin"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/document/ocr/ppocr"
|
||||
@ -346,40 +345,43 @@ func initKnowledgeEventBusProducer() (eventbus.Producer, error) {
|
||||
}
|
||||
|
||||
func initCodeRunner() coderunner.Runner {
|
||||
switch typ := os.Getenv(consts.CodeRunnerType); typ {
|
||||
case "sandbox":
|
||||
getAndSplit := func(key string) []string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(v, ",")
|
||||
// 为了安全考虑,移除不安全的direct runner,强制使用sandbox
|
||||
getAndSplit := func(key string) []string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
config := &sandbox.Config{
|
||||
AllowEnv: getAndSplit(consts.CodeRunnerAllowEnv),
|
||||
AllowRead: getAndSplit(consts.CodeRunnerAllowRead),
|
||||
AllowWrite: getAndSplit(consts.CodeRunnerAllowWrite),
|
||||
AllowNet: getAndSplit(consts.CodeRunnerAllowNet),
|
||||
AllowRun: getAndSplit(consts.CodeRunnerAllowRun),
|
||||
AllowFFI: getAndSplit(consts.CodeRunnerAllowFFI),
|
||||
NodeModulesDir: os.Getenv(consts.CodeRunnerNodeModulesDir),
|
||||
TimeoutSeconds: 0,
|
||||
MemoryLimitMB: 0,
|
||||
}
|
||||
if f, err := strconv.ParseFloat(os.Getenv(consts.CodeRunnerTimeoutSeconds), 64); err == nil {
|
||||
config.TimeoutSeconds = f
|
||||
} else {
|
||||
config.TimeoutSeconds = 60.0
|
||||
}
|
||||
if mem, err := strconv.ParseInt(os.Getenv(consts.CodeRunnerMemoryLimitMB), 10, 64); err == nil {
|
||||
config.MemoryLimitMB = mem
|
||||
} else {
|
||||
config.MemoryLimitMB = 100
|
||||
}
|
||||
return sandbox.NewRunner(config)
|
||||
default:
|
||||
return direct.NewRunner()
|
||||
return strings.Split(v, ",")
|
||||
}
|
||||
|
||||
// 使用安全的默认配置
|
||||
config := &sandbox.Config{
|
||||
AllowEnv: getAndSplit(consts.CodeRunnerAllowEnv), // 默认为空,禁止环境变量访问
|
||||
AllowRead: getAndSplit(consts.CodeRunnerAllowRead), // 默认为空,禁止文件读取
|
||||
AllowWrite: getAndSplit(consts.CodeRunnerAllowWrite), // 默认为空,禁止文件写入
|
||||
AllowNet: getAndSplit(consts.CodeRunnerAllowNet), // 默认为空,禁止网络访问
|
||||
AllowRun: getAndSplit(consts.CodeRunnerAllowRun), // 默认为空,禁止运行外部程序
|
||||
AllowFFI: getAndSplit(consts.CodeRunnerAllowFFI), // 默认为空,禁止FFI调用
|
||||
NodeModulesDir: os.Getenv(consts.CodeRunnerNodeModulesDir),
|
||||
TimeoutSeconds: 0,
|
||||
MemoryLimitMB: 0,
|
||||
}
|
||||
|
||||
// 设置安全的超时时间,最大30秒
|
||||
if f, err := strconv.ParseFloat(os.Getenv(consts.CodeRunnerTimeoutSeconds), 64); err == nil && f > 0 && f <= 30 {
|
||||
config.TimeoutSeconds = f
|
||||
} else {
|
||||
config.TimeoutSeconds = 30.0 // 默认30秒超时
|
||||
}
|
||||
|
||||
// 设置安全的内存限制,最大100MB
|
||||
if mem, err := strconv.ParseInt(os.Getenv(consts.CodeRunnerMemoryLimitMB), 10, 64); err == nil && mem > 0 && mem <= 100 {
|
||||
config.MemoryLimitMB = mem
|
||||
} else {
|
||||
config.MemoryLimitMB = 100 // 默认100MB内存限制
|
||||
}
|
||||
|
||||
return sandbox.NewRunner(config)
|
||||
}
|
||||
|
||||
func initOCR() ocr.OCR {
|
||||
@ -798,4 +800,4 @@ func getEmbedding(ctx context.Context) (embedding.Embedder, error) {
|
||||
}
|
||||
|
||||
return emb, nil
|
||||
}
|
||||
}
|
||||
@ -209,7 +209,7 @@ type publishFn func(ctx context.Context, appContext *ServiceComponents, publishI
|
||||
|
||||
func publishAgentVariables(ctx context.Context, appContext *ServiceComponents, publishInfo *entity.SingleAgentPublish, agent *entity.SingleAgent) (*entity.SingleAgent, error) {
|
||||
draftAgent := agent
|
||||
if draftAgent.VariablesMetaID != nil || *draftAgent.VariablesMetaID == 0 {
|
||||
if draftAgent.VariablesMetaID == nil || *draftAgent.VariablesMetaID == 0 {
|
||||
return draftAgent, nil
|
||||
}
|
||||
|
||||
|
||||
@ -221,7 +221,7 @@ const (
|
||||
"id": "5fJt3qKpSz",
|
||||
"name": "list",
|
||||
"defaultValue": [
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -504,7 +504,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
workflowID = mustParseInt64(req.GetWorkflowID())
|
||||
isDebug = req.GetExecuteMode() == "DEBUG"
|
||||
appID, agentID *int64
|
||||
resolveAppID int64
|
||||
bizID int64
|
||||
conversationID int64
|
||||
sectionID int64
|
||||
version string
|
||||
@ -521,11 +521,11 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
|
||||
if req.IsSetAppID() {
|
||||
appID = ptr.Of(mustParseInt64(req.GetAppID()))
|
||||
resolveAppID = mustParseInt64(req.GetAppID())
|
||||
bizID = mustParseInt64(req.GetAppID())
|
||||
}
|
||||
if req.IsSetBotID() {
|
||||
agentID = ptr.Of(mustParseInt64(req.GetBotID()))
|
||||
resolveAppID = mustParseInt64(req.GetBotID())
|
||||
bizID = mustParseInt64(req.GetBotID())
|
||||
}
|
||||
|
||||
if appID != nil && agentID != nil {
|
||||
@ -564,16 +564,16 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
sectionID = cInfo.SectionID
|
||||
|
||||
// only trust the conversation name under the app
|
||||
conversationName, existed, err := GetWorkflowDomainSVC().GetConversationNameByID(ctx, ternary.IFElse(isDebug, vo.Draft, vo.Online), resolveAppID, connectorID, conversationID)
|
||||
conversationName, existed, err := GetWorkflowDomainSVC().GetConversationNameByID(ctx, ternary.IFElse(isDebug, vo.Draft, vo.Online), bizID, connectorID, conversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existed {
|
||||
return nil, fmt.Errorf("conversation not found")
|
||||
}
|
||||
parameters["CONVERSATION_NAME"] = conversationName
|
||||
parameters[vo.ConversationNameKey] = conversationName
|
||||
} else if req.IsSetConversationID() && req.IsSetBotID() {
|
||||
parameters["CONVERSATION_NAME"] = "Default"
|
||||
parameters[vo.ConversationNameKey] = "Default"
|
||||
conversationID = mustParseInt64(req.GetConversationID())
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID)
|
||||
if err != nil {
|
||||
@ -581,11 +581,11 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
}
|
||||
sectionID = cInfo.SectionID
|
||||
} else {
|
||||
conversationName, ok := parameters["CONVERSATION_NAME"].(string)
|
||||
conversationName, ok := parameters[vo.ConversationNameKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("conversation name is requried")
|
||||
}
|
||||
cID, sID, err := GetWorkflowDomainSVC().GetOrCreateConversation(ctx, ternary.IFElse(isDebug, vo.Draft, vo.Online), resolveAppID, connectorID, userID, conversationName)
|
||||
cID, sID, err := GetWorkflowDomainSVC().GetOrCreateConversation(ctx, ternary.IFElse(isDebug, vo.Draft, vo.Online), bizID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -594,7 +594,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
}
|
||||
|
||||
runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{
|
||||
AgentID: resolveAppID,
|
||||
AgentID: bizID,
|
||||
ConversationID: conversationID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
ConnectorID: connectorID,
|
||||
@ -606,7 +606,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
|
||||
roundID := runRecord.ID
|
||||
|
||||
userMessage, err := toConversationMessage(ctx, resolveAppID, conversationID, userID, roundID, sectionID, message.MessageTypeQuestion, lastUserMessage)
|
||||
userMessage, err := toConversationMessage(ctx, bizID, conversationID, userID, roundID, sectionID, message.MessageTypeQuestion, lastUserMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -648,7 +648,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
return nil, err
|
||||
}
|
||||
return schema.StreamReaderWithConvert(sr, w.convertToChatFlowRunResponseList(ctx, convertToChatFlowInfo{
|
||||
appID: resolveAppID,
|
||||
bizID: bizID,
|
||||
conversationID: conversationID,
|
||||
roundID: roundID,
|
||||
workflowID: workflowID,
|
||||
@ -684,7 +684,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
Cancellable: isDebug,
|
||||
}
|
||||
|
||||
historyMessages, err := makeChatFlowHistoryMessages(ctx, resolveAppID, conversationID, userID, sectionID, connectorID, messages[:len(req.GetAdditionalMessages())-1])
|
||||
historyMessages, err := makeChatFlowHistoryMessages(ctx, bizID, conversationID, userID, sectionID, connectorID, messages[:len(req.GetAdditionalMessages())-1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -706,7 +706,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
logs.CtxWarnf(ctx, "create history message failed, err=%v", err)
|
||||
}
|
||||
}
|
||||
parameters["USER_INPUT"], err = w.makeChatFlowUserInput(ctx, lastUserMessage)
|
||||
parameters[vo.UserInputKey], err = w.makeChatFlowUserInput(ctx, lastUserMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -717,7 +717,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
}
|
||||
|
||||
return schema.StreamReaderWithConvert(sr, w.convertToChatFlowRunResponseList(ctx, convertToChatFlowInfo{
|
||||
appID: resolveAppID,
|
||||
bizID: bizID,
|
||||
conversationID: conversationID,
|
||||
roundID: roundID,
|
||||
workflowID: workflowID,
|
||||
@ -731,7 +731,7 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
|
||||
|
||||
func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Context, info convertToChatFlowInfo) func(msg *entity.Message) (responses []*workflow.ChatFlowRunResponse, err error) {
|
||||
var (
|
||||
appID = info.appID
|
||||
bizID = info.bizID
|
||||
conversationID = info.conversationID
|
||||
roundID = info.roundID
|
||||
workflowID = info.workflowID
|
||||
@ -798,7 +798,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
ChatID: strconv.FormatInt(roundID, 10),
|
||||
ConversationID: strconv.FormatInt(conversationID, 10),
|
||||
SectionID: strconv.FormatInt(sectionID, 10),
|
||||
BotID: strconv.FormatInt(appID, 10),
|
||||
BotID: strconv.FormatInt(bizID, 10),
|
||||
Role: string(schema.Assistant),
|
||||
Type: "follow_up",
|
||||
ContentType: "text",
|
||||
@ -815,7 +815,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
ID: strconv.FormatInt(roundID, 10),
|
||||
ConversationID: strconv.FormatInt(conversationID, 10),
|
||||
SectionID: strconv.FormatInt(sectionID, 10),
|
||||
BotID: strconv.FormatInt(appID, 10),
|
||||
BotID: strconv.FormatInt(bizID, 10),
|
||||
Status: vo.Completed,
|
||||
ExecuteID: strconv.FormatInt(executeID, 10),
|
||||
Usage: &vo.Usage{
|
||||
@ -929,7 +929,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
}
|
||||
|
||||
_, err = crossmessage.DefaultSVC().Create(ctx, &message.Message{
|
||||
AgentID: appID,
|
||||
AgentID: bizID,
|
||||
RunID: roundID,
|
||||
SectionID: sectionID,
|
||||
Content: msgContent,
|
||||
@ -947,7 +947,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
ChatID: strconv.FormatInt(roundID, 10),
|
||||
ConversationID: strconv.FormatInt(conversationID, 10),
|
||||
SectionID: strconv.FormatInt(sectionID, 10),
|
||||
BotID: strconv.FormatInt(appID, 10),
|
||||
BotID: strconv.FormatInt(bizID, 10),
|
||||
Role: string(schema.Assistant),
|
||||
Type: string(entity.Answer),
|
||||
ContentType: string(contentType),
|
||||
@ -1046,7 +1046,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
}
|
||||
intermediateMessage = &message.Message{
|
||||
ID: id,
|
||||
AgentID: appID,
|
||||
AgentID: bizID,
|
||||
RunID: roundID,
|
||||
SectionID: sectionID,
|
||||
ConversationID: conversationID,
|
||||
@ -1066,7 +1066,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
ChatID: strconv.FormatInt(roundID, 10),
|
||||
ConversationID: strconv.FormatInt(conversationID, 10),
|
||||
SectionID: strconv.FormatInt(sectionID, 10),
|
||||
BotID: strconv.FormatInt(appID, 10),
|
||||
BotID: strconv.FormatInt(bizID, 10),
|
||||
Role: string(dataMessage.Role),
|
||||
Type: string(dataMessage.Type),
|
||||
ContentType: string(message.ContentTypeText),
|
||||
@ -1092,7 +1092,7 @@ func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Contex
|
||||
ChatID: strconv.FormatInt(roundID, 10),
|
||||
ConversationID: strconv.FormatInt(conversationID, 10),
|
||||
SectionID: strconv.FormatInt(sectionID, 10),
|
||||
BotID: strconv.FormatInt(appID, 10),
|
||||
BotID: strconv.FormatInt(bizID, 10),
|
||||
Role: string(dataMessage.Role),
|
||||
Type: string(dataMessage.Type),
|
||||
ContentType: string(message.ContentTypeText),
|
||||
@ -1155,9 +1155,9 @@ func (w *ApplicationService) makeChatFlowUserInput(ctx context.Context, message
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid message ccontent type %v", message.ContentType)
|
||||
}
|
||||
|
||||
}
|
||||
func makeChatFlowHistoryMessages(ctx context.Context, appID, conversationID, userID, sectionID, connectorID int64, messages []*workflow.EnterMessage) ([]*message.Message, error) {
|
||||
|
||||
func makeChatFlowHistoryMessages(ctx context.Context, bizID, conversationID, userID, sectionID, connectorID int64, messages []*workflow.EnterMessage) ([]*message.Message, error) {
|
||||
|
||||
var (
|
||||
rID int64
|
||||
@ -1170,7 +1170,7 @@ func makeChatFlowHistoryMessages(ctx context.Context, appID, conversationID, use
|
||||
for _, msg := range messages {
|
||||
if msg.Role == userRole {
|
||||
runRecord, err = crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{
|
||||
AgentID: appID,
|
||||
AgentID: bizID,
|
||||
ConversationID: conversationID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
ConnectorID: connectorID,
|
||||
@ -1180,13 +1180,15 @@ func makeChatFlowHistoryMessages(ctx context.Context, appID, conversationID, use
|
||||
return nil, err
|
||||
}
|
||||
rID = runRecord.ID
|
||||
} else if msg.Role == assistantRole && rID == 0 {
|
||||
continue
|
||||
} else if msg.Role == assistantRole {
|
||||
if rID == 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid role type %v", msg.Role)
|
||||
}
|
||||
|
||||
m, err := toConversationMessage(ctx, appID, conversationID, userID, rID, sectionID, ternary.IFElse(msg.Role == userRole, message.MessageTypeQuestion, message.MessageTypeAnswer), msg)
|
||||
m, err := toConversationMessage(ctx, bizID, conversationID, userID, rID, sectionID, ternary.IFElse(msg.Role == userRole, message.MessageTypeQuestion, message.MessageTypeAnswer), msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1274,7 +1276,7 @@ func (w *ApplicationService) OpenAPICreateConversation(ctx context.Context, req
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toConversationMessage(ctx context.Context, appID, cid, userID, roundID, sectionID int64, messageType message.MessageType, msg *workflow.EnterMessage) (*message.Message, error) {
|
||||
func toConversationMessage(ctx context.Context, bizID, cid, userID, roundID, sectionID int64, messageType message.MessageType, msg *workflow.EnterMessage) (*message.Message, error) {
|
||||
type content struct {
|
||||
Type string `json:"type"`
|
||||
FileID *string `json:"file_id"`
|
||||
@ -1284,7 +1286,7 @@ func toConversationMessage(ctx context.Context, appID, cid, userID, roundID, sec
|
||||
return &message.Message{
|
||||
Role: schema.User,
|
||||
ConversationID: cid,
|
||||
AgentID: appID,
|
||||
AgentID: bizID,
|
||||
RunID: roundID,
|
||||
Content: msg.Content,
|
||||
ContentType: message.ContentTypeText,
|
||||
@ -1304,7 +1306,7 @@ func toConversationMessage(ctx context.Context, appID, cid, userID, roundID, sec
|
||||
Role: schema.User,
|
||||
MessageType: messageType,
|
||||
ConversationID: cid,
|
||||
AgentID: appID,
|
||||
AgentID: bizID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
RunID: roundID,
|
||||
ContentType: message.ContentTypeMix,
|
||||
@ -1432,7 +1434,7 @@ func toSchemaMessage(ctx context.Context, msg *workflow.EnterMessage) (*schema.M
|
||||
|
||||
type convertToChatFlowInfo struct {
|
||||
userMessage *schema.Message
|
||||
appID int64
|
||||
bizID int64
|
||||
conversationID int64
|
||||
roundID int64
|
||||
workflowID int64
|
||||
|
||||
604
backend/application/workflow/chatflow_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -108,5 +108,10 @@ func InitService(_ context.Context, components *ServiceComponents) (*Application
|
||||
SVC.TosClient = components.Tos
|
||||
SVC.IDGenerator = components.IDGen
|
||||
|
||||
err = SVC.InitNodeIconURLCache(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return SVC, nil
|
||||
}
|
||||
|
||||
@ -23,10 +23,12 @@ import (
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
|
||||
@ -75,12 +77,56 @@ type ApplicationService struct {
|
||||
IDGenerator idgen.IDGenerator
|
||||
}
|
||||
|
||||
var SVC = &ApplicationService{}
|
||||
var (
|
||||
SVC = &ApplicationService{}
|
||||
nodeIconURLCache = make(map[string]string)
|
||||
nodeIconURLCacheMu sync.Mutex
|
||||
)
|
||||
|
||||
func GetWorkflowDomainSVC() domainWorkflow.Service {
|
||||
return SVC.DomainSVC
|
||||
}
|
||||
|
||||
func (w *ApplicationService) InitNodeIconURLCache(ctx context.Context) error {
|
||||
category2NodeMetaList, _, err := GetWorkflowDomainSVC().ListNodeMeta(ctx, nil)
|
||||
if err != nil {
|
||||
logs.Errorf("failed to list node meta for icon url cache: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
eg, gCtx := errgroup.WithContext(ctx)
|
||||
for _, nodeMetaList := range category2NodeMetaList {
|
||||
for _, nodeMeta := range nodeMetaList {
|
||||
eg.Go(func() error {
|
||||
if len(nodeMeta.IconURI) == 0 {
|
||||
// For custom nodes, if IconURI is not set, there will be no icon.
|
||||
logs.Warnf("node '%s' has an empty IconURI, it will have no icon", nodeMeta.Name)
|
||||
return nil
|
||||
}
|
||||
url, err := w.TosClient.GetObjectUrl(gCtx, nodeMeta.IconURI)
|
||||
if err != nil {
|
||||
logs.Warnf("failed to get object url for node %s: %v", nodeMeta.Name, err)
|
||||
return err
|
||||
}
|
||||
nodeTypeStr := entity.IDStrToNodeType(strconv.FormatInt(nodeMeta.ID, 10))
|
||||
if len(nodeTypeStr) > 0 {
|
||||
nodeIconURLCacheMu.Lock()
|
||||
nodeIconURLCache[string(nodeTypeStr)] = url
|
||||
nodeIconURLCacheMu.Unlock()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logs.Infof("node icon url cache initialized with %d entries", len(nodeIconURLCache))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *ApplicationService) GetNodeTemplateList(ctx context.Context, req *workflow.NodeTemplateListRequest) (
|
||||
_ *workflow.NodeTemplateListResponse, err error,
|
||||
) {
|
||||
@ -119,19 +165,22 @@ func (w *ApplicationService) GetNodeTemplateList(ctx context.Context, req *workf
|
||||
Name: category,
|
||||
}
|
||||
for _, nodeMeta := range nodeMetaList {
|
||||
nodeID := fmt.Sprintf("%d", nodeMeta.ID)
|
||||
nodeType := entity.IDStrToNodeType(nodeID)
|
||||
url := nodeIconURLCache[string(nodeType)]
|
||||
tpl := &workflow.NodeTemplate{
|
||||
ID: fmt.Sprintf("%d", nodeMeta.ID),
|
||||
ID: nodeID,
|
||||
Type: workflow.NodeTemplateType(nodeMeta.ID),
|
||||
Name: ternary.IFElse(i18n.GetLocale(ctx) == i18n.LocaleEN, nodeMeta.EnUSName, nodeMeta.Name),
|
||||
Desc: ternary.IFElse(i18n.GetLocale(ctx) == i18n.LocaleEN, nodeMeta.EnUSDescription, nodeMeta.Desc),
|
||||
IconURL: nodeMeta.IconURL,
|
||||
IconURL: url,
|
||||
SupportBatch: ternary.IFElse(nodeMeta.SupportBatch, workflow.SupportBatch_SUPPORT, workflow.SupportBatch_NOT_SUPPORT),
|
||||
NodeType: fmt.Sprintf("%d", nodeMeta.ID),
|
||||
NodeType: nodeID,
|
||||
Color: nodeMeta.Color,
|
||||
}
|
||||
|
||||
resp.Data.TemplateList = append(resp.Data.TemplateList, tpl)
|
||||
categoryMap[category].NodeTypeList = append(categoryMap[category].NodeTypeList, fmt.Sprintf("%d", nodeMeta.ID))
|
||||
categoryMap[category].NodeTypeList = append(categoryMap[category].NodeTypeList, nodeID)
|
||||
}
|
||||
}
|
||||
|
||||
@ -750,11 +799,13 @@ func (w *ApplicationService) GetProcess(ctx context.Context, req *workflow.GetWo
|
||||
}
|
||||
}
|
||||
|
||||
iconURL := nodeIconURLCache[string(ie.NodeType)]
|
||||
|
||||
resp.Data.NodeEvents = append(resp.Data.NodeEvents, &workflow.NodeEvent{
|
||||
ID: strconv.FormatInt(ie.ID, 10),
|
||||
NodeID: string(ie.NodeKey),
|
||||
NodeTitle: ie.NodeTitle,
|
||||
NodeIcon: ie.NodeIcon,
|
||||
NodeIcon: iconURL,
|
||||
Data: ie.InterruptData,
|
||||
Type: ie.EventType,
|
||||
SchemaNodeID: string(ie.NodeKey),
|
||||
|
||||
@ -59,7 +59,7 @@ type MessageListRequest struct {
|
||||
BeforeID *string
|
||||
AfterID *string
|
||||
UserID int64
|
||||
AppID int64
|
||||
BizID int64
|
||||
OrderBy *string
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ type WfMessage struct {
|
||||
type GetLatestRunIDsRequest struct {
|
||||
ConversationID int64
|
||||
UserID int64
|
||||
AppID int64
|
||||
BizID int64
|
||||
Rounds int64
|
||||
SectionID int64
|
||||
InitRunID *int64
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
|
||||
var defaultSVC Uploader
|
||||
|
||||
//go:generate mockgen -destination uploadmock/upload_mock.go --package uploadmock -source upload.go
|
||||
type Uploader interface {
|
||||
GetFile(ctx context.Context, req *service.GetFileRequest) (resp *service.GetFileResponse, err error)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -54,7 +54,7 @@ func (c *impl) MessageList(ctx context.Context, req *crossmessage.MessageListReq
|
||||
ConversationID: req.ConversationID,
|
||||
Limit: int(req.Limit), // Since the value of limit is checked inside the node, the type cast here is safe
|
||||
UserID: strconv.FormatInt(req.UserID, 10),
|
||||
AgentID: req.AppID,
|
||||
AgentID: req.BizID,
|
||||
OrderBy: req.OrderBy,
|
||||
}
|
||||
if req.BeforeID != nil {
|
||||
@ -96,7 +96,7 @@ func (c *impl) MessageList(ctx context.Context, req *crossmessage.MessageListReq
|
||||
func (c *impl) GetLatestRunIDs(ctx context.Context, req *crossmessage.GetLatestRunIDsRequest) ([]int64, error) {
|
||||
listMeta := &agententity.ListRunRecordMeta{
|
||||
ConversationID: req.ConversationID,
|
||||
AgentID: req.AppID,
|
||||
AgentID: req.BizID,
|
||||
Limit: int32(req.Rounds),
|
||||
SectionID: req.SectionID,
|
||||
}
|
||||
|
||||
@ -75,11 +75,11 @@ type Conversation interface {
|
||||
ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error)
|
||||
ReleaseConversationTemplate(ctx context.Context, appID int64, version string) error
|
||||
InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID int64, appID int64, userID int64) error
|
||||
GetOrCreateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, int64, error)
|
||||
GetOrCreateConversation(ctx context.Context, env vo.Env, bizID, connectorID, userID int64, conversationName string) (int64, int64, error)
|
||||
UpdateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, error)
|
||||
GetTemplateByName(ctx context.Context, env vo.Env, appID int64, templateName string) (*entity.ConversationTemplate, bool, error)
|
||||
GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error)
|
||||
GetConversationNameByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error)
|
||||
GetConversationNameByID(ctx context.Context, env vo.Env, bizID, connectorID, conversationID int64) (string, bool, error)
|
||||
}
|
||||
|
||||
type InterruptEventStore interface {
|
||||
@ -143,8 +143,8 @@ type ConversationRepository interface {
|
||||
UpdateStaticConversation(ctx context.Context, env vo.Env, templateID int64, connectorID int64, userID int64, newConversationID int64) error
|
||||
UpdateDynamicConversation(ctx context.Context, env vo.Env, conversationID, newConversationID int64) error
|
||||
CopyTemplateConversationByAppID(ctx context.Context, appID int64, toAppID int64) error
|
||||
GetStaticConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error)
|
||||
GetDynamicConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error)
|
||||
GetStaticConversationByID(ctx context.Context, env vo.Env, bizID, connectorID, conversationID int64) (string, bool, error)
|
||||
GetDynamicConversationByID(ctx context.Context, env vo.Env, bizID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error)
|
||||
}
|
||||
type WorkflowConfig interface {
|
||||
GetNodeOfCodeConfig() *config.NodeOfCodeConfig
|
||||
|
||||
@ -53,7 +53,7 @@ type NodeTypeMeta struct {
|
||||
Category string `json:"category"`
|
||||
Color string `json:"color"`
|
||||
Desc string `json:"desc"`
|
||||
IconURL string `json:"icon_url"`
|
||||
IconURI string `json:"icon_uri"`
|
||||
SupportBatch bool `json:"support_batch"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
EnUSName string `json:"en_us_name,omitempty"`
|
||||
@ -265,7 +265,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "input&output",
|
||||
Desc: "工作流的起始节点,用于设定启动工作流需要的信息",
|
||||
Color: "#5C62FF",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-start.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PostFillNil: true,
|
||||
@ -281,7 +281,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "input&output",
|
||||
Desc: "工作流的最终节点,用于返回工作流运行后的结果信息",
|
||||
Color: "#5C62FF",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-end.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -299,7 +299,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "",
|
||||
Desc: "调用大语言模型,使用变量和提示词生成回复",
|
||||
Color: "#5C62FF",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-LLM-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-llm.jpg",
|
||||
SupportBatch: true,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -319,7 +319,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "",
|
||||
Desc: "通过添加工具访问实时数据和执行外部操作",
|
||||
Color: "#CA61FF",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Plugin-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-plugin.jpg",
|
||||
SupportBatch: true,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -337,7 +337,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "编写代码,处理输入变量来生成返回值",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Code-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-code.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -355,7 +355,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "data",
|
||||
Desc: "在选定的知识中,根据输入变量召回最匹配的信息,并以列表形式返回",
|
||||
Color: "#FF811A",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-KnowledgeQuery-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-knowledge-query.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -374,7 +374,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "连接多个下游分支,若设定的条件成立则仅运行对应的分支,若均不成立则只运行“否则”分支",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Condition-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-condition.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{},
|
||||
EnUSName: "Condition",
|
||||
@ -388,7 +388,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "",
|
||||
Desc: "集成已发布工作流,可以执行嵌套子任务",
|
||||
Color: "#00B83E",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Workflow-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-workflow.jpg",
|
||||
SupportBatch: true,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
BlockEndStream: true,
|
||||
@ -404,7 +404,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "database",
|
||||
Desc: "基于用户自定义的 SQL 完成对数据库的增删改查操作",
|
||||
Color: "#FF811A",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Database-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-database.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -422,7 +422,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "input&output",
|
||||
Desc: "节点从“消息”更名为“输出”,支持中间过程的消息输出,支持流式和非流式两种方式",
|
||||
Color: "#5C62FF",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Output-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-output.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -441,7 +441,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "utilities",
|
||||
Desc: "用于处理多个字符串类型变量的格式",
|
||||
Color: "#3071F2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-StrConcat-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-text.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -458,7 +458,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "utilities",
|
||||
Desc: "支持中间向用户提问问题,支持预置选项提问和开放式问题提问两种方式",
|
||||
Color: "#3071F2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Direct-Question-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-question.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -477,7 +477,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "用于立即终止当前所在的循环,跳出循环体",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Break-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-break.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{},
|
||||
EnUSName: "Break",
|
||||
@ -491,7 +491,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "用于重置循环变量的值,使其下次循环使用重置后的值",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-LoopSetVariable-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-loop-set-variable.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{},
|
||||
EnUSName: "Set Variable",
|
||||
@ -505,7 +505,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "用于通过设定循环次数和逻辑,重复执行一系列任务",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Loop-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-loop.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
IsComposite: true,
|
||||
@ -524,7 +524,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "用于用户输入的意图识别,并将其与预设意图选项进行匹配。",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Intent-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-intent.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -543,7 +543,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "data",
|
||||
Desc: "写入节点可以添加 文本类型 的知识库,仅可以添加一个知识库",
|
||||
Color: "#FF811A",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-KnowledgeWriting-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-knowledge-write.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -561,7 +561,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "通过设定批量运行次数和逻辑,运行批处理体内的任务",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Batch-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-batch.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
IsComposite: true,
|
||||
@ -580,7 +580,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "用于终止当前循环,执行下次循环",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Continue-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-continue.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{},
|
||||
EnUSName: "Continue",
|
||||
@ -594,7 +594,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "input&output",
|
||||
Desc: "支持中间过程的信息输入",
|
||||
Color: "#5C62FF",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Input-v2.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon_input.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PostFillNil: true,
|
||||
@ -609,8 +609,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "", // Not found in cate_list
|
||||
Desc: "comment_desc", // Placeholder from JSON
|
||||
Color: "",
|
||||
IconURL: "comment_icon", // Placeholder from JSON
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
EnUSName: "Comment",
|
||||
},
|
||||
NodeTypeVariableAggregator: {
|
||||
@ -620,7 +619,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "logic",
|
||||
Desc: "对多个分支的输出进行聚合处理",
|
||||
Color: "#00B2B2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/VariableMerge-icon.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-variable-merge.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PostFillNil: true,
|
||||
@ -638,7 +637,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "message",
|
||||
Desc: "用于查询消息列表",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-List.jpeg",
|
||||
IconURI: "default_icon/workflow_icon/icon-query-message-list.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -654,7 +653,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "conversation_history", // Mapped from cate_list
|
||||
Desc: "用于清空会话历史,清空后LLM看到的会话历史为空",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Delete.jpeg",
|
||||
IconURI: "default_icon/workflow_icon/icon-clear-context.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -670,7 +669,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "conversation_management",
|
||||
Desc: "用于创建会话",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
IconURI: "default_icon/workflow_icon/icon-create-conversation.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -687,7 +686,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "data",
|
||||
Desc: "用于给支持写入的变量赋值,包括应用变量、用户变量",
|
||||
Color: "#FF811A",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/Variable.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-variable-assign.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{},
|
||||
EnUSName: "Variable assign",
|
||||
@ -701,7 +700,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "database",
|
||||
Desc: "修改表中已存在的数据记录,用户指定更新条件和内容来更新数据",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-database-update.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-database-update.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -718,7 +717,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "database",
|
||||
Desc: "从表获取数据,用户可定义查询条件、选择列等,输出符合条件的数据",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icaon-database-select.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-database-query.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -735,7 +734,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "database",
|
||||
Desc: "从表中删除数据记录,用户指定删除条件来删除符合条件的记录",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-database-delete.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-database-delete.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -752,7 +751,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "utilities",
|
||||
Desc: "用于发送API请求,从接口返回数据",
|
||||
Color: "#3071F2",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-HTTP.png",
|
||||
IconURI: "default_icon/workflow_icon/icon-http.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -769,7 +768,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "database",
|
||||
Desc: "向表添加新数据记录,用户输入数据内容后插入数据库",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-database-insert.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-database-create.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -785,7 +784,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "conversation_management",
|
||||
Desc: "用于修改会话的名字",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-update-conversation.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -802,7 +801,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "conversation_management",
|
||||
Desc: "用于删除会话",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除会话.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-delete-conversation.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -818,7 +817,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "conversation_management",
|
||||
Desc: "用于查询所有会话,包含静态会话、动态会话",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-query-conversation-list.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PostFillNil: true,
|
||||
@ -833,7 +832,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "conversation_history", // Mapped from cate_list
|
||||
Desc: "用于查询会话历史,返回LLM可见的会话消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话历史.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-query-conversation-history.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -849,7 +848,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "message",
|
||||
Desc: "用于创建消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-create-message.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -865,7 +864,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "message",
|
||||
Desc: "用于修改消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-修改消息.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-update-message.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -881,7 +880,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "message",
|
||||
Desc: "用于删除消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除消息.jpg",
|
||||
IconURI: "default_icon/workflow_icon/icon-delete-message.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -916,8 +915,8 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
// Color is the color of the upper edge of the node displayed on Canvas.
|
||||
Color: "F2B600",
|
||||
|
||||
// IconURL is the URL of the icon displayed on Canvas.
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-to_json.png",
|
||||
// IconURI is the resource identifier for the icon displayed on the Canvas. It's resolved into a full URL by the backend to support different deployment environments.
|
||||
IconURI: "default_icon/workflow_icon/icon-json-stringify.jpg",
|
||||
|
||||
// SupportBatch indicates whether this node can set batch mode.
|
||||
// NOTE: ultimately it's frontend that decides which node can enable batch mode.
|
||||
@ -943,7 +942,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "utilities",
|
||||
Desc: "用于将JSON字符串解析为变量",
|
||||
Color: "F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-from_json.png",
|
||||
IconURI: "default_icon/workflow_icon/icon-json-parser.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
@ -961,7 +960,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Category: "data",
|
||||
Desc: "用于删除知识库中的文档",
|
||||
Color: "#FF811A",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icons-dataset-delete.png",
|
||||
IconURI: "default_icon/workflow_icon/icon-knowledge-delete.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
|
||||
@ -32,6 +32,11 @@ const (
|
||||
ChatFlowMessageCompleted ChatFlowEvent = "conversation.message.completed"
|
||||
)
|
||||
|
||||
const (
|
||||
ConversationNameKey = "CONVERSATION_NAME"
|
||||
UserInputKey = "USER_INPUT"
|
||||
)
|
||||
|
||||
type Usage struct {
|
||||
TokenCount *int32 `form:"token_count" json:"token_count,omitempty"`
|
||||
OutputTokens *int32 `form:"output_count" json:"output_count,omitempty"`
|
||||
|
||||
@ -59,14 +59,14 @@ type ListConversationPolicy struct {
|
||||
}
|
||||
|
||||
type CreateStaticConversation struct {
|
||||
AppID int64
|
||||
BizID int64
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
|
||||
TemplateID int64
|
||||
}
|
||||
type CreateDynamicConversation struct {
|
||||
AppID int64
|
||||
BizID int64
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
|
||||
|
||||
@ -44,8 +44,14 @@ type Reference struct {
|
||||
}
|
||||
|
||||
type FieldSource struct {
|
||||
Ref *Reference `json:"ref,omitempty"`
|
||||
Val any `json:"val,omitempty"`
|
||||
Ref *Reference `json:"ref,omitempty"`
|
||||
Val any `json:"val,omitempty"`
|
||||
FileExtra *FileExtra `json:"file_extra,omitempty"`
|
||||
}
|
||||
|
||||
type FileExtra struct {
|
||||
FileName *string `json:"file_name,omitempty"`
|
||||
FileNames []string `json:"file_names,omitempty"`
|
||||
}
|
||||
|
||||
type TypeInfo struct {
|
||||
|
||||
@ -731,6 +731,20 @@ func TestKnowledgeDeleter(t *testing.T) {
|
||||
UserID: 123,
|
||||
})
|
||||
|
||||
defer mockey.Mock(execute.GetExeCtx).Return(&execute.Context{
|
||||
RootCtx: execute.RootCtx{
|
||||
ExeCfg: workflowModel.ExecuteConfig{
|
||||
InputFileFields: map[string]*workflowModel.FileInfo{
|
||||
"https://p26-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/5264fa1295da4a6483cd236b1316c454.pdf~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1782379180&x-signature=mlaXPIk9VJjOXu87xGaRmNRg9%2BA%3D": &workflowModel.FileInfo{
|
||||
FileName: "1706.03762v7.pdf",
|
||||
FileURL: "https://p26-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/5264fa1295da4a6483cd236b1316c454.pdf~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1782379180&x-signature=mlaXPIk9VJjOXu87xGaRmNRg9%2BA%3D",
|
||||
FileExtension: ".pdf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Build().UnPatch()
|
||||
|
||||
workflowSC, err := CanvasToWorkflowSchema(ctx, c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
@ -235,6 +235,7 @@ func WorkflowSchemaFromNode(ctx context.Context, c *vo.Canvas, nodeID string) (
|
||||
if enabled {
|
||||
trimmedSC.GeneratedNodes = append(trimmedSC.GeneratedNodes, ns.Key)
|
||||
}
|
||||
trimmedSC.Init()
|
||||
|
||||
return trimmedSC, nil
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -25,6 +26,7 @@ import (
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
@ -180,7 +182,10 @@ func CanvasBlockInputToFieldInfo(b *vo.BlockInput, path einoCompose.FieldPath, p
|
||||
if value == nil {
|
||||
return nil, fmt.Errorf("input %v has no value, type= %s", path, b.Type)
|
||||
}
|
||||
|
||||
var fileExtra *vo.FileExtra
|
||||
isFileAssistType := func(assistType vo.AssistType) bool {
|
||||
return assistType >= vo.AssistTypeDefault && assistType <= vo.AssistTypeVoice
|
||||
}
|
||||
switch value.Type {
|
||||
case vo.BlockInputValueTypeObjectRef:
|
||||
sc := b.Schema
|
||||
@ -214,7 +219,6 @@ func CanvasBlockInputToFieldInfo(b *vo.BlockInput, path einoCompose.FieldPath, p
|
||||
if content == nil {
|
||||
return nil, fmt.Errorf("input %v is literal but has no value, type= %s", path, b.Type)
|
||||
}
|
||||
|
||||
switch b.Type {
|
||||
case vo.VariableTypeObject:
|
||||
m := make(map[string]any)
|
||||
@ -223,11 +227,43 @@ func CanvasBlockInputToFieldInfo(b *vo.BlockInput, path einoCompose.FieldPath, p
|
||||
}
|
||||
content = m
|
||||
case vo.VariableTypeList:
|
||||
l := make([]any, 0)
|
||||
if err = sonic.UnmarshalString(content.(string), &l); err != nil {
|
||||
return nil, err
|
||||
switch content.(type) {
|
||||
case string:
|
||||
if _, ok := content.(string); ok {
|
||||
l := make([]any, 0)
|
||||
if err = sonic.UnmarshalString(content.(string), &l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content = l
|
||||
}
|
||||
case []string:
|
||||
content = content.([]string)
|
||||
case []any:
|
||||
content = content.([]any)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported variable type fot list: %s", b.Type)
|
||||
}
|
||||
eleSchema, err := vo.ParseVariable(b.Schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not parse schema from %v", b.Schema)
|
||||
}
|
||||
|
||||
if isFileAssistType(eleSchema.AssistType) {
|
||||
rawMeta, ok := b.Value.RawMeta.(map[string]any)
|
||||
if ok {
|
||||
filenames, ok := rawMeta["fileName"].([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not get filename from %v", rawMeta)
|
||||
}
|
||||
fileExtra = &vo.FileExtra{
|
||||
FileNames: make([]string, 0, len(filenames)),
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
fileExtra.FileNames = append(fileExtra.FileNames, filename.(string))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
content = l
|
||||
case vo.VariableTypeInteger:
|
||||
switch content.(type) {
|
||||
case string:
|
||||
@ -268,13 +304,27 @@ func CanvasBlockInputToFieldInfo(b *vo.BlockInput, path einoCompose.FieldPath, p
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported variable type for boolean: %s", b.Type)
|
||||
}
|
||||
case vo.VariableTypeString:
|
||||
if isFileAssistType(b.AssistType) {
|
||||
rawMeta, ok := b.Value.RawMeta.(map[string]any)
|
||||
if ok {
|
||||
filename, ok := rawMeta["fileName"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not get filename from %v", rawMeta)
|
||||
}
|
||||
fileExtra = &vo.FileExtra{
|
||||
FileName: ptr.Of(filename),
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
return []*vo.FieldInfo{
|
||||
{
|
||||
Path: path,
|
||||
Source: vo.FieldSource{
|
||||
Val: content,
|
||||
Val: content,
|
||||
FileExtra: fileExtra,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
@ -466,8 +516,8 @@ func SetInputsForNodeSchema(n *vo.Node, ns *schema.NodeSchema) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns.AddInputSource(sources...)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -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,344 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 180,
|
||||
"y": 26.700000000000003
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"description": "工作流的起始节点,用于设定启动工作流需要的信息",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg",
|
||||
"subTitle": "",
|
||||
"title": "开始"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"assistType": 6,
|
||||
"name": "f",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "fs",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"assistType": 2
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"trigger_parameters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 2020,
|
||||
"y": 13.700000000000003
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"description": "工作流的最终节点,用于返回工作流运行后的结果信息",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg",
|
||||
"subTitle": "",
|
||||
"title": "结束"
|
||||
},
|
||||
"inputs": {
|
||||
"terminatePlan": "returnVariables",
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "output",
|
||||
"input": {
|
||||
"type": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"assistType": 2
|
||||
},
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "fs"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 104
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "filename",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "129362",
|
||||
"name": "fileName"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "191034",
|
||||
"type": "5",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1532.6813186813188,
|
||||
"y": -19.285714285714285
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"title": "代码",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Code-v2.jpg",
|
||||
"description": "编写代码,处理输入变量来生成返回值",
|
||||
"mainColor": "#00B2B2",
|
||||
"subTitle": "代码"
|
||||
},
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "input",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "129362",
|
||||
"name": "fileName"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"code": "# 在这里,您可以通过 'args' 获取节点中的输入变量,并通过 'ret' 输出结果\n# 'args' 已经被正确地注入到环境中\n# 下面是一个示例,首先获取节点的全部输入参数params,其次获取其中参数名为'input'的值:\n# params = args.params; \n# input = params['input'];\n# 下面是一个示例,输出一个包含多种数据类型的 'ret' 对象:\n# ret: Output = { \"name\": '小明', \"hobbies\": [\"看书\", \"旅游\"] };\n\nasync def main(args: Args) -> Output:\n params = args.params\n # 构建输出对象\n ret: Output = {\n \"key0\": params['input'] + params['input'], # 拼接两次入参 input 的值\n \"key1\": [\"hello\", \"world\"], # 输出一个数组\n \"key2\": { # 输出一个Object \n \"key21\": \"hi\"\n },\n }\n return ret",
|
||||
"language": 3,
|
||||
"settingOnError": {
|
||||
"processType": 1,
|
||||
"timeoutMs": 60000,
|
||||
"retryTimes": 0
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "key0"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "key1",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"name": "key2",
|
||||
"schema": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "key21"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "129362",
|
||||
"type": "27",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 640,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"title": "知识库写入",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-KnowledgeWriting-v2.jpg",
|
||||
"description": "写入节点可以添加 文本类型 的知识库,仅可以添加一个知识库",
|
||||
"mainColor": "#FF811A",
|
||||
"subTitle": "知识库写入"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "documentId"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "fileName"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "fileUrl"
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "knowledge",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"assistType": 1,
|
||||
"value": {
|
||||
"type": "literal",
|
||||
"content": "http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/f34d246d-7179-4f0d-856d-5d4c135ffee5.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T074330Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=2208774d8ffc6f116c3f9851a184d851fbcb745ff78cd001359a873f19f6dad4&x-wf-file_name=%E5%8C%97%E4%BA%AC%E6%97%85%E6%B8%B8%E6%99%AF%E7%82%B9.txt",
|
||||
"rawMeta": {
|
||||
"fileName": "北京旅游景点.txt",
|
||||
"type": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"datasetParam": [
|
||||
{
|
||||
"name": "datasetList",
|
||||
"input": {
|
||||
"type": "list",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "literal",
|
||||
"content": ["7548363002819379200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"strategyParam": {
|
||||
"parsingStrategy": {
|
||||
"parsingType": "fast"
|
||||
},
|
||||
"chunkStrategy": {
|
||||
"chunkType": "default"
|
||||
},
|
||||
"indexStrategy": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "164528",
|
||||
"type": "27",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1100,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"title": "知识库写入_1",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-KnowledgeWriting-v2.jpg",
|
||||
"description": "写入节点可以添加 文本类型 的知识库,仅可以添加一个知识库",
|
||||
"mainColor": "#FF811A",
|
||||
"subTitle": "知识库写入"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "documentId"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "fileName"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "fileUrl"
|
||||
}
|
||||
],
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "knowledge",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"assistType": 1,
|
||||
"value": {
|
||||
"type": "literal",
|
||||
"content": "http://coze.fanlv.fun:8889/opencoze/tos-cn-i-v4nquku3lp/b84062e4-d79e-4b3d-bf42-eaab754250df.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20250910%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250910T075358Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=346934ac110db0153afc1759c372e856afc9c5298c8c72e1210bba0c979f84cb&x-wf-file_name=%E5%8C%97%E4%BA%AC%E6%97%85%E6%B8%B8%E6%99%AF%E7%82%B9.txt",
|
||||
"rawMeta": {
|
||||
"fileName": "北京旅游景点.txt",
|
||||
"type": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"datasetParam": [
|
||||
{
|
||||
"name": "datasetList",
|
||||
"input": {
|
||||
"type": "list",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "literal",
|
||||
"content": ["7548363002819379200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"strategyParam": {
|
||||
"parsingStrategy": {
|
||||
"parsingType": "fast"
|
||||
},
|
||||
"chunkStrategy": {
|
||||
"chunkType": "default"
|
||||
},
|
||||
"indexStrategy": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "129362"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "191034",
|
||||
"targetNodeID": "900001"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "164528",
|
||||
"targetNodeID": "191034"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "129362",
|
||||
"targetNodeID": "164528"
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@ -85,6 +85,7 @@ func init() {
|
||||
_ = compose.RegisterSerializableType[*vo.TypeInfo]("type_info")
|
||||
_ = compose.RegisterSerializableType[vo.DataType]("data_type")
|
||||
_ = compose.RegisterSerializableType[vo.FileSubType]("file_sub_type")
|
||||
_ = compose.RegisterSerializableType[*workflowModel.FileInfo]("file_info")
|
||||
}
|
||||
|
||||
func (s *State) GetNodeCtx(key vo.NodeKey) (*execute.Context, bool, error) {
|
||||
|
||||
@ -27,17 +27,17 @@ import (
|
||||
|
||||
func TestAddFieldMappingsWithDeduplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
name string
|
||||
initialCarryOvers map[vo.NodeKey][]*compose.FieldMapping
|
||||
fromNodeKey vo.NodeKey
|
||||
fieldMappings []*compose.FieldMapping
|
||||
expectedCount int
|
||||
description string
|
||||
fromNodeKey vo.NodeKey
|
||||
fieldMappings []*compose.FieldMapping
|
||||
expectedCount int
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "empty_carry_overs",
|
||||
name: "empty_carry_overs",
|
||||
initialCarryOvers: make(map[vo.NodeKey][]*compose.FieldMapping),
|
||||
fromNodeKey: "node1",
|
||||
fromNodeKey: "node1",
|
||||
fieldMappings: []*compose.FieldMapping{
|
||||
compose.MapFieldPaths(compose.FieldPath{"input1"}, compose.FieldPath{"output1"}),
|
||||
compose.MapFieldPaths(compose.FieldPath{"input2"}, compose.FieldPath{"output2"}),
|
||||
@ -94,7 +94,7 @@ func TestAddFieldMappingsWithDeduplication(t *testing.T) {
|
||||
description: "should not add any mappings when all are duplicates",
|
||||
},
|
||||
{
|
||||
name: "new_node_key",
|
||||
name: "new_node_key",
|
||||
initialCarryOvers: map[vo.NodeKey][]*compose.FieldMapping{
|
||||
"node1": {
|
||||
compose.MapFieldPaths(compose.FieldPath{"input1"}, compose.FieldPath{"output1"}),
|
||||
@ -109,7 +109,7 @@ func TestAddFieldMappingsWithDeduplication(t *testing.T) {
|
||||
description: "should add all mappings for new node key",
|
||||
},
|
||||
{
|
||||
name: "empty_field_mappings",
|
||||
name: "empty_field_mappings",
|
||||
initialCarryOvers: map[vo.NodeKey][]*compose.FieldMapping{
|
||||
"node1": {
|
||||
compose.MapFieldPaths(compose.FieldPath{"input1"}, compose.FieldPath{"output1"}),
|
||||
|
||||
@ -25,6 +25,8 @@ import (
|
||||
"github.com/cloudwego/eino/components/tool"
|
||||
einoCompose "github.com/cloudwego/eino/compose"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
|
||||
wf "github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
@ -91,6 +93,9 @@ func (wt *workflowTool) prepare(ctx context.Context, rInfo *entity.ResumeRequest
|
||||
lastEventChan <-chan *execute.Event, callOpts []einoCompose.Option, err error) {
|
||||
cfg := execute.GetExecuteConfig(opts...)
|
||||
|
||||
cfg.InputFileFields = slices.ToMap(wt.sc.GetAllNodesInputFileFields(ctx), func(e *workflowModel.FileInfo) (string, *workflowModel.FileInfo) {
|
||||
return e.FileURL, e
|
||||
})
|
||||
var runOpts []WorkflowRunnerOption
|
||||
if rInfo != nil && !rInfo.Resumed {
|
||||
runOpts = append(runOpts, WithResumeReq(rInfo))
|
||||
@ -121,12 +126,19 @@ func (wt *workflowTool) prepare(ctx context.Context, rInfo *entity.ResumeRequest
|
||||
if entryNode == nil {
|
||||
panic("entry node not found in tool workflow")
|
||||
}
|
||||
input, ws, err = nodes.ConvertInputs(ctx, input, entryNode.OutputTypes)
|
||||
collectFileFields := make(map[string]*workflowModel.FileInfo, 0)
|
||||
input, ws, err = nodes.ConvertInputs(ctx, input, entryNode.OutputTypes, nodes.WithCollectFileFields(collectFileFields))
|
||||
if err != nil {
|
||||
return
|
||||
} else if ws != nil {
|
||||
logs.CtxWarnf(ctx, "convert inputs warnings: %v", *ws)
|
||||
}
|
||||
|
||||
if len(collectFileFields) > 0 {
|
||||
for k, v := range collectFileFields {
|
||||
cfg.InputFileFields[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelCtx, executeID, callOpts, lastEventChan, err = NewWorkflowRunner(wt.wfEntity.GetBasic(), wt.sc, cfg, runOpts...).Prepare(ctx)
|
||||
|
||||
@ -148,7 +148,7 @@ func (ch *ConversationHistory) Invoke(ctx context.Context, input map[string]any)
|
||||
runIDs, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, &crossmessage.GetLatestRunIDsRequest{
|
||||
ConversationID: conversationID,
|
||||
UserID: userID,
|
||||
AppID: *appID,
|
||||
BizID: *appID,
|
||||
Rounds: rounds,
|
||||
InitRunID: initRunID,
|
||||
SectionID: sectionID,
|
||||
|
||||
@ -109,7 +109,7 @@ func (c *CreateConversation) Invoke(ctx context.Context, input map[string]any) (
|
||||
|
||||
if existed {
|
||||
cID, _, existed, err := workflow.GetRepository().GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{
|
||||
AppID: ptr.From(appID),
|
||||
BizID: ptr.From(appID),
|
||||
TemplateID: template.TemplateID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
@ -125,7 +125,7 @@ func (c *CreateConversation) Invoke(ctx context.Context, input map[string]any) (
|
||||
}
|
||||
|
||||
cID, _, existed, err := workflow.GetRepository().GetOrCreateDynamicConversation(ctx, env, conversationIDGenerator, &vo.CreateDynamicConversation{
|
||||
AppID: ptr.From(appID),
|
||||
BizID: ptr.From(appID),
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
Name: conversationName,
|
||||
|
||||
@ -98,7 +98,7 @@ func (c *CreateMessage) getConversationIDByName(ctx context.Context, env vo.Env,
|
||||
var conversationID int64
|
||||
if isExist {
|
||||
cID, _, _, err := workflow.GetRepository().GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{
|
||||
AppID: ptr.From(appID),
|
||||
BizID: ptr.From(appID),
|
||||
TemplateID: template.TemplateID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
@ -150,7 +150,7 @@ func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[s
|
||||
|
||||
var conversationID int64
|
||||
var err error
|
||||
var resolvedAppID int64
|
||||
var bizID int64
|
||||
if appID == nil {
|
||||
if conversationName != "Default" {
|
||||
return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, errors.New("conversation node only allow in application"))
|
||||
@ -167,13 +167,13 @@ func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[s
|
||||
}, nil
|
||||
}
|
||||
conversationID = *execCtx.ExeCfg.ConversationID
|
||||
resolvedAppID = *agentID
|
||||
bizID = *agentID
|
||||
} else {
|
||||
conversationID, err = c.getConversationIDByName(ctx, env, appID, version, conversationName, userID, connectorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAppID = *appID
|
||||
bizID = *appID
|
||||
}
|
||||
|
||||
if conversationID == 0 {
|
||||
@ -209,7 +209,7 @@ func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[s
|
||||
if role == "user" {
|
||||
// For user messages, always create a new run and store the ID in the context.
|
||||
runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{
|
||||
AgentID: resolvedAppID,
|
||||
AgentID: bizID,
|
||||
ConversationID: conversationID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
ConnectorID: connectorID,
|
||||
@ -244,7 +244,7 @@ func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[s
|
||||
runIDs, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, &crossmessage.GetLatestRunIDsRequest{
|
||||
ConversationID: conversationID,
|
||||
UserID: userID,
|
||||
AppID: resolvedAppID,
|
||||
BizID: bizID,
|
||||
Rounds: 1,
|
||||
})
|
||||
if err != nil {
|
||||
@ -254,7 +254,7 @@ func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[s
|
||||
runID = runIDs[0]
|
||||
} else {
|
||||
runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{
|
||||
AgentID: resolvedAppID,
|
||||
AgentID: bizID,
|
||||
ConversationID: conversationID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
ConnectorID: connectorID,
|
||||
@ -273,7 +273,7 @@ func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[s
|
||||
Content: content,
|
||||
ContentType: model.ContentType("text"),
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
AgentID: resolvedAppID,
|
||||
AgentID: bizID,
|
||||
RunID: runID,
|
||||
SectionID: sectionID,
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ func (m *MessageList) Invoke(ctx context.Context, input map[string]any) (map[str
|
||||
|
||||
var conversationID int64
|
||||
var err error
|
||||
var resolvedAppID int64
|
||||
var bizID int64
|
||||
if appID == nil {
|
||||
if conversationName != "Default" {
|
||||
return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, errors.New("conversation node only allow in application"))
|
||||
@ -129,18 +129,18 @@ func (m *MessageList) Invoke(ctx context.Context, input map[string]any) (map[str
|
||||
}, nil
|
||||
}
|
||||
conversationID = *execCtx.ExeCfg.ConversationID
|
||||
resolvedAppID = *agentID
|
||||
bizID = *agentID
|
||||
} else {
|
||||
conversationID, err = m.getConversationIDByName(ctx, env, appID, version, conversationName, userID, connectorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAppID = *appID
|
||||
bizID = *appID
|
||||
}
|
||||
|
||||
req := &crossmessage.MessageListRequest{
|
||||
UserID: userID,
|
||||
AppID: resolvedAppID,
|
||||
BizID: bizID,
|
||||
ConversationID: conversationID,
|
||||
}
|
||||
|
||||
|
||||
@ -20,9 +20,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
@ -127,9 +130,23 @@ func ConvertInputs(ctx context.Context, in map[string]any, tInfo map[string]*vo.
|
||||
}
|
||||
|
||||
type convertOptions struct {
|
||||
skipUnknownFields bool
|
||||
failFast bool
|
||||
skipRequireCheck bool
|
||||
skipUnknownFields bool
|
||||
failFast bool
|
||||
skipRequireCheck bool
|
||||
collectFileFields map[string]*workflowModel.FileInfo
|
||||
notNeedTrimQueryFileName bool
|
||||
}
|
||||
|
||||
func WithCollectFileFields(fs map[string]*workflowModel.FileInfo) ConvertOption {
|
||||
return func(o *convertOptions) {
|
||||
o.collectFileFields = fs
|
||||
}
|
||||
}
|
||||
|
||||
func WithNotNeedTrimQueryFileName(b bool) ConvertOption {
|
||||
return func(o *convertOptions) {
|
||||
o.notNeedTrimQueryFileName = b
|
||||
}
|
||||
}
|
||||
|
||||
type ConvertOption func(*convertOptions)
|
||||
@ -161,6 +178,23 @@ func Convert(ctx context.Context, in any, path string, t *vo.TypeInfo, opts ...C
|
||||
return convert(ctx, in, path, t, options)
|
||||
}
|
||||
|
||||
func adaptorFileURL(in string) (string, *workflowModel.FileInfo, error) {
|
||||
u, err := url.Parse(in)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
query := u.Query()
|
||||
fileName := query.Get("x-wf-file_name")
|
||||
fileInfo := &workflowModel.FileInfo{
|
||||
FileName: fileName,
|
||||
FileExtension: filepath.Ext(fileName),
|
||||
}
|
||||
query.Del("x-wf-file_name")
|
||||
u.RawQuery = query.Encode()
|
||||
fileInfo.FileURL = u.String()
|
||||
return u.String(), fileInfo, nil
|
||||
}
|
||||
|
||||
func convert(ctx context.Context, in any, path string, t *vo.TypeInfo, options *convertOptions) (
|
||||
any, *ConversionWarnings, error) {
|
||||
if in == nil { // nil is valid for ALL types
|
||||
@ -168,8 +202,28 @@ func convert(ctx context.Context, in any, path string, t *vo.TypeInfo, options *
|
||||
}
|
||||
|
||||
switch t.Type {
|
||||
case vo.DataTypeString, vo.DataTypeFile, vo.DataTypeTime:
|
||||
case vo.DataTypeString, vo.DataTypeTime:
|
||||
return convertToString(ctx, in, path, options)
|
||||
case vo.DataTypeFile:
|
||||
ret, warns, err := convertToString(ctx, in, path, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if warns != nil {
|
||||
return ret, warns, nil
|
||||
}
|
||||
|
||||
fileURL, fileInfo, err := adaptorFileURL(ret.(string))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if options.collectFileFields != nil {
|
||||
options.collectFileFields[fileInfo.FileURL] = fileInfo
|
||||
}
|
||||
if options.notNeedTrimQueryFileName {
|
||||
return ret, nil, nil
|
||||
}
|
||||
return fileURL, nil, nil
|
||||
case vo.DataTypeInteger:
|
||||
return convertToInt64(ctx, in, path, options)
|
||||
case vo.DataTypeNumber:
|
||||
|
||||
@ -82,7 +82,9 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*
|
||||
return nil, fmt.Errorf("exit node's content value type must be %s, got %s", vo.BlockInputValueTypeLiteral, content.Value.Type)
|
||||
}
|
||||
|
||||
c.Template = content.Value.Content.(string)
|
||||
if content.Value.Content != nil {
|
||||
c.Template = content.Value.Content.(string)
|
||||
}
|
||||
}
|
||||
|
||||
if n.Data.Inputs.TerminatePlan == nil {
|
||||
|
||||
@ -20,8 +20,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
@ -31,6 +32,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/convert"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/document/parser"
|
||||
@ -125,7 +127,7 @@ func (k *Indexer) Invoke(ctx context.Context, input map[string]any) (map[string]
|
||||
return nil, errors.New("knowledge is required")
|
||||
}
|
||||
|
||||
fileName, ext, err := parseToFileNameAndFileExtension(fileURL)
|
||||
fileName, ext, err := parseToFileNameAndFileExtension(ctx, fileURL)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -153,23 +155,25 @@ func (k *Indexer) Invoke(ctx context.Context, input map[string]any) (map[string]
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseToFileNameAndFileExtension(fileURL string) (string, parser.FileExtension, error) {
|
||||
|
||||
u, err := url.Parse(fileURL)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
func parseToFileNameAndFileExtension(ctx context.Context, fileURL string) (string, parser.FileExtension, error) {
|
||||
inputFileFields := execute.GetExeCtx(ctx).ExeCfg.InputFileFields
|
||||
fileInfo, ok := inputFileFields[fileURL]
|
||||
if !ok {
|
||||
u, err := url.Parse(fileURL)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
fileExt := strings.TrimPrefix(strings.ToLower(filepath.Ext(u.Path)), ".")
|
||||
ext, support := parser.ValidateFileExtension(fileExt)
|
||||
if !support {
|
||||
return "", "", fmt.Errorf("unsupported file type: %s", fileExt)
|
||||
}
|
||||
return u.Path, ext, nil
|
||||
}
|
||||
|
||||
fileName := u.Query().Get("x-wf-file_name")
|
||||
if len(fileName) == 0 {
|
||||
return "", "", errors.New("file name is required")
|
||||
}
|
||||
|
||||
fileExt := strings.ToLower(strings.TrimPrefix(filepath.Ext(fileName), "."))
|
||||
|
||||
ext, support := parser.ValidateFileExtension(fileExt)
|
||||
ext, support := parser.ValidateFileExtension(strings.ToLower(strings.TrimPrefix(fileInfo.FileExtension, ".")))
|
||||
if !support {
|
||||
return "", "", fmt.Errorf("unsupported file type: %s", fileExt)
|
||||
return "", "", fmt.Errorf("unsupported file type: %s", fileInfo.FileExtension)
|
||||
}
|
||||
return fileName, ext, nil
|
||||
return fileInfo.FileName, ext, nil
|
||||
|
||||
}
|
||||
|
||||
@ -100,9 +100,9 @@ const (
|
||||
ReasoningOutputKey = "reasoning_content"
|
||||
)
|
||||
|
||||
const knowledgeUserPromptTemplate = `根据引用的内容回答问题:
|
||||
1.如果引用的内容里面包含 <img src=""> 的标签, 标签里的 src 字段表示图片地址, 需要在回答问题的时候展示出去, 输出格式为"" 。
|
||||
2.如果引用的内容不包含 <img src=""> 的标签, 你回答问题时不需要展示图片 。
|
||||
const knowledgeUserPromptTemplate = `根据引用的内容回答问题:
|
||||
1.如果引用的内容里面包含 <img src=""> 的标签, 标签里的 src 字段表示图片地址, 需要在回答问题的时候展示出去, 输出格式为"" 。
|
||||
2.如果引用的内容不包含 <img src=""> 的标签, 你回答问题时不需要展示图片 。
|
||||
例如:
|
||||
如果内容为<img src="https://example.com/image.jpg">一只小猫,你的输出应为:。
|
||||
如果内容为<img src="https://example.com/image1.jpg">一只小猫 和 <img src="https://example.com/image2.jpg">一只小狗 和 <img src="https://example.com/image3.jpg">一只小牛,你的输出应为: 和  和 
|
||||
@ -290,7 +290,7 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*
|
||||
c.AssociateStartNodeUserInputFields = make(map[string]struct{})
|
||||
for _, info := range ns.InputSources {
|
||||
if len(info.Path) == 1 && info.Source.Ref != nil && info.Source.Ref.FromNodeKey == entity.EntryNodeKey {
|
||||
if compose.FromFieldPath(info.Source.Ref.FromPath).Equals(compose.FromField("USER_INPUT")) {
|
||||
if compose.FromFieldPath(info.Source.Ref.FromPath).Equals(compose.FromField(vo.UserInputKey)) {
|
||||
c.AssociateStartNodeUserInputFields[info.Path[0]] = struct{}{}
|
||||
}
|
||||
}
|
||||
@ -1115,7 +1115,7 @@ func (l *LLM) handleInterrupt(ctx context.Context, err error, resumingEvent *ent
|
||||
NodeKey: c.NodeKey,
|
||||
NodeType: entity.NodeTypeLLM,
|
||||
NodeTitle: c.NodeName,
|
||||
NodeIcon: entity.NodeMetaByNodeType(entity.NodeTypeLLM).IconURL,
|
||||
NodeIcon: entity.NodeMetaByNodeType(entity.NodeTypeLLM).IconURI,
|
||||
EventType: entity.InterruptEventLLM,
|
||||
}
|
||||
|
||||
|
||||
@ -192,8 +192,3 @@ type StreamGenerator interface {
|
||||
FieldStreamType(path compose.FieldPath, ns *schema.NodeSchema,
|
||||
sc *schema.WorkflowSchema) (schema.FieldStreamType, error)
|
||||
}
|
||||
|
||||
type ChatHistoryAware interface {
|
||||
ChatHistoryEnabled() bool
|
||||
ChatHistoryRounds() int64
|
||||
}
|
||||
|
||||
@ -343,14 +343,14 @@ const (
|
||||
你是一个参数提取 agent,你的工作是从用户的回答中提取出多个字段的值,每个字段遵循以下规则
|
||||
# 字段说明
|
||||
%s
|
||||
## 输出要求
|
||||
- 严格以 json 格式返回答案。
|
||||
- 严格确保答案采用有效的 JSON 格式。
|
||||
- 按照字段说明提取出字段的值,将已经提取到的字段放在 fields 字段
|
||||
- 对于未提取到的<必填字段>生成一个新的追问问题question
|
||||
- 确保在追问问题中只包含所有未提取的<必填字段>
|
||||
- 不要重复问之前问过的问题
|
||||
- 问题的语种请和用户的输入保持一致,如英文、中文等
|
||||
## 输出要求
|
||||
- 严格以 json 格式返回答案。
|
||||
- 严格确保答案采用有效的 JSON 格式。
|
||||
- 按照字段说明提取出字段的值,将已经提取到的字段放在 fields 字段
|
||||
- 对于未提取到的<必填字段>生成一个新的追问问题question
|
||||
- 确保在追问问题中只包含所有未提取的<必填字段>
|
||||
- 不要重复问之前问过的问题
|
||||
- 问题的语种请和用户的输入保持一致,如英文、中文等
|
||||
- 输出按照下面结构体格式返回,包含提取到的字段或者追问的问题
|
||||
- 不要回复和提取无关的问题
|
||||
type Output struct {
|
||||
@ -359,7 +359,7 @@ question string // Follow-up question for the next round
|
||||
}`
|
||||
extractUserPromptSuffix = `
|
||||
- 严格以 json 格式返回答案。
|
||||
- 严格确保答案采用有效的 JSON 格式。
|
||||
- 严格确保答案采用有效的 JSON 格式。
|
||||
- - 必填字段没有获取全则继续追问
|
||||
- 必填字段: %s
|
||||
%s
|
||||
@ -728,7 +728,7 @@ func (q *QuestionAnswer) interrupt(ctx context.Context, newQuestion string, choi
|
||||
NodeKey: q.nodeKey,
|
||||
NodeType: entity.NodeTypeQuestionAnswer,
|
||||
NodeTitle: q.nodeMeta.Name,
|
||||
NodeIcon: q.nodeMeta.IconURL,
|
||||
NodeIcon: q.nodeMeta.IconURI,
|
||||
InterruptData: interruptData,
|
||||
EventType: entity.InterruptEventQuestion,
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ func (i *InputReceiver) Invoke(ctx context.Context, _ map[string]any) (map[strin
|
||||
NodeKey: i.nodeKey,
|
||||
NodeType: entity.NodeTypeInputReceiver,
|
||||
NodeTitle: i.nodeMeta.Name,
|
||||
NodeIcon: i.nodeMeta.IconURL,
|
||||
NodeIcon: i.nodeMeta.IconURI,
|
||||
InterruptData: i.interruptData,
|
||||
EventType: entity.InterruptEventInput,
|
||||
})
|
||||
|
||||
@ -432,7 +432,7 @@ func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env
|
||||
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
ret, err := appDynamicConversationDraft.WithContext(ctx).Where(
|
||||
appDynamicConversationDraft.AppID.Eq(meta.AppID),
|
||||
appDynamicConversationDraft.AppID.Eq(meta.BizID),
|
||||
appDynamicConversationDraft.ConnectorID.Eq(meta.ConnectorID),
|
||||
appDynamicConversationDraft.UserID.Eq(meta.UserID),
|
||||
appDynamicConversationDraft.Name.Eq(meta.Name),
|
||||
@ -452,7 +452,7 @@ func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
conv, err := idGen(ctx, meta.BizID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
@ -464,7 +464,7 @@ func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env
|
||||
|
||||
err = r.query.AppDynamicConversationDraft.WithContext(ctx).Create(&model.AppDynamicConversationDraft{
|
||||
ID: id,
|
||||
AppID: meta.AppID,
|
||||
AppID: meta.BizID,
|
||||
Name: meta.Name,
|
||||
UserID: meta.UserID,
|
||||
ConnectorID: meta.ConnectorID,
|
||||
@ -479,7 +479,7 @@ func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
ret, err := appDynamicConversationOnline.WithContext(ctx).Where(
|
||||
appDynamicConversationOnline.AppID.Eq(meta.AppID),
|
||||
appDynamicConversationOnline.AppID.Eq(meta.BizID),
|
||||
appDynamicConversationOnline.ConnectorID.Eq(meta.ConnectorID),
|
||||
appDynamicConversationOnline.UserID.Eq(meta.UserID),
|
||||
appDynamicConversationOnline.Name.Eq(meta.Name),
|
||||
@ -498,7 +498,7 @@ func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
conv, err := idGen(ctx, meta.BizID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
@ -509,7 +509,7 @@ func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env
|
||||
|
||||
err = r.query.AppDynamicConversationOnline.WithContext(ctx).Create(&model.AppDynamicConversationOnline{
|
||||
ID: id,
|
||||
AppID: meta.AppID,
|
||||
AppID: meta.BizID,
|
||||
Name: meta.Name,
|
||||
UserID: meta.UserID,
|
||||
ConnectorID: meta.ConnectorID,
|
||||
@ -586,7 +586,7 @@ func (r *RepositoryImpl) getOrCreateDraftStaticConversation(ctx context.Context,
|
||||
return cs[0].ConversationID, cInfo.SectionID, true, nil
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
conv, err := idGen(ctx, meta.BizID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
@ -627,7 +627,7 @@ func (r *RepositoryImpl) getOrCreateOnlineStaticConversation(ctx context.Context
|
||||
return cs[0].ConversationID, cInfo.SectionID, true, nil
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
conv, err := idGen(ctx, meta.BizID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
@ -841,7 +841,7 @@ func (r *RepositoryImpl) CopyTemplateConversationByAppID(ctx context.Context, ap
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) {
|
||||
func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.Env, bizID, connectorID, conversationID int64) (string, bool, error) {
|
||||
if env == vo.Draft {
|
||||
appStaticConversationDraft := r.query.AppStaticConversationDraft
|
||||
ret, err := appStaticConversationDraft.WithContext(ctx).Where(
|
||||
@ -857,7 +857,7 @@ func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.E
|
||||
appConversationTemplateDraft := r.query.AppConversationTemplateDraft
|
||||
template, err := appConversationTemplateDraft.WithContext(ctx).Where(
|
||||
appConversationTemplateDraft.TemplateID.Eq(ret.TemplateID),
|
||||
appConversationTemplateDraft.AppID.Eq(appID),
|
||||
appConversationTemplateDraft.AppID.Eq(bizID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@ -881,7 +881,7 @@ func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.E
|
||||
appConversationTemplateOnline := r.query.AppConversationTemplateOnline
|
||||
template, err := appConversationTemplateOnline.WithContext(ctx).Where(
|
||||
appConversationTemplateOnline.TemplateID.Eq(ret.TemplateID),
|
||||
appConversationTemplateOnline.AppID.Eq(appID),
|
||||
appConversationTemplateOnline.AppID.Eq(bizID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@ -894,11 +894,11 @@ func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.E
|
||||
return "", false, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetDynamicConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error) {
|
||||
func (r *RepositoryImpl) GetDynamicConversationByID(ctx context.Context, env vo.Env, bizID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error) {
|
||||
if env == vo.Draft {
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
ret, err := appDynamicConversationDraft.WithContext(ctx).Where(
|
||||
appDynamicConversationDraft.AppID.Eq(appID),
|
||||
appDynamicConversationDraft.AppID.Eq(bizID),
|
||||
appDynamicConversationDraft.ConnectorID.Eq(connectorID),
|
||||
appDynamicConversationDraft.ConversationID.Eq(conversationID),
|
||||
).First()
|
||||
@ -918,7 +918,7 @@ func (r *RepositoryImpl) GetDynamicConversationByID(ctx context.Context, env vo.
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
ret, err := appDynamicConversationOnline.WithContext(ctx).Where(
|
||||
appDynamicConversationOnline.AppID.Eq(appID),
|
||||
appDynamicConversationOnline.AppID.Eq(bizID),
|
||||
appDynamicConversationOnline.ConnectorID.Eq(connectorID),
|
||||
appDynamicConversationOnline.ConversationID.Eq(conversationID),
|
||||
).First()
|
||||
|
||||
@ -129,3 +129,8 @@ func (s *NodeSchema) SetOutputType(key string, t *vo.TypeInfo) {
|
||||
func (s *NodeSchema) AddOutputSource(info ...*vo.FieldInfo) {
|
||||
s.OutputSources = append(s.OutputSources, info...)
|
||||
}
|
||||
|
||||
type ChatHistoryAware interface {
|
||||
ChatHistoryEnabled() bool
|
||||
ChatHistoryRounds() int64
|
||||
}
|
||||
|
||||
@ -17,13 +17,19 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
type WorkflowSchema struct {
|
||||
@ -38,6 +44,7 @@ type WorkflowSchema struct {
|
||||
compositeNodes []*CompositeNode // won't serialize this
|
||||
requireCheckPoint bool // won't serialize this
|
||||
requireStreaming bool
|
||||
historyRounds int64
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
@ -69,15 +76,22 @@ func (w *WorkflowSchema) Init() {
|
||||
|
||||
w.doGetCompositeNodes()
|
||||
|
||||
historyRounds := int64(0)
|
||||
for _, node := range w.Nodes {
|
||||
if node.Type == entity.NodeTypeSubWorkflow {
|
||||
node.SubWorkflowSchema.Init()
|
||||
historyRounds = max(historyRounds, node.SubWorkflowSchema.HistoryRounds())
|
||||
if node.SubWorkflowSchema.requireCheckPoint {
|
||||
w.requireCheckPoint = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
chatHistoryAware, ok := node.Configs.(ChatHistoryAware)
|
||||
if ok && chatHistoryAware.ChatHistoryEnabled() {
|
||||
historyRounds = max(historyRounds, chatHistoryAware.ChatHistoryRounds())
|
||||
}
|
||||
|
||||
if rc, ok := node.Configs.(RequireCheckpoint); ok {
|
||||
if rc.RequireCheckpoint() {
|
||||
w.requireCheckPoint = true
|
||||
@ -86,6 +100,7 @@ func (w *WorkflowSchema) Init() {
|
||||
}
|
||||
}
|
||||
|
||||
w.historyRounds = historyRounds
|
||||
w.requireStreaming = w.doRequireStreaming()
|
||||
})
|
||||
}
|
||||
@ -122,6 +137,12 @@ func (w *WorkflowSchema) RequireStreaming() bool {
|
||||
return w.requireStreaming
|
||||
}
|
||||
|
||||
func (w *WorkflowSchema) HistoryRounds() int64 { return w.historyRounds }
|
||||
|
||||
func (w *WorkflowSchema) SetHistoryRounds(historyRounds int64) {
|
||||
w.historyRounds = historyRounds
|
||||
}
|
||||
|
||||
func (w *WorkflowSchema) doGetCompositeNodes() (cNodes []*CompositeNode) {
|
||||
if w.Hierarchy == nil {
|
||||
return nil
|
||||
@ -307,3 +328,65 @@ func (w *WorkflowSchema) doRequireStreaming() bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WorkflowSchema) GetAllNodesInputFileFields(ctx context.Context) []*workflowModel.FileInfo {
|
||||
|
||||
adaptorURL := func(s string) (string, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query := u.Query()
|
||||
query.Del("x-wf-file_name")
|
||||
u.RawQuery = query.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
result := make([]*workflowModel.FileInfo, 0)
|
||||
for _, node := range w.Nodes {
|
||||
for _, source := range node.InputSources {
|
||||
if source.Source.Val != nil && source.Source.FileExtra != nil {
|
||||
fileExtra := source.Source.FileExtra
|
||||
if fileExtra.FileName != nil {
|
||||
fileURL, err := adaptorURL(source.Source.Val.(string))
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "failed to parse adaptorURL for node %v: %v", node.Key, err)
|
||||
continue
|
||||
}
|
||||
result = append(result, &workflowModel.FileInfo{
|
||||
FileName: *fileExtra.FileName,
|
||||
FileURL: fileURL,
|
||||
FileExtension: filepath.Ext(strings.TrimSpace(*fileExtra.FileName)),
|
||||
})
|
||||
source.Source.Val = fileURL
|
||||
|
||||
}
|
||||
if fileExtra.FileNames != nil {
|
||||
vals := source.Source.Val.([]any)
|
||||
for idx, fileName := range fileExtra.FileNames {
|
||||
fileURL := vals[idx].(string)
|
||||
fileURL, err := adaptorURL(fileURL)
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "failed to parse adaptorURL for node %v: %v", node.Key, err)
|
||||
continue
|
||||
}
|
||||
result = append(result, &workflowModel.FileInfo{
|
||||
FileName: fileName,
|
||||
FileURL: fileURL,
|
||||
FileExtension: filepath.Ext(strings.TrimSpace(fileName)),
|
||||
})
|
||||
vals[idx] = fileURL
|
||||
}
|
||||
source.Source.Val = vals
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if node.SubWorkflowSchema != nil {
|
||||
result = append(result, node.SubWorkflowSchema.GetAllNodesInputFileFields(ctx)...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -248,7 +248,7 @@ func (c *conversationImpl) findReplaceWorkflowByConversationName(ctx context.Con
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if v.Name == "CONVERSATION_NAME" && v.DefaultValue == name {
|
||||
if v.Name == vo.ConversationNameKey && v.DefaultValue == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
@ -296,7 +296,7 @@ func (c *conversationImpl) replaceWorkflowsConversationName(ctx context.Context,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Name == "CONVERSATION_NAME" {
|
||||
if v.Name == vo.ConversationNameKey {
|
||||
v.DefaultValue = conversionName
|
||||
}
|
||||
startNode.Data.Outputs[idx] = v
|
||||
@ -351,18 +351,18 @@ func (c *conversationImpl) DeleteDynamicConversation(ctx context.Context, env vo
|
||||
return c.repo.DeleteDynamicConversation(ctx, env, templateID)
|
||||
}
|
||||
|
||||
func (c *conversationImpl) GetOrCreateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, int64, error) {
|
||||
func (c *conversationImpl) GetOrCreateConversation(ctx context.Context, env vo.Env, bizID, connectorID, userID int64, conversationName string) (int64, int64, error) {
|
||||
t, existed, err := c.repo.GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: ptr.Of(appID),
|
||||
AppID: ptr.Of(bizID),
|
||||
Name: ptr.Of(conversationName),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
conversationIDGenerator := workflow.ConversationIDGenerator(func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) {
|
||||
conversationIDGenerator := workflow.ConversationIDGenerator(func(ctx context.Context, bizID int64, userID, connectorID int64) (*conventity.Conversation, error) {
|
||||
return crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{
|
||||
AgentID: appID,
|
||||
AgentID: bizID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
Scene: common.Scene_SceneWorkflow,
|
||||
@ -371,7 +371,7 @@ func (c *conversationImpl) GetOrCreateConversation(ctx context.Context, env vo.E
|
||||
|
||||
if existed {
|
||||
conversationID, sectionID, _, err := c.repo.GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{
|
||||
AppID: appID,
|
||||
BizID: bizID,
|
||||
ConnectorID: connectorID,
|
||||
UserID: userID,
|
||||
TemplateID: t.TemplateID,
|
||||
@ -383,7 +383,7 @@ func (c *conversationImpl) GetOrCreateConversation(ctx context.Context, env vo.E
|
||||
}
|
||||
|
||||
conversationID, sectionID, _, err := c.repo.GetOrCreateDynamicConversation(ctx, env, conversationIDGenerator, &vo.CreateDynamicConversation{
|
||||
AppID: appID,
|
||||
BizID: bizID,
|
||||
ConnectorID: connectorID,
|
||||
UserID: userID,
|
||||
Name: conversationName,
|
||||
@ -465,8 +465,8 @@ func (c *conversationImpl) GetDynamicConversationByName(ctx context.Context, env
|
||||
return c.repo.GetDynamicConversationByName(ctx, env, appID, connectorID, userID, name)
|
||||
}
|
||||
|
||||
func (c *conversationImpl) GetConversationNameByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) {
|
||||
sc, existed, err := c.repo.GetStaticConversationByID(ctx, env, appID, connectorID, conversationID)
|
||||
func (c *conversationImpl) GetConversationNameByID(ctx context.Context, env vo.Env, bizID, connectorID, conversationID int64) (string, bool, error) {
|
||||
sc, existed, err := c.repo.GetStaticConversationByID(ctx, env, bizID, connectorID, conversationID)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
@ -474,7 +474,7 @@ func (c *conversationImpl) GetConversationNameByID(ctx context.Context, env vo.E
|
||||
return sc, true, nil
|
||||
}
|
||||
|
||||
dc, existed, err := c.repo.GetDynamicConversationByID(ctx, env, appID, connectorID, conversationID)
|
||||
dc, existed, err := c.repo.GetDynamicConversationByID(ctx, env, bizID, connectorID, conversationID)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
|
||||
einoCompose "github.com/cloudwego/eino/compose"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
@ -84,6 +86,9 @@ func (i *impl) SyncExecute(ctx context.Context, config workflowModel.ExecuteConf
|
||||
return nil, "", fmt.Errorf("failed to convert canvas to workflow schema: %w", err)
|
||||
}
|
||||
|
||||
config.InputFileFields = slices.ToMap(workflowSC.GetAllNodesInputFileFields(ctx), func(e *workflowModel.FileInfo) (string, *workflowModel.FileInfo) {
|
||||
return e.FileURL, e
|
||||
})
|
||||
var wfOpts []compose.WorkflowOption
|
||||
wfOpts = append(wfOpts, compose.WithIDAsName(wfEntity.ID))
|
||||
if s := execute.GetStaticConfig(); s != nil && s.MaxNodeCountPerWorkflow > 0 {
|
||||
@ -100,6 +105,8 @@ func (i *impl) SyncExecute(ctx context.Context, config workflowModel.ExecuteConf
|
||||
}
|
||||
|
||||
var cOpts []nodes.ConvertOption
|
||||
inputFileFields := make(map[string]*workflowModel.FileInfo)
|
||||
cOpts = append(cOpts, nodes.WithCollectFileFields(inputFileFields), nodes.WithNotNeedTrimQueryFileName(true))
|
||||
if config.InputFailFast {
|
||||
cOpts = append(cOpts, nodes.FailFast())
|
||||
}
|
||||
@ -111,6 +118,10 @@ func (i *impl) SyncExecute(ctx context.Context, config workflowModel.ExecuteConf
|
||||
logs.CtxWarnf(ctx, "convert inputs warnings: %v", *ws)
|
||||
}
|
||||
|
||||
for k, v := range inputFileFields {
|
||||
config.InputFileFields[k] = v
|
||||
}
|
||||
|
||||
inStr, err := sonic.MarshalString(input)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
@ -231,6 +242,10 @@ func (i *impl) AsyncExecute(ctx context.Context, config workflowModel.ExecuteCon
|
||||
return 0, fmt.Errorf("failed to convert canvas to workflow schema: %w", err)
|
||||
}
|
||||
|
||||
config.InputFileFields = slices.ToMap(workflowSC.GetAllNodesInputFileFields(ctx), func(e *workflowModel.FileInfo) (string, *workflowModel.FileInfo) {
|
||||
return e.FileURL, e
|
||||
})
|
||||
|
||||
var wfOpts []compose.WorkflowOption
|
||||
wfOpts = append(wfOpts, compose.WithIDAsName(wfEntity.ID))
|
||||
if s := execute.GetStaticConfig(); s != nil && s.MaxNodeCountPerWorkflow > 0 {
|
||||
@ -249,6 +264,8 @@ func (i *impl) AsyncExecute(ctx context.Context, config workflowModel.ExecuteCon
|
||||
config.CommitID = wfEntity.CommitID
|
||||
|
||||
var cOpts []nodes.ConvertOption
|
||||
inputFileFields := make(map[string]*workflowModel.FileInfo)
|
||||
cOpts = append(cOpts, nodes.WithCollectFileFields(inputFileFields), nodes.WithNotNeedTrimQueryFileName(true))
|
||||
if config.InputFailFast {
|
||||
cOpts = append(cOpts, nodes.FailFast())
|
||||
}
|
||||
@ -260,6 +277,10 @@ func (i *impl) AsyncExecute(ctx context.Context, config workflowModel.ExecuteCon
|
||||
logs.CtxWarnf(ctx, "convert inputs warnings: %v", *ws)
|
||||
}
|
||||
|
||||
for k, v := range inputFileFields {
|
||||
config.InputFileFields[k] = v
|
||||
}
|
||||
|
||||
inStr, err := sonic.MarshalString(input)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -282,6 +303,50 @@ func (i *impl) AsyncExecute(ctx context.Context, config workflowModel.ExecuteCon
|
||||
return executeID, nil
|
||||
}
|
||||
|
||||
func (i *impl) handleHistory(ctx context.Context, config *workflowModel.ExecuteConfig, input map[string]any, historyRounds int64, shouldFetchConversationByName bool) error {
|
||||
if historyRounds <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if shouldFetchConversationByName {
|
||||
var cID, sID, bizID int64
|
||||
var err error
|
||||
if config.AppID != nil {
|
||||
bizID = *config.AppID
|
||||
} else if config.AgentID != nil {
|
||||
bizID = *config.AgentID
|
||||
}
|
||||
for k, v := range input {
|
||||
if k == vo.ConversationNameKey {
|
||||
cName, ok := v.(string)
|
||||
if !ok {
|
||||
return errors.New("CONVERSATION_NAME must be string")
|
||||
}
|
||||
cID, sID, err = i.GetOrCreateConversation(ctx, vo.Draft, bizID, consts.CozeConnectorID, config.Operator, cName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.ConversationID = ptr.Of(cID)
|
||||
config.SectionID = ptr.Of(sID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages, scMessages, err := i.prefetchChatHistory(ctx, *config, historyRounds)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to prefetch chat history: %v", err)
|
||||
}
|
||||
|
||||
if len(messages) > 0 {
|
||||
config.ConversationHistory = messages
|
||||
}
|
||||
|
||||
if len(scMessages) > 0 {
|
||||
config.ConversationHistorySchemaMessages = scMessages
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *impl) AsyncExecuteNode(ctx context.Context, nodeID string, config workflowModel.ExecuteConfig, input map[string]any) (int64, error) {
|
||||
var (
|
||||
err error
|
||||
@ -308,30 +373,6 @@ func (i *impl) AsyncExecuteNode(ctx context.Context, nodeID string, config workf
|
||||
}
|
||||
}
|
||||
|
||||
historyRounds := int64(0)
|
||||
if config.WorkflowMode == workflowapimodel.WorkflowMode_ChatFlow {
|
||||
|
||||
historyRounds, err = getHistoryRoundsFromNode(ctx, wfEntity, nodeID, i.repo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if historyRounds > 0 {
|
||||
messages, scMessages, err := i.prefetchChatHistory(ctx, config, historyRounds)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to prefetch chat history: %v", err)
|
||||
}
|
||||
|
||||
if len(messages) > 0 {
|
||||
config.ConversationHistory = messages
|
||||
}
|
||||
|
||||
if len(scMessages) > 0 {
|
||||
config.ConversationHistorySchemaMessages = scMessages
|
||||
}
|
||||
|
||||
}
|
||||
c := &vo.Canvas{}
|
||||
if err = sonic.UnmarshalString(wfEntity.Canvas, c); err != nil {
|
||||
return 0, fmt.Errorf("failed to unmarshal canvas: %w", err)
|
||||
@ -342,12 +383,27 @@ func (i *impl) AsyncExecuteNode(ctx context.Context, nodeID string, config workf
|
||||
return 0, fmt.Errorf("failed to convert canvas to workflow schema: %w", err)
|
||||
}
|
||||
|
||||
historyRounds := int64(0)
|
||||
if config.WorkflowMode == workflowapimodel.WorkflowMode_ChatFlow {
|
||||
historyRounds = workflowSC.HistoryRounds()
|
||||
}
|
||||
if historyRounds > 0 {
|
||||
if err = i.handleHistory(ctx, &config, input, historyRounds, true); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
config.InputFileFields = slices.ToMap(workflowSC.GetAllNodesInputFileFields(ctx), func(e *workflowModel.FileInfo) (string, *workflowModel.FileInfo) {
|
||||
return e.FileURL, e
|
||||
})
|
||||
|
||||
wf, err := compose.NewWorkflowFromNode(ctx, workflowSC, vo.NodeKey(nodeID), einoCompose.WithGraphName(fmt.Sprintf("%d", wfEntity.ID)))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to create workflow: %w", err)
|
||||
}
|
||||
|
||||
var cOpts []nodes.ConvertOption
|
||||
inputFileFields := make(map[string]*workflowModel.FileInfo)
|
||||
cOpts = append(cOpts, nodes.WithCollectFileFields(inputFileFields), nodes.WithNotNeedTrimQueryFileName(true))
|
||||
if config.InputFailFast {
|
||||
cOpts = append(cOpts, nodes.FailFast())
|
||||
}
|
||||
@ -358,6 +414,9 @@ func (i *impl) AsyncExecuteNode(ctx context.Context, nodeID string, config workf
|
||||
} else if ws != nil {
|
||||
logs.CtxWarnf(ctx, "convert inputs warnings: %v", *ws)
|
||||
}
|
||||
for k, v := range inputFileFields {
|
||||
config.InputFileFields[k] = v
|
||||
}
|
||||
|
||||
if wfEntity.AppID != nil && config.AppID == nil {
|
||||
config.AppID = wfEntity.AppID
|
||||
@ -417,29 +476,6 @@ func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteCo
|
||||
}
|
||||
}
|
||||
|
||||
historyRounds := int64(0)
|
||||
if config.WorkflowMode == workflowapimodel.WorkflowMode_ChatFlow {
|
||||
historyRounds, err = i.calculateMaxChatHistoryRounds(ctx, wfEntity, i.repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if historyRounds > 0 {
|
||||
messages, scMessages, err := i.prefetchChatHistory(ctx, config, historyRounds)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to prefetch chat history: %v", err)
|
||||
}
|
||||
|
||||
if len(messages) > 0 {
|
||||
config.ConversationHistory = messages
|
||||
}
|
||||
|
||||
if len(scMessages) > 0 {
|
||||
config.ConversationHistorySchemaMessages = scMessages
|
||||
}
|
||||
|
||||
}
|
||||
c := &vo.Canvas{}
|
||||
if err = sonic.UnmarshalString(wfEntity.Canvas, c); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal canvas: %w", err)
|
||||
@ -450,7 +486,23 @@ func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteCo
|
||||
return nil, fmt.Errorf("failed to convert canvas to workflow schema: %w", err)
|
||||
}
|
||||
|
||||
historyRounds := int64(0)
|
||||
if config.WorkflowMode == workflowapimodel.WorkflowMode_ChatFlow {
|
||||
historyRounds = workflowSC.HistoryRounds()
|
||||
}
|
||||
|
||||
if historyRounds > 0 {
|
||||
if err = i.handleHistory(ctx, &config, input, historyRounds, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
config.InputFileFields = slices.ToMap(workflowSC.GetAllNodesInputFileFields(ctx), func(e *workflowModel.FileInfo) (string, *workflowModel.FileInfo) {
|
||||
return e.FileURL, e
|
||||
})
|
||||
|
||||
var wfOpts []compose.WorkflowOption
|
||||
|
||||
wfOpts = append(wfOpts, compose.WithIDAsName(wfEntity.ID))
|
||||
if s := execute.GetStaticConfig(); s != nil && s.MaxNodeCountPerWorkflow > 0 {
|
||||
wfOpts = append(wfOpts, compose.WithMaxNodeCount(s.MaxNodeCountPerWorkflow))
|
||||
@ -468,6 +520,8 @@ func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteCo
|
||||
config.CommitID = wfEntity.CommitID
|
||||
|
||||
var cOpts []nodes.ConvertOption
|
||||
inputFileFields := make(map[string]*workflowModel.FileInfo)
|
||||
cOpts = append(cOpts, nodes.WithCollectFileFields(inputFileFields), nodes.WithNotNeedTrimQueryFileName(true))
|
||||
if config.InputFailFast {
|
||||
cOpts = append(cOpts, nodes.FailFast())
|
||||
}
|
||||
@ -478,6 +532,9 @@ func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteCo
|
||||
} else if ws != nil {
|
||||
logs.CtxWarnf(ctx, "convert inputs warnings: %v", *ws)
|
||||
}
|
||||
for k, v := range inputFileFields {
|
||||
config.InputFileFields[k] = v
|
||||
}
|
||||
|
||||
inStr, err := sonic.MarshalString(input)
|
||||
if err != nil {
|
||||
@ -997,20 +1054,6 @@ func (i *impl) checkApplicationWorkflowReleaseVersion(ctx context.Context, appID
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxHistoryRounds int64 = 30
|
||||
|
||||
func (i *impl) calculateMaxChatHistoryRounds(ctx context.Context, wfEntity *entity.Workflow, repo workflow.Repository) (int64, error) {
|
||||
if wfEntity == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
maxRounds, err := getMaxHistoryRoundsRecursively(ctx, wfEntity, repo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return min(maxRounds, maxHistoryRounds), nil
|
||||
}
|
||||
|
||||
func (i *impl) prefetchChatHistory(ctx context.Context, config workflowModel.ExecuteConfig, historyRounds int64) ([]*crossmessage.WfMessage, []*schema.Message, error) {
|
||||
convID := config.ConversationID
|
||||
agentID := config.AgentID
|
||||
@ -1027,11 +1070,11 @@ func (i *impl) prefetchChatHistory(ctx context.Context, config workflowModel.Exe
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var resolvedAppID int64
|
||||
var bizID int64
|
||||
if appID != nil {
|
||||
resolvedAppID = *appID
|
||||
bizID = *appID
|
||||
} else if agentID != nil {
|
||||
resolvedAppID = *agentID
|
||||
bizID = *agentID
|
||||
} else {
|
||||
logs.CtxWarnf(ctx, "AppID and AgentID are both nil, skipping chat history")
|
||||
return nil, nil, nil
|
||||
@ -1039,7 +1082,7 @@ func (i *impl) prefetchChatHistory(ctx context.Context, config workflowModel.Exe
|
||||
|
||||
runIdsReq := &crossmessage.GetLatestRunIDsRequest{
|
||||
ConversationID: *convID,
|
||||
AppID: resolvedAppID,
|
||||
BizID: bizID,
|
||||
UserID: userID,
|
||||
Rounds: historyRounds + 1,
|
||||
SectionID: *sectionID,
|
||||
@ -1048,7 +1091,7 @@ func (i *impl) prefetchChatHistory(ctx context.Context, config workflowModel.Exe
|
||||
runIds, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, runIdsReq)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to get latest run ids: %v", err)
|
||||
return nil, nil, nil
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(runIds) <= 1 {
|
||||
return []*crossmessage.WfMessage{}, []*schema.Message{}, nil
|
||||
@ -1061,7 +1104,7 @@ func (i *impl) prefetchChatHistory(ctx context.Context, config workflowModel.Exe
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to get messages by run ids: %v", err)
|
||||
return nil, nil, nil
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response.Messages, response.SchemaMessages, nil
|
||||
|
||||
286
backend/domain/workflow/service/executable_impl_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,6 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/adaptor"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/convert"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/cache"
|
||||
@ -522,7 +521,7 @@ func isEnableChatHistory(s *schema.NodeSchema) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
chatHistoryAware, ok := s.Configs.(nodes.ChatHistoryAware)
|
||||
chatHistoryAware, ok := s.Configs.(schema.ChatHistoryAware)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@ -2171,15 +2170,15 @@ func (i *impl) adaptToChatFlow(ctx context.Context, wID int64) error {
|
||||
vMap[v.Name] = true
|
||||
}
|
||||
|
||||
if _, ok := vMap["USER_INPUT"]; !ok {
|
||||
if _, ok := vMap[vo.UserInputKey]; !ok {
|
||||
startNode.Data.Outputs = append(startNode.Data.Outputs, &vo.Variable{
|
||||
Name: "USER_INPUT",
|
||||
Name: vo.UserInputKey,
|
||||
Type: vo.VariableTypeString,
|
||||
})
|
||||
}
|
||||
if _, ok := vMap["CONVERSATION_NAME"]; !ok {
|
||||
if _, ok := vMap[vo.ConversationNameKey]; !ok {
|
||||
startNode.Data.Outputs = append(startNode.Data.Outputs, &vo.Variable{
|
||||
Name: "CONVERSATION_NAME",
|
||||
Name: vo.ConversationNameKey,
|
||||
Type: vo.VariableTypeString,
|
||||
DefaultValue: "Default",
|
||||
})
|
||||
|
||||
@ -22,15 +22,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
wf "github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/adaptor"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/validate"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/variable"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
@ -201,214 +197,3 @@ func isIncremental(prev version, next version) bool {
|
||||
|
||||
return next.Patch > prev.Patch
|
||||
}
|
||||
|
||||
func getMaxHistoryRoundsRecursively(ctx context.Context, wfEntity *entity.Workflow, repo wf.Repository) (int64, error) {
|
||||
visited := make(map[string]struct{})
|
||||
maxRounds := int64(0)
|
||||
err := getMaxHistoryRoundsRecursiveHelper(ctx, wfEntity, repo, visited, &maxRounds)
|
||||
return maxRounds, err
|
||||
}
|
||||
|
||||
func getMaxHistoryRoundsRecursiveHelper(ctx context.Context, wfEntity *entity.Workflow, repo wf.Repository, visited map[string]struct{}, maxRounds *int64) error {
|
||||
visitedKey := fmt.Sprintf("%d:%s", wfEntity.ID, wfEntity.GetVersion())
|
||||
if _, ok := visited[visitedKey]; ok {
|
||||
return nil
|
||||
}
|
||||
visited[visitedKey] = struct{}{}
|
||||
|
||||
var canvas vo.Canvas
|
||||
if err := sonic.UnmarshalString(wfEntity.Canvas, &canvas); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal canvas for workflow %d: %w", wfEntity.ID, err)
|
||||
}
|
||||
|
||||
return collectMaxHistoryRounds(ctx, canvas.Nodes, repo, visited, maxRounds)
|
||||
}
|
||||
|
||||
func collectMaxHistoryRounds(ctx context.Context, nodes []*vo.Node, repo wf.Repository, visited map[string]struct{}, maxRounds *int64) error {
|
||||
for _, node := range nodes {
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if node.Data != nil && node.Data.Inputs != nil && node.Data.Inputs.ChatHistorySetting != nil && node.Data.Inputs.ChatHistorySetting.EnableChatHistory {
|
||||
if node.Data.Inputs.ChatHistorySetting.ChatHistoryRound > *maxRounds {
|
||||
*maxRounds = node.Data.Inputs.ChatHistorySetting.ChatHistoryRound
|
||||
}
|
||||
} else if node.Type == entity.NodeTypeLLM.IDStr() && node.Data != nil && node.Data.Inputs != nil && node.Data.Inputs.LLMParam != nil {
|
||||
param := node.Data.Inputs.LLMParam
|
||||
bs, _ := sonic.Marshal(param)
|
||||
llmParam := make(vo.LLMParam, 0)
|
||||
if err := sonic.Unmarshal(bs, &llmParam); err != nil {
|
||||
return err
|
||||
}
|
||||
var chatHistoryEnabled bool
|
||||
var chatHistoryRound int64
|
||||
for _, param := range llmParam {
|
||||
switch param.Name {
|
||||
case "enableChatHistory":
|
||||
if val, ok := param.Input.Value.Content.(bool); ok {
|
||||
b := val
|
||||
chatHistoryEnabled = b
|
||||
}
|
||||
case "chatHistoryRound":
|
||||
if strVal, ok := param.Input.Value.Content.(string); ok {
|
||||
int64Val, err := strconv.ParseInt(strVal, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chatHistoryRound = int64Val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chatHistoryEnabled {
|
||||
if chatHistoryRound > *maxRounds {
|
||||
*maxRounds = chatHistoryRound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isSubWorkflow := node.Type == entity.NodeTypeSubWorkflow.IDStr() && node.Data != nil && node.Data.Inputs != nil
|
||||
if isSubWorkflow {
|
||||
workflowIDStr := node.Data.Inputs.WorkflowID
|
||||
if workflowIDStr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
workflowID, err := strconv.ParseInt(workflowIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid workflow ID in sub-workflow node %s: %w", node.ID, err)
|
||||
}
|
||||
|
||||
subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{
|
||||
ID: workflowID,
|
||||
QType: ternary.IFElse(len(node.Data.Inputs.WorkflowVersion) == 0, workflowModel.FromDraft, workflowModel.FromSpecificVersion),
|
||||
Version: node.Data.Inputs.WorkflowVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sub-workflow entity %d: %w", workflowID, err)
|
||||
}
|
||||
|
||||
if err := getMaxHistoryRoundsRecursiveHelper(ctx, subWfEntity, repo, visited, maxRounds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(node.Blocks) > 0 {
|
||||
if err := collectMaxHistoryRounds(ctx, node.Blocks, repo, visited, maxRounds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHistoryRoundsFromNode(ctx context.Context, wfEntity *entity.Workflow, nodeID string, repo wf.Repository) (int64, error) {
|
||||
if wfEntity == nil {
|
||||
return 0, nil
|
||||
}
|
||||
visited := make(map[string]struct{})
|
||||
visitedKey := fmt.Sprintf("%d:%s", wfEntity.ID, wfEntity.GetVersion())
|
||||
if _, ok := visited[visitedKey]; ok {
|
||||
return 0, nil
|
||||
}
|
||||
visited[visitedKey] = struct{}{}
|
||||
maxRounds := int64(0)
|
||||
c := &vo.Canvas{}
|
||||
if err := sonic.UnmarshalString(wfEntity.Canvas, c); err != nil {
|
||||
return 0, fmt.Errorf("failed to unmarshal canvas: %w", err)
|
||||
}
|
||||
var (
|
||||
n *vo.Node
|
||||
nodeFinder func(nodes []*vo.Node) *vo.Node
|
||||
)
|
||||
nodeFinder = func(nodes []*vo.Node) *vo.Node {
|
||||
for i := range nodes {
|
||||
if nodes[i].ID == nodeID {
|
||||
return nodes[i]
|
||||
}
|
||||
if len(nodes[i].Blocks) > 0 {
|
||||
if n := nodeFinder(nodes[i].Blocks); n != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
n = nodeFinder(c.Nodes)
|
||||
if n.Type == entity.NodeTypeLLM.IDStr() {
|
||||
if n.Data == nil || n.Data.Inputs == nil {
|
||||
return 0, nil
|
||||
}
|
||||
param := n.Data.Inputs.LLMParam
|
||||
bs, _ := sonic.Marshal(param)
|
||||
llmParam := make(vo.LLMParam, 0)
|
||||
if err := sonic.Unmarshal(bs, &llmParam); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var chatHistoryEnabled bool
|
||||
var chatHistoryRound int64
|
||||
for _, param := range llmParam {
|
||||
switch param.Name {
|
||||
case "enableChatHistory":
|
||||
if val, ok := param.Input.Value.Content.(bool); ok {
|
||||
b := val
|
||||
chatHistoryEnabled = b
|
||||
}
|
||||
case "chatHistoryRound":
|
||||
if strVal, ok := param.Input.Value.Content.(string); ok {
|
||||
int64Val, err := strconv.ParseInt(strVal, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
chatHistoryRound = int64Val
|
||||
}
|
||||
}
|
||||
}
|
||||
if chatHistoryEnabled {
|
||||
return chatHistoryRound, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if n.Type == entity.NodeTypeIntentDetector.IDStr() || n.Type == entity.NodeTypeKnowledgeRetriever.IDStr() {
|
||||
if n.Data != nil && n.Data.Inputs != nil && n.Data.Inputs.ChatHistorySetting != nil && n.Data.Inputs.ChatHistorySetting.EnableChatHistory {
|
||||
return n.Data.Inputs.ChatHistorySetting.ChatHistoryRound, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if n.Type == entity.NodeTypeSubWorkflow.IDStr() {
|
||||
if n.Data != nil && n.Data.Inputs != nil {
|
||||
workflowIDStr := n.Data.Inputs.WorkflowID
|
||||
if workflowIDStr == "" {
|
||||
return 0, nil
|
||||
}
|
||||
workflowID, err := strconv.ParseInt(workflowIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid workflow ID in sub-workflow node %s: %w", n.ID, err)
|
||||
}
|
||||
subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{
|
||||
ID: workflowID,
|
||||
QType: ternary.IFElse(len(n.Data.Inputs.WorkflowVersion) == 0, workflowModel.FromDraft, workflowModel.FromSpecificVersion),
|
||||
Version: n.Data.Inputs.WorkflowVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get sub-workflow entity %d: %w", workflowID, err)
|
||||
}
|
||||
if err := getMaxHistoryRoundsRecursiveHelper(ctx, subWfEntity, repo, visited, &maxRounds); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return maxRounds, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(n.Blocks) > 0 {
|
||||
if err := collectMaxHistoryRounds(ctx, n.Blocks, repo, visited, &maxRounds); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return maxRounds, nil
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@ require (
|
||||
github.com/apache/rocketmq-client-go/v2 v2.1.3-0.20250427084711-67ec50b93040
|
||||
github.com/apache/thrift v0.21.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.54
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.9
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.62
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1
|
||||
github.com/bytedance/gopkg v0.1.3
|
||||
github.com/bytedance/mockey v1.2.14
|
||||
@ -51,7 +51,7 @@ require (
|
||||
github.com/nikolalohinski/gonja v1.5.3 // indirect
|
||||
github.com/nsqio/go-nsq v1.1.0
|
||||
github.com/ollama/ollama v0.9.6
|
||||
github.com/onsi/gomega v1.27.3
|
||||
github.com/onsi/gomega v1.35.1
|
||||
github.com/pingcap/tidb/pkg/parser v0.0.0-20250417044355-c5882b1f6c58
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rbretecher/go-postman-collection v0.9.0
|
||||
@ -71,7 +71,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/image v0.22.0
|
||||
golang.org/x/mod v0.25.0
|
||||
golang.org/x/oauth2 v0.25.0
|
||||
golang.org/x/oauth2 v0.28.0
|
||||
golang.org/x/sync v0.16.0
|
||||
google.golang.org/genai v1.18.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@ -84,26 +84,26 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/auth v0.9.3 // indirect
|
||||
cloud.google.com/go v0.118.3 // indirect
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.4.0 // indirect
|
||||
github.com/avast/retry-go v3.0.0+incompatible // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
|
||||
github.com/aws/smithy-go v1.22.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
@ -146,15 +146,14 @@ require (
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.7.0-rc.1 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
|
||||
github.com/goph/emperror v0.17.2 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
@ -187,7 +186,6 @@ require (
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250620092828-0d508a1dcdde // indirect
|
||||
github.com/milvus-io/milvus/pkg/v2 v2.0.0-20250319085209-5a6b4e56d59e // indirect
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
@ -207,10 +205,10 @@ require (
|
||||
github.com/pingcap/log v1.1.1-0.20221015072633-39906604fb81 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
@ -219,7 +217,7 @@ require (
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/samber/lo v1.27.0 // indirect
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.22.9 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f
|
||||
github.com/smarty/assertions v1.16.0 // indirect
|
||||
@ -249,9 +247,8 @@ require (
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.5 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.5.5 // indirect
|
||||
go.etcd.io/etcd/server/v3 v3.5.5 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect
|
||||
@ -270,8 +267,8 @@ require (
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
@ -280,15 +277,46 @@ require (
|
||||
gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
|
||||
gorm.io/driver/postgres v1.5.11 // indirect
|
||||
gorm.io/hints v1.1.0 // indirect
|
||||
k8s.io/apimachinery v0.28.6 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
k8s.io/apimachinery v0.32.3 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
stathat.com/c/consistent v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require github.com/apache/pulsar-client-go v0.16.0
|
||||
|
||||
require (
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/99designs/keyring v1.2.1 // indirect
|
||||
github.com/AthenZ/athenz v1.12.13 // indirect
|
||||
github.com/DataDog/zstd v1.5.0 // indirect
|
||||
github.com/ardielle/ardielle-go v1.5.2 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.4.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
|
||||
github.com/eino-contrib/jsonschema v1.0.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||
github.com/hamba/avro/v2 v2.26.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
k8s.io/client-go v0.32.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||
)
|
||||
|
||||
221
backend/go.sum
@ -13,10 +13,10 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
|
||||
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
|
||||
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
|
||||
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
|
||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
@ -37,10 +37,20 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||
github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
|
||||
github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/AthenZ/athenz v1.12.13 h1:OhZNqZsoBXNrKBJobeUUEirPDnwt0HRo4kQMIO1UwwQ=
|
||||
github.com/AthenZ/athenz v1.12.13/go.mod h1:XXDXXgaQzXaBXnJX6x/bH4yF6eon2lkyzQZ0z/dxprE=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@ -49,12 +59,16 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo=
|
||||
github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/IBM/sarama v1.45.1 h1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=
|
||||
github.com/IBM/sarama v1.45.1/go.mod h1:qifDhA3VWSrQ1TjSMyxDl3nYL3oX2C83u+G6L79sq4w=
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
@ -78,10 +92,14 @@ github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqR
|
||||
github.com/anthropics/anthropic-sdk-go v1.4.0 h1:fU1jKxYbQdQDiEXCxeW5XZRIOwKevn/PMg8Ay1nnUx0=
|
||||
github.com/anthropics/anthropic-sdk-go v1.4.0/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/pulsar-client-go v0.16.0 h1:SnmGzqcTu6WpK4D6I2Jdwe/VCFkMUk516OiIF3DHqI8=
|
||||
github.com/apache/pulsar-client-go v0.16.0/go.mod h1:ow9PhLoGUY6ncrKOtjnWeJycFnTKOwrIV39j3kNV54M=
|
||||
github.com/apache/rocketmq-client-go/v2 v2.1.3-0.20250427084711-67ec50b93040 h1:c2o4/foDm9LXc3jSmm3SUxVZb5I5KNtztw/bstf836s=
|
||||
github.com/apache/rocketmq-client-go/v2 v2.1.3-0.20250427084711-67ec50b93040/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE=
|
||||
github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/ardielle/ardielle-go v1.5.2 h1:TilHTpHIQJ27R1Tl/iITBzMwiUGSlVfiVhwDNGM3Zj4=
|
||||
github.com/ardielle/ardielle-go v1.5.2/go.mod h1:I4hy1n795cUhaVt/ojz83SNVCYIGsAFAONtv2Dr7HUI=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
@ -96,18 +114,18 @@ github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQ
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.1 h1:JZhGawAyZ/EuJeBtbQYnaoftczcb2drR2Iq36Wgz4sQ=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.1/go.mod h1:7bR2YD5euaxBhzt2y/oDkt3uNRb6tjFp98GlTFueRwk=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.54 h1:4UmqeOqJPvdvASZWrKlhzpRahAulBfyTJQUaYy4+hEI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.54/go.mod h1:RTdfo0P0hbbTxIhmQrOsC/PquBZGabEPnCaxxKRPSnI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 h1:5grmdTdMsovn9kPZPI23Hhvp0ZyNm5cRO+IZFIYiAfw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24/go.mod h1:zqi7TVKTswH3Ozq28PkmBmgzG1tona7mo9G2IJg4Cis=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 h1:XTZZ0I3SZUHAtBLBU6395ad+VOblE0DwQP6MuaNeics=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37/go.mod h1:Pi6ksbniAWVwu2S8pEzcYPyhUkAcLaufxN7PfAUQjBk=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
||||
@ -121,12 +139,12 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 h1:OS2e0SKqsU2Li
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18/go.mod h1:+Yrk+MDGzlNGxCXieljNeWpoZTCQUQVL+Jk9hGGJ8qM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 h1:RkHXU9jP0DptGy7qKI8CBGsUJruWz0v5IgwBa2DwWcU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1/go.mod h1:3xAOf7tdKF+qbb+XpU+EPhNXAdun3Lu1RcDrj8KC24I=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 h1:kuIyu4fTT38Kj7YCC7ouNbVZSSpqkZ+LzIfhCr6Dg+I=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11/go.mod h1:Ro744S4fKiCCuZECXgOi760TiYylUM8ZBf6OGiZzJtY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 h1:l+dgv/64iVlQ3WsBbnn+JSbkj01jIi+SM0wYsj3y/hY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10/go.mod h1:Fzsj6lZEb8AkTE5S68OhcbBqeWPsR8RnGuKPr8Todl8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 h1:BRVDbewN6VZcwr+FBOszDKvYeXY1kJ+GGMCcpghlw0U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9/go.mod h1:f6vjfZER1M17Fokn0IzssOTMT2N8ZSq+7jnNF0tArvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
|
||||
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
@ -140,6 +158,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/bits-and-blooms/bitset v1.4.0 h1:+YZ8ePm+He2pU3dZlIZiOeAKfrBkXi1lSrXJ/Xzgbu8=
|
||||
github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
@ -249,6 +269,12 @@ github.com/cohesion-org/deepseek-go v1.3.2 h1:WTZ/2346KFYca+n+DL5p+Ar1RQxF2w/wGk
|
||||
github.com/cohesion-org/deepseek-go v1.3.2/go.mod h1:bOVyKj38r90UEYZFrmJOzJKPxuAh8sIzHOCnLOpiXeI=
|
||||
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
|
||||
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
|
||||
github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
|
||||
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@ -260,11 +286,15 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@ -277,11 +307,21 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/dimfeld/httptreemux v5.0.1+incompatible h1:Qj3gVcDNoOthBAqftuD596rm4wg/adLLz5xh5CmpiCA=
|
||||
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
|
||||
@ -324,6 +364,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
@ -339,6 +381,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
|
||||
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
|
||||
@ -359,6 +403,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
|
||||
@ -389,9 +435,10 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
|
||||
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
@ -400,6 +447,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@ -414,6 +463,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
@ -426,7 +477,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@ -481,6 +531,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
@ -501,16 +552,20 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
|
||||
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@ -535,6 +590,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||
github.com/hamba/avro/v2 v2.26.0 h1:IaT5l6W3zh7K67sMrT2+RreJyDTllBGVJm4+Hedk9qE=
|
||||
github.com/hamba/avro/v2 v2.26.0/go.mod h1:I8glyswHnpED3Nlx2ZdUe+4LJnCOOyiCzLMno9i/Uu0=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
@ -692,12 +751,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
@ -730,8 +793,6 @@ github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOj
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250620092828-0d508a1dcdde h1:pq2I0uxUR4lfr4OmqvE8QdHj9UML9b1jZu8L3dI2eu8=
|
||||
github.com/meguminnnnnnnnn/go-openai v0.0.0-20250620092828-0d508a1dcdde/go.mod h1:CqSFsV6AkkL2fixd25WYjRAolns+gQrY1x/Cz9c30v8=
|
||||
@ -767,6 +828,20 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -776,7 +851,13 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
@ -795,6 +876,7 @@ github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTf
|
||||
github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
|
||||
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
|
||||
github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
|
||||
@ -811,8 +893,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
|
||||
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
@ -820,9 +902,13 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.27.3 h1:5VwIwnBY3vbBDOJrNtA4rVdiTZCsq9B5F12pvy1Drmk=
|
||||
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@ -881,14 +967,14 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
@ -896,8 +982,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@ -905,8 +991,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=
|
||||
github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
|
||||
@ -945,8 +1031,12 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
|
||||
github.com/shirou/gopsutil/v3 v3.22.9 h1:yibtJhIVEMcdw+tCTbOPiF1VcsuDeTE4utJ8Dm4c5eA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
@ -1011,6 +1101,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tealeg/xlsx/v3 v3.3.13 h1:Zk1Stj11MGRnOYI1st6av/Z2lIXp/jFZomrSWSeJLmY=
|
||||
github.com/tealeg/xlsx/v3 v3.3.13/go.mod h1:KV4FTFtvGy0TBlOivJLZu/YNZk6e0Qtk7eOSglWksuA=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@ -1025,10 +1117,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@ -1099,7 +1189,7 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
@ -1129,13 +1219,13 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
@ -1323,8 +1413,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -1406,13 +1496,13 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -1422,6 +1512,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
@ -1587,10 +1678,10 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
@ -1638,6 +1729,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
@ -1654,6 +1746,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
@ -1703,15 +1796,25 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/apimachinery v0.28.6 h1:RsTeR4z6S07srPg6XYrwXpTJVMXsjPXn0ODakMytSW0=
|
||||
k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA=
|
||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
|
||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
|
||||
stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=
|
||||
|
||||
@ -18,10 +18,15 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrObjectNotFound = errors.New("object not found")
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination ../../../internal/mock/infra/contract/storage/storage_mock.go -package mock -source storage.go Factory
|
||||
type Storage interface {
|
||||
// PutObject puts the object with the specified key.
|
||||
@ -68,10 +73,10 @@ type ListObjectsPaginatedOutput struct {
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Key string
|
||||
LastModified time.Time
|
||||
ETag string
|
||||
Size int64
|
||||
URL string
|
||||
Tagging map[string]string
|
||||
Key string `json:"key"`
|
||||
LastModified time.Time `json:"last_modified"`
|
||||
ETag string `json:"etag"`
|
||||
Size int64 `json:"size"`
|
||||
URL string `json:"url"`
|
||||
Tagging map[string]string `json:"tagging"`
|
||||
}
|
||||
|
||||
@ -74,20 +74,23 @@ func (r *runner) Run(ctx context.Context, request *coderunner.RunRequest) (*code
|
||||
}
|
||||
|
||||
func (r *runner) pythonCmdRun(_ context.Context, code string, params map[string]any) (map[string]any, error) {
|
||||
bs, _ := sonic.Marshal(params)
|
||||
bs, err := sonic.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal params to json, err: %w", err)
|
||||
}
|
||||
cmd := exec.Command(goutil.GetPython3Path(), "-c", fmt.Sprintf(pythonCode, code), string(bs)) // ignore_security_alert RCE
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run python script err: %s, std err: %s", err.Error(), stderr.String())
|
||||
}
|
||||
|
||||
if stderr.String() != "" {
|
||||
return nil, fmt.Errorf("failed to run python script err: %s", stderr.String())
|
||||
}
|
||||
|
||||
ret := make(map[string]any)
|
||||
err = sonic.Unmarshal(stdout.Bytes(), &ret)
|
||||
if err != nil {
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -47,6 +48,7 @@ func ParseMarkdown(config *contract.Config, storage storage.Storage, ocr ocr.OCR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
node := mdParser.Parse(text.NewReader(b))
|
||||
cs := config.ChunkingStrategy
|
||||
ps := config.ParsingStrategy
|
||||
@ -101,13 +103,118 @@ func ParseMarkdown(config *contract.Config, storage storage.Storage, ocr ocr.OCR
|
||||
return text
|
||||
}
|
||||
|
||||
// validateImageURL 验证图片URL的安全性
|
||||
validateImageURL := func(urlString string) error {
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 只允许HTTP/HTTPS
|
||||
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||
return fmt.Errorf("unsupported scheme: %s", parsedURL.Scheme)
|
||||
}
|
||||
|
||||
// 检查域名白名单
|
||||
allowedDomains := []string{
|
||||
"images.unsplash.com",
|
||||
"cdn.example.com",
|
||||
"github.com",
|
||||
"githubusercontent.com",
|
||||
// 可以根据需要添加其他受信任的域名
|
||||
}
|
||||
|
||||
hostname := parsedURL.Hostname()
|
||||
for _, domain := range allowedDomains {
|
||||
if hostname == domain || strings.HasSuffix(hostname, "."+domain) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("domain not allowed: %s", hostname)
|
||||
}
|
||||
|
||||
// isPrivateIPAddress 检查IP地址是否为私有地址
|
||||
isPrivateIPAddress := func(ip net.IP) bool {
|
||||
// 检查私有IP范围
|
||||
privateRanges := []struct {
|
||||
cidr string
|
||||
}{
|
||||
{"10.0.0.0/8"},
|
||||
{"172.16.0.0/12"},
|
||||
{"192.168.0.0/16"},
|
||||
{"127.0.0.0/8"},
|
||||
{"169.254.0.0/16"}, // 链路本地地址
|
||||
{"::1/128"}, // IPv6 loopback
|
||||
{"fc00::/7"}, // IPv6 私有地址
|
||||
}
|
||||
|
||||
for _, r := range privateRanges {
|
||||
_, cidr, _ := net.ParseCIDR(r.cidr)
|
||||
if cidr.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isPrivateIP 检查是否为私有IP地址
|
||||
isPrivateIP := func(host string) bool {
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
// 可能是域名,需要解析
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return true // 解析失败,拒绝访问
|
||||
}
|
||||
|
||||
// 检查所有解析的IP
|
||||
for _, resolvedIP := range ips {
|
||||
if isPrivateIPAddress(resolvedIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return isPrivateIPAddress(ip)
|
||||
}
|
||||
|
||||
downloadImage := func(ctx context.Context, url string) ([]byte, error) {
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
// URL验证
|
||||
if err := validateImageURL(url); err != nil {
|
||||
return nil, fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
// 使用安全的HTTP客户端
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// 禁止访问私有IP
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isPrivateIP(host) {
|
||||
return nil, fmt.Errorf("access to private IP denied: %s", host)
|
||||
}
|
||||
|
||||
return (&net.Dialer{}).DialContext(ctx, network, addr)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 添加安全头
|
||||
req.Header.Set("User-Agent", "CozeStudio/1.0")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to download image: %w", err)
|
||||
@ -118,7 +225,11 @@ func ParseMarkdown(config *contract.Config, storage storage.Storage, ocr ocr.OCR
|
||||
return nil, fmt.Errorf("failed to download image, status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
// 限制响应大小
|
||||
const maxImageSize = 10 * 1024 * 1024 // 10MB
|
||||
limitedReader := io.LimitReader(resp.Body, maxImageSize)
|
||||
|
||||
data, err := io.ReadAll(limitedReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read image content: %w", err)
|
||||
}
|
||||
|
||||
@ -21,12 +21,12 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/parsex"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
"github.com/elastic/go-elasticsearch/v7/esutil"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
@ -39,7 +39,7 @@ type es7Client struct {
|
||||
}
|
||||
|
||||
func newES7() (Client, error) {
|
||||
addresses, err := parsex.ParseClusterEndpoints(os.Getenv("ES_ADDR"))
|
||||
addresses, err := parseClusterEndpoints(os.Getenv("ES_ADDR"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -123,8 +123,8 @@ func (c *es7Client) CreateIndex(ctx context.Context, index string, properties ma
|
||||
"properties": properties,
|
||||
},
|
||||
"settings": map[string]any{
|
||||
"number_of_shards": parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
|
||||
"number_of_replicas": parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
|
||||
"number_of_shards": getEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
|
||||
"number_of_replicas": getEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@ package es
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/parsex"
|
||||
"os"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v8"
|
||||
"github.com/elastic/go-elasticsearch/v8/esutil"
|
||||
"github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
|
||||
@ -30,7 +31,6 @@ import (
|
||||
"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/operator"
|
||||
"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/sortorder"
|
||||
"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/textquerytype"
|
||||
"os"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
@ -51,7 +51,7 @@ type es8BulkIndexer struct {
|
||||
type es8Types struct{}
|
||||
|
||||
func newES8() (Client, error) {
|
||||
addresses, err := parsex.ParseClusterEndpoints(os.Getenv("ES_ADDR"))
|
||||
addresses, err := parseClusterEndpoints(os.Getenv("ES_ADDR"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -243,8 +243,8 @@ func (c *es8Client) CreateIndex(ctx context.Context, index string, properties ma
|
||||
Properties: propertiesMap,
|
||||
},
|
||||
Settings: &types.IndexSettings{
|
||||
NumberOfShards: parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
|
||||
NumberOfReplicas: parsex.GetEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
|
||||
NumberOfShards: getEnvDefaultIntSetting("ES_NUMBER_OF_SHARDS", "1"),
|
||||
NumberOfReplicas: getEnvDefaultIntSetting("ES_NUMBER_OF_REPLICAS", "1"),
|
||||
},
|
||||
}).Do(ctx); err != nil {
|
||||
return err
|
||||
|
||||
@ -18,8 +18,9 @@ package es
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
"os"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@ -14,18 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package parsex
|
||||
package es
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
// ParseClusterEndpoints 解析 ES /kafka 地址,多个地址用逗号分隔
|
||||
func ParseClusterEndpoints(address string) ([]string, error) {
|
||||
func parseClusterEndpoints(address string) ([]string, error) {
|
||||
if strings.TrimSpace(address) == "" {
|
||||
return nil, fmt.Errorf("endpoints environment variable is required")
|
||||
}
|
||||
@ -52,8 +52,7 @@ func ParseClusterEndpoints(address string) ([]string, error) {
|
||||
return validEndpoints, nil
|
||||
}
|
||||
|
||||
// GetEnvDefaultIntSetting 获取环境变量的值,如果不存在或无效则返回默认值
|
||||
func GetEnvDefaultIntSetting(envVar, defaultValue string) string {
|
||||
func getEnvDefaultIntSetting(envVar, defaultValue string) string {
|
||||
value := os.Getenv(envVar)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
@ -23,6 +23,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/kafka"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/nsq"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/pulsar"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/eventbus/rmq"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
)
|
||||
@ -54,9 +55,11 @@ func (consumerServiceImpl) RegisterConsumer(nameServer, topic, group string, con
|
||||
return kafka.RegisterConsumer(nameServer, topic, group, consumerHandler, opts...)
|
||||
case "rmq":
|
||||
return rmq.RegisterConsumer(nameServer, topic, group, consumerHandler, opts...)
|
||||
case "pulsar":
|
||||
return pulsar.RegisterConsumer(nameServer, topic, group, consumerHandler, opts...)
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq", tp)
|
||||
return fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq, pulsar", tp)
|
||||
}
|
||||
|
||||
func NewProducer(nameServer, topic, group string, retries int) (eventbus.Producer, error) {
|
||||
@ -68,7 +71,9 @@ func NewProducer(nameServer, topic, group string, retries int) (eventbus.Produce
|
||||
return kafka.NewProducer(nameServer, topic)
|
||||
case "rmq":
|
||||
return rmq.NewProducer(nameServer, topic, group, retries)
|
||||
case "pulsar":
|
||||
return pulsar.NewProducer(nameServer, topic, group)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq", tp)
|
||||
return nil, fmt.Errorf("invalid mq type: %s , only support nsq, kafka, rmq, pulsar", tp)
|
||||
}
|
||||
|
||||
141
backend/infra/impl/eventbus/pulsar/consumer.go
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 pulsar
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/apache/pulsar-client-go/pulsar"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/signal"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/safego"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
)
|
||||
|
||||
func RegisterConsumer(serviceURL, topic, group string, consumerHandler eventbus.ConsumerHandler, opts ...eventbus.ConsumerOpt) error {
|
||||
if serviceURL == "" {
|
||||
return fmt.Errorf("service URL is empty")
|
||||
}
|
||||
if topic == "" {
|
||||
return fmt.Errorf("topic is empty")
|
||||
}
|
||||
if group == "" {
|
||||
return fmt.Errorf("group is empty")
|
||||
}
|
||||
if consumerHandler == nil {
|
||||
return fmt.Errorf("consumer handler is nil")
|
||||
}
|
||||
|
||||
// Parse consumer options
|
||||
option := &eventbus.ConsumerOption{}
|
||||
for _, opt := range opts {
|
||||
opt(option)
|
||||
}
|
||||
|
||||
// Prepare client options
|
||||
clientOptions := pulsar.ClientOptions{
|
||||
URL: serviceURL,
|
||||
}
|
||||
|
||||
// Add JWT authentication if token is provided
|
||||
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
|
||||
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
|
||||
}
|
||||
|
||||
// Create Pulsar client
|
||||
client, err := pulsar.NewClient(clientOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create pulsar client failed: %w", err)
|
||||
}
|
||||
|
||||
// Configure consumer options
|
||||
consumerOptions := pulsar.ConsumerOptions{
|
||||
Topic: topic,
|
||||
SubscriptionName: group,
|
||||
Type: pulsar.Exclusive, // Exclusive mode ensures single consumer for message ordering
|
||||
}
|
||||
|
||||
// Create consumer
|
||||
consumer, err := client.Subscribe(consumerOptions)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
return fmt.Errorf("create pulsar consumer failed: %w", err)
|
||||
}
|
||||
|
||||
// Create cancellable context for better resource management
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Start consuming messages in a goroutine
|
||||
safego.Go(ctx, func() {
|
||||
defer func() {
|
||||
consumer.Close()
|
||||
client.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logs.Infof("pulsar consumer stopped for topic: %s, group: %s", topic, group)
|
||||
return
|
||||
default:
|
||||
// Receive message with context timeout
|
||||
msg, err := consumer.Receive(ctx)
|
||||
if err != nil {
|
||||
// Check if context was cancelled
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
logs.Errorf("receive pulsar message error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to eventbus message
|
||||
eventMsg := &eventbus.Message{
|
||||
Topic: topic,
|
||||
Group: group,
|
||||
Body: msg.Payload(),
|
||||
}
|
||||
|
||||
// Handle message with context
|
||||
if err := consumerHandler.HandleMessage(ctx, eventMsg); err != nil {
|
||||
logs.Errorf("handle pulsar message failed, topic: %s, group: %s, err: %v", topic, group, err)
|
||||
// Negative acknowledge on error
|
||||
consumer.Nack(msg)
|
||||
continue
|
||||
}
|
||||
|
||||
// Acknowledge message on success
|
||||
consumer.Ack(msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Handle graceful shutdown
|
||||
safego.Go(context.Background(), func() {
|
||||
signal.WaitExit()
|
||||
logs.Infof("shutting down pulsar consumer for topic: %s, group: %s", topic, group)
|
||||
cancel() // Cancel the context to stop consumer loop
|
||||
consumer.Close()
|
||||
client.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
155
backend/infra/impl/eventbus/pulsar/producer.go
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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 pulsar
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/apache/pulsar-client-go/pulsar"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/signal"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/safego"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
)
|
||||
|
||||
type producerImpl struct {
|
||||
topic string
|
||||
client pulsar.Client
|
||||
producer pulsar.Producer
|
||||
}
|
||||
|
||||
func NewProducer(serviceURL, topic, group string) (eventbus.Producer, error) {
|
||||
if serviceURL == "" {
|
||||
return nil, fmt.Errorf("pulsar service URL is required")
|
||||
}
|
||||
if topic == "" {
|
||||
return nil, fmt.Errorf("topic is required")
|
||||
}
|
||||
|
||||
// Prepare client options
|
||||
clientOptions := pulsar.ClientOptions{
|
||||
URL: serviceURL,
|
||||
}
|
||||
|
||||
// Add JWT authentication if token is provided
|
||||
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
|
||||
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
|
||||
}
|
||||
|
||||
// Create Pulsar client
|
||||
logs.Debugf("Creating Pulsar client with URL: %s", serviceURL)
|
||||
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
|
||||
logs.Debugf("Using JWT authentication, token length: %d", len(jwtToken))
|
||||
} else {
|
||||
logs.Debugf("No JWT token provided")
|
||||
}
|
||||
|
||||
client, err := pulsar.NewClient(clientOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create pulsar client with URL %s: %w", serviceURL, err)
|
||||
}
|
||||
logs.Debugf("Pulsar client created successfully")
|
||||
|
||||
// Create producer
|
||||
logs.Debugf("Creating producer for topic: %s, group: %s", topic, group)
|
||||
producer, err := client.CreateProducer(pulsar.ProducerOptions{
|
||||
Topic: topic,
|
||||
Name: fmt.Sprintf("%s-producer", group),
|
||||
})
|
||||
if err != nil {
|
||||
logs.Errorf("Failed to create producer: %v", err)
|
||||
client.Close()
|
||||
return nil, fmt.Errorf("create pulsar producer failed: %w", err)
|
||||
}
|
||||
logs.Debugf("Producer created successfully")
|
||||
|
||||
impl := &producerImpl{
|
||||
topic: topic,
|
||||
client: client,
|
||||
producer: producer,
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
safego.Go(context.Background(), func() {
|
||||
signal.WaitExit()
|
||||
impl.close()
|
||||
})
|
||||
|
||||
return impl, nil
|
||||
}
|
||||
|
||||
func (p *producerImpl) Send(ctx context.Context, body []byte, opts ...eventbus.SendOpt) error {
|
||||
return p.BatchSend(ctx, [][]byte{body}, opts...)
|
||||
}
|
||||
|
||||
func (p *producerImpl) BatchSend(ctx context.Context, bodyArr [][]byte, opts ...eventbus.SendOpt) error {
|
||||
option := eventbus.SendOption{}
|
||||
for _, opt := range opts {
|
||||
opt(&option)
|
||||
}
|
||||
|
||||
// Use Pulsar's async send with batch collection for better performance
|
||||
type sendResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
resultChan := make(chan sendResult, len(bodyArr))
|
||||
pendingCount := len(bodyArr)
|
||||
|
||||
for _, body := range bodyArr {
|
||||
msg := &pulsar.ProducerMessage{
|
||||
Payload: body,
|
||||
}
|
||||
|
||||
// Set partition key if sharding key is provided
|
||||
if option.ShardingKey != nil {
|
||||
msg.Key = *option.ShardingKey
|
||||
}
|
||||
|
||||
// Send message asynchronously for better batching performance
|
||||
p.producer.SendAsync(ctx, msg, func(messageID pulsar.MessageID, producerMessage *pulsar.ProducerMessage, err error) {
|
||||
resultChan <- sendResult{err: err}
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for all messages to be sent
|
||||
for i := 0; i < pendingCount; i++ {
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
if result.err != nil {
|
||||
return fmt.Errorf("[pulsarProducer] batch send message failed: %w", result.err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("[pulsarProducer] batch send cancelled: %w", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *producerImpl) close() {
|
||||
if p.producer != nil {
|
||||
p.producer.Close()
|
||||
}
|
||||
if p.client != nil {
|
||||
p.client.Close()
|
||||
}
|
||||
}
|
||||
125
backend/infra/impl/eventbus/pulsar/pulsar_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 pulsar
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/apache/pulsar-client-go/pulsar"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
)
|
||||
|
||||
var serviceURL = "pulsar://localhost:6650"
|
||||
|
||||
func TestProducer(t *testing.T) {
|
||||
if os.Getenv("PULSAR_LOCAL_TEST") != "true" {
|
||||
return
|
||||
}
|
||||
|
||||
// JWT token should be set via environment variable PULSAR_JWT_TOKEN if needed
|
||||
// For local testing, you can set: export PULSAR_JWT_TOKEN="your-jwt-token"
|
||||
|
||||
clientOptions := pulsar.ClientOptions{
|
||||
URL: serviceURL,
|
||||
}
|
||||
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
|
||||
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
|
||||
}
|
||||
|
||||
client, err := pulsar.NewClient(clientOptions)
|
||||
assert.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
producer, err := client.CreateProducer(pulsar.ProducerOptions{
|
||||
Topic: "test_topic",
|
||||
Name: "test_group_producer",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
defer producer.Close()
|
||||
|
||||
msgID, err := producer.Send(context.Background(), &pulsar.ProducerMessage{
|
||||
Payload: []byte("hello"),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
t.Logf("Message sent with ID: %v", msgID)
|
||||
}
|
||||
|
||||
func TestConsumer(t *testing.T) {
|
||||
if os.Getenv("PULSAR_LOCAL_TEST") != "true" {
|
||||
return
|
||||
}
|
||||
|
||||
// JWT token should be set via environment variable PULSAR_JWT_TOKEN if needed
|
||||
// For local testing, you can set: export PULSAR_JWT_TOKEN="your-jwt-token"
|
||||
|
||||
clientOptions := pulsar.ClientOptions{
|
||||
URL: serviceURL,
|
||||
}
|
||||
if jwtToken := os.Getenv(consts.PulsarJWTToken); jwtToken != "" {
|
||||
clientOptions.Authentication = pulsar.NewAuthenticationToken(jwtToken)
|
||||
}
|
||||
|
||||
client, err := pulsar.NewClient(clientOptions)
|
||||
assert.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
// First create consumer
|
||||
consumer, err := client.Subscribe(pulsar.ConsumerOptions{
|
||||
Topic: "test_topic",
|
||||
SubscriptionName: "test_group_consumer",
|
||||
Type: pulsar.Shared,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
defer consumer.Close()
|
||||
|
||||
// Then create producer and send a message
|
||||
producer, err := client.CreateProducer(pulsar.ProducerOptions{
|
||||
Topic: "test_topic",
|
||||
Name: "test_consumer_producer",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
defer producer.Close()
|
||||
|
||||
// Send a test message
|
||||
_, err = producer.Send(context.Background(), &pulsar.ProducerMessage{
|
||||
Payload: []byte("consumer test message"),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
msg, err := consumer.Receive(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("Failed to receive message: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("Received message: %s", string(msg.Payload()))
|
||||
consumer.Ack(msg)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
@ -64,15 +64,16 @@ func (c *OceanBaseClient) BatchInsertVectors(ctx context.Context, collectionName
|
||||
}
|
||||
|
||||
func (c *OceanBaseClient) DeleteVector(ctx context.Context, collectionName string, vectorID string) error {
|
||||
return c.official.GetDB().WithContext(ctx).Exec("DELETE FROM "+collectionName+" WHERE vector_id = ?", vectorID).Error
|
||||
return c.official.GetDB().WithContext(ctx).Table(collectionName).Where("vector_id = ?", vectorID).Delete(nil).Error
|
||||
}
|
||||
|
||||
func (c *OceanBaseClient) InitDatabase(ctx context.Context) error {
|
||||
return c.official.GetDB().WithContext(ctx).Exec("SELECT 1").Error
|
||||
var result int
|
||||
return c.official.GetDB().WithContext(ctx).Raw("SELECT 1").Scan(&result).Error
|
||||
}
|
||||
|
||||
func (c *OceanBaseClient) DropCollection(ctx context.Context, collectionName string) error {
|
||||
return c.official.GetDB().WithContext(ctx).Exec("DROP TABLE IF EXISTS " + collectionName).Error
|
||||
return c.official.GetDB().WithContext(ctx).Migrator().DropTable(collectionName)
|
||||
}
|
||||
|
||||
type SearchStrategy interface {
|
||||
|
||||
@ -43,6 +43,15 @@ type VectorResult struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type VectorRecord struct {
|
||||
VectorID string `gorm:"column:vector_id;primaryKey"`
|
||||
Content string `gorm:"column:content;type:text;not null"`
|
||||
Metadata string `gorm:"column:metadata;type:json"`
|
||||
Embedding string `gorm:"column:embedding;type:vector;not null"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;default:CURRENT_TIMESTAMP"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"`
|
||||
}
|
||||
|
||||
type CollectionInfo struct {
|
||||
Name string `json:"name"`
|
||||
Dimension int `json:"dimension"`
|
||||
@ -83,21 +92,23 @@ func (c *OceanBaseOfficialClient) setVectorParameters() error {
|
||||
}
|
||||
|
||||
func (c *OceanBaseOfficialClient) CreateCollection(ctx context.Context, collectionName string, dimension int) error {
|
||||
createTableSQL := fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
vector_id VARCHAR(255) PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
metadata JSON,
|
||||
embedding VECTOR(%d) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_content (content(100))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`, collectionName, dimension)
|
||||
if !c.db.WithContext(ctx).Migrator().HasTable(collectionName) {
|
||||
createTableSQL := fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
vector_id VARCHAR(255) PRIMARY KEY,
|
||||
content TEXT NOT NULL,
|
||||
metadata JSON,
|
||||
embedding VECTOR(%d) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_content (content(100))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`, collectionName, dimension)
|
||||
|
||||
if err := c.db.WithContext(ctx).Exec(createTableSQL).Error; err != nil {
|
||||
return fmt.Errorf("failed to create table: %v", err)
|
||||
if err := c.db.WithContext(ctx).Exec(createTableSQL).Error; err != nil {
|
||||
return fmt.Errorf("failed to create table: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
createIndexSQL := fmt.Sprintf(`
|
||||
@ -136,30 +147,19 @@ func (c *OceanBaseOfficialClient) InsertVectors(ctx context.Context, collectionN
|
||||
}
|
||||
|
||||
func (c *OceanBaseOfficialClient) insertBatch(ctx context.Context, collectionName string, batch []VectorResult) error {
|
||||
placeholders := make([]string, len(batch))
|
||||
values := make([]interface{}, 0, len(batch)*5)
|
||||
|
||||
for j, vector := range batch {
|
||||
placeholders[j] = "(?, ?, ?, ?, NOW())"
|
||||
values = append(values,
|
||||
vector.VectorID,
|
||||
vector.Content,
|
||||
vector.Metadata,
|
||||
c.vectorToString(vector.Embedding),
|
||||
)
|
||||
records := make([]VectorRecord, len(batch))
|
||||
for i, vector := range batch {
|
||||
records[i] = VectorRecord{
|
||||
VectorID: vector.VectorID,
|
||||
Content: vector.Content,
|
||||
Metadata: vector.Metadata,
|
||||
Embedding: c.vectorToString(vector.Embedding),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(`
|
||||
INSERT INTO %s (vector_id, content, metadata, embedding, created_at)
|
||||
VALUES %s
|
||||
ON DUPLICATE KEY UPDATE
|
||||
content = VALUES(content),
|
||||
metadata = VALUES(metadata),
|
||||
embedding = VALUES(embedding),
|
||||
updated_at = NOW()
|
||||
`, collectionName, strings.Join(placeholders, ","))
|
||||
|
||||
return c.db.WithContext(ctx).Exec(sql, values...).Error
|
||||
return c.db.WithContext(ctx).Table(collectionName).Save(&records).Error
|
||||
}
|
||||
|
||||
func (c *OceanBaseOfficialClient) SearchVectors(
|
||||
@ -341,24 +341,28 @@ func (c *OceanBaseOfficialClient) DebugCollectionData(ctx context.Context, colle
|
||||
log.Printf("[Debug] Collection '%s' exists with %d vectors", collectionName, count)
|
||||
|
||||
log.Printf("[Debug] Sample data from collection '%s':", collectionName)
|
||||
rows, err := c.db.WithContext(ctx).Raw(`
|
||||
SELECT vector_id, content, created_at
|
||||
FROM ` + collectionName + `
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
`).Rows()
|
||||
var samples []struct {
|
||||
VectorID string `gorm:"column:vector_id"`
|
||||
Content string `gorm:"column:content"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
err := c.db.WithContext(ctx).Table(collectionName).
|
||||
Select("vector_id, content, created_at").
|
||||
Order("created_at DESC").
|
||||
Limit(5).
|
||||
Find(&samples).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[Debug] Failed to get sample data: %v", err)
|
||||
} else {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var vectorID, content string
|
||||
var createdAt time.Time
|
||||
if err := rows.Scan(&vectorID, &content, &createdAt); err != nil {
|
||||
log.Printf("[Debug] Failed to scan sample row: %v", err)
|
||||
continue
|
||||
for _, sample := range samples {
|
||||
contentPreview := sample.Content
|
||||
if len(contentPreview) > 50 {
|
||||
contentPreview = contentPreview[:50]
|
||||
}
|
||||
log.Printf("[Debug] Sample: ID=%s, Content=%s, Created=%s", vectorID, content[:min(50, len(content))], createdAt)
|
||||
log.Printf("[Debug] Sample: ID=%s, Content=%s, Created=%s",
|
||||
sample.VectorID, contentPreview, sample.CreatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -646,15 +646,15 @@ func (m *mysqlService) ExecuteSQL(ctx context.Context, req *rdb.ExecuteSQLReques
|
||||
var processedParams []interface{}
|
||||
var err error
|
||||
|
||||
// Handle SQLType: if raw, do not process params
|
||||
// 禁用原始SQL执行以防止SQL注入攻击
|
||||
if req.SQLType == entity2.SQLType_Raw {
|
||||
processedSQL = req.SQL
|
||||
processedParams = nil
|
||||
} else {
|
||||
processedSQL, processedParams, err = m.processSliceParams(req.SQL, req.Params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process parameters: %v", err)
|
||||
}
|
||||
return nil, fmt.Errorf("raw SQL execution is not allowed for security reasons")
|
||||
}
|
||||
|
||||
// 强制使用参数化查询
|
||||
processedSQL, processedParams, err = m.processSliceParams(req.SQL, req.Params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process parameters: %v", err)
|
||||
}
|
||||
|
||||
operation, err := sqlparser.NewSQLParser().GetSQLOperation(processedSQL)
|
||||
@ -1011,4 +1011,4 @@ func (m *mysqlService) buildNestedConditions(condition *rdb.ComplexCondition) (s
|
||||
return whereClause.String(), values, nil
|
||||
}
|
||||
return "", values, nil
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ func AssembleFileUrl(ctx context.Context, urlExpire *int64, files []*storage.Fil
|
||||
taskGroup := taskgroup.NewTaskGroup(ctx, 5)
|
||||
for idx := range files {
|
||||
f := files[idx]
|
||||
expire := int64(60 * 60 * 24)
|
||||
expire := int64(7 * 60 * 60 * 24)
|
||||
if urlExpire != nil && *urlExpire > 0 {
|
||||
expire = *urlExpire
|
||||
}
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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 proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
)
|
||||
|
||||
func CheckIfNeedReplaceHost(ctx context.Context, originURLStr string) (ok bool, proxyURL string) {
|
||||
// url parse
|
||||
originURL, err := url.Parse(originURLStr)
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "[CheckIfNeedReplaceHost] url parse failed, err: %v", err)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
proxyPort := os.Getenv(consts.MinIOProxyEndpoint) // :8889
|
||||
if proxyPort == "" {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
currentHost, ok := ctxcache.Get[string](ctx, consts.HostKeyInCtx)
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
currentScheme, ok := ctxcache.Get[string](ctx, consts.RequestSchemeKeyInCtx)
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(currentHost)
|
||||
if err != nil {
|
||||
host = currentHost
|
||||
}
|
||||
|
||||
minioProxyHost := host + proxyPort
|
||||
originURL.Host = minioProxyHost
|
||||
originURL.Scheme = currentScheme
|
||||
logs.CtxDebugf(ctx, "[CheckIfNeedReplaceHost] reset originURL.String = %s", originURL.String())
|
||||
return true, originURL.String()
|
||||
}
|
||||
@ -30,7 +30,6 @@ import (
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/fileutil"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/proxy"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
@ -232,12 +231,6 @@ func (m *minioClient) GetObjectUrl(ctx context.Context, objectKey string, opts .
|
||||
return "", fmt.Errorf("GetObjectUrl failed: %v", err)
|
||||
}
|
||||
|
||||
// logs.CtxDebugf(ctx, "[GetObjectUrl] origin presignedURL.String = %s", presignedURL.String())
|
||||
ok, proxyURL := proxy.CheckIfNeedReplaceHost(ctx, presignedURL.String())
|
||||
if ok {
|
||||
return proxyURL, nil
|
||||
}
|
||||
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
@ -317,7 +310,7 @@ func (m *minioClient) HeadObject(ctx context.Context, objectKey string, opts ...
|
||||
stat, err := m.client.StatObject(ctx, m.bucketName, objectKey, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
if minio.ToErrorResponse(err).Code == "NoSuchKey" {
|
||||
return nil, nil
|
||||
return nil, storage.ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("HeadObject failed for key %s: %w", objectKey, err)
|
||||
|
||||
@ -32,7 +32,6 @@ import (
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/fileutil"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/proxy"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/goutil"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/taskgroup"
|
||||
@ -230,21 +229,26 @@ func (t *s3Client) GetObjectUrl(ctx context.Context, objectKey string, opts ...s
|
||||
bucket := t.bucketName
|
||||
presignClient := s3.NewPresignClient(client)
|
||||
|
||||
opt := storage.GetOption{}
|
||||
for _, optFn := range opts {
|
||||
optFn(&opt)
|
||||
}
|
||||
|
||||
expire := int64(60 * 60 * 24)
|
||||
if opt.Expire > 0 {
|
||||
expire = opt.Expire
|
||||
}
|
||||
|
||||
req, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(objectKey),
|
||||
}, func(options *s3.PresignOptions) {
|
||||
options.Expires = time.Duration(60*60*24) * time.Second
|
||||
options.Expires = time.Duration(expire) * time.Second
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get object presigned url failed: %v", err)
|
||||
}
|
||||
|
||||
ok, proxyURL := proxy.CheckIfNeedReplaceHost(ctx, req.URL)
|
||||
if ok {
|
||||
return proxyURL, nil
|
||||
}
|
||||
|
||||
return req.URL, nil
|
||||
}
|
||||
|
||||
@ -381,7 +385,7 @@ func (t *s3Client) HeadObject(ctx context.Context, objectKey string, opts ...sto
|
||||
if err != nil {
|
||||
var nsk *types.NotFound
|
||||
if errors.As(err, &nsk) {
|
||||
return nil, nil
|
||||
return nil, storage.ErrObjectNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -30,7 +30,6 @@ import (
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/fileutil"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage/internal/proxy"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/goutil"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
@ -247,9 +246,19 @@ func (t *tosClient) GetObjectUrl(ctx context.Context, objectKey string, opts ...
|
||||
client := t.client
|
||||
bucketName := t.bucketName
|
||||
|
||||
opt := storage.GetOption{}
|
||||
for _, optFn := range opts {
|
||||
optFn(&opt)
|
||||
}
|
||||
|
||||
expire := int64(7 * 24 * 60 * 60)
|
||||
if opt.Expire > 0 {
|
||||
expire = opt.Expire
|
||||
}
|
||||
|
||||
output, err := client.PreSignedURL(&tos.PreSignedURLInput{
|
||||
HTTPMethod: enum.HttpMethodGet,
|
||||
Expires: 60 * 60 * 24,
|
||||
Expires: expire,
|
||||
Bucket: bucketName,
|
||||
Key: objectKey,
|
||||
})
|
||||
@ -257,11 +266,6 @@ func (t *tosClient) GetObjectUrl(ctx context.Context, objectKey string, opts ...
|
||||
return "", err
|
||||
}
|
||||
|
||||
ok, proxyURL := proxy.CheckIfNeedReplaceHost(ctx, output.SignedUrl)
|
||||
if ok {
|
||||
return proxyURL, nil
|
||||
}
|
||||
|
||||
return output.SignedUrl, nil
|
||||
}
|
||||
|
||||
@ -389,7 +393,7 @@ func (t *tosClient) HeadObject(ctx context.Context, objectKey string, opts ...st
|
||||
if err != nil {
|
||||
if serverErr, ok := err.(*tos.TosServerError); ok {
|
||||
if serverErr.StatusCode == http.StatusNotFound {
|
||||
return nil, nil
|
||||
return nil, storage.ErrObjectNotFound
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
|
||||
@ -22,10 +22,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
@ -41,7 +37,6 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/safego"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
)
|
||||
|
||||
@ -60,7 +55,6 @@ func main() {
|
||||
panic("InitializeInfra failed, err=" + err.Error())
|
||||
}
|
||||
|
||||
asyncStartMinioProxyServer(ctx)
|
||||
startHttpServer()
|
||||
}
|
||||
|
||||
@ -160,56 +154,3 @@ func setCrashOutput() {
|
||||
crashFile, _ := os.Create("crash.log")
|
||||
debug.SetCrashOutput(crashFile, debug.CrashOptions{})
|
||||
}
|
||||
|
||||
// TODO: remove me later
|
||||
func asyncStartMinioProxyServer(ctx context.Context) {
|
||||
storageType := getEnv(consts.StorageType, "minio")
|
||||
proxyURL := getEnv(consts.MinIOAPIHost, "http://localhost:9000")
|
||||
|
||||
if storageType == "tos" {
|
||||
proxyURL = getEnv(consts.TOSBucketEndpoint, "https://opencoze.tos-cn-beijing.volces.com")
|
||||
}
|
||||
|
||||
if storageType == "s3" {
|
||||
proxyURL = getEnv(consts.S3BucketEndpoint, "")
|
||||
}
|
||||
|
||||
minioProxyEndpoint := getEnv(consts.MinIOProxyEndpoint, "")
|
||||
if len(minioProxyEndpoint) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
safego.Go(ctx, func() {
|
||||
target, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
originDirector := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
q := req.URL.Query()
|
||||
q.Del("x-wf-file_name")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
originDirector(req)
|
||||
req.Host = req.URL.Host
|
||||
}
|
||||
useSSL := getEnv(consts.UseSSL, "0")
|
||||
if useSSL == "1" {
|
||||
logs.Infof("Minio proxy server is listening on %s with SSL", minioProxyEndpoint)
|
||||
err := http.ListenAndServeTLS(minioProxyEndpoint,
|
||||
getEnv(consts.SSLCertFile, ""),
|
||||
getEnv(consts.SSLKeyFile, ""), proxy)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
logs.Infof("Minio proxy server is listening on %s", minioProxyEndpoint)
|
||||
err := http.ListenAndServe(minioProxyEndpoint, proxy)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -59,6 +59,8 @@ const (
|
||||
MQServer = "MQ_NAME_SERVER"
|
||||
RMQSecretKey = "RMQ_SECRET_KEY"
|
||||
RMQAccessKey = "RMQ_ACCESS_KEY"
|
||||
PulsarServiceURL = "PULSAR_SERVICE_URL"
|
||||
PulsarJWTToken = "PULSAR_JWT_TOKEN"
|
||||
RMQTopicApp = "opencoze_search_app"
|
||||
RMQTopicResource = "opencoze_search_resource"
|
||||
RMQTopicKnowledge = "opencoze_knowledge"
|
||||
|
||||
@ -74,12 +74,17 @@ export ES_PASSWORD=""
|
||||
export ES_NUMBER_OF_SHARDS = "1"
|
||||
export ES_NUMBER_OF_REPLICAS = "1"
|
||||
|
||||
|
||||
export COZE_MQ_TYPE="nsq" # nsq / kafka / rmq
|
||||
# Backend Event Bus
|
||||
export COZE_MQ_TYPE="nsq" # nsq / kafka / rmq / pulsar
|
||||
export MQ_NAME_SERVER="nsqd:4150"
|
||||
# RocketMQ
|
||||
export RMQ_ACCESS_KEY=""
|
||||
export RMQ_SECRET_KEY=""
|
||||
# Pulsar
|
||||
# Use Pulsar as backend eventbus, MQ_NAME_SERVER example is: "pulsar:6650"
|
||||
# Fill PULSAR_JWT_TOKEN for JWT auth, leave empty for no auth
|
||||
export PULSAR_SERVICE_URL="pulsar://pulsar-service:6650"
|
||||
export PULSAR_JWT_TOKEN=""
|
||||
|
||||
# Settings for VectorStore
|
||||
# VectorStore type: milvus / vikingdb / oceanbase
|
||||
|
||||
@ -20,7 +20,7 @@ services:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
- ./volumes/mysql/schema.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- ./atlas/opencoze_latest_schema.hcl:/opencoze_latest_schema.hcl:ro
|
||||
entrypoint:
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
@ -43,7 +43,7 @@ services:
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo 'MySQL is ready, installing Atlas CLI...'
|
||||
|
||||
if ! command -v atlas >/dev/null 2>&1; then
|
||||
@ -53,7 +53,7 @@ services:
|
||||
else
|
||||
echo 'Atlas CLI already installed'
|
||||
fi
|
||||
|
||||
|
||||
if [ -f '/opencoze_latest_schema.hcl' ]; then
|
||||
echo 'Running Atlas migrations...'
|
||||
ATLAS_URL="mysql://$${MYSQL_USER}:$${MYSQL_PASSWORD}@localhost:3306/$${MYSQL_DATABASE}"
|
||||
@ -274,7 +274,7 @@ services:
|
||||
|
||||
# Download plugin package locally
|
||||
echo 'Copying smartcn plugin...';
|
||||
cp /opt/bitnami/elasticsearch/analysis-smartcn.zip /tmp/analysis-smartcn.zip
|
||||
cp /opt/bitnami/elasticsearch/analysis-smartcn.zip /tmp/analysis-smartcn.zip
|
||||
|
||||
elasticsearch-plugin install file:///tmp/analysis-smartcn.zip
|
||||
if [[ "$$?" != "0" ]]; then
|
||||
|
||||
@ -252,6 +252,7 @@ services:
|
||||
OB_DATAFILE_SIZE: 1G
|
||||
OB_SYS_PASSWORD: ${OCEANBASE_PASSWORD:-coze123}
|
||||
OB_TENANT_PASSWORD: ${OCEANBASE_PASSWORD:-coze123}
|
||||
OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-cozeAi}
|
||||
ports:
|
||||
- '2881:2881'
|
||||
volumes:
|
||||
|
||||
@ -345,6 +345,7 @@ services:
|
||||
OB_DATAFILE_SIZE: 1G
|
||||
OB_SYS_PASSWORD: ${OCEANBASE_PASSWORD:-coze123}
|
||||
OB_TENANT_PASSWORD: ${OCEANBASE_PASSWORD:-coze123}
|
||||
OB_CLUSTER_NAME: ${OCEANBASE_CLUSTER_NAME:-cozeAi}
|
||||
profiles: ['middleware']
|
||||
env_file: *env_file
|
||||
ports:
|
||||
|
||||
@ -19,7 +19,7 @@ services:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
- ./volumes/mysql/schema.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- ./atlas/opencoze_latest_schema.hcl:/opencoze_latest_schema.hcl:ro
|
||||
entrypoint:
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
- |
|
||||
@ -42,7 +42,7 @@ services:
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo 'MySQL is ready, installing Atlas CLI...'
|
||||
|
||||
if ! command -v atlas >/dev/null 2>&1; then
|
||||
@ -52,7 +52,7 @@ services:
|
||||
else
|
||||
echo 'Atlas CLI already installed'
|
||||
fi
|
||||
|
||||
|
||||
if [ -f '/opencoze_latest_schema.hcl' ]; then
|
||||
echo 'Running Atlas migrations...'
|
||||
ATLAS_URL="mysql://$${MYSQL_USER}:$${MYSQL_PASSWORD}@localhost:3306/$${MYSQL_DATABASE}"
|
||||
@ -161,7 +161,7 @@ services:
|
||||
|
||||
# Download plugin package locally
|
||||
echo 'Copying smartcn plugin...';
|
||||
cp /opt/bitnami/elasticsearch/analysis-smartcn.zip /tmp/analysis-smartcn.zip
|
||||
cp /opt/bitnami/elasticsearch/analysis-smartcn.zip /tmp/analysis-smartcn.zip
|
||||
|
||||
elasticsearch-plugin install file:///tmp/analysis-smartcn.zip
|
||||
if [[ "$$?" != "0" ]]; then
|
||||
|
||||
BIN
docker/volumes/minio/default_icon/workflow_icon/icon-batch.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docker/volumes/minio/default_icon/workflow_icon/icon-break.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 24 KiB |
BIN
docker/volumes/minio/default_icon/workflow_icon/icon-code.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
BIN
docker/volumes/minio/default_icon/workflow_icon/icon-end.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docker/volumes/minio/default_icon/workflow_icon/icon-http.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docker/volumes/minio/default_icon/workflow_icon/icon-intent.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |