feat: Support for Chat Flow & Agent Support for binding a single chat flow (#765)
Co-authored-by: Yu Yang <72337138+tomasyu985@users.noreply.github.com> Co-authored-by: zengxiaohui <csu.zengxiaohui@gmail.com> Co-authored-by: lijunwen.gigoo <lijunwen.gigoo@bytedance.com> Co-authored-by: lvxinyu.1117 <lvxinyu.1117@bytedance.com> Co-authored-by: liuyunchao.0510 <liuyunchao.0510@bytedance.com> Co-authored-by: haozhenfei <37089575+haozhenfei@users.noreply.github.com> Co-authored-by: July <jiangxujin@bytedance.com> Co-authored-by: tecvan-fe <fanwenjie.fe@bytedance.com>
This commit is contained in:
@@ -50,7 +50,9 @@ type SingleAgentDraft struct {
|
||||
JumpConfig *bot_common.JumpConfig `gorm:"column:jump_config;comment:Jump Configuration;serializer:json" json:"jump_config"` // Jump Configuration
|
||||
BackgroundImageInfoList []*bot_common.BackgroundImageInfo `gorm:"column:background_image_info_list;comment:Background image;serializer:json" json:"background_image_info_list"` // Background image
|
||||
DatabaseConfig []*bot_common.Database `gorm:"column:database_config;comment:Agent Database Base Configuration;serializer:json" json:"database_config"` // Agent Database Base Configuration
|
||||
BotMode int32 `gorm:"column:bot_mode;not null;comment:mod,0:single mode 2:chatflow mode" json:"bot_mode"` // mod,0:single mode 2:chatflow mode
|
||||
ShortcutCommand []string `gorm:"column:shortcut_command;comment:shortcut command;serializer:json" json:"shortcut_command"` // shortcut command
|
||||
LayoutInfo *bot_common.LayoutInfo `gorm:"column:layout_info;comment:chatflow layout info;serializer:json" json:"layout_info"` // chatflow layout info
|
||||
}
|
||||
|
||||
// TableName SingleAgentDraft's table name
|
||||
|
||||
@@ -52,7 +52,9 @@ type SingleAgentVersion struct {
|
||||
Version string `gorm:"column:version;not null;comment:Agent Version" json:"version"` // Agent Version
|
||||
BackgroundImageInfoList []*bot_common.BackgroundImageInfo `gorm:"column:background_image_info_list;comment:Background image;serializer:json" json:"background_image_info_list"` // Background image
|
||||
DatabaseConfig []*bot_common.Database `gorm:"column:database_config;comment:Agent Database Base Configuration;serializer:json" json:"database_config"` // Agent Database Base Configuration
|
||||
BotMode int32 `gorm:"column:bot_mode;not null;comment:mod,0:single mode 2:chatflow mode" json:"bot_mode"` // mod,0:single mode 2:chatflow mode
|
||||
ShortcutCommand []string `gorm:"column:shortcut_command;comment:shortcut command;serializer:json" json:"shortcut_command"` // shortcut command
|
||||
LayoutInfo *bot_common.LayoutInfo `gorm:"column:layout_info;comment:chatflow layout info;serializer:json" json:"layout_info"` // chatflow layout info
|
||||
}
|
||||
|
||||
// TableName SingleAgentVersion's table name
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@@ -48,7 +64,9 @@ func newSingleAgentDraft(db *gorm.DB, opts ...gen.DOOption) singleAgentDraft {
|
||||
_singleAgentDraft.JumpConfig = field.NewField(tableName, "jump_config")
|
||||
_singleAgentDraft.BackgroundImageInfoList = field.NewField(tableName, "background_image_info_list")
|
||||
_singleAgentDraft.DatabaseConfig = field.NewField(tableName, "database_config")
|
||||
_singleAgentDraft.BotMode = field.NewInt32(tableName, "bot_mode")
|
||||
_singleAgentDraft.ShortcutCommand = field.NewField(tableName, "shortcut_command")
|
||||
_singleAgentDraft.LayoutInfo = field.NewField(tableName, "layout_info")
|
||||
|
||||
_singleAgentDraft.fillFieldMap()
|
||||
|
||||
@@ -81,7 +99,9 @@ type singleAgentDraft struct {
|
||||
JumpConfig field.Field // Jump Configuration
|
||||
BackgroundImageInfoList field.Field // Background image
|
||||
DatabaseConfig field.Field // Agent Database Base Configuration
|
||||
BotMode field.Int32 // mod,0:single mode 2:chatflow mode
|
||||
ShortcutCommand field.Field // shortcut command
|
||||
LayoutInfo field.Field // chatflow layout info
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
@@ -119,7 +139,9 @@ func (s *singleAgentDraft) updateTableName(table string) *singleAgentDraft {
|
||||
s.JumpConfig = field.NewField(table, "jump_config")
|
||||
s.BackgroundImageInfoList = field.NewField(table, "background_image_info_list")
|
||||
s.DatabaseConfig = field.NewField(table, "database_config")
|
||||
s.BotMode = field.NewInt32(table, "bot_mode")
|
||||
s.ShortcutCommand = field.NewField(table, "shortcut_command")
|
||||
s.LayoutInfo = field.NewField(table, "layout_info")
|
||||
|
||||
s.fillFieldMap()
|
||||
|
||||
@@ -136,7 +158,7 @@ func (s *singleAgentDraft) GetFieldByName(fieldName string) (field.OrderExpr, bo
|
||||
}
|
||||
|
||||
func (s *singleAgentDraft) fillFieldMap() {
|
||||
s.fieldMap = make(map[string]field.Expr, 22)
|
||||
s.fieldMap = make(map[string]field.Expr, 24)
|
||||
s.fieldMap["id"] = s.ID
|
||||
s.fieldMap["agent_id"] = s.AgentID
|
||||
s.fieldMap["creator_id"] = s.CreatorID
|
||||
@@ -158,7 +180,9 @@ func (s *singleAgentDraft) fillFieldMap() {
|
||||
s.fieldMap["jump_config"] = s.JumpConfig
|
||||
s.fieldMap["background_image_info_list"] = s.BackgroundImageInfoList
|
||||
s.fieldMap["database_config"] = s.DatabaseConfig
|
||||
s.fieldMap["bot_mode"] = s.BotMode
|
||||
s.fieldMap["shortcut_command"] = s.ShortcutCommand
|
||||
s.fieldMap["layout_info"] = s.LayoutInfo
|
||||
}
|
||||
|
||||
func (s singleAgentDraft) clone(db *gorm.DB) singleAgentDraft {
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@@ -50,7 +66,9 @@ func newSingleAgentVersion(db *gorm.DB, opts ...gen.DOOption) singleAgentVersion
|
||||
_singleAgentVersion.Version = field.NewString(tableName, "version")
|
||||
_singleAgentVersion.BackgroundImageInfoList = field.NewField(tableName, "background_image_info_list")
|
||||
_singleAgentVersion.DatabaseConfig = field.NewField(tableName, "database_config")
|
||||
_singleAgentVersion.BotMode = field.NewInt32(tableName, "bot_mode")
|
||||
_singleAgentVersion.ShortcutCommand = field.NewField(tableName, "shortcut_command")
|
||||
_singleAgentVersion.LayoutInfo = field.NewField(tableName, "layout_info")
|
||||
|
||||
_singleAgentVersion.fillFieldMap()
|
||||
|
||||
@@ -85,7 +103,9 @@ type singleAgentVersion struct {
|
||||
Version field.String // Agent Version
|
||||
BackgroundImageInfoList field.Field // Background image
|
||||
DatabaseConfig field.Field // Agent Database Base Configuration
|
||||
BotMode field.Int32 // mod,0:single mode 2:chatflow mode
|
||||
ShortcutCommand field.Field // shortcut command
|
||||
LayoutInfo field.Field // chatflow layout info
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
@@ -125,7 +145,9 @@ func (s *singleAgentVersion) updateTableName(table string) *singleAgentVersion {
|
||||
s.Version = field.NewString(table, "version")
|
||||
s.BackgroundImageInfoList = field.NewField(table, "background_image_info_list")
|
||||
s.DatabaseConfig = field.NewField(table, "database_config")
|
||||
s.BotMode = field.NewInt32(table, "bot_mode")
|
||||
s.ShortcutCommand = field.NewField(table, "shortcut_command")
|
||||
s.LayoutInfo = field.NewField(table, "layout_info")
|
||||
|
||||
s.fillFieldMap()
|
||||
|
||||
@@ -142,7 +164,7 @@ func (s *singleAgentVersion) GetFieldByName(fieldName string) (field.OrderExpr,
|
||||
}
|
||||
|
||||
func (s *singleAgentVersion) fillFieldMap() {
|
||||
s.fieldMap = make(map[string]field.Expr, 24)
|
||||
s.fieldMap = make(map[string]field.Expr, 26)
|
||||
s.fieldMap["id"] = s.ID
|
||||
s.fieldMap["agent_id"] = s.AgentID
|
||||
s.fieldMap["creator_id"] = s.CreatorID
|
||||
@@ -166,7 +188,9 @@ func (s *singleAgentVersion) fillFieldMap() {
|
||||
s.fieldMap["version"] = s.Version
|
||||
s.fieldMap["background_image_info_list"] = s.BackgroundImageInfoList
|
||||
s.fieldMap["database_config"] = s.DatabaseConfig
|
||||
s.fieldMap["bot_mode"] = s.BotMode
|
||||
s.fieldMap["shortcut_command"] = s.ShortcutCommand
|
||||
s.fieldMap["layout_info"] = s.LayoutInfo
|
||||
}
|
||||
|
||||
func (s singleAgentVersion) clone(db *gorm.DB) singleAgentVersion {
|
||||
|
||||
@@ -22,7 +22,9 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/internal/dal/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/internal/dal/query"
|
||||
@@ -118,7 +120,7 @@ func (sa *SingleAgentDraftDAO) Update(ctx context.Context, agentInfo *entity.Sin
|
||||
po := sa.singleAgentDraftDo2Po(agentInfo)
|
||||
singleAgentDAOModel := sa.dbQuery.SingleAgentDraft
|
||||
|
||||
_, err = singleAgentDAOModel.Where(singleAgentDAOModel.AgentID.Eq(agentInfo.AgentID)).Updates(po)
|
||||
err = singleAgentDAOModel.Where(singleAgentDAOModel.AgentID.Eq(agentInfo.AgentID)).Save(po)
|
||||
if err != nil {
|
||||
return errorx.WrapByCode(err, errno.ErrAgentUpdateCode)
|
||||
}
|
||||
@@ -156,6 +158,8 @@ func (sa *SingleAgentDraftDAO) singleAgentDraftPo2Do(po *model.SingleAgentDraft)
|
||||
BackgroundImageInfoList: po.BackgroundImageInfoList,
|
||||
Database: po.DatabaseConfig,
|
||||
ShortcutCommand: po.ShortcutCommand,
|
||||
BotMode: bot_common.BotMode(po.BotMode),
|
||||
LayoutInfo: po.LayoutInfo,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -183,5 +187,7 @@ func (sa *SingleAgentDraftDAO) singleAgentDraftDo2Po(do *entity.SingleAgent) *mo
|
||||
BackgroundImageInfoList: do.BackgroundImageInfoList,
|
||||
DatabaseConfig: do.Database,
|
||||
ShortcutCommand: do.ShortcutCommand,
|
||||
BotMode: int32(do.BotMode),
|
||||
LayoutInfo: do.LayoutInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ type SingleAgentDraftRepo interface {
|
||||
Delete(ctx context.Context, spaceID, agentID int64) (err error)
|
||||
Update(ctx context.Context, agentInfo *entity.SingleAgent) (err error)
|
||||
Save(ctx context.Context, agentInfo *entity.SingleAgent) (err error)
|
||||
|
||||
GetDisplayInfo(ctx context.Context, userID, agentID int64) (*entity.AgentDraftDisplayInfo, error)
|
||||
UpdateDisplayInfo(ctx context.Context, userID int64, e *entity.AgentDraftDisplayInfo) error
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
)
|
||||
|
||||
var ConnectorIDWhiteList = []int64{
|
||||
consts.WebSDKConnectorID,
|
||||
consts.APIConnectorID,
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func (a *appServiceImpl) publishByConnectors(ctx context.Context, recordID int64
|
||||
for cid := range req.ConnectorPublishConfigs {
|
||||
connectorIDs = append(connectorIDs, cid)
|
||||
}
|
||||
failedResources, err := a.packResources(ctx, req.APPID, req.Version, connectorIDs)
|
||||
failedResources, err := a.packResources(ctx, req.APPID, req.Version, connectorIDs, req.ConnectorPublishConfigs)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func (a *appServiceImpl) publishByConnectors(ctx context.Context, recordID int64
|
||||
|
||||
for cid := range req.ConnectorPublishConfigs {
|
||||
switch cid {
|
||||
case commonConsts.APIConnectorID:
|
||||
case commonConsts.APIConnectorID, commonConsts.WebSDKConnectorID:
|
||||
updateSuccessErr := a.APPRepo.UpdateConnectorPublishStatus(ctx, recordID, entity.ConnectorPublishStatusOfSuccess)
|
||||
if updateSuccessErr == nil {
|
||||
continue
|
||||
@@ -172,7 +172,8 @@ func (a *appServiceImpl) createPublishVersion(ctx context.Context, req *PublishA
|
||||
return recordID, nil
|
||||
}
|
||||
|
||||
func (a *appServiceImpl) packResources(ctx context.Context, appID int64, version string, connectorIDs []int64) (failedResources []*entity.PackResourceFailedInfo, err error) {
|
||||
func (a *appServiceImpl) packResources(ctx context.Context, appID int64, version string, connectorIDs []int64, pConfig map[int64]entity.PublishConfig) (failedResources []*entity.PackResourceFailedInfo, err error) {
|
||||
|
||||
failedPlugins, allDraftPlugins, err := a.packPlugins(ctx, appID, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -25,10 +25,13 @@ import (
|
||||
connectorModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/connector"
|
||||
databaseModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
|
||||
knowledgeModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
|
||||
|
||||
crossconnector "github.com/coze-dev/coze-studio/backend/crossdomain/contract/connector"
|
||||
crossdatabase "github.com/coze-dev/coze-studio/backend/crossdomain/contract/database"
|
||||
crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/contract/knowledge"
|
||||
crossplugin "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin"
|
||||
crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/app/repository"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
@@ -67,6 +70,11 @@ func (a *appServiceImpl) CreateDraftAPP(ctx context.Context, req *CreateDraftAPP
|
||||
return 0, errorx.Wrapf(err, "CreateDraftAPP failed, spaceID=%d", req.SpaceID)
|
||||
}
|
||||
|
||||
err = crossworkflow.DefaultSVC().InitApplicationDefaultConversationTemplate(ctx, req.SpaceID, appID, req.OwnerID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return appID, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func NewService(tos storage.Storage) Connector {
|
||||
|
||||
var i18n2ConnectorDesc = map[i18n.Locale]map[int64]string{
|
||||
i18n.LocaleEN: {
|
||||
consts.WebSDKConnectorID: "Deploy the bot as a Web SDK",
|
||||
consts.WebSDKConnectorID: "Deploy your project to the Chat SDK. This publishing method is supported only for projects that have created a conversation flow, please refer to [Installation Guidelines](coze://web-sdk-guide) for installation methods.",
|
||||
consts.APIConnectorID: "Supports OAuth 2.0 and personal access tokens",
|
||||
consts.CozeConnectorID: "Coze",
|
||||
},
|
||||
@@ -54,7 +54,7 @@ func (c *connectorImpl) AllConnectorInfo(ctx context.Context) []*entity.Connecto
|
||||
ID: consts.WebSDKConnectorID,
|
||||
Name: "Chat SDK",
|
||||
URI: "default_icon/connector-chat-sdk.jpg",
|
||||
Desc: "将Bot部署为Web SDK",
|
||||
Desc: "将项目部署到Chat SDK。仅创建过对话流的项目支持该发布方式,安装方式请查看[安装指引](coze://web-sdk-guide)",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -43,6 +43,7 @@ type RunRecordMeta struct {
|
||||
ChatRequest *string `json:"chat_message"`
|
||||
CompletedAt int64 `json:"completed_at"`
|
||||
FailedAt int64 `json:"failed_at"`
|
||||
CreatorID int64 `json:"creator_id"`
|
||||
}
|
||||
|
||||
type ChunkRunItem = RunRecordMeta
|
||||
@@ -158,3 +159,18 @@ type ModelAnswerEvent struct {
|
||||
Message *schema.Message
|
||||
Err error
|
||||
}
|
||||
|
||||
type ListRunRecordMeta struct {
|
||||
ConversationID int64 `json:"conversation_id"`
|
||||
AgentID int64 `json:"agent_id"`
|
||||
SectionID int64 `json:"section_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
OrderBy string `json:"order_by"` //desc asc
|
||||
BeforeID int64 `json:"before_id"`
|
||||
AfterID int64 `json:"after_id"`
|
||||
}
|
||||
|
||||
type CancelRunMeta struct {
|
||||
ConversationID int64 `json:"conversation_id"`
|
||||
RunID int64 `json:"run_id"`
|
||||
}
|
||||
|
||||
45
backend/domain/conversation/agentrun/internal/agent_info.go
Normal file
45
backend/domain/conversation/agentrun/internal/agent_info.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
|
||||
crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
)
|
||||
|
||||
func getAgentHistoryRounds(agentInfo *singleagent.SingleAgent) int32 {
|
||||
var conversationTurns int32 = entity.ConversationTurnsDefault
|
||||
if agentInfo != nil && agentInfo.ModelInfo != nil && agentInfo.ModelInfo.ShortMemoryPolicy != nil && ptr.From(agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) > 0 {
|
||||
conversationTurns = ptr.From(agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound)
|
||||
}
|
||||
return conversationTurns
|
||||
}
|
||||
|
||||
func getAgentInfo(ctx context.Context, agentID int64, isDraft bool) (*singleagent.SingleAgent, error) {
|
||||
agentInfo, err := crossagent.DefaultSVC().ObtainAgentByIdentity(ctx, &singleagent.AgentIdentity{
|
||||
AgentID: agentID,
|
||||
IsDraft: isDraft,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return agentInfo, nil
|
||||
}
|
||||
215
backend/domain/conversation/agentrun/internal/chatflow_run.go
Normal file
215
backend/domain/conversation/agentrun/internal/chatflow_run.go
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"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/errno"
|
||||
)
|
||||
|
||||
func (art *AgentRuntime) ChatflowRun(ctx context.Context, imagex imagex.ImageX) (err error) {
|
||||
|
||||
mh := &MesssageEventHanlder{
|
||||
sw: art.SW,
|
||||
messageEvent: art.MessageEvent,
|
||||
}
|
||||
resumeInfo := parseResumeInfo(ctx, art.GetHistory())
|
||||
wfID, _ := strconv.ParseInt(art.GetAgentInfo().LayoutInfo.WorkflowId, 10, 64)
|
||||
|
||||
if wfID == 0 {
|
||||
mh.handlerErr(ctx, errorx.New(errno.ErrAgentRunWorkflowNotFound))
|
||||
return
|
||||
}
|
||||
var wfStreamer *schema.StreamReader[*crossworkflow.WorkflowMessage]
|
||||
|
||||
executeConfig := crossworkflow.ExecuteConfig{
|
||||
ID: wfID,
|
||||
ConnectorID: art.GetRunMeta().ConnectorID,
|
||||
ConnectorUID: art.GetRunMeta().UserID,
|
||||
AgentID: ptr.Of(art.GetRunMeta().AgentID),
|
||||
Mode: crossworkflow.ExecuteModeRelease,
|
||||
BizType: crossworkflow.BizTypeAgent,
|
||||
SyncPattern: crossworkflow.SyncPatternStream,
|
||||
From: crossworkflow.FromLatestVersion,
|
||||
}
|
||||
|
||||
if resumeInfo != nil {
|
||||
wfStreamer, err = crossworkflow.DefaultSVC().StreamResume(ctx, &crossworkflow.ResumeRequest{
|
||||
ResumeData: concatWfInput(art),
|
||||
EventID: resumeInfo.ChatflowInterrupt.InterruptEvent.ID,
|
||||
ExecuteID: resumeInfo.ChatflowInterrupt.ExecuteID,
|
||||
}, executeConfig)
|
||||
} else {
|
||||
executeConfig.ConversationID = &art.GetRunMeta().ConversationID
|
||||
executeConfig.SectionID = &art.GetRunMeta().SectionID
|
||||
executeConfig.InitRoundID = &art.RunRecord.ID
|
||||
executeConfig.RoundID = &art.RunRecord.ID
|
||||
executeConfig.UserMessage = transMessageToSchemaMessage(ctx, []*msgEntity.Message{art.GetInput()}, imagex)[0]
|
||||
executeConfig.MaxHistoryRounds = ptr.Of(getAgentHistoryRounds(art.GetAgentInfo()))
|
||||
wfStreamer, err = crossworkflow.DefaultSVC().StreamExecute(ctx, executeConfig, map[string]any{
|
||||
"USER_INPUT": concatWfInput(art),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
safego.Go(ctx, func() {
|
||||
defer wg.Done()
|
||||
art.pullWfStream(ctx, wfStreamer, mh)
|
||||
})
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func concatWfInput(rtDependence *AgentRuntime) string {
|
||||
var input string
|
||||
for _, content := range rtDependence.RunMeta.Content {
|
||||
if content.Type == message.InputTypeText {
|
||||
input = content.Text + "," + input
|
||||
} else {
|
||||
for _, file := range content.FileData {
|
||||
input += file.Url + ","
|
||||
}
|
||||
}
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) pullWfStream(ctx context.Context, events *schema.StreamReader[*crossworkflow.WorkflowMessage], mh *MesssageEventHanlder) {
|
||||
|
||||
fullAnswerContent := bytes.NewBuffer([]byte{})
|
||||
var usage *msgEntity.UsageExt
|
||||
|
||||
preAnswerMsg, cErr := preCreateAnswer(ctx, art)
|
||||
|
||||
if cErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var preMsgIsFinish = false
|
||||
var lastAnswerMsg *entity.ChunkMessageItem
|
||||
|
||||
for {
|
||||
st, re := events.Recv()
|
||||
if re != nil {
|
||||
if errors.Is(re, io.EOF) {
|
||||
|
||||
if lastAnswerMsg != nil && usage != nil {
|
||||
art.SetUsage(&agentrun.Usage{
|
||||
LlmPromptTokens: usage.InputTokens,
|
||||
LlmCompletionTokens: usage.OutputTokens,
|
||||
LlmTotalTokens: usage.TotalCount,
|
||||
})
|
||||
_ = mh.handlerWfUsage(ctx, lastAnswerMsg, usage)
|
||||
}
|
||||
|
||||
finishErr := mh.handlerFinalAnswerFinish(ctx, art)
|
||||
if finishErr != nil {
|
||||
logs.CtxErrorf(ctx, "handlerFinalAnswerFinish error: %v", finishErr)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
logs.CtxErrorf(ctx, "pullWfStream Recv error: %v", re)
|
||||
mh.handlerErr(ctx, re)
|
||||
return
|
||||
}
|
||||
if st == nil {
|
||||
continue
|
||||
}
|
||||
if st.StateMessage != nil {
|
||||
if st.StateMessage.Status == crossworkflow.WorkflowFailed {
|
||||
mh.handlerErr(ctx, st.StateMessage.LastError)
|
||||
continue
|
||||
}
|
||||
if st.StateMessage.Usage != nil {
|
||||
usage = &msgEntity.UsageExt{
|
||||
InputTokens: st.StateMessage.Usage.InputTokens,
|
||||
OutputTokens: st.StateMessage.Usage.OutputTokens,
|
||||
TotalCount: st.StateMessage.Usage.InputTokens + st.StateMessage.Usage.OutputTokens,
|
||||
}
|
||||
}
|
||||
|
||||
if st.StateMessage.InterruptEvent != nil { // interrupt
|
||||
mh.handlerWfInterruptMsg(ctx, st.StateMessage, art)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if st.DataMessage == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch st.DataMessage.Type {
|
||||
case crossworkflow.Answer:
|
||||
|
||||
// input node & question node skip
|
||||
if st.DataMessage != nil && (st.DataMessage.NodeType == crossworkflow.NodeTypeInputReceiver || st.DataMessage.NodeType == crossworkflow.NodeTypeQuestion) {
|
||||
break
|
||||
}
|
||||
|
||||
if preMsgIsFinish {
|
||||
preAnswerMsg, cErr = preCreateAnswer(ctx, art)
|
||||
if cErr != nil {
|
||||
return
|
||||
}
|
||||
preMsgIsFinish = false
|
||||
}
|
||||
if st.DataMessage.Content != "" {
|
||||
fullAnswerContent.WriteString(st.DataMessage.Content)
|
||||
}
|
||||
|
||||
sendAnswerMsg := buildSendMsg(ctx, preAnswerMsg, false, art)
|
||||
sendAnswerMsg.Content = st.DataMessage.Content
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendAnswerMsg, mh.sw)
|
||||
|
||||
if st.DataMessage.Last {
|
||||
preMsgIsFinish = true
|
||||
sendAnswerMsg := buildSendMsg(ctx, preAnswerMsg, false, art)
|
||||
sendAnswerMsg.Content = fullAnswerContent.String()
|
||||
fullAnswerContent.Reset()
|
||||
hfErr := mh.handlerAnswer(ctx, sendAnswerMsg, usage, art, preAnswerMsg)
|
||||
if hfErr != nil {
|
||||
return
|
||||
}
|
||||
lastAnswerMsg = sendAnswerMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package dal
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -27,6 +29,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/query"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
@@ -59,8 +62,12 @@ func (dao *RunRecordDAO) Create(ctx context.Context, runMeta *entity.AgentRunMet
|
||||
return dao.buildPo2Do(createPO), nil
|
||||
}
|
||||
|
||||
func (dao *RunRecordDAO) GetByID(ctx context.Context, id int64) (*model.RunRecord, error) {
|
||||
return dao.query.RunRecord.WithContext(ctx).Where(dao.query.RunRecord.ID.Eq(id)).First()
|
||||
func (dao *RunRecordDAO) GetByID(ctx context.Context, id int64) (*entity.RunRecordMeta, error) {
|
||||
po, err := dao.query.RunRecord.WithContext(ctx).Where(dao.query.RunRecord.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dao.buildPo2Do(po), nil
|
||||
}
|
||||
|
||||
func (dao *RunRecordDAO) UpdateByID(ctx context.Context, id int64, updateMeta *entity.UpdateMeta) error {
|
||||
@@ -106,20 +113,40 @@ func (dao *RunRecordDAO) Delete(ctx context.Context, id []int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (dao *RunRecordDAO) List(ctx context.Context, conversationID int64, sectionID int64, limit int32) ([]*model.RunRecord, error) {
|
||||
logs.CtxInfof(ctx, "list run record req:%v, sectionID:%v, limit:%v", conversationID, sectionID, limit)
|
||||
func (dao *RunRecordDAO) List(ctx context.Context, meta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) {
|
||||
logs.CtxInfof(ctx, "list run record req:%v, sectionID:%v, limit:%v", meta.ConversationID, meta.SectionID, meta.Limit)
|
||||
m := dao.query.RunRecord
|
||||
do := m.WithContext(ctx).Where(m.ConversationID.Eq(conversationID)).Debug().Where(m.Status.NotIn(string(entity.RunStatusDeleted)))
|
||||
|
||||
if sectionID > 0 {
|
||||
do = do.Where(m.SectionID.Eq(sectionID))
|
||||
do := m.WithContext(ctx).Where(m.ConversationID.Eq(meta.ConversationID)).Debug().Where(m.Status.NotIn(string(entity.RunStatusDeleted)))
|
||||
if meta.BeforeID > 0 {
|
||||
runRecord, err := m.Where(m.ID.Eq(meta.BeforeID)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
do = do.Where(m.CreatedAt.Lt(runRecord.CreatedAt))
|
||||
}
|
||||
if limit > 0 {
|
||||
do = do.Limit(int(limit))
|
||||
if meta.AfterID > 0 {
|
||||
runRecord, err := m.Where(m.ID.Eq(meta.AfterID)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
do = do.Where(m.CreatedAt.Gt(runRecord.CreatedAt))
|
||||
}
|
||||
if meta.SectionID > 0 {
|
||||
do = do.Where(m.SectionID.Eq(meta.SectionID))
|
||||
}
|
||||
if meta.Limit > 0 {
|
||||
do = do.Limit(int(meta.Limit))
|
||||
}
|
||||
if strings.ToLower(meta.OrderBy) == "asc" {
|
||||
do = do.Order(m.CreatedAt.Asc())
|
||||
} else {
|
||||
do = do.Order(m.CreatedAt.Desc())
|
||||
}
|
||||
|
||||
runRecords, err := do.Order(m.CreatedAt.Desc()).Find()
|
||||
return runRecords, err
|
||||
runRecords, err := do.Find()
|
||||
return slices.Transform(runRecords, func(item *model.RunRecord) *entity.RunRecordMeta {
|
||||
return dao.buildPo2Do(item)
|
||||
}), err
|
||||
}
|
||||
|
||||
func (dao *RunRecordDAO) buildCreatePO(ctx context.Context, runMeta *entity.AgentRunMeta) (*model.RunRecord, error) {
|
||||
@@ -135,7 +162,10 @@ func (dao *RunRecordDAO) buildCreatePO(ctx context.Context, runMeta *entity.Agen
|
||||
}
|
||||
|
||||
timeNow := time.Now().UnixMilli()
|
||||
|
||||
creatorID, err := strconv.ParseInt(runMeta.UserID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.RunRecord{
|
||||
ID: runID,
|
||||
ConversationID: runMeta.ConversationID,
|
||||
@@ -145,6 +175,7 @@ func (dao *RunRecordDAO) buildCreatePO(ctx context.Context, runMeta *entity.Agen
|
||||
ChatRequest: string(reqOrigin),
|
||||
UserID: runMeta.UserID,
|
||||
CreatedAt: timeNow,
|
||||
CreatorID: creatorID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -161,7 +192,21 @@ func (dao *RunRecordDAO) buildPo2Do(po *model.RunRecord) *entity.RunRecordMeta {
|
||||
CompletedAt: po.CompletedAt,
|
||||
FailedAt: po.FailedAt,
|
||||
Usage: po.Usage,
|
||||
CreatorID: po.CreatorID,
|
||||
}
|
||||
|
||||
return runMeta
|
||||
}
|
||||
|
||||
func (dao *RunRecordDAO) Cancel(ctx context.Context, meta *entity.CancelRunMeta) (*entity.RunRecordMeta, error) {
|
||||
|
||||
m := dao.query.RunRecord
|
||||
_, err := m.WithContext(ctx).Where(m.ID.Eq(meta.RunID)).UpdateColumns(map[string]interface{}{
|
||||
"updated_at": time.Now().UnixMilli(),
|
||||
"status": entity.RunEventCancelled,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dao.GetByID(ctx, meta.RunID)
|
||||
}
|
||||
|
||||
@@ -1,78 +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 internal
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
}
|
||||
|
||||
func NewEvent() *Event {
|
||||
return &Event{}
|
||||
}
|
||||
|
||||
func (e *Event) buildMessageEvent(runEvent entity.RunEvent, chunkMsgItem *entity.ChunkMessageItem) *entity.AgentRunResponse {
|
||||
return &entity.AgentRunResponse{
|
||||
Event: runEvent,
|
||||
ChunkMessageItem: chunkMsgItem,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) buildRunEvent(runEvent entity.RunEvent, chunkRunItem *entity.ChunkRunItem) *entity.AgentRunResponse {
|
||||
return &entity.AgentRunResponse{
|
||||
Event: runEvent,
|
||||
ChunkRunItem: chunkRunItem,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) buildErrEvent(runEvent entity.RunEvent, err *entity.RunError) *entity.AgentRunResponse {
|
||||
return &entity.AgentRunResponse{
|
||||
Event: runEvent,
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) buildStreamDoneEvent() *entity.AgentRunResponse {
|
||||
|
||||
return &entity.AgentRunResponse{
|
||||
Event: entity.RunEventStreamDone,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) SendRunEvent(runEvent entity.RunEvent, runItem *entity.ChunkRunItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
resp := e.buildRunEvent(runEvent, runItem)
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
func (e *Event) SendMsgEvent(runEvent entity.RunEvent, messageItem *entity.ChunkMessageItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
resp := e.buildMessageEvent(runEvent, messageItem)
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
func (e *Event) SendErrEvent(runEvent entity.RunEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], err *entity.RunError) {
|
||||
resp := e.buildErrEvent(runEvent, err)
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
func (e *Event) SendStreamDoneEvent(sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
resp := e.buildStreamDoneEvent()
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
512
backend/domain/conversation/agentrun/internal/message_builder.go
Normal file
512
backend/domain/conversation/agentrun/internal/message_builder.go
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
messageModel "github.com/coze-dev/coze-studio/backend/api/model/conversation/message"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
|
||||
crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
|
||||
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
func buildSendMsg(_ context.Context, msg *msgEntity.Message, isFinish bool, rtDependence *AgentRuntime) *entity.ChunkMessageItem {
|
||||
|
||||
copyMap := make(map[string]string)
|
||||
for k, v := range msg.Ext {
|
||||
copyMap[k] = v
|
||||
}
|
||||
|
||||
return &entity.ChunkMessageItem{
|
||||
ID: msg.ID,
|
||||
ConversationID: msg.ConversationID,
|
||||
SectionID: msg.SectionID,
|
||||
AgentID: msg.AgentID,
|
||||
Content: msg.Content,
|
||||
Role: entity.RoleTypeAssistant,
|
||||
ContentType: msg.ContentType,
|
||||
MessageType: msg.MessageType,
|
||||
ReplyID: rtDependence.GetQuestionMsgID(),
|
||||
Type: msg.MessageType,
|
||||
CreatedAt: msg.CreatedAt,
|
||||
UpdatedAt: msg.UpdatedAt,
|
||||
RunID: rtDependence.GetRunRecord().ID,
|
||||
Ext: copyMap,
|
||||
IsFinish: isFinish,
|
||||
ReasoningContent: ptr.Of(msg.ReasoningContent),
|
||||
}
|
||||
}
|
||||
|
||||
func buildKnowledge(_ context.Context, chunk *entity.AgentRespEvent) *msgEntity.VerboseInfo {
|
||||
var recallDatas []msgEntity.RecallDataInfo
|
||||
for _, kOne := range chunk.Knowledge {
|
||||
recallDatas = append(recallDatas, msgEntity.RecallDataInfo{
|
||||
Slice: kOne.Content,
|
||||
Meta: msgEntity.MetaInfo{
|
||||
Dataset: msgEntity.DatasetInfo{
|
||||
ID: kOne.MetaData["dataset_id"].(string),
|
||||
Name: kOne.MetaData["dataset_name"].(string),
|
||||
},
|
||||
Document: msgEntity.DocumentInfo{
|
||||
ID: kOne.MetaData["document_id"].(string),
|
||||
Name: kOne.MetaData["document_name"].(string),
|
||||
},
|
||||
},
|
||||
Score: kOne.Score(),
|
||||
})
|
||||
}
|
||||
|
||||
verboseData := &msgEntity.VerboseData{
|
||||
Chunks: recallDatas,
|
||||
OriReq: "",
|
||||
StatusCode: 0,
|
||||
}
|
||||
data, err := json.Marshal(verboseData)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
knowledgeInfo := &msgEntity.VerboseInfo{
|
||||
MessageType: string(entity.MessageSubTypeKnowledgeCall),
|
||||
Data: string(data),
|
||||
}
|
||||
return knowledgeInfo
|
||||
}
|
||||
|
||||
func buildBotStateExt(arm *entity.AgentRunMeta) *msgEntity.BotStateExt {
|
||||
agentID := strconv.FormatInt(arm.AgentID, 10)
|
||||
botStateExt := &msgEntity.BotStateExt{
|
||||
AgentID: agentID,
|
||||
AgentName: arm.Name,
|
||||
Awaiting: agentID,
|
||||
BotID: agentID,
|
||||
}
|
||||
|
||||
return botStateExt
|
||||
}
|
||||
|
||||
type irMsg struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
ContentType string `json:"content_type"`
|
||||
Content any `json:"content"` // either optionContent or string
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func parseInterruptData(_ context.Context, interruptData *singleagent.InterruptInfo) (string, message.ContentType, error) {
|
||||
|
||||
defaultContentType := message.ContentTypeText
|
||||
switch interruptData.InterruptType {
|
||||
case singleagent.InterruptEventType_OauthPlugin:
|
||||
data := interruptData.AllToolInterruptData[interruptData.ToolCallID].ToolNeedOAuth.Message
|
||||
return data, defaultContentType, nil
|
||||
case singleagent.InterruptEventType_Question:
|
||||
data := interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData
|
||||
return processQuestionInterruptData(data)
|
||||
case singleagent.InterruptEventType_InputNode:
|
||||
data := interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData
|
||||
return processInputNodeInterruptData(data)
|
||||
case singleagent.InterruptEventType_WorkflowLLM:
|
||||
toolInterruptEvent := interruptData.AllWfInterruptData[interruptData.ToolCallID].ToolInterruptEvent
|
||||
data := toolInterruptEvent.InterruptData
|
||||
if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_InputNode {
|
||||
return processInputNodeInterruptData(data)
|
||||
}
|
||||
if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_Question {
|
||||
return processQuestionInterruptData(data)
|
||||
}
|
||||
return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType)
|
||||
|
||||
}
|
||||
return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType)
|
||||
}
|
||||
|
||||
func processQuestionInterruptData(data string) (string, message.ContentType, error) {
|
||||
defaultContentType := message.ContentTypeText
|
||||
var iData map[string][]*irMsg
|
||||
err := json.Unmarshal([]byte(data), &iData)
|
||||
if err != nil {
|
||||
return "", defaultContentType, err
|
||||
}
|
||||
if len(iData["messages"]) == 0 {
|
||||
return "", defaultContentType, errorx.New(errno.ErrInterruptDataEmpty)
|
||||
}
|
||||
interruptMsg := iData["messages"][0]
|
||||
|
||||
if interruptMsg.ContentType == "text" {
|
||||
return interruptMsg.Content.(string), defaultContentType, nil
|
||||
} else if interruptMsg.ContentType == "option" || interruptMsg.ContentType == "form_schema" {
|
||||
iMarshalData, err := json.Marshal(interruptMsg)
|
||||
if err != nil {
|
||||
return "", defaultContentType, err
|
||||
}
|
||||
return string(iMarshalData), message.ContentTypeCard, nil
|
||||
}
|
||||
return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType)
|
||||
}
|
||||
|
||||
func processInputNodeInterruptData(data string) (string, message.ContentType, error) {
|
||||
return data, message.ContentTypeCard, nil
|
||||
}
|
||||
|
||||
func handlerUsage(meta *schema.ResponseMeta) *msgEntity.UsageExt {
|
||||
if meta == nil || meta.Usage == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &msgEntity.UsageExt{
|
||||
TotalCount: int64(meta.Usage.TotalTokens),
|
||||
InputTokens: int64(meta.Usage.PromptTokens),
|
||||
OutputTokens: int64(meta.Usage.CompletionTokens),
|
||||
}
|
||||
}
|
||||
|
||||
func preCreateAnswer(ctx context.Context, rtDependence *AgentRuntime) (*msgEntity.Message, error) {
|
||||
arm := rtDependence.RunMeta
|
||||
msgMeta := &msgEntity.Message{
|
||||
ConversationID: arm.ConversationID,
|
||||
RunID: rtDependence.RunRecord.ID,
|
||||
AgentID: arm.AgentID,
|
||||
SectionID: arm.SectionID,
|
||||
UserID: arm.UserID,
|
||||
Role: schema.Assistant,
|
||||
MessageType: message.MessageTypeAnswer,
|
||||
ContentType: message.ContentTypeText,
|
||||
Ext: arm.Ext,
|
||||
}
|
||||
|
||||
if arm.Ext == nil {
|
||||
msgMeta.Ext = map[string]string{}
|
||||
}
|
||||
|
||||
botStateExt := buildBotStateExt(arm)
|
||||
bseString, err := json.Marshal(botStateExt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)]; !ok {
|
||||
msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)] = string(bseString)
|
||||
}
|
||||
|
||||
msgMeta.Ext = arm.Ext
|
||||
return crossmessage.DefaultSVC().PreCreate(ctx, msgMeta)
|
||||
}
|
||||
|
||||
func buildAgentMessage2Create(ctx context.Context, chunk *entity.AgentRespEvent, messageType message.MessageType, rtDependence *AgentRuntime) *message.Message {
|
||||
arm := rtDependence.GetRunMeta()
|
||||
msg := &msgEntity.Message{
|
||||
ConversationID: arm.ConversationID,
|
||||
RunID: rtDependence.RunRecord.ID,
|
||||
AgentID: arm.AgentID,
|
||||
SectionID: arm.SectionID,
|
||||
UserID: arm.UserID,
|
||||
MessageType: messageType,
|
||||
}
|
||||
buildExt := map[string]string{}
|
||||
|
||||
timeCost := fmt.Sprintf("%.1f", float64(time.Since(rtDependence.GetStartTime()).Milliseconds())/1000.00)
|
||||
|
||||
switch messageType {
|
||||
case message.MessageTypeQuestion:
|
||||
msg.Role = schema.User
|
||||
msg.ContentType = arm.ContentType
|
||||
for _, content := range arm.Content {
|
||||
if content.Type == message.InputTypeText {
|
||||
msg.Content = content.Text
|
||||
break
|
||||
}
|
||||
}
|
||||
msg.MultiContent = arm.Content
|
||||
buildExt = arm.Ext
|
||||
|
||||
msg.DisplayContent = arm.DisplayContent
|
||||
case message.MessageTypeAnswer, message.MessageTypeToolAsAnswer:
|
||||
msg.Role = schema.Assistant
|
||||
msg.ContentType = message.ContentTypeText
|
||||
|
||||
case message.MessageTypeToolResponse:
|
||||
msg.Role = schema.Assistant
|
||||
msg.ContentType = message.ContentTypeText
|
||||
msg.Content = chunk.ToolsMessage[0].Content
|
||||
|
||||
buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost
|
||||
modelContent := chunk.ToolsMessage[0]
|
||||
mc, err := json.Marshal(modelContent)
|
||||
if err == nil {
|
||||
msg.ModelContent = string(mc)
|
||||
}
|
||||
|
||||
case message.MessageTypeKnowledge:
|
||||
msg.Role = schema.Assistant
|
||||
msg.ContentType = message.ContentTypeText
|
||||
|
||||
knowledgeContent := buildKnowledge(ctx, chunk)
|
||||
if knowledgeContent != nil {
|
||||
knInfo, err := json.Marshal(knowledgeContent)
|
||||
if err == nil {
|
||||
msg.Content = string(knInfo)
|
||||
}
|
||||
}
|
||||
|
||||
buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost
|
||||
|
||||
modelContent := chunk.Knowledge
|
||||
mc, err := json.Marshal(modelContent)
|
||||
if err == nil {
|
||||
msg.ModelContent = string(mc)
|
||||
}
|
||||
|
||||
case message.MessageTypeFunctionCall:
|
||||
msg.Role = schema.Assistant
|
||||
msg.ContentType = message.ContentTypeText
|
||||
|
||||
if len(chunk.FuncCall.ToolCalls) > 0 {
|
||||
toolCall := chunk.FuncCall.ToolCalls[0]
|
||||
toolCalling, err := json.Marshal(toolCall)
|
||||
if err == nil {
|
||||
msg.Content = string(toolCalling)
|
||||
}
|
||||
buildExt[string(msgEntity.MessageExtKeyPlugin)] = toolCall.Function.Name
|
||||
buildExt[string(msgEntity.MessageExtKeyToolName)] = toolCall.Function.Name
|
||||
buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost
|
||||
|
||||
modelContent := chunk.FuncCall
|
||||
mc, err := json.Marshal(modelContent)
|
||||
if err == nil {
|
||||
msg.ModelContent = string(mc)
|
||||
}
|
||||
}
|
||||
case message.MessageTypeFlowUp:
|
||||
msg.Role = schema.Assistant
|
||||
msg.ContentType = message.ContentTypeText
|
||||
msg.Content = chunk.Suggest.Content
|
||||
|
||||
case message.MessageTypeVerbose:
|
||||
msg.Role = schema.Assistant
|
||||
msg.ContentType = message.ContentTypeText
|
||||
|
||||
d := &entity.Data{
|
||||
FinishReason: 0,
|
||||
FinData: "",
|
||||
}
|
||||
dByte, _ := json.Marshal(d)
|
||||
afc := &entity.AnswerFinshContent{
|
||||
MsgType: entity.MessageSubTypeGenerateFinish,
|
||||
Data: string(dByte),
|
||||
}
|
||||
afcMarshal, _ := json.Marshal(afc)
|
||||
msg.Content = string(afcMarshal)
|
||||
case message.MessageTypeInterrupt:
|
||||
msg.Role = schema.Assistant
|
||||
msg.MessageType = message.MessageTypeVerbose
|
||||
msg.ContentType = message.ContentTypeText
|
||||
|
||||
afc := &entity.AnswerFinshContent{
|
||||
MsgType: entity.MessageSubTypeInterrupt,
|
||||
Data: "",
|
||||
}
|
||||
afcMarshal, _ := json.Marshal(afc)
|
||||
msg.Content = string(afcMarshal)
|
||||
|
||||
// Add ext to save to context_message
|
||||
interruptByte, err := json.Marshal(chunk.Interrupt)
|
||||
if err == nil {
|
||||
buildExt[string(msgEntity.ExtKeyResumeInfo)] = string(interruptByte)
|
||||
}
|
||||
buildExt[string(msgEntity.ExtKeyToolCallsIDs)] = chunk.Interrupt.ToolCallID
|
||||
rc := &messageModel.RequiredAction{
|
||||
Type: "submit_tool_outputs",
|
||||
SubmitToolOutputs: &messageModel.SubmitToolOutputs{},
|
||||
}
|
||||
msg.RequiredAction = rc
|
||||
rcExtByte, err := json.Marshal(rc)
|
||||
if err == nil {
|
||||
buildExt[string(msgEntity.ExtKeyRequiresAction)] = string(rcExtByte)
|
||||
}
|
||||
}
|
||||
|
||||
if messageType != message.MessageTypeQuestion {
|
||||
botStateExt := buildBotStateExt(arm)
|
||||
bseString, err := json.Marshal(botStateExt)
|
||||
if err == nil {
|
||||
buildExt[string(msgEntity.MessageExtKeyBotState)] = string(bseString)
|
||||
}
|
||||
}
|
||||
msg.Ext = buildExt
|
||||
return msg
|
||||
}
|
||||
|
||||
func handlerWfInterruptEvent(_ context.Context, interruptEventData *crossworkflow.InterruptEvent) (string, message.ContentType, error) {
|
||||
defaultContentType := message.ContentTypeText
|
||||
switch singleagent.InterruptEventType(interruptEventData.EventType) {
|
||||
case singleagent.InterruptEventType_OauthPlugin:
|
||||
|
||||
case singleagent.InterruptEventType_Question:
|
||||
data := interruptEventData.InterruptData
|
||||
return processQuestionInterruptData(data)
|
||||
case singleagent.InterruptEventType_InputNode:
|
||||
data := interruptEventData.InterruptData
|
||||
return processInputNodeInterruptData(data)
|
||||
case singleagent.InterruptEventType_WorkflowLLM:
|
||||
data := interruptEventData.ToolInterruptEvent.InterruptData
|
||||
if singleagent.InterruptEventType(interruptEventData.EventType) == singleagent.InterruptEventType_InputNode {
|
||||
return processInputNodeInterruptData(data)
|
||||
}
|
||||
if singleagent.InterruptEventType(interruptEventData.EventType) == singleagent.InterruptEventType_Question {
|
||||
return processQuestionInterruptData(data)
|
||||
}
|
||||
return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType)
|
||||
}
|
||||
return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType)
|
||||
}
|
||||
|
||||
func historyPairs(historyMsg []*message.Message) []*message.Message {
|
||||
|
||||
fcMsgPairs := make(map[int64][]*message.Message)
|
||||
for _, one := range historyMsg {
|
||||
if one.MessageType != message.MessageTypeFunctionCall && one.MessageType != message.MessageTypeToolResponse {
|
||||
continue
|
||||
}
|
||||
if _, ok := fcMsgPairs[one.RunID]; !ok {
|
||||
fcMsgPairs[one.RunID] = []*message.Message{one}
|
||||
} else {
|
||||
fcMsgPairs[one.RunID] = append(fcMsgPairs[one.RunID], one)
|
||||
}
|
||||
}
|
||||
|
||||
var historyAfterPairs []*message.Message
|
||||
for _, value := range historyMsg {
|
||||
if value.MessageType == message.MessageTypeFunctionCall {
|
||||
if len(fcMsgPairs[value.RunID])%2 == 0 {
|
||||
historyAfterPairs = append(historyAfterPairs, value)
|
||||
}
|
||||
} else {
|
||||
historyAfterPairs = append(historyAfterPairs, value)
|
||||
}
|
||||
}
|
||||
return historyAfterPairs
|
||||
|
||||
}
|
||||
|
||||
func transMessageToSchemaMessage(ctx context.Context, msgs []*message.Message, imagexClient imagex.ImageX) []*schema.Message {
|
||||
schemaMessage := make([]*schema.Message, 0, len(msgs))
|
||||
|
||||
for _, msgOne := range msgs {
|
||||
if msgOne.ModelContent == "" {
|
||||
continue
|
||||
}
|
||||
if msgOne.MessageType == message.MessageTypeVerbose || msgOne.MessageType == message.MessageTypeFlowUp {
|
||||
continue
|
||||
}
|
||||
var sm *schema.Message
|
||||
err := json.Unmarshal([]byte(msgOne.ModelContent), &sm)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(sm.ReasoningContent) > 0 {
|
||||
sm.ReasoningContent = ""
|
||||
}
|
||||
schemaMessage = append(schemaMessage, parseMessageURI(ctx, sm, imagexClient))
|
||||
}
|
||||
|
||||
return schemaMessage
|
||||
}
|
||||
|
||||
func parseMessageURI(ctx context.Context, mcMsg *schema.Message, imagexClient imagex.ImageX) *schema.Message {
|
||||
if mcMsg.MultiContent == nil {
|
||||
return mcMsg
|
||||
}
|
||||
for k, one := range mcMsg.MultiContent {
|
||||
switch one.Type {
|
||||
case schema.ChatMessagePartTypeImageURL:
|
||||
|
||||
if one.ImageURL.URI != "" {
|
||||
url, err := imagexClient.GetResourceURL(ctx, one.ImageURL.URI)
|
||||
if err == nil {
|
||||
mcMsg.MultiContent[k].ImageURL.URL = url.URL
|
||||
}
|
||||
}
|
||||
case schema.ChatMessagePartTypeFileURL:
|
||||
if one.FileURL.URI != "" {
|
||||
url, err := imagexClient.GetResourceURL(ctx, one.FileURL.URI)
|
||||
if err == nil {
|
||||
mcMsg.MultiContent[k].FileURL.URL = url.URL
|
||||
}
|
||||
}
|
||||
case schema.ChatMessagePartTypeAudioURL:
|
||||
if one.AudioURL.URI != "" {
|
||||
url, err := imagexClient.GetResourceURL(ctx, one.AudioURL.URI)
|
||||
if err == nil {
|
||||
mcMsg.MultiContent[k].AudioURL.URL = url.URL
|
||||
}
|
||||
}
|
||||
case schema.ChatMessagePartTypeVideoURL:
|
||||
if one.VideoURL.URI != "" {
|
||||
url, err := imagexClient.GetResourceURL(ctx, one.VideoURL.URI)
|
||||
if err == nil {
|
||||
mcMsg.MultiContent[k].VideoURL.URL = url.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mcMsg
|
||||
}
|
||||
|
||||
func parseResumeInfo(_ context.Context, historyMsg []*message.Message) *crossagent.ResumeInfo {
|
||||
|
||||
var resumeInfo *crossagent.ResumeInfo
|
||||
for i := len(historyMsg) - 1; i >= 0; i-- {
|
||||
if historyMsg[i].MessageType == message.MessageTypeQuestion {
|
||||
break
|
||||
}
|
||||
if historyMsg[i].MessageType == message.MessageTypeVerbose {
|
||||
if historyMsg[i].Ext[string(msgEntity.ExtKeyResumeInfo)] != "" {
|
||||
err := json.Unmarshal([]byte(historyMsg[i].Ext[string(msgEntity.ExtKeyResumeInfo)]), &resumeInfo)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resumeInfo
|
||||
}
|
||||
|
||||
func buildSendRunRecord(_ context.Context, runRecord *entity.RunRecordMeta, runStatus entity.RunStatus) *entity.ChunkRunItem {
|
||||
return &entity.ChunkRunItem{
|
||||
ID: runRecord.ID,
|
||||
ConversationID: runRecord.ConversationID,
|
||||
AgentID: runRecord.AgentID,
|
||||
SectionID: runRecord.SectionID,
|
||||
Status: runStatus,
|
||||
CreatedAt: runRecord.CreatedAt,
|
||||
}
|
||||
}
|
||||
428
backend/domain/conversation/agentrun/internal/message_event.go
Normal file
428
backend/domain/conversation/agentrun/internal/message_event.go
Normal file
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/mohae/deepcopy"
|
||||
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
|
||||
crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
}
|
||||
|
||||
func NewMessageEvent() *Event {
|
||||
return &Event{}
|
||||
}
|
||||
|
||||
func (e *Event) buildMessageEvent(runEvent entity.RunEvent, chunkMsgItem *entity.ChunkMessageItem) *entity.AgentRunResponse {
|
||||
return &entity.AgentRunResponse{
|
||||
Event: runEvent,
|
||||
ChunkMessageItem: chunkMsgItem,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) buildRunEvent(runEvent entity.RunEvent, chunkRunItem *entity.ChunkRunItem) *entity.AgentRunResponse {
|
||||
return &entity.AgentRunResponse{
|
||||
Event: runEvent,
|
||||
ChunkRunItem: chunkRunItem,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) buildErrEvent(runEvent entity.RunEvent, err *entity.RunError) *entity.AgentRunResponse {
|
||||
return &entity.AgentRunResponse{
|
||||
Event: runEvent,
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) buildStreamDoneEvent() *entity.AgentRunResponse {
|
||||
|
||||
return &entity.AgentRunResponse{
|
||||
Event: entity.RunEventStreamDone,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Event) SendRunEvent(runEvent entity.RunEvent, runItem *entity.ChunkRunItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
resp := e.buildRunEvent(runEvent, runItem)
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
func (e *Event) SendMsgEvent(runEvent entity.RunEvent, messageItem *entity.ChunkMessageItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
resp := e.buildMessageEvent(runEvent, messageItem)
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
func (e *Event) SendErrEvent(runEvent entity.RunEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], err *entity.RunError) {
|
||||
resp := e.buildErrEvent(runEvent, err)
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
func (e *Event) SendStreamDoneEvent(sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
resp := e.buildStreamDoneEvent()
|
||||
sw.Send(resp, nil)
|
||||
}
|
||||
|
||||
type MesssageEventHanlder struct {
|
||||
messageEvent *Event
|
||||
sw *schema.StreamWriter[*entity.AgentRunResponse]
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerErr(_ context.Context, err error) {
|
||||
|
||||
var errMsg string
|
||||
var statusErr errorx.StatusError
|
||||
if errors.As(err, &statusErr) {
|
||||
errMsg = statusErr.Msg()
|
||||
} else {
|
||||
if strings.ToLower(os.Getenv(consts.RunMode)) != "debug" {
|
||||
errMsg = "Internal Server Error"
|
||||
} else {
|
||||
errMsg = errorx.ErrorWithoutStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
mh.messageEvent.SendErrEvent(entity.RunEventError, mh.sw, &entity.RunError{
|
||||
Code: errno.ErrAgentRun,
|
||||
Msg: errMsg,
|
||||
})
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerAckMessage(_ context.Context, input *msgEntity.Message) error {
|
||||
sendMsg := &entity.ChunkMessageItem{
|
||||
ID: input.ID,
|
||||
ConversationID: input.ConversationID,
|
||||
SectionID: input.SectionID,
|
||||
AgentID: input.AgentID,
|
||||
Role: entity.RoleType(input.Role),
|
||||
MessageType: message.MessageTypeAck,
|
||||
ReplyID: input.ID,
|
||||
Content: input.Content,
|
||||
ContentType: message.ContentTypeText,
|
||||
IsFinish: true,
|
||||
}
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventAck, sendMsg, mh.sw)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerFunctionCall(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
|
||||
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeFunctionCall, rtDependence)
|
||||
|
||||
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendMsg := buildSendMsg(ctx, cmData, true, rtDependence)
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerTooResponse(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, preToolResponseMsg *msgEntity.Message, toolResponseMsgContent string) error {
|
||||
|
||||
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeToolResponse, rtDependence)
|
||||
|
||||
var cmData *message.Message
|
||||
var err error
|
||||
|
||||
if preToolResponseMsg != nil {
|
||||
cm.ID = preToolResponseMsg.ID
|
||||
cm.CreatedAt = preToolResponseMsg.CreatedAt
|
||||
cm.UpdatedAt = preToolResponseMsg.UpdatedAt
|
||||
if len(toolResponseMsgContent) > 0 {
|
||||
cm.Content = toolResponseMsgContent + "\n" + cm.Content
|
||||
}
|
||||
}
|
||||
|
||||
cmData, err = crossmessage.DefaultSVC().Create(ctx, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendMsg := buildSendMsg(ctx, cmData, true, rtDependence)
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerSuggest(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
|
||||
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeFlowUp, rtDependence)
|
||||
|
||||
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendMsg := buildSendMsg(ctx, cmData, true, rtDependence)
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerKnowledge(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
|
||||
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeKnowledge, rtDependence)
|
||||
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendMsg := buildSendMsg(ctx, cmData, true, rtDependence)
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerAnswer(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt, rtDependence *AgentRuntime, preAnswerMsg *msgEntity.Message) error {
|
||||
|
||||
if len(msg.Content) == 0 && len(ptr.From(msg.ReasoningContent)) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg.IsFinish = true
|
||||
|
||||
if msg.Ext == nil {
|
||||
msg.Ext = map[string]string{}
|
||||
}
|
||||
if usage != nil {
|
||||
msg.Ext[string(msgEntity.MessageExtKeyToken)] = strconv.FormatInt(usage.TotalCount, 10)
|
||||
msg.Ext[string(msgEntity.MessageExtKeyInputTokens)] = strconv.FormatInt(usage.InputTokens, 10)
|
||||
msg.Ext[string(msgEntity.MessageExtKeyOutputTokens)] = strconv.FormatInt(usage.OutputTokens, 10)
|
||||
|
||||
rtDependence.Usage = &agentrun.Usage{
|
||||
LlmPromptTokens: usage.InputTokens,
|
||||
LlmCompletionTokens: usage.OutputTokens,
|
||||
LlmTotalTokens: usage.TotalCount,
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := msg.Ext[string(msgEntity.MessageExtKeyTimeCost)]; !ok {
|
||||
msg.Ext[string(msgEntity.MessageExtKeyTimeCost)] = fmt.Sprintf("%.1f", float64(time.Since(rtDependence.GetStartTime()).Milliseconds())/1000.00)
|
||||
}
|
||||
|
||||
buildModelContent := &schema.Message{
|
||||
Role: schema.Assistant,
|
||||
Content: msg.Content,
|
||||
}
|
||||
|
||||
mc, err := json.Marshal(buildModelContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
preAnswerMsg.Content = msg.Content
|
||||
preAnswerMsg.ReasoningContent = ptr.From(msg.ReasoningContent)
|
||||
preAnswerMsg.Ext = msg.Ext
|
||||
preAnswerMsg.ContentType = msg.ContentType
|
||||
preAnswerMsg.ModelContent = string(mc)
|
||||
preAnswerMsg.CreatedAt = 0
|
||||
preAnswerMsg.UpdatedAt = 0
|
||||
|
||||
_, err = crossmessage.DefaultSVC().Create(ctx, preAnswerMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, msg, mh.sw)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerFinalAnswerFinish(ctx context.Context, rtDependence *AgentRuntime) error {
|
||||
cm := buildAgentMessage2Create(ctx, nil, message.MessageTypeVerbose, rtDependence)
|
||||
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendMsg := buildSendMsg(ctx, cmData, true, rtDependence)
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerInterruptVerbose(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error {
|
||||
cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeInterrupt, rtDependence)
|
||||
cmData, err := crossmessage.DefaultSVC().Create(ctx, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendMsg := buildSendMsg(ctx, cmData, true, rtDependence)
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerWfUsage(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt) error {
|
||||
|
||||
if msg.Ext == nil {
|
||||
msg.Ext = map[string]string{}
|
||||
}
|
||||
if usage != nil {
|
||||
msg.Ext[string(msgEntity.MessageExtKeyToken)] = strconv.FormatInt(usage.TotalCount, 10)
|
||||
msg.Ext[string(msgEntity.MessageExtKeyInputTokens)] = strconv.FormatInt(usage.InputTokens, 10)
|
||||
msg.Ext[string(msgEntity.MessageExtKeyOutputTokens)] = strconv.FormatInt(usage.OutputTokens, 10)
|
||||
}
|
||||
|
||||
_, err := crossmessage.DefaultSVC().Edit(ctx, &msgEntity.Message{
|
||||
ID: msg.ID,
|
||||
Ext: msg.Ext,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, msg, mh.sw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerInterrupt(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, firstAnswerMsg *msgEntity.Message, reasoningContent string) error {
|
||||
interruptData, cType, err := parseInterruptData(ctx, chunk.Interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
preMsg, err := preCreateAnswer(ctx, rtDependence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deltaAnswer := &entity.ChunkMessageItem{
|
||||
ID: preMsg.ID,
|
||||
ConversationID: preMsg.ConversationID,
|
||||
SectionID: preMsg.SectionID,
|
||||
RunID: preMsg.RunID,
|
||||
AgentID: preMsg.AgentID,
|
||||
Role: entity.RoleType(preMsg.Role),
|
||||
Content: interruptData,
|
||||
MessageType: preMsg.MessageType,
|
||||
ContentType: cType,
|
||||
ReplyID: preMsg.RunID,
|
||||
Ext: preMsg.Ext,
|
||||
IsFinish: false,
|
||||
}
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageDelta, deltaAnswer, mh.sw)
|
||||
finalAnswer := deepcopy.Copy(deltaAnswer).(*entity.ChunkMessageItem)
|
||||
if len(reasoningContent) > 0 && firstAnswerMsg == nil {
|
||||
finalAnswer.ReasoningContent = ptr.Of(reasoningContent)
|
||||
}
|
||||
usage := func() *msgEntity.UsageExt {
|
||||
if rtDependence.GetUsage() != nil {
|
||||
return &msgEntity.UsageExt{
|
||||
TotalCount: rtDependence.GetUsage().LlmTotalTokens,
|
||||
InputTokens: rtDependence.GetUsage().LlmPromptTokens,
|
||||
OutputTokens: rtDependence.GetUsage().LlmCompletionTokens,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = mh.handlerAnswer(ctx, finalAnswer, usage(), rtDependence, preMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = mh.handlerInterruptVerbose(ctx, chunk, rtDependence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) handlerWfInterruptMsg(ctx context.Context, stateMsg *crossworkflow.StateMessage, rtDependence *AgentRuntime) {
|
||||
interruptData, cType, err := handlerWfInterruptEvent(ctx, stateMsg.InterruptEvent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
preMsg, err := preCreateAnswer(ctx, rtDependence)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
deltaAnswer := &entity.ChunkMessageItem{
|
||||
ID: preMsg.ID,
|
||||
ConversationID: preMsg.ConversationID,
|
||||
SectionID: preMsg.SectionID,
|
||||
RunID: preMsg.RunID,
|
||||
AgentID: preMsg.AgentID,
|
||||
Role: entity.RoleType(preMsg.Role),
|
||||
Content: interruptData,
|
||||
MessageType: preMsg.MessageType,
|
||||
ContentType: cType,
|
||||
ReplyID: preMsg.RunID,
|
||||
Ext: preMsg.Ext,
|
||||
IsFinish: false,
|
||||
}
|
||||
|
||||
mh.messageEvent.SendMsgEvent(entity.RunEventMessageDelta, deltaAnswer, mh.sw)
|
||||
finalAnswer := deepcopy.Copy(deltaAnswer).(*entity.ChunkMessageItem)
|
||||
|
||||
err = mh.handlerAnswer(ctx, finalAnswer, nil, rtDependence, preMsg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = mh.handlerInterruptVerbose(ctx, &entity.AgentRespEvent{
|
||||
EventType: message.MessageTypeInterrupt,
|
||||
Interrupt: &singleagent.InterruptInfo{
|
||||
|
||||
InterruptType: singleagent.InterruptEventType(stateMsg.InterruptEvent.EventType),
|
||||
InterruptID: strconv.FormatInt(stateMsg.InterruptEvent.ID, 10),
|
||||
ChatflowInterrupt: stateMsg,
|
||||
},
|
||||
}, rtDependence)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (mh *MesssageEventHanlder) HandlerInput(ctx context.Context, rtDependence *AgentRuntime) (*msgEntity.Message, error) {
|
||||
msgMeta := buildAgentMessage2Create(ctx, nil, message.MessageTypeQuestion, rtDependence)
|
||||
|
||||
cm, err := crossmessage.DefaultSVC().Create(ctx, msgMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ackErr := mh.handlerAckMessage(ctx, cm)
|
||||
if ackErr != nil {
|
||||
return msgMeta, ackErr
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
214
backend/domain/conversation/agentrun/internal/run.go
Normal file
214
backend/domain/conversation/agentrun/internal/run.go
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/repository"
|
||||
msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type AgentRuntime struct {
|
||||
RunRecord *entity.RunRecordMeta
|
||||
AgentInfo *singleagent.SingleAgent
|
||||
QuestionMsgID int64
|
||||
RunMeta *entity.AgentRunMeta
|
||||
StartTime time.Time
|
||||
Input *msgEntity.Message
|
||||
HistoryMsg []*msgEntity.Message
|
||||
Usage *agentrun.Usage
|
||||
SW *schema.StreamWriter[*entity.AgentRunResponse]
|
||||
|
||||
RunProcess *RunProcess
|
||||
RunRecordRepo repository.RunRecordRepo
|
||||
ImagexClient imagex.ImageX
|
||||
MessageEvent *Event
|
||||
}
|
||||
|
||||
func (rd *AgentRuntime) SetRunRecord(runRecord *entity.RunRecordMeta) {
|
||||
rd.RunRecord = runRecord
|
||||
}
|
||||
|
||||
func (rd *AgentRuntime) GetRunRecord() *entity.RunRecordMeta {
|
||||
return rd.RunRecord
|
||||
}
|
||||
|
||||
func (rd *AgentRuntime) SetUsage(usage *agentrun.Usage) {
|
||||
rd.Usage = usage
|
||||
}
|
||||
func (rd *AgentRuntime) GetUsage() *agentrun.Usage {
|
||||
return rd.Usage
|
||||
}
|
||||
|
||||
func (rd *AgentRuntime) SetRunMeta(arm *entity.AgentRunMeta) {
|
||||
rd.RunMeta = arm
|
||||
}
|
||||
func (rd *AgentRuntime) GetRunMeta() *entity.AgentRunMeta {
|
||||
return rd.RunMeta
|
||||
}
|
||||
func (rd *AgentRuntime) SetAgentInfo(agentInfo *singleagent.SingleAgent) {
|
||||
rd.AgentInfo = agentInfo
|
||||
}
|
||||
func (rd *AgentRuntime) GetAgentInfo() *singleagent.SingleAgent {
|
||||
return rd.AgentInfo
|
||||
}
|
||||
func (rd *AgentRuntime) SetQuestionMsgID(msgID int64) {
|
||||
rd.QuestionMsgID = msgID
|
||||
}
|
||||
func (rd *AgentRuntime) GetQuestionMsgID() int64 {
|
||||
return rd.QuestionMsgID
|
||||
}
|
||||
func (rd *AgentRuntime) SetStartTime(t time.Time) {
|
||||
rd.StartTime = t
|
||||
}
|
||||
func (rd *AgentRuntime) GetStartTime() time.Time {
|
||||
return rd.StartTime
|
||||
}
|
||||
func (rd *AgentRuntime) SetInput(input *msgEntity.Message) {
|
||||
rd.Input = input
|
||||
}
|
||||
func (rd *AgentRuntime) GetInput() *msgEntity.Message {
|
||||
return rd.Input
|
||||
}
|
||||
|
||||
func (rd *AgentRuntime) SetHistoryMsg(histroyMsg []*msgEntity.Message) {
|
||||
rd.HistoryMsg = histroyMsg
|
||||
}
|
||||
|
||||
func (rd *AgentRuntime) GetHistory() []*msgEntity.Message {
|
||||
return rd.HistoryMsg
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) Run(ctx context.Context) (err error) {
|
||||
|
||||
agentInfo, err := getAgentInfo(ctx, art.GetRunMeta().AgentID, art.GetRunMeta().IsDraft)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
art.SetAgentInfo(agentInfo)
|
||||
|
||||
history, err := art.getHistory(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
runRecord, err := art.createRunRecord(ctx)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
art.SetRunRecord(runRecord)
|
||||
art.SetHistoryMsg(history)
|
||||
|
||||
defer func() {
|
||||
srRecord := buildSendRunRecord(ctx, runRecord, entity.RunStatusCompleted)
|
||||
if err != nil {
|
||||
srRecord.Error = &entity.RunError{
|
||||
Code: errno.ErrConversationAgentRunError,
|
||||
Msg: err.Error(),
|
||||
}
|
||||
art.RunProcess.StepToFailed(ctx, srRecord, art.SW)
|
||||
return
|
||||
}
|
||||
art.RunProcess.StepToComplete(ctx, srRecord, art.SW, art.GetUsage())
|
||||
}()
|
||||
mh := &MesssageEventHanlder{
|
||||
messageEvent: art.MessageEvent,
|
||||
sw: art.SW,
|
||||
}
|
||||
input, err := mh.HandlerInput(ctx, art)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
art.SetInput(input)
|
||||
|
||||
art.SetQuestionMsgID(input.ID)
|
||||
|
||||
if art.GetAgentInfo().BotMode == bot_common.BotMode_WorkflowMode {
|
||||
err = art.ChatflowRun(ctx, art.ImagexClient)
|
||||
} else {
|
||||
err = art.AgentStreamExecute(ctx, art.ImagexClient)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) getHistory(ctx context.Context) ([]*msgEntity.Message, error) {
|
||||
|
||||
conversationTurns := getAgentHistoryRounds(art.GetAgentInfo())
|
||||
|
||||
runRecordList, err := art.RunRecordRepo.List(ctx, &entity.ListRunRecordMeta{
|
||||
ConversationID: art.GetRunMeta().ConversationID,
|
||||
SectionID: art.GetRunMeta().SectionID,
|
||||
Limit: conversationTurns,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(runRecordList) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
runIDS := concactRunID(runRecordList)
|
||||
history, err := crossmessage.DefaultSVC().GetByRunIDs(ctx, art.GetRunMeta().ConversationID, runIDS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return history, nil
|
||||
}
|
||||
|
||||
func concactRunID(rr []*entity.RunRecordMeta) []int64 {
|
||||
ids := make([]int64, 0, len(rr))
|
||||
for _, c := range rr {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) createRunRecord(ctx context.Context) (*entity.RunRecordMeta, error) {
|
||||
runPoData, err := art.RunRecordRepo.Create(ctx, art.GetRunMeta())
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "RunRecordRepo.Create error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srRecord := buildSendRunRecord(ctx, runPoData, entity.RunStatusCreated)
|
||||
|
||||
art.RunProcess.StepToCreate(ctx, srRecord, art.SW)
|
||||
|
||||
err = art.RunProcess.StepToInProgress(ctx, srRecord, art.SW)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "runProcess.StepToInProgress error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return runPoData, nil
|
||||
}
|
||||
@@ -30,8 +30,8 @@ import (
|
||||
)
|
||||
|
||||
type RunProcess struct {
|
||||
event *Event
|
||||
|
||||
event *Event
|
||||
SW *schema.StreamWriter[*entity.AgentRunResponse]
|
||||
RunRecordRepo repository.RunRecordRepo
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ func (r *RunProcess) StepToFailed(ctx context.Context, srRecord *entity.ChunkRun
|
||||
Code: srRecord.Error.Code,
|
||||
Msg: srRecord.Error.Msg,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RunProcess) StepToDone(sw *schema.StreamWriter[*entity.AgentRunResponse]) {
|
||||
450
backend/domain/conversation/agentrun/internal/singleagent_run.go
Normal file
450
backend/domain/conversation/agentrun/internal/singleagent_run.go
Normal file
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* 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 internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/mohae/deepcopy"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
|
||||
crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"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/errno"
|
||||
)
|
||||
|
||||
func (art *AgentRuntime) AgentStreamExecute(ctx context.Context, imagex imagex.ImageX) (err error) {
|
||||
mainChan := make(chan *entity.AgentRespEvent, 100)
|
||||
|
||||
ar := &crossagent.AgentRuntime{
|
||||
AgentVersion: art.GetRunMeta().Version,
|
||||
SpaceID: art.GetRunMeta().SpaceID,
|
||||
AgentID: art.GetRunMeta().AgentID,
|
||||
IsDraft: art.GetRunMeta().IsDraft,
|
||||
UserID: art.GetRunMeta().UserID,
|
||||
ConnectorID: art.GetRunMeta().ConnectorID,
|
||||
PreRetrieveTools: art.GetRunMeta().PreRetrieveTools,
|
||||
Input: transMessageToSchemaMessage(ctx, []*msgEntity.Message{art.GetInput()}, imagex)[0],
|
||||
HistoryMsg: transMessageToSchemaMessage(ctx, historyPairs(art.GetHistory()), imagex),
|
||||
ResumeInfo: parseResumeInfo(ctx, art.GetHistory()),
|
||||
}
|
||||
|
||||
streamer, err := crossagent.DefaultSVC().StreamExecute(ctx, ar)
|
||||
if err != nil {
|
||||
return errors.New(errorx.ErrorWithoutStack(err))
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
safego.Go(ctx, func() {
|
||||
defer wg.Done()
|
||||
art.pull(ctx, mainChan, streamer)
|
||||
})
|
||||
|
||||
safego.Go(ctx, func() {
|
||||
defer wg.Done()
|
||||
art.push(ctx, mainChan)
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) push(ctx context.Context, mainChan chan *entity.AgentRespEvent) {
|
||||
|
||||
mh := &MesssageEventHanlder{
|
||||
sw: art.SW,
|
||||
messageEvent: art.MessageEvent,
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "run.push error: %v", err)
|
||||
mh.handlerErr(ctx, err)
|
||||
}
|
||||
}()
|
||||
|
||||
reasoningContent := bytes.NewBuffer([]byte{})
|
||||
|
||||
var firstAnswerMsg *msgEntity.Message
|
||||
var reasoningMsg *msgEntity.Message
|
||||
isSendFinishAnswer := false
|
||||
var preToolResponseMsg *msgEntity.Message
|
||||
toolResponseMsgContent := bytes.NewBuffer([]byte{})
|
||||
for {
|
||||
chunk, ok := <-mainChan
|
||||
if !ok || chunk == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if chunk.Err != nil {
|
||||
if errors.Is(chunk.Err, io.EOF) {
|
||||
if !isSendFinishAnswer {
|
||||
isSendFinishAnswer = true
|
||||
if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 {
|
||||
art.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String())
|
||||
reasoningContent.Reset()
|
||||
}
|
||||
|
||||
finishErr := mh.handlerFinalAnswerFinish(ctx, art)
|
||||
if finishErr != nil {
|
||||
err = finishErr
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
mh.handlerErr(ctx, chunk.Err)
|
||||
return
|
||||
}
|
||||
|
||||
switch chunk.EventType {
|
||||
case message.MessageTypeFunctionCall:
|
||||
if chunk.FuncCall != nil && chunk.FuncCall.ResponseMeta != nil {
|
||||
if usage := handlerUsage(chunk.FuncCall.ResponseMeta); usage != nil {
|
||||
art.SetUsage(&agentrun.Usage{
|
||||
LlmPromptTokens: usage.InputTokens,
|
||||
LlmCompletionTokens: usage.OutputTokens,
|
||||
LlmTotalTokens: usage.TotalCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
err = mh.handlerFunctionCall(ctx, chunk, art)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if preToolResponseMsg == nil {
|
||||
var cErr error
|
||||
preToolResponseMsg, cErr = preCreateAnswer(ctx, art)
|
||||
if cErr != nil {
|
||||
err = cErr
|
||||
return
|
||||
}
|
||||
}
|
||||
case message.MessageTypeToolResponse:
|
||||
err = mh.handlerTooResponse(ctx, chunk, art, preToolResponseMsg, toolResponseMsgContent.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
preToolResponseMsg = nil // reset
|
||||
case message.MessageTypeKnowledge:
|
||||
err = mh.handlerKnowledge(ctx, chunk, art)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case message.MessageTypeToolMidAnswer:
|
||||
fullMidAnswerContent := bytes.NewBuffer([]byte{})
|
||||
var usage *msgEntity.UsageExt
|
||||
toolMidAnswerMsg, cErr := preCreateAnswer(ctx, art)
|
||||
|
||||
if cErr != nil {
|
||||
err = cErr
|
||||
return
|
||||
}
|
||||
|
||||
var preMsgIsFinish = false
|
||||
for {
|
||||
streamMsg, receErr := chunk.ToolMidAnswer.Recv()
|
||||
if receErr != nil {
|
||||
if errors.Is(receErr, io.EOF) {
|
||||
break
|
||||
}
|
||||
err = receErr
|
||||
return
|
||||
}
|
||||
if preMsgIsFinish {
|
||||
toolMidAnswerMsg, cErr = preCreateAnswer(ctx, art)
|
||||
if cErr != nil {
|
||||
err = cErr
|
||||
return
|
||||
}
|
||||
preMsgIsFinish = false
|
||||
}
|
||||
if streamMsg == nil {
|
||||
continue
|
||||
}
|
||||
if firstAnswerMsg == nil && len(streamMsg.Content) > 0 {
|
||||
if reasoningMsg != nil {
|
||||
toolMidAnswerMsg = deepcopy.Copy(reasoningMsg).(*msgEntity.Message)
|
||||
}
|
||||
firstAnswerMsg = deepcopy.Copy(toolMidAnswerMsg).(*msgEntity.Message)
|
||||
}
|
||||
|
||||
if streamMsg.Extra != nil {
|
||||
if val, ok := streamMsg.Extra["workflow_node_name"]; ok && val != nil {
|
||||
toolMidAnswerMsg.Ext["message_title"] = val.(string)
|
||||
}
|
||||
}
|
||||
|
||||
sendMidAnswerMsg := buildSendMsg(ctx, toolMidAnswerMsg, false, art)
|
||||
sendMidAnswerMsg.Content = streamMsg.Content
|
||||
toolResponseMsgContent.WriteString(streamMsg.Content)
|
||||
fullMidAnswerContent.WriteString(streamMsg.Content)
|
||||
|
||||
art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMidAnswerMsg, art.SW)
|
||||
|
||||
if streamMsg != nil && streamMsg.ResponseMeta != nil {
|
||||
usage = handlerUsage(streamMsg.ResponseMeta)
|
||||
}
|
||||
|
||||
if streamMsg.Extra["is_finish"] == true {
|
||||
preMsgIsFinish = true
|
||||
sendMidAnswerMsg := buildSendMsg(ctx, toolMidAnswerMsg, false, art)
|
||||
sendMidAnswerMsg.Content = fullMidAnswerContent.String()
|
||||
fullMidAnswerContent.Reset()
|
||||
hfErr := mh.handlerAnswer(ctx, sendMidAnswerMsg, usage, art, toolMidAnswerMsg)
|
||||
if hfErr != nil {
|
||||
err = hfErr
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case message.MessageTypeToolAsAnswer:
|
||||
var usage *msgEntity.UsageExt
|
||||
fullContent := bytes.NewBuffer([]byte{})
|
||||
toolAsAnswerMsg, cErr := preCreateAnswer(ctx, art)
|
||||
if cErr != nil {
|
||||
err = cErr
|
||||
return
|
||||
}
|
||||
if firstAnswerMsg == nil {
|
||||
firstAnswerMsg = toolAsAnswerMsg
|
||||
}
|
||||
|
||||
for {
|
||||
streamMsg, receErr := chunk.ToolAsAnswer.Recv()
|
||||
if receErr != nil {
|
||||
if errors.Is(receErr, io.EOF) {
|
||||
|
||||
answer := buildSendMsg(ctx, toolAsAnswerMsg, false, art)
|
||||
answer.Content = fullContent.String()
|
||||
hfErr := mh.handlerAnswer(ctx, answer, usage, art, toolAsAnswerMsg)
|
||||
if hfErr != nil {
|
||||
err = hfErr
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
err = receErr
|
||||
return
|
||||
}
|
||||
|
||||
if streamMsg != nil && streamMsg.ResponseMeta != nil {
|
||||
usage = handlerUsage(streamMsg.ResponseMeta)
|
||||
}
|
||||
sendMsg := buildSendMsg(ctx, toolAsAnswerMsg, false, art)
|
||||
fullContent.WriteString(streamMsg.Content)
|
||||
sendMsg.Content = streamMsg.Content
|
||||
art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMsg, art.SW)
|
||||
}
|
||||
|
||||
case message.MessageTypeAnswer:
|
||||
fullContent := bytes.NewBuffer([]byte{})
|
||||
var usage *msgEntity.UsageExt
|
||||
var isToolCalls = false
|
||||
var modelAnswerMsg *msgEntity.Message
|
||||
for {
|
||||
streamMsg, receErr := chunk.ModelAnswer.Recv()
|
||||
if receErr != nil {
|
||||
if errors.Is(receErr, io.EOF) {
|
||||
|
||||
if isToolCalls {
|
||||
break
|
||||
}
|
||||
if modelAnswerMsg == nil {
|
||||
break
|
||||
}
|
||||
answer := buildSendMsg(ctx, modelAnswerMsg, false, art)
|
||||
answer.Content = fullContent.String()
|
||||
hfErr := mh.handlerAnswer(ctx, answer, usage, art, modelAnswerMsg)
|
||||
if hfErr != nil {
|
||||
err = hfErr
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
err = receErr
|
||||
return
|
||||
}
|
||||
|
||||
if streamMsg != nil && len(streamMsg.ToolCalls) > 0 {
|
||||
isToolCalls = true
|
||||
}
|
||||
|
||||
if streamMsg != nil && streamMsg.ResponseMeta != nil {
|
||||
usage = handlerUsage(streamMsg.ResponseMeta)
|
||||
}
|
||||
|
||||
if streamMsg != nil && len(streamMsg.ReasoningContent) == 0 && len(streamMsg.Content) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(streamMsg.ReasoningContent) > 0 {
|
||||
if reasoningMsg == nil {
|
||||
reasoningMsg, err = preCreateAnswer(ctx, art)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sendReasoningMsg := buildSendMsg(ctx, reasoningMsg, false, art)
|
||||
reasoningContent.WriteString(streamMsg.ReasoningContent)
|
||||
sendReasoningMsg.ReasoningContent = ptr.Of(streamMsg.ReasoningContent)
|
||||
art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendReasoningMsg, art.SW)
|
||||
}
|
||||
if len(streamMsg.Content) > 0 {
|
||||
|
||||
if modelAnswerMsg == nil {
|
||||
modelAnswerMsg, err = preCreateAnswer(ctx, art)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if firstAnswerMsg == nil {
|
||||
if reasoningMsg != nil {
|
||||
modelAnswerMsg.ID = reasoningMsg.ID
|
||||
}
|
||||
firstAnswerMsg = modelAnswerMsg
|
||||
}
|
||||
}
|
||||
|
||||
sendAnswerMsg := buildSendMsg(ctx, modelAnswerMsg, false, art)
|
||||
fullContent.WriteString(streamMsg.Content)
|
||||
sendAnswerMsg.Content = streamMsg.Content
|
||||
art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendAnswerMsg, art.SW)
|
||||
}
|
||||
}
|
||||
|
||||
case message.MessageTypeFlowUp:
|
||||
if isSendFinishAnswer {
|
||||
|
||||
if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 {
|
||||
art.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String())
|
||||
}
|
||||
|
||||
isSendFinishAnswer = true
|
||||
finishErr := mh.handlerFinalAnswerFinish(ctx, art)
|
||||
if finishErr != nil {
|
||||
err = finishErr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = mh.handlerSuggest(ctx, chunk, art)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case message.MessageTypeInterrupt:
|
||||
err = mh.handlerInterrupt(ctx, chunk, art, firstAnswerMsg, reasoningContent.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) pull(_ context.Context, mainChan chan *entity.AgentRespEvent, events *schema.StreamReader[*crossagent.AgentEvent]) {
|
||||
defer func() {
|
||||
close(mainChan)
|
||||
}()
|
||||
|
||||
for {
|
||||
rm, re := events.Recv()
|
||||
if re != nil {
|
||||
errChunk := &entity.AgentRespEvent{
|
||||
Err: re,
|
||||
}
|
||||
mainChan <- errChunk
|
||||
return
|
||||
}
|
||||
|
||||
eventType, tErr := transformEventMap(rm.EventType)
|
||||
|
||||
if tErr != nil {
|
||||
errChunk := &entity.AgentRespEvent{
|
||||
Err: tErr,
|
||||
}
|
||||
mainChan <- errChunk
|
||||
return
|
||||
}
|
||||
|
||||
respChunk := &entity.AgentRespEvent{
|
||||
EventType: eventType,
|
||||
ModelAnswer: rm.ChatModelAnswer,
|
||||
ToolsMessage: rm.ToolsMessage,
|
||||
FuncCall: rm.FuncCall,
|
||||
Knowledge: rm.Knowledge,
|
||||
Suggest: rm.Suggest,
|
||||
Interrupt: rm.Interrupt,
|
||||
|
||||
ToolMidAnswer: rm.ToolMidAnswer,
|
||||
ToolAsAnswer: rm.ToolAsChatModelAnswer,
|
||||
}
|
||||
|
||||
mainChan <- respChunk
|
||||
}
|
||||
}
|
||||
|
||||
func transformEventMap(eventType singleagent.EventType) (message.MessageType, error) {
|
||||
var eType message.MessageType
|
||||
switch eventType {
|
||||
case singleagent.EventTypeOfFuncCall:
|
||||
return message.MessageTypeFunctionCall, nil
|
||||
case singleagent.EventTypeOfKnowledge:
|
||||
return message.MessageTypeKnowledge, nil
|
||||
case singleagent.EventTypeOfToolsMessage:
|
||||
return message.MessageTypeToolResponse, nil
|
||||
case singleagent.EventTypeOfChatModelAnswer:
|
||||
return message.MessageTypeAnswer, nil
|
||||
case singleagent.EventTypeOfToolsAsChatModelStream:
|
||||
return message.MessageTypeToolAsAnswer, nil
|
||||
case singleagent.EventTypeOfToolMidAnswer:
|
||||
return message.MessageTypeToolMidAnswer, nil
|
||||
case singleagent.EventTypeOfSuggest:
|
||||
return message.MessageTypeFlowUp, nil
|
||||
case singleagent.EventTypeOfInterrupt:
|
||||
return message.MessageTypeInterrupt, nil
|
||||
}
|
||||
return eType, errorx.New(errno.ErrReplyUnknowEventType)
|
||||
}
|
||||
|
||||
func (art *AgentRuntime) saveReasoningContent(ctx context.Context, firstAnswerMsg *msgEntity.Message, reasoningContent string) {
|
||||
_, err := crossmessage.DefaultSVC().Edit(ctx, &message.Message{
|
||||
ID: firstAnswerMsg.ID,
|
||||
ReasoningContent: reasoningContent,
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxInfof(ctx, "save reasoning content failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
)
|
||||
|
||||
@@ -34,8 +33,9 @@ func NewRunRecordRepo(db *gorm.DB, idGen idgen.IDGenerator) RunRecordRepo {
|
||||
|
||||
type RunRecordRepo interface {
|
||||
Create(ctx context.Context, runMeta *entity.AgentRunMeta) (*entity.RunRecordMeta, error)
|
||||
GetByID(ctx context.Context, id int64) (*entity.RunRecord, error)
|
||||
GetByID(ctx context.Context, id int64) (*entity.RunRecordMeta, error)
|
||||
Cancel(ctx context.Context, req *entity.CancelRunMeta) (*entity.RunRecordMeta, error)
|
||||
Delete(ctx context.Context, id []int64) error
|
||||
UpdateByID(ctx context.Context, id int64, update *entity.UpdateMeta) error
|
||||
List(ctx context.Context, conversationID int64, sectionID int64, limit int32) ([]*model.RunRecord, error)
|
||||
List(ctx context.Context, meta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ import (
|
||||
|
||||
type Run interface {
|
||||
AgentRun(ctx context.Context, req *entity.AgentRunMeta) (*schema.StreamReader[*entity.AgentRunResponse], error)
|
||||
|
||||
Delete(ctx context.Context, runID []int64) error
|
||||
Create(ctx context.Context, runRecord *entity.AgentRunMeta) (*entity.RunRecordMeta, error)
|
||||
List(ctx context.Context, ListMeta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error)
|
||||
GetByID(ctx context.Context, runID int64) (*entity.RunRecordMeta, error)
|
||||
Cancel(ctx context.Context, req *entity.CancelRunMeta) (*entity.RunRecordMeta, error)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,18 @@
|
||||
package agentrun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/repository"
|
||||
mock "github.com/coze-dev/coze-studio/backend/internal/mock/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/internal/mock/infra/contract/orm"
|
||||
)
|
||||
|
||||
func TestAgentRun(t *testing.T) {
|
||||
@@ -97,3 +108,158 @@ func TestAgentRun(t *testing.T) {
|
||||
// assert.NoError(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestRunImpl_List(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockDBGen := orm.NewMockDB()
|
||||
mockDBGen.AddTable(&model.RunRecord{}).AddRows(
|
||||
&model.RunRecord{
|
||||
ID: 1,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix(),
|
||||
},
|
||||
&model.RunRecord{
|
||||
ID: 2,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 1,
|
||||
}, &model.RunRecord{
|
||||
ID: 3,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 2,
|
||||
}, &model.RunRecord{
|
||||
ID: 4,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 3,
|
||||
}, &model.RunRecord{
|
||||
ID: 5,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 4,
|
||||
},
|
||||
&model.RunRecord{
|
||||
ID: 6,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 5,
|
||||
}, &model.RunRecord{
|
||||
ID: 7,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 6,
|
||||
}, &model.RunRecord{
|
||||
ID: 8,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 7,
|
||||
}, &model.RunRecord{
|
||||
ID: 9,
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
UserID: "123456",
|
||||
CreatedAt: time.Now().Unix() + 8,
|
||||
},
|
||||
)
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
mockIDGen := mock.NewMockIDGenerator(ctrl)
|
||||
|
||||
runRecordRepo := repository.NewRunRecordRepo(mockDB, mockIDGen)
|
||||
|
||||
service := &runImpl{
|
||||
Components: Components{
|
||||
RunRecordRepo: runRecordRepo,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("list success", func(t *testing.T) {
|
||||
|
||||
meta := &entity.ListRunRecordMeta{
|
||||
ConversationID: 123,
|
||||
AgentID: 456,
|
||||
SectionID: 789,
|
||||
Limit: 10,
|
||||
OrderBy: "desc",
|
||||
}
|
||||
|
||||
result, err := service.List(ctx, meta)
|
||||
// check result
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 9)
|
||||
assert.Equal(t, int64(123), result[0].ConversationID)
|
||||
assert.Equal(t, int64(456), result[0].AgentID)
|
||||
})
|
||||
|
||||
t.Run("empty list", func(t *testing.T) {
|
||||
meta := &entity.ListRunRecordMeta{
|
||||
ConversationID: 999, //
|
||||
Limit: 10,
|
||||
OrderBy: "desc",
|
||||
}
|
||||
|
||||
// check result
|
||||
result, err := service.List(ctx, meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("search with before id", func(t *testing.T) {
|
||||
|
||||
meta := &entity.ListRunRecordMeta{
|
||||
ConversationID: 123,
|
||||
SectionID: 789,
|
||||
AgentID: 456,
|
||||
BeforeID: 5,
|
||||
Limit: 3,
|
||||
OrderBy: "desc",
|
||||
}
|
||||
|
||||
result, err := service.List(ctx, meta)
|
||||
|
||||
// check result
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 3)
|
||||
assert.Equal(t, int64(4), result[0].ID)
|
||||
})
|
||||
t.Run("search with after id and limit", func(t *testing.T) {
|
||||
|
||||
meta := &entity.ListRunRecordMeta{
|
||||
ConversationID: 123,
|
||||
SectionID: 789,
|
||||
AgentID: 456,
|
||||
AfterID: 5,
|
||||
Limit: 3,
|
||||
OrderBy: "desc",
|
||||
}
|
||||
|
||||
result, err := service.List(ctx, meta)
|
||||
|
||||
// check result
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 3)
|
||||
assert.Equal(t, int64(9), result[0].ID)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
type Conversation = conversation.Conversation
|
||||
|
||||
type CreateMeta struct {
|
||||
Name string `json:"name"`
|
||||
AgentID int64 `json:"agent_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
ConnectorID int64 `json:"connector_id"`
|
||||
@@ -50,3 +51,8 @@ type ListMeta struct {
|
||||
Limit int `json:"limit"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
type UpdateMeta struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
@@ -107,6 +107,20 @@ func (dao *ConversationDAO) Delete(ctx context.Context, id int64) (int64, error)
|
||||
return updateRes.RowsAffected, err
|
||||
}
|
||||
|
||||
func (dao *ConversationDAO) Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) {
|
||||
updateColumn := make(map[string]interface{})
|
||||
updateColumn[dao.query.Conversation.UpdatedAt.ColumnName().String()] = time.Now().UnixMilli()
|
||||
if len(req.Name) > 0 {
|
||||
updateColumn[dao.query.Conversation.Name.ColumnName().String()] = req.Name
|
||||
}
|
||||
|
||||
_, err := dao.query.Conversation.WithContext(ctx).Where(dao.query.Conversation.ID.Eq(req.ID)).UpdateColumns(updateColumn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dao.GetByID(ctx, req.ID)
|
||||
}
|
||||
|
||||
func (dao *ConversationDAO) Get(ctx context.Context, userID int64, agentID int64, scene int32, connectorID int64) (*entity.Conversation, error) {
|
||||
po, err := dao.query.Conversation.WithContext(ctx).Debug().
|
||||
Where(dao.query.Conversation.CreatorID.Eq(userID)).
|
||||
@@ -133,13 +147,15 @@ func (dao *ConversationDAO) List(ctx context.Context, userID int64, agentID int6
|
||||
do = do.Where(dao.query.Conversation.CreatorID.Eq(userID)).
|
||||
Where(dao.query.Conversation.AgentID.Eq(agentID)).
|
||||
Where(dao.query.Conversation.Scene.Eq(scene)).
|
||||
Where(dao.query.Conversation.ConnectorID.Eq(connectorID))
|
||||
Where(dao.query.Conversation.ConnectorID.Eq(connectorID)).
|
||||
Where(dao.query.Conversation.Status.Eq(int32(conversation.ConversationStatusNormal)))
|
||||
|
||||
do = do.Offset((page - 1) * limit)
|
||||
|
||||
if limit > 0 {
|
||||
do = do.Limit(int(limit) + 1)
|
||||
}
|
||||
do = do.Order(dao.query.Conversation.CreatedAt.Desc())
|
||||
|
||||
poList, err := do.Find()
|
||||
|
||||
@@ -173,6 +189,7 @@ func (dao *ConversationDAO) conversationDO2PO(ctx context.Context, conversation
|
||||
Ext: conversation.Ext,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
Name: conversation.Name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +205,7 @@ func (dao *ConversationDAO) conversationPO2DO(ctx context.Context, c *model.Conv
|
||||
Ext: c.Ext,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
Name: c.Name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +222,7 @@ func (dao *ConversationDAO) conversationBatchPO2DO(ctx context.Context, conversa
|
||||
Ext: c.Ext,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
Name: c.Name,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@@ -9,6 +25,7 @@ const TableNameConversation = "conversation"
|
||||
// Conversation conversation info record
|
||||
type Conversation struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id
|
||||
Name string `gorm:"column:name;not null;comment:conversation name" json:"name"` // conversation name
|
||||
ConnectorID int64 `gorm:"column:connector_id;not null;comment:Publish Connector ID" json:"connector_id"` // Publish Connector ID
|
||||
AgentID int64 `gorm:"column:agent_id;not null;comment:agent_id" json:"agent_id"` // agent_id
|
||||
Scene int32 `gorm:"column:scene;not null;comment:conversation scene" json:"scene"` // conversation scene
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@@ -28,6 +44,7 @@ func newConversation(db *gorm.DB, opts ...gen.DOOption) conversation {
|
||||
tableName := _conversation.conversationDo.TableName()
|
||||
_conversation.ALL = field.NewAsterisk(tableName)
|
||||
_conversation.ID = field.NewInt64(tableName, "id")
|
||||
_conversation.Name = field.NewString(tableName, "name")
|
||||
_conversation.ConnectorID = field.NewInt64(tableName, "connector_id")
|
||||
_conversation.AgentID = field.NewInt64(tableName, "agent_id")
|
||||
_conversation.Scene = field.NewInt32(tableName, "scene")
|
||||
@@ -49,6 +66,7 @@ type conversation struct {
|
||||
|
||||
ALL field.Asterisk
|
||||
ID field.Int64 // id
|
||||
Name field.String // conversation name
|
||||
ConnectorID field.Int64 // Publish Connector ID
|
||||
AgentID field.Int64 // agent_id
|
||||
Scene field.Int32 // conversation scene
|
||||
@@ -75,6 +93,7 @@ func (c conversation) As(alias string) *conversation {
|
||||
func (c *conversation) updateTableName(table string) *conversation {
|
||||
c.ALL = field.NewAsterisk(table)
|
||||
c.ID = field.NewInt64(table, "id")
|
||||
c.Name = field.NewString(table, "name")
|
||||
c.ConnectorID = field.NewInt64(table, "connector_id")
|
||||
c.AgentID = field.NewInt64(table, "agent_id")
|
||||
c.Scene = field.NewInt32(table, "scene")
|
||||
@@ -100,8 +119,9 @@ func (c *conversation) GetFieldByName(fieldName string) (field.OrderExpr, bool)
|
||||
}
|
||||
|
||||
func (c *conversation) fillFieldMap() {
|
||||
c.fieldMap = make(map[string]field.Expr, 10)
|
||||
c.fieldMap = make(map[string]field.Expr, 11)
|
||||
c.fieldMap["id"] = c.ID
|
||||
c.fieldMap["name"] = c.Name
|
||||
c.fieldMap["connector_id"] = c.ConnectorID
|
||||
c.fieldMap["agent_id"] = c.AgentID
|
||||
c.fieldMap["scene"] = c.Scene
|
||||
|
||||
@@ -35,6 +35,7 @@ type ConversationRepo interface {
|
||||
GetByID(ctx context.Context, id int64) (*entity.Conversation, error)
|
||||
UpdateSection(ctx context.Context, id int64) (int64, error)
|
||||
Get(ctx context.Context, userID int64, agentID int64, scene int32, connectorID int64) (*entity.Conversation, error)
|
||||
Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error)
|
||||
Delete(ctx context.Context, id int64) (int64, error)
|
||||
List(ctx context.Context, userID int64, agentID int64, connectorID int64, scene int32, limit int, page int) ([]*entity.Conversation, bool, error)
|
||||
}
|
||||
|
||||
@@ -29,4 +29,5 @@ type Conversation interface {
|
||||
GetCurrentConversation(ctx context.Context, req *entity.GetCurrent) (*entity.Conversation, error)
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error)
|
||||
Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ func (c *conversationImpl) Delete(ctx context.Context, id int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conversationImpl) Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) {
|
||||
// get conversation
|
||||
return c.ConversationRepo.Update(ctx, req)
|
||||
}
|
||||
|
||||
func (c *conversationImpl) List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error) {
|
||||
conversationList, hasMore, err := c.ConversationRepo.List(ctx, req.UserID, req.AgentID, req.ConnectorID, int32(req.Scene), req.Limit, req.Page)
|
||||
|
||||
|
||||
@@ -16,19 +16,22 @@
|
||||
|
||||
package entity
|
||||
|
||||
import "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
import (
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
)
|
||||
|
||||
type Message = message.Message
|
||||
|
||||
type ListMeta struct {
|
||||
ConversationID int64 `json:"conversation_id"`
|
||||
RunID []*int64 `json:"run_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AgentID int64 `json:"agent_id"`
|
||||
OrderBy *string `json:"order_by"`
|
||||
Limit int `json:"limit"`
|
||||
Cursor int64 `json:"cursor"` // message id
|
||||
Direction ScrollPageDirection `json:"direction"` // "prev" "Next"
|
||||
ConversationID int64 `json:"conversation_id"`
|
||||
RunID []*int64 `json:"run_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AgentID int64 `json:"agent_id"`
|
||||
OrderBy *string `json:"order_by"`
|
||||
Limit int `json:"limit"`
|
||||
Cursor int64 `json:"cursor"` // message id
|
||||
Direction ScrollPageDirection `json:"direction"` // "prev" "Next"
|
||||
MessageType []*message.MessageType `json:"message_type"`
|
||||
}
|
||||
|
||||
type ListResult struct {
|
||||
@@ -45,8 +48,9 @@ type GetByRunIDsRequest struct {
|
||||
}
|
||||
|
||||
type DeleteMeta struct {
|
||||
MessageIDs []int64 `json:"message_ids"`
|
||||
RunIDs []int64 `json:"run_ids"`
|
||||
ConversationID *int64 `json:"conversation_id"`
|
||||
MessageIDs []int64 `json:"message_ids"`
|
||||
RunIDs []int64 `json:"run_ids"`
|
||||
}
|
||||
|
||||
type BrokenMeta struct {
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/message/internal/dal/query"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
@@ -71,27 +72,41 @@ func (dao *MessageDAO) Create(ctx context.Context, msg *entity.Message) (*entity
|
||||
return dao.messagePO2DO(poData), nil
|
||||
}
|
||||
|
||||
func (dao *MessageDAO) List(ctx context.Context, conversationID int64, limit int, cursor int64, direction entity.ScrollPageDirection, messageType *message.MessageType) ([]*entity.Message, bool, error) {
|
||||
func (dao *MessageDAO) List(ctx context.Context, listMeta *entity.ListMeta) ([]*entity.Message, bool, error) {
|
||||
m := dao.query.Message
|
||||
do := m.WithContext(ctx).Debug().Where(m.ConversationID.Eq(conversationID)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable)))
|
||||
do := m.WithContext(ctx).Debug().Where(m.ConversationID.Eq(listMeta.ConversationID)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable)))
|
||||
|
||||
if messageType != nil {
|
||||
do = do.Where(m.MessageType.Eq(string(*messageType)))
|
||||
if len(listMeta.RunID) > 0 {
|
||||
do = do.Where(m.RunID.In(slices.Transform(listMeta.RunID, func(t *int64) int64 {
|
||||
return *t
|
||||
})...))
|
||||
}
|
||||
if len(listMeta.MessageType) > 0 {
|
||||
do = do.Where(m.MessageType.In(slices.Transform(listMeta.MessageType, func(t *message.MessageType) string {
|
||||
return string(*t)
|
||||
})...))
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
do = do.Limit(int(limit) + 1)
|
||||
if listMeta.Limit > 0 {
|
||||
do = do.Limit(int(listMeta.Limit) + 1)
|
||||
}
|
||||
|
||||
if cursor > 0 {
|
||||
if direction == entity.ScrollPageDirectionPrev {
|
||||
do = do.Where(m.CreatedAt.Lt(cursor))
|
||||
} else {
|
||||
do = do.Where(m.CreatedAt.Gt(cursor))
|
||||
if listMeta.Cursor > 0 {
|
||||
msg, err := m.Where(m.ID.Eq(listMeta.Cursor)).First()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if listMeta.Direction == entity.ScrollPageDirectionPrev {
|
||||
do = do.Where(m.CreatedAt.Lt(msg.CreatedAt))
|
||||
do = do.Order(m.CreatedAt.Desc())
|
||||
} else {
|
||||
do = do.Where(m.CreatedAt.Gt(msg.CreatedAt))
|
||||
do = do.Order(m.CreatedAt.Asc())
|
||||
}
|
||||
} else {
|
||||
do = do.Order(m.CreatedAt.Desc())
|
||||
}
|
||||
|
||||
do = do.Order(m.CreatedAt.Desc())
|
||||
messageList, err := do.Find()
|
||||
|
||||
var hasMore bool
|
||||
@@ -103,9 +118,9 @@ func (dao *MessageDAO) List(ctx context.Context, conversationID int64, limit int
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if len(messageList) > limit {
|
||||
if len(messageList) > int(listMeta.Limit) {
|
||||
hasMore = true
|
||||
messageList = messageList[:limit]
|
||||
messageList = messageList[:int(listMeta.Limit)]
|
||||
}
|
||||
|
||||
return dao.batchMessagePO2DO(messageList), hasMore, nil
|
||||
@@ -113,7 +128,8 @@ func (dao *MessageDAO) List(ctx context.Context, conversationID int64, limit int
|
||||
|
||||
func (dao *MessageDAO) GetByRunIDs(ctx context.Context, runIDs []int64, orderBy string) ([]*entity.Message, error) {
|
||||
m := dao.query.Message
|
||||
do := m.WithContext(ctx).Debug().Where(m.RunID.In(runIDs...))
|
||||
do := m.WithContext(ctx).Debug().Where(m.RunID.In(runIDs...)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable)))
|
||||
|
||||
if orderBy == "DESC" {
|
||||
do = do.Order(m.CreatedAt.Desc())
|
||||
} else {
|
||||
@@ -133,19 +149,37 @@ func (dao *MessageDAO) GetByRunIDs(ctx context.Context, runIDs []int64, orderBy
|
||||
|
||||
func (dao *MessageDAO) Edit(ctx context.Context, msgID int64, msg *message.Message) (int64, error) {
|
||||
m := dao.query.Message
|
||||
columns := dao.buildEditColumns(msg)
|
||||
|
||||
originMsg, err := dao.GetByID(ctx, msgID)
|
||||
if originMsg == nil {
|
||||
return 0, errorx.New(errno.ErrRecordNotFound)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
columns := dao.buildEditColumns(msg, originMsg)
|
||||
do, err := m.WithContext(ctx).Where(m.ID.Eq(msgID)).UpdateColumns(columns)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if do.RowsAffected == 0 {
|
||||
return 0, errorx.New(errno.ErrRecordNotFound)
|
||||
}
|
||||
|
||||
return do.RowsAffected, nil
|
||||
}
|
||||
|
||||
func (dao *MessageDAO) buildEditColumns(msg *message.Message) map[string]interface{} {
|
||||
func (dao *MessageDAO) buildEditColumns(msg *message.Message, originMsg *entity.Message) map[string]interface{} {
|
||||
columns := make(map[string]interface{})
|
||||
table := dao.query.Message
|
||||
if msg.Content != "" {
|
||||
msg.Role = originMsg.Role
|
||||
columns[table.Content.ColumnName().String()] = msg.Content
|
||||
modelContent, err := dao.buildModelContent(msg)
|
||||
if err == nil {
|
||||
columns[table.ModelContent.ColumnName().String()] = modelContent
|
||||
}
|
||||
}
|
||||
if msg.MessageType != "" {
|
||||
columns[table.MessageType.ColumnName().String()] = msg.MessageType
|
||||
@@ -170,6 +204,11 @@ func (dao *MessageDAO) buildEditColumns(msg *message.Message) map[string]interfa
|
||||
|
||||
columns[table.UpdatedAt.ColumnName().String()] = time.Now().UnixMilli()
|
||||
if msg.Ext != nil {
|
||||
if originMsg.Ext != nil {
|
||||
for k, v := range originMsg.Ext {
|
||||
msg.Ext[k] = v
|
||||
}
|
||||
}
|
||||
ext, err := sonic.MarshalString(msg.Ext)
|
||||
if err == nil {
|
||||
columns[table.Ext.ColumnName().String()] = ext
|
||||
@@ -192,8 +231,8 @@ func (dao *MessageDAO) GetByID(ctx context.Context, msgID int64) (*entity.Messag
|
||||
return dao.messagePO2DO(po), nil
|
||||
}
|
||||
|
||||
func (dao *MessageDAO) Delete(ctx context.Context, msgIDs []int64, runIDs []int64) error {
|
||||
if len(msgIDs) == 0 && len(runIDs) == 0 {
|
||||
func (dao *MessageDAO) Delete(ctx context.Context, delMeta *entity.DeleteMeta) error {
|
||||
if len(delMeta.MessageIDs) == 0 && len(delMeta.RunIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -202,11 +241,14 @@ func (dao *MessageDAO) Delete(ctx context.Context, msgIDs []int64, runIDs []int6
|
||||
m := dao.query.Message
|
||||
do := m.WithContext(ctx)
|
||||
|
||||
if len(runIDs) > 0 {
|
||||
do = do.Where(m.RunID.In(runIDs...))
|
||||
if len(delMeta.RunIDs) > 0 {
|
||||
do = do.Where(m.RunID.In(delMeta.RunIDs...))
|
||||
}
|
||||
if len(msgIDs) > 0 {
|
||||
do = do.Where(m.ID.In(msgIDs...))
|
||||
if len(delMeta.MessageIDs) > 0 {
|
||||
do = do.Where(m.ID.In(delMeta.MessageIDs...))
|
||||
}
|
||||
if delMeta.ConversationID != nil && ptr.From(delMeta.ConversationID) > 0 {
|
||||
do = do.Where(m.ConversationID.Eq(*delMeta.ConversationID))
|
||||
}
|
||||
_, err := do.UpdateColumns(&updateColumns)
|
||||
return err
|
||||
@@ -284,6 +326,9 @@ func (dao *MessageDAO) buildModelContent(msgDO *entity.Message) (string, error)
|
||||
var multiContent []schema.ChatMessagePart
|
||||
for _, contentData := range msgDO.MultiContent {
|
||||
if contentData.Type == message.InputTypeText {
|
||||
if len(msgDO.Content) == 0 && len(contentData.Text) > 0 {
|
||||
msgDO.Content = contentData.Text
|
||||
}
|
||||
continue
|
||||
}
|
||||
one := schema.ChatMessagePart{}
|
||||
|
||||
@@ -34,10 +34,9 @@ func NewMessageRepo(db *gorm.DB, idGen idgen.IDGenerator) MessageRepo {
|
||||
type MessageRepo interface {
|
||||
PreCreate(ctx context.Context, msg *entity.Message) (*entity.Message, error)
|
||||
Create(ctx context.Context, msg *entity.Message) (*entity.Message, error)
|
||||
List(ctx context.Context, conversationID int64, limit int, cursor int64,
|
||||
direction entity.ScrollPageDirection, messageType *message.MessageType) ([]*entity.Message, bool, error)
|
||||
List(ctx context.Context, listMeta *entity.ListMeta) ([]*entity.Message, bool, error)
|
||||
GetByRunIDs(ctx context.Context, runIDs []int64, orderBy string) ([]*entity.Message, error)
|
||||
Edit(ctx context.Context, msgID int64, message *message.Message) (int64, error)
|
||||
GetByID(ctx context.Context, msgID int64) (*entity.Message, error)
|
||||
Delete(ctx context.Context, msgIDs []int64, runIDs []int64) error
|
||||
Delete(ctx context.Context, delMeta *entity.DeleteMeta) error
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
type Message interface {
|
||||
List(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error)
|
||||
ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error)
|
||||
PreCreate(ctx context.Context, req *entity.Message) (*entity.Message, error)
|
||||
Create(ctx context.Context, req *entity.Message) (*entity.Message, error)
|
||||
GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*entity.Message, error)
|
||||
|
||||
@@ -18,6 +18,7 @@ package message
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
@@ -51,9 +52,9 @@ func (m *messageImpl) Create(ctx context.Context, msg *entity.Message) (*entity.
|
||||
|
||||
func (m *messageImpl) List(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) {
|
||||
resp := &entity.ListResult{}
|
||||
|
||||
req.MessageType = []*message.MessageType{ptr.Of(message.MessageTypeQuestion)}
|
||||
// get message with query
|
||||
messageList, hasMore, err := m.MessageRepo.List(ctx, req.ConversationID, req.Limit, req.Cursor, req.Direction, ptr.Of(message.MessageTypeQuestion))
|
||||
messageList, hasMore, err := m.MessageRepo.List(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
@@ -62,8 +63,11 @@ func (m *messageImpl) List(ctx context.Context, req *entity.ListMeta) (*entity.L
|
||||
resp.HasMore = hasMore
|
||||
|
||||
if len(messageList) > 0 {
|
||||
resp.PrevCursor = messageList[len(messageList)-1].CreatedAt
|
||||
resp.NextCursor = messageList[0].CreatedAt
|
||||
sort.Slice(messageList, func(i, j int) bool {
|
||||
return messageList[i].CreatedAt > messageList[j].CreatedAt
|
||||
})
|
||||
resp.PrevCursor = messageList[len(messageList)-1].ID
|
||||
resp.NextCursor = messageList[0].ID
|
||||
|
||||
var runIDs []int64
|
||||
for _, m := range messageList {
|
||||
@@ -82,6 +86,23 @@ func (m *messageImpl) List(ctx context.Context, req *entity.ListMeta) (*entity.L
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (m *messageImpl) ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) {
|
||||
resp := &entity.ListResult{}
|
||||
messageList, hasMore, err := m.MessageRepo.List(ctx, req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.Direction = req.Direction
|
||||
resp.HasMore = hasMore
|
||||
resp.Messages = messageList
|
||||
if len(messageList) > 0 {
|
||||
resp.PrevCursor = messageList[0].ID
|
||||
resp.NextCursor = messageList[len(messageList)-1].ID
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (m *messageImpl) GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*entity.Message, error) {
|
||||
return m.MessageRepo.GetByRunIDs(ctx, runIDs, "ASC")
|
||||
}
|
||||
@@ -96,7 +117,7 @@ func (m *messageImpl) Edit(ctx context.Context, req *entity.Message) (*entity.Me
|
||||
}
|
||||
|
||||
func (m *messageImpl) Delete(ctx context.Context, req *entity.DeleteMeta) error {
|
||||
return m.MessageRepo.Delete(ctx, req.MessageIDs, req.RunIDs)
|
||||
return m.MessageRepo.Delete(ctx, req)
|
||||
}
|
||||
|
||||
func (m *messageImpl) GetByID(ctx context.Context, id int64) (*entity.Message, error) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package message
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -145,20 +146,26 @@ func TestCreateMessage(t *testing.T) {
|
||||
func TestEditMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockDBGen := orm.NewMockDB()
|
||||
|
||||
extData := map[string]string{
|
||||
"test": "test",
|
||||
}
|
||||
ext, _ := json.Marshal(extData)
|
||||
mockDBGen.AddTable(&model.Message{}).
|
||||
AddRows(
|
||||
&model.Message{
|
||||
ID: 1,
|
||||
ConversationID: 1,
|
||||
UserID: "1",
|
||||
Role: string(schema.User),
|
||||
RunID: 123,
|
||||
},
|
||||
&model.Message{
|
||||
ID: 2,
|
||||
ConversationID: 1,
|
||||
UserID: "1",
|
||||
Role: string(schema.User),
|
||||
RunID: 124,
|
||||
Ext: string(ext),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -177,7 +184,7 @@ func TestEditMessage(t *testing.T) {
|
||||
Url: "https://xxxxx.xxxx/file",
|
||||
Name: "test_file",
|
||||
}
|
||||
content := []*message.InputMetaData{
|
||||
_ = []*message.InputMetaData{
|
||||
{
|
||||
Type: message.InputTypeText,
|
||||
Text: "解析图片中的内容",
|
||||
@@ -197,56 +204,293 @@ func TestEditMessage(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, err := NewService(components).Edit(ctx, &entity.Message{
|
||||
ID: 2,
|
||||
Content: "test edit message",
|
||||
MultiContent: content,
|
||||
ID: 2,
|
||||
Content: "test edit message",
|
||||
Ext: map[string]string{"newext": "true"},
|
||||
|
||||
// MultiContent: content,
|
||||
})
|
||||
_ = resp
|
||||
|
||||
msOne, err := NewService(components).GetByRunIDs(ctx, 1, []int64{124})
|
||||
msg, err := NewService(components).GetByID(ctx, 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(124), msOne[0].RunID)
|
||||
assert.Equal(t, int64(2), msg.ID)
|
||||
assert.Equal(t, "test edit message", msg.Content)
|
||||
var modelContent *schema.Message
|
||||
err = json.Unmarshal([]byte(msg.ModelContent), &modelContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "test edit message", modelContent.Content)
|
||||
|
||||
assert.Equal(t, "true", msg.Ext["newext"])
|
||||
}
|
||||
|
||||
func TestGetByRunIDs(t *testing.T) {
|
||||
//func TestGetByRunIDs(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
//
|
||||
// mockDBGen := orm.NewMockDB()
|
||||
//
|
||||
// mockDBGen.AddTable(&model.Message{}).
|
||||
// AddRows(
|
||||
// &model.Message{
|
||||
// ID: 1,
|
||||
// ConversationID: 1,
|
||||
// UserID: "1",
|
||||
// RunID: 123,
|
||||
// Content: "test content123",
|
||||
// },
|
||||
// &model.Message{
|
||||
// ID: 2,
|
||||
// ConversationID: 1,
|
||||
// UserID: "1",
|
||||
// Content: "test content124",
|
||||
// RunID: 124,
|
||||
// },
|
||||
// &model.Message{
|
||||
// ID: 3,
|
||||
// ConversationID: 1,
|
||||
// UserID: "1",
|
||||
// Content: "test content124",
|
||||
// RunID: 124,
|
||||
// },
|
||||
// )
|
||||
// mockDB, err := mockDBGen.DB()
|
||||
// assert.NoError(t, err)
|
||||
// components := &Components{
|
||||
// MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
// }
|
||||
//
|
||||
// resp, err := NewService(components).GetByRunIDs(ctx, 1, []int64{124})
|
||||
//
|
||||
// assert.NoError(t, err)
|
||||
//
|
||||
// assert.Len(t, resp, 2)
|
||||
//}
|
||||
|
||||
func TestListWithoutPair(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("success_with_messages", func(t *testing.T) {
|
||||
mockDBGen := orm.NewMockDB()
|
||||
|
||||
mockDBGen := orm.NewMockDB()
|
||||
mockDBGen.AddTable(&model.Message{}).
|
||||
AddRows(
|
||||
&model.Message{
|
||||
ID: 1,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 200,
|
||||
Content: "Hello",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1, // MessageStatusAvailable
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
},
|
||||
&model.Message{
|
||||
ID: 2,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 201,
|
||||
Content: "World",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1, // MessageStatusAvailable
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
},
|
||||
)
|
||||
|
||||
mockDBGen.AddTable(&model.Message{}).
|
||||
AddRows(
|
||||
&model.Message{
|
||||
ID: 1,
|
||||
ConversationID: 1,
|
||||
UserID: "1",
|
||||
RunID: 123,
|
||||
Content: "test content123",
|
||||
},
|
||||
&model.Message{
|
||||
ID: 2,
|
||||
ConversationID: 1,
|
||||
UserID: "1",
|
||||
Content: "test content124",
|
||||
RunID: 124,
|
||||
},
|
||||
&model.Message{
|
||||
ID: 3,
|
||||
ConversationID: 1,
|
||||
UserID: "1",
|
||||
Content: "test content124",
|
||||
RunID: 124,
|
||||
},
|
||||
)
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
components := &Components{
|
||||
MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
}
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
|
||||
resp, err := NewService(components).GetByRunIDs(ctx, 1, []int64{124})
|
||||
components := &Components{
|
||||
MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
req := &entity.ListMeta{
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
Limit: 10,
|
||||
Direction: entity.ScrollPageDirectionNext,
|
||||
}
|
||||
|
||||
assert.Len(t, resp, 2)
|
||||
resp, err := NewService(components).ListWithoutPair(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, entity.ScrollPageDirectionNext, resp.Direction)
|
||||
assert.False(t, resp.HasMore)
|
||||
assert.Len(t, resp.Messages, 2)
|
||||
assert.Equal(t, "Hello", resp.Messages[0].Content)
|
||||
assert.Equal(t, "World", resp.Messages[1].Content)
|
||||
})
|
||||
|
||||
t.Run("empty_result", func(t *testing.T) {
|
||||
mockDBGen := orm.NewMockDB()
|
||||
mockDBGen.AddTable(&model.Message{})
|
||||
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
|
||||
components := &Components{
|
||||
MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
}
|
||||
|
||||
req := &entity.ListMeta{
|
||||
ConversationID: 999,
|
||||
UserID: "user123",
|
||||
Limit: 10,
|
||||
Direction: entity.ScrollPageDirectionNext,
|
||||
}
|
||||
|
||||
resp, err := NewService(components).ListWithoutPair(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, entity.ScrollPageDirectionNext, resp.Direction)
|
||||
assert.False(t, resp.HasMore)
|
||||
assert.Len(t, resp.Messages, 0)
|
||||
})
|
||||
|
||||
t.Run("pagination_has_more", func(t *testing.T) {
|
||||
mockDBGen := orm.NewMockDB()
|
||||
|
||||
mockDBGen.AddTable(&model.Message{}).
|
||||
AddRows(
|
||||
&model.Message{
|
||||
ID: 1,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 200,
|
||||
Content: "Message 1",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1,
|
||||
CreatedAt: time.Now().UnixMilli() - 3000,
|
||||
},
|
||||
&model.Message{
|
||||
ID: 2,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 201,
|
||||
Content: "Message 2",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1,
|
||||
CreatedAt: time.Now().UnixMilli() - 2000,
|
||||
},
|
||||
&model.Message{
|
||||
ID: 3,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 202,
|
||||
Content: "Message 3",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1,
|
||||
CreatedAt: time.Now().UnixMilli() - 1000,
|
||||
},
|
||||
)
|
||||
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
|
||||
components := &Components{
|
||||
MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
}
|
||||
|
||||
req := &entity.ListMeta{
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
Limit: 2,
|
||||
Direction: entity.ScrollPageDirectionNext,
|
||||
}
|
||||
|
||||
resp, err := NewService(components).ListWithoutPair(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, entity.ScrollPageDirectionNext, resp.Direction)
|
||||
assert.True(t, resp.HasMore)
|
||||
assert.Len(t, resp.Messages, 2)
|
||||
})
|
||||
|
||||
t.Run("direction_prev", func(t *testing.T) {
|
||||
mockDBGen := orm.NewMockDB()
|
||||
|
||||
mockDBGen.AddTable(&model.Message{}).
|
||||
AddRows(
|
||||
&model.Message{
|
||||
ID: 1,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 200,
|
||||
Content: "Test message",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
},
|
||||
)
|
||||
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
|
||||
components := &Components{
|
||||
MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
}
|
||||
|
||||
req := &entity.ListMeta{
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
Limit: 10,
|
||||
Direction: entity.ScrollPageDirectionPrev,
|
||||
}
|
||||
|
||||
resp, err := NewService(components).ListWithoutPair(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, entity.ScrollPageDirectionPrev, resp.Direction)
|
||||
assert.False(t, resp.HasMore)
|
||||
assert.Len(t, resp.Messages, 1)
|
||||
})
|
||||
|
||||
t.Run("with_message_type_filter", func(t *testing.T) {
|
||||
mockDBGen := orm.NewMockDB()
|
||||
|
||||
mockDBGen.AddTable(&model.Message{}).
|
||||
AddRows(
|
||||
&model.Message{
|
||||
ID: 1,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 200,
|
||||
Content: "Answer message",
|
||||
MessageType: string(message.MessageTypeAnswer),
|
||||
Status: 1,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
},
|
||||
&model.Message{
|
||||
ID: 2,
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
RunID: 201,
|
||||
Content: "Question message",
|
||||
MessageType: string(message.MessageTypeQuestion),
|
||||
Status: 1,
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
},
|
||||
)
|
||||
|
||||
mockDB, err := mockDBGen.DB()
|
||||
assert.NoError(t, err)
|
||||
|
||||
components := &Components{
|
||||
MessageRepo: repository.NewMessageRepo(mockDB, nil),
|
||||
}
|
||||
|
||||
req := &entity.ListMeta{
|
||||
ConversationID: 100,
|
||||
UserID: "user123",
|
||||
Limit: 10,
|
||||
Direction: entity.ScrollPageDirectionNext,
|
||||
MessageType: []*message.MessageType{&[]message.MessageType{message.MessageTypeAnswer}[0]},
|
||||
}
|
||||
|
||||
resp, err := NewService(components).ListWithoutPair(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Len(t, resp.Messages, 1)
|
||||
assert.Equal(t, "Answer message", resp.Messages[0].Content)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type CreateApiKey struct {
|
||||
Name string `json:"name"`
|
||||
Expire int64 `json:"expire"`
|
||||
UserID int64 `json:"user_id"`
|
||||
AkType AkType `json:"ak_type"`
|
||||
}
|
||||
|
||||
type DeleteApiKey struct {
|
||||
|
||||
24
backend/domain/openauth/openapiauth/entity/consts.go
Normal file
24
backend/domain/openauth/openapiauth/entity/consts.go
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 entity
|
||||
|
||||
type AkType int32
|
||||
|
||||
const (
|
||||
AkTypeCustomer AkType = 0
|
||||
AkTypeTemporary AkType = 1
|
||||
)
|
||||
@@ -72,6 +72,7 @@ func (a *ApiKeyDAO) doToPo(ctx context.Context, do *entity.CreateApiKey) (*model
|
||||
Name: do.Name,
|
||||
ExpiredAt: do.Expire,
|
||||
UserID: do.UserID,
|
||||
AkType: int32(do.AkType),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
return po, nil
|
||||
@@ -119,7 +120,7 @@ func (a *ApiKeyDAO) FindByKey(ctx context.Context, key string) (*model.APIKey, e
|
||||
|
||||
func (a *ApiKeyDAO) List(ctx context.Context, userID int64, limit int, page int) ([]*model.APIKey, bool, error) {
|
||||
do := a.dbQuery.APIKey.WithContext(ctx).Where(a.dbQuery.APIKey.UserID.Eq(userID))
|
||||
|
||||
do = do.Where(a.dbQuery.APIKey.AkType.Eq(int32(entity.AkTypeCustomer)))
|
||||
do = do.Offset((page - 1) * limit).Limit(limit + 1)
|
||||
|
||||
list, err := do.Order(a.dbQuery.APIKey.CreatedAt.Desc()).Find()
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@@ -17,6 +33,7 @@ type APIKey struct {
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds
|
||||
LastUsedAt int64 `gorm:"column:last_used_at;not null;comment:Used Time in Milliseconds" json:"last_used_at"` // Used Time in Milliseconds
|
||||
AkType int32 `gorm:"column:ak_type;not null;comment:api key type" json:"ak_type"` // api key type
|
||||
}
|
||||
|
||||
// TableName APIKey's table name
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@@ -36,6 +52,7 @@ func newAPIKey(db *gorm.DB, opts ...gen.DOOption) aPIKey {
|
||||
_aPIKey.CreatedAt = field.NewInt64(tableName, "created_at")
|
||||
_aPIKey.UpdatedAt = field.NewInt64(tableName, "updated_at")
|
||||
_aPIKey.LastUsedAt = field.NewInt64(tableName, "last_used_at")
|
||||
_aPIKey.AkType = field.NewInt32(tableName, "ak_type")
|
||||
|
||||
_aPIKey.fillFieldMap()
|
||||
|
||||
@@ -56,6 +73,7 @@ type aPIKey struct {
|
||||
CreatedAt field.Int64 // Create Time in Milliseconds
|
||||
UpdatedAt field.Int64 // Update Time in Milliseconds
|
||||
LastUsedAt field.Int64 // Used Time in Milliseconds
|
||||
AkType field.Int32 // api key type
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
@@ -81,6 +99,7 @@ func (a *aPIKey) updateTableName(table string) *aPIKey {
|
||||
a.CreatedAt = field.NewInt64(table, "created_at")
|
||||
a.UpdatedAt = field.NewInt64(table, "updated_at")
|
||||
a.LastUsedAt = field.NewInt64(table, "last_used_at")
|
||||
a.AkType = field.NewInt32(table, "ak_type")
|
||||
|
||||
a.fillFieldMap()
|
||||
|
||||
@@ -97,7 +116,7 @@ func (a *aPIKey) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
}
|
||||
|
||||
func (a *aPIKey) fillFieldMap() {
|
||||
a.fieldMap = make(map[string]field.Expr, 9)
|
||||
a.fieldMap = make(map[string]field.Expr, 10)
|
||||
a.fieldMap["id"] = a.ID
|
||||
a.fieldMap["api_key"] = a.APIKey
|
||||
a.fieldMap["name"] = a.Name
|
||||
@@ -107,6 +126,7 @@ func (a *aPIKey) fillFieldMap() {
|
||||
a.fieldMap["created_at"] = a.CreatedAt
|
||||
a.fieldMap["updated_at"] = a.UpdatedAt
|
||||
a.fieldMap["last_used_at"] = a.LastUsedAt
|
||||
a.fieldMap["ak_type"] = a.AkType
|
||||
}
|
||||
|
||||
func (a aPIKey) clone(db *gorm.DB) aPIKey {
|
||||
|
||||
46
backend/domain/upload/entity/file.go
Normal file
46
backend/domain/upload/entity/file.go
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 entity
|
||||
|
||||
type File struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
TosURI string `json:"tos_uri"`
|
||||
Status FileStatus `json:"status"`
|
||||
Comment string `json:"comment"`
|
||||
Source FileSource `json:"source"`
|
||||
CreatorID string `json:"creator_id"`
|
||||
CozeAccountID int64 `json:"coze_account_id"`
|
||||
ContentType string `json:"content_type"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type FileStatus int32
|
||||
|
||||
const (
|
||||
FileStatusInvalid FileStatus = 0
|
||||
FileStatusValid FileStatus = 1
|
||||
)
|
||||
|
||||
type FileSource int32
|
||||
|
||||
const (
|
||||
FileSourceAPI FileSource = 1
|
||||
)
|
||||
113
backend/domain/upload/internal/dal/dao/files.go
Normal file
113
backend/domain/upload/internal/dal/dao/files.go
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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 dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/query"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
)
|
||||
|
||||
type FilesDAO struct {
|
||||
DB *gorm.DB
|
||||
Query *query.Query
|
||||
}
|
||||
|
||||
func NewFilesDAO(db *gorm.DB) *FilesDAO {
|
||||
return &FilesDAO{
|
||||
DB: db,
|
||||
Query: query.Use(db),
|
||||
}
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) Create(ctx context.Context, file *entity.File) error {
|
||||
f := dao.fromEntityToModel(file)
|
||||
return dao.Query.Files.WithContext(ctx).Create(f)
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) BatchCreate(ctx context.Context, files []*entity.File) error {
|
||||
if len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
return dao.Query.Files.WithContext(ctx).CreateInBatches(slices.Transform(files, dao.fromEntityToModel), len(files))
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) Delete(ctx context.Context, id int64) error {
|
||||
_, err := dao.Query.Files.WithContext(ctx).Where(dao.Query.Files.ID.Eq(id)).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) GetByID(ctx context.Context, id int64) (*entity.File, error) {
|
||||
file, err := dao.Query.Files.WithContext(ctx).Where(dao.Query.Files.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dao.fromModelToEntity(file), nil
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) MGetByIDs(ctx context.Context, ids []int64) ([]*entity.File, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
files, err := dao.Query.Files.WithContext(ctx).Where(dao.Query.Files.ID.In(ids...)).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slices.Transform(files, dao.fromModelToEntity), nil
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) fromModelToEntity(model *model.Files) *entity.File {
|
||||
if model == nil {
|
||||
return nil
|
||||
}
|
||||
return &entity.File{
|
||||
ID: model.ID,
|
||||
Name: model.Name,
|
||||
FileSize: model.FileSize,
|
||||
TosURI: model.TosURI,
|
||||
Status: entity.FileStatus(model.Status),
|
||||
Comment: model.Comment,
|
||||
Source: entity.FileSource(model.Source),
|
||||
CreatorID: model.CreatorID,
|
||||
CozeAccountID: model.CozeAccountID,
|
||||
ContentType: model.ContentType,
|
||||
CreatedAt: model.CreatedAt,
|
||||
UpdatedAt: model.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (dao *FilesDAO) fromEntityToModel(entity *entity.File) *model.Files {
|
||||
return &model.Files{
|
||||
ID: entity.ID,
|
||||
Name: entity.Name,
|
||||
FileSize: entity.FileSize,
|
||||
TosURI: entity.TosURI,
|
||||
Status: int32(entity.Status),
|
||||
Comment: entity.Comment,
|
||||
Source: int32(entity.Source),
|
||||
CreatorID: entity.CreatorID,
|
||||
CozeAccountID: entity.CozeAccountID,
|
||||
ContentType: entity.ContentType,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
UpdatedAt: entity.UpdatedAt,
|
||||
}
|
||||
}
|
||||
49
backend/domain/upload/internal/dal/model/files.gen.go
Normal file
49
backend/domain/upload/internal/dal/model/files.gen.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const TableNameFiles = "files"
|
||||
|
||||
// Files file resource table
|
||||
type Files struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id
|
||||
Name string `gorm:"column:name;not null;comment:file name" json:"name"` // file name
|
||||
FileSize int64 `gorm:"column:file_size;not null;comment:file size" json:"file_size"` // file size
|
||||
TosURI string `gorm:"column:tos_uri;not null;comment:TOS URI" json:"tos_uri"` // TOS URI
|
||||
Status int32 `gorm:"column:status;not null;comment:status,0invalid,1valid" json:"status"` // status,0invalid,1valid
|
||||
Comment string `gorm:"column:comment;not null;comment:file comment" json:"comment"` // file comment
|
||||
Source int32 `gorm:"column:source;not null;comment:source:1 from API," json:"source"` // source:1 from API,
|
||||
CreatorID string `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id
|
||||
ContentType string `gorm:"column:content_type;not null;comment:content type" json:"content_type"` // content type
|
||||
CozeAccountID int64 `gorm:"column:coze_account_id;not null;comment:coze account id" json:"coze_account_id"` // coze account id
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:Delete Time" json:"deleted_at"` // Delete Time
|
||||
}
|
||||
|
||||
// TableName Files's table name
|
||||
func (*Files) TableName() string {
|
||||
return TableNameFiles
|
||||
}
|
||||
445
backend/domain/upload/internal/dal/query/files.gen.go
Normal file
445
backend/domain/upload/internal/dal/query/files.gen.go
Normal file
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/schema"
|
||||
|
||||
"gorm.io/gen"
|
||||
"gorm.io/gen/field"
|
||||
|
||||
"gorm.io/plugin/dbresolver"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/model"
|
||||
)
|
||||
|
||||
func newFiles(db *gorm.DB, opts ...gen.DOOption) files {
|
||||
_files := files{}
|
||||
|
||||
_files.filesDo.UseDB(db, opts...)
|
||||
_files.filesDo.UseModel(&model.Files{})
|
||||
|
||||
tableName := _files.filesDo.TableName()
|
||||
_files.ALL = field.NewAsterisk(tableName)
|
||||
_files.ID = field.NewInt64(tableName, "id")
|
||||
_files.Name = field.NewString(tableName, "name")
|
||||
_files.FileSize = field.NewInt64(tableName, "file_size")
|
||||
_files.TosURI = field.NewString(tableName, "tos_uri")
|
||||
_files.Status = field.NewInt32(tableName, "status")
|
||||
_files.Comment = field.NewString(tableName, "comment")
|
||||
_files.Source = field.NewInt32(tableName, "source")
|
||||
_files.CreatorID = field.NewString(tableName, "creator_id")
|
||||
_files.ContentType = field.NewString(tableName, "content_type")
|
||||
_files.CozeAccountID = field.NewInt64(tableName, "coze_account_id")
|
||||
_files.CreatedAt = field.NewInt64(tableName, "created_at")
|
||||
_files.UpdatedAt = field.NewInt64(tableName, "updated_at")
|
||||
_files.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
|
||||
_files.fillFieldMap()
|
||||
|
||||
return _files
|
||||
}
|
||||
|
||||
// files file resource table
|
||||
type files struct {
|
||||
filesDo
|
||||
|
||||
ALL field.Asterisk
|
||||
ID field.Int64 // id
|
||||
Name field.String // file name
|
||||
FileSize field.Int64 // file size
|
||||
TosURI field.String // TOS URI
|
||||
Status field.Int32 // status,0invalid,1valid
|
||||
Comment field.String // file comment
|
||||
Source field.Int32 // source:1 from API,
|
||||
CreatorID field.String // creator id
|
||||
ContentType field.String // content type
|
||||
CozeAccountID field.Int64 // coze account id
|
||||
CreatedAt field.Int64 // Create Time in Milliseconds
|
||||
UpdatedAt field.Int64 // Update Time in Milliseconds
|
||||
DeletedAt field.Field // Delete Time
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
|
||||
func (f files) Table(newTableName string) *files {
|
||||
f.filesDo.UseTable(newTableName)
|
||||
return f.updateTableName(newTableName)
|
||||
}
|
||||
|
||||
func (f files) As(alias string) *files {
|
||||
f.filesDo.DO = *(f.filesDo.As(alias).(*gen.DO))
|
||||
return f.updateTableName(alias)
|
||||
}
|
||||
|
||||
func (f *files) updateTableName(table string) *files {
|
||||
f.ALL = field.NewAsterisk(table)
|
||||
f.ID = field.NewInt64(table, "id")
|
||||
f.Name = field.NewString(table, "name")
|
||||
f.FileSize = field.NewInt64(table, "file_size")
|
||||
f.TosURI = field.NewString(table, "tos_uri")
|
||||
f.Status = field.NewInt32(table, "status")
|
||||
f.Comment = field.NewString(table, "comment")
|
||||
f.Source = field.NewInt32(table, "source")
|
||||
f.CreatorID = field.NewString(table, "creator_id")
|
||||
f.ContentType = field.NewString(table, "content_type")
|
||||
f.CozeAccountID = field.NewInt64(table, "coze_account_id")
|
||||
f.CreatedAt = field.NewInt64(table, "created_at")
|
||||
f.UpdatedAt = field.NewInt64(table, "updated_at")
|
||||
f.DeletedAt = field.NewField(table, "deleted_at")
|
||||
|
||||
f.fillFieldMap()
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *files) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
_f, ok := f.fieldMap[fieldName]
|
||||
if !ok || _f == nil {
|
||||
return nil, false
|
||||
}
|
||||
_oe, ok := _f.(field.OrderExpr)
|
||||
return _oe, ok
|
||||
}
|
||||
|
||||
func (f *files) fillFieldMap() {
|
||||
f.fieldMap = make(map[string]field.Expr, 13)
|
||||
f.fieldMap["id"] = f.ID
|
||||
f.fieldMap["name"] = f.Name
|
||||
f.fieldMap["file_size"] = f.FileSize
|
||||
f.fieldMap["tos_uri"] = f.TosURI
|
||||
f.fieldMap["status"] = f.Status
|
||||
f.fieldMap["comment"] = f.Comment
|
||||
f.fieldMap["source"] = f.Source
|
||||
f.fieldMap["creator_id"] = f.CreatorID
|
||||
f.fieldMap["content_type"] = f.ContentType
|
||||
f.fieldMap["coze_account_id"] = f.CozeAccountID
|
||||
f.fieldMap["created_at"] = f.CreatedAt
|
||||
f.fieldMap["updated_at"] = f.UpdatedAt
|
||||
f.fieldMap["deleted_at"] = f.DeletedAt
|
||||
}
|
||||
|
||||
func (f files) clone(db *gorm.DB) files {
|
||||
f.filesDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f files) replaceDB(db *gorm.DB) files {
|
||||
f.filesDo.ReplaceDB(db)
|
||||
return f
|
||||
}
|
||||
|
||||
type filesDo struct{ gen.DO }
|
||||
|
||||
type IFilesDo interface {
|
||||
gen.SubQuery
|
||||
Debug() IFilesDo
|
||||
WithContext(ctx context.Context) IFilesDo
|
||||
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
|
||||
ReplaceDB(db *gorm.DB)
|
||||
ReadDB() IFilesDo
|
||||
WriteDB() IFilesDo
|
||||
As(alias string) gen.Dao
|
||||
Session(config *gorm.Session) IFilesDo
|
||||
Columns(cols ...field.Expr) gen.Columns
|
||||
Clauses(conds ...clause.Expression) IFilesDo
|
||||
Not(conds ...gen.Condition) IFilesDo
|
||||
Or(conds ...gen.Condition) IFilesDo
|
||||
Select(conds ...field.Expr) IFilesDo
|
||||
Where(conds ...gen.Condition) IFilesDo
|
||||
Order(conds ...field.Expr) IFilesDo
|
||||
Distinct(cols ...field.Expr) IFilesDo
|
||||
Omit(cols ...field.Expr) IFilesDo
|
||||
Join(table schema.Tabler, on ...field.Expr) IFilesDo
|
||||
LeftJoin(table schema.Tabler, on ...field.Expr) IFilesDo
|
||||
RightJoin(table schema.Tabler, on ...field.Expr) IFilesDo
|
||||
Group(cols ...field.Expr) IFilesDo
|
||||
Having(conds ...gen.Condition) IFilesDo
|
||||
Limit(limit int) IFilesDo
|
||||
Offset(offset int) IFilesDo
|
||||
Count() (count int64, err error)
|
||||
Scopes(funcs ...func(gen.Dao) gen.Dao) IFilesDo
|
||||
Unscoped() IFilesDo
|
||||
Create(values ...*model.Files) error
|
||||
CreateInBatches(values []*model.Files, batchSize int) error
|
||||
Save(values ...*model.Files) error
|
||||
First() (*model.Files, error)
|
||||
Take() (*model.Files, error)
|
||||
Last() (*model.Files, error)
|
||||
Find() ([]*model.Files, error)
|
||||
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Files, err error)
|
||||
FindInBatches(result *[]*model.Files, batchSize int, fc func(tx gen.Dao, batch int) error) error
|
||||
Pluck(column field.Expr, dest interface{}) error
|
||||
Delete(...*model.Files) (info gen.ResultInfo, err error)
|
||||
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
|
||||
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
|
||||
Updates(value interface{}) (info gen.ResultInfo, err error)
|
||||
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
|
||||
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
|
||||
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
|
||||
UpdateFrom(q gen.SubQuery) gen.Dao
|
||||
Attrs(attrs ...field.AssignExpr) IFilesDo
|
||||
Assign(attrs ...field.AssignExpr) IFilesDo
|
||||
Joins(fields ...field.RelationField) IFilesDo
|
||||
Preload(fields ...field.RelationField) IFilesDo
|
||||
FirstOrInit() (*model.Files, error)
|
||||
FirstOrCreate() (*model.Files, error)
|
||||
FindByPage(offset int, limit int) (result []*model.Files, count int64, err error)
|
||||
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
|
||||
Scan(result interface{}) (err error)
|
||||
Returning(value interface{}, columns ...string) IFilesDo
|
||||
UnderlyingDB() *gorm.DB
|
||||
schema.Tabler
|
||||
}
|
||||
|
||||
func (f filesDo) Debug() IFilesDo {
|
||||
return f.withDO(f.DO.Debug())
|
||||
}
|
||||
|
||||
func (f filesDo) WithContext(ctx context.Context) IFilesDo {
|
||||
return f.withDO(f.DO.WithContext(ctx))
|
||||
}
|
||||
|
||||
func (f filesDo) ReadDB() IFilesDo {
|
||||
return f.Clauses(dbresolver.Read)
|
||||
}
|
||||
|
||||
func (f filesDo) WriteDB() IFilesDo {
|
||||
return f.Clauses(dbresolver.Write)
|
||||
}
|
||||
|
||||
func (f filesDo) Session(config *gorm.Session) IFilesDo {
|
||||
return f.withDO(f.DO.Session(config))
|
||||
}
|
||||
|
||||
func (f filesDo) Clauses(conds ...clause.Expression) IFilesDo {
|
||||
return f.withDO(f.DO.Clauses(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Returning(value interface{}, columns ...string) IFilesDo {
|
||||
return f.withDO(f.DO.Returning(value, columns...))
|
||||
}
|
||||
|
||||
func (f filesDo) Not(conds ...gen.Condition) IFilesDo {
|
||||
return f.withDO(f.DO.Not(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Or(conds ...gen.Condition) IFilesDo {
|
||||
return f.withDO(f.DO.Or(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Select(conds ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.Select(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Where(conds ...gen.Condition) IFilesDo {
|
||||
return f.withDO(f.DO.Where(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Order(conds ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.Order(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Distinct(cols ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.Distinct(cols...))
|
||||
}
|
||||
|
||||
func (f filesDo) Omit(cols ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.Omit(cols...))
|
||||
}
|
||||
|
||||
func (f filesDo) Join(table schema.Tabler, on ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.Join(table, on...))
|
||||
}
|
||||
|
||||
func (f filesDo) LeftJoin(table schema.Tabler, on ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.LeftJoin(table, on...))
|
||||
}
|
||||
|
||||
func (f filesDo) RightJoin(table schema.Tabler, on ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.RightJoin(table, on...))
|
||||
}
|
||||
|
||||
func (f filesDo) Group(cols ...field.Expr) IFilesDo {
|
||||
return f.withDO(f.DO.Group(cols...))
|
||||
}
|
||||
|
||||
func (f filesDo) Having(conds ...gen.Condition) IFilesDo {
|
||||
return f.withDO(f.DO.Having(conds...))
|
||||
}
|
||||
|
||||
func (f filesDo) Limit(limit int) IFilesDo {
|
||||
return f.withDO(f.DO.Limit(limit))
|
||||
}
|
||||
|
||||
func (f filesDo) Offset(offset int) IFilesDo {
|
||||
return f.withDO(f.DO.Offset(offset))
|
||||
}
|
||||
|
||||
func (f filesDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IFilesDo {
|
||||
return f.withDO(f.DO.Scopes(funcs...))
|
||||
}
|
||||
|
||||
func (f filesDo) Unscoped() IFilesDo {
|
||||
return f.withDO(f.DO.Unscoped())
|
||||
}
|
||||
|
||||
func (f filesDo) Create(values ...*model.Files) error {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return f.DO.Create(values)
|
||||
}
|
||||
|
||||
func (f filesDo) CreateInBatches(values []*model.Files, batchSize int) error {
|
||||
return f.DO.CreateInBatches(values, batchSize)
|
||||
}
|
||||
|
||||
// Save : !!! underlying implementation is different with GORM
|
||||
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||
func (f filesDo) Save(values ...*model.Files) error {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return f.DO.Save(values)
|
||||
}
|
||||
|
||||
func (f filesDo) First() (*model.Files, error) {
|
||||
if result, err := f.DO.First(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.Files), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f filesDo) Take() (*model.Files, error) {
|
||||
if result, err := f.DO.Take(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.Files), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f filesDo) Last() (*model.Files, error) {
|
||||
if result, err := f.DO.Last(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.Files), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f filesDo) Find() ([]*model.Files, error) {
|
||||
result, err := f.DO.Find()
|
||||
return result.([]*model.Files), err
|
||||
}
|
||||
|
||||
func (f filesDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Files, err error) {
|
||||
buf := make([]*model.Files, 0, batchSize)
|
||||
err = f.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||
defer func() { results = append(results, buf...) }()
|
||||
return fc(tx, batch)
|
||||
})
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (f filesDo) FindInBatches(result *[]*model.Files, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||
return f.DO.FindInBatches(result, batchSize, fc)
|
||||
}
|
||||
|
||||
func (f filesDo) Attrs(attrs ...field.AssignExpr) IFilesDo {
|
||||
return f.withDO(f.DO.Attrs(attrs...))
|
||||
}
|
||||
|
||||
func (f filesDo) Assign(attrs ...field.AssignExpr) IFilesDo {
|
||||
return f.withDO(f.DO.Assign(attrs...))
|
||||
}
|
||||
|
||||
func (f filesDo) Joins(fields ...field.RelationField) IFilesDo {
|
||||
for _, _f := range fields {
|
||||
f = *f.withDO(f.DO.Joins(_f))
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
func (f filesDo) Preload(fields ...field.RelationField) IFilesDo {
|
||||
for _, _f := range fields {
|
||||
f = *f.withDO(f.DO.Preload(_f))
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
func (f filesDo) FirstOrInit() (*model.Files, error) {
|
||||
if result, err := f.DO.FirstOrInit(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.Files), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f filesDo) FirstOrCreate() (*model.Files, error) {
|
||||
if result, err := f.DO.FirstOrCreate(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*model.Files), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f filesDo) FindByPage(offset int, limit int) (result []*model.Files, count int64, err error) {
|
||||
result, err = f.Offset(offset).Limit(limit).Find()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||
count = int64(size + offset)
|
||||
return
|
||||
}
|
||||
|
||||
count, err = f.Offset(-1).Limit(-1).Count()
|
||||
return
|
||||
}
|
||||
|
||||
func (f filesDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||
count, err = f.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = f.Offset(offset).Limit(limit).Scan(result)
|
||||
return
|
||||
}
|
||||
|
||||
func (f filesDo) Scan(result interface{}) (err error) {
|
||||
return f.DO.Scan(result)
|
||||
}
|
||||
|
||||
func (f filesDo) Delete(models ...*model.Files) (result gen.ResultInfo, err error) {
|
||||
return f.DO.Delete(models)
|
||||
}
|
||||
|
||||
func (f *filesDo) withDO(do gen.Dao) *filesDo {
|
||||
f.DO = *do.(*gen.DO)
|
||||
return f
|
||||
}
|
||||
119
backend/domain/upload/internal/dal/query/gen.go
Normal file
119
backend/domain/upload/internal/dal/query/gen.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gorm.io/gen"
|
||||
|
||||
"gorm.io/plugin/dbresolver"
|
||||
)
|
||||
|
||||
var (
|
||||
Q = new(Query)
|
||||
Files *files
|
||||
)
|
||||
|
||||
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||
*Q = *Use(db, opts...)
|
||||
Files = &Q.Files
|
||||
}
|
||||
|
||||
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||
return &Query{
|
||||
db: db,
|
||||
Files: newFiles(db, opts...),
|
||||
}
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
db *gorm.DB
|
||||
|
||||
Files files
|
||||
}
|
||||
|
||||
func (q *Query) Available() bool { return q.db != nil }
|
||||
|
||||
func (q *Query) clone(db *gorm.DB) *Query {
|
||||
return &Query{
|
||||
db: db,
|
||||
Files: q.Files.clone(db),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Query) ReadDB() *Query {
|
||||
return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
|
||||
}
|
||||
|
||||
func (q *Query) WriteDB() *Query {
|
||||
return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
|
||||
}
|
||||
|
||||
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
||||
return &Query{
|
||||
db: db,
|
||||
Files: q.Files.replaceDB(db),
|
||||
}
|
||||
}
|
||||
|
||||
type queryCtx struct {
|
||||
Files IFilesDo
|
||||
}
|
||||
|
||||
func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||
return &queryCtx{
|
||||
Files: q.Files.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
|
||||
return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
|
||||
}
|
||||
|
||||
func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
|
||||
tx := q.db.Begin(opts...)
|
||||
return &QueryTx{Query: q.clone(tx), Error: tx.Error}
|
||||
}
|
||||
|
||||
type QueryTx struct {
|
||||
*Query
|
||||
Error error
|
||||
}
|
||||
|
||||
func (q *QueryTx) Commit() error {
|
||||
return q.db.Commit().Error
|
||||
}
|
||||
|
||||
func (q *QueryTx) Rollback() error {
|
||||
return q.db.Rollback().Error
|
||||
}
|
||||
|
||||
func (q *QueryTx) SavePoint(name string) error {
|
||||
return q.db.SavePoint(name).Error
|
||||
}
|
||||
|
||||
func (q *QueryTx) RollbackTo(name string) error {
|
||||
return q.db.RollbackTo(name).Error
|
||||
}
|
||||
39
backend/domain/upload/repository/repository.go
Normal file
39
backend/domain/upload/repository/repository.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/dao"
|
||||
)
|
||||
|
||||
func NewFilesRepo(db *gorm.DB) FilesRepo {
|
||||
return dao.NewFilesDAO(db)
|
||||
}
|
||||
|
||||
//go:generate mockgen -destination ../internal/mock/dal/dao/knowledge_document.go --package dao -source knowledge_document.go
|
||||
type FilesRepo interface {
|
||||
Create(ctx context.Context, file *entity.File) error
|
||||
BatchCreate(ctx context.Context, files []*entity.File) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
GetByID(ctx context.Context, id int64) (*entity.File, error)
|
||||
MGetByIDs(ctx context.Context, ids []int64) ([]*entity.File, error)
|
||||
}
|
||||
60
backend/domain/upload/service/interface.go
Normal file
60
backend/domain/upload/service/interface.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/entity"
|
||||
)
|
||||
|
||||
type UploadService interface {
|
||||
UploadFile(ctx context.Context, req *UploadFileRequest) (resp *UploadFileResponse, err error)
|
||||
UploadFiles(ctx context.Context, req *UploadFilesRequest) (resp *UploadFilesResponse, err error)
|
||||
GetFiles(ctx context.Context, req *GetFilesRequest) (resp *GetFilesResponse, err error)
|
||||
GetFile(ctx context.Context, req *GetFileRequest) (resp *GetFileResponse, err error)
|
||||
}
|
||||
|
||||
type UploadFileRequest struct {
|
||||
File *entity.File `json:"file"`
|
||||
}
|
||||
type UploadFileResponse struct {
|
||||
File *entity.File `json:"file"`
|
||||
}
|
||||
type UploadFilesRequest struct {
|
||||
Files []*entity.File `json:"files"`
|
||||
}
|
||||
|
||||
type UploadFilesResponse struct {
|
||||
Files []*entity.File `json:"files"`
|
||||
}
|
||||
|
||||
type GetFilesRequest struct {
|
||||
IDs []int64 `json:"ids"`
|
||||
}
|
||||
|
||||
type GetFilesResponse struct {
|
||||
Files []*entity.File `json:"files"`
|
||||
}
|
||||
|
||||
type GetFileRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type GetFileResponse struct {
|
||||
File *entity.File `json:"file"`
|
||||
}
|
||||
97
backend/domain/upload/service/service.go
Normal file
97
backend/domain/upload/service/service.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/repository"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/impl/storage"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type uploadSVC struct {
|
||||
fileRepo repository.FilesRepo
|
||||
idgen idgen.IDGenerator
|
||||
oss storage.Storage
|
||||
}
|
||||
|
||||
func NewUploadSVC(db *gorm.DB, idgen idgen.IDGenerator, oss storage.Storage) UploadService {
|
||||
return &uploadSVC{fileRepo: repository.NewFilesRepo(db), idgen: idgen, oss: oss}
|
||||
}
|
||||
|
||||
func (u *uploadSVC) UploadFile(ctx context.Context, req *UploadFileRequest) (resp *UploadFileResponse, err error) {
|
||||
resp = &UploadFileResponse{}
|
||||
if req.File.ID == 0 {
|
||||
req.File.ID, err = u.idgen.GenID(ctx)
|
||||
if err != nil {
|
||||
return nil, errorx.New(errno.ErrIDGenError)
|
||||
}
|
||||
}
|
||||
err = u.fileRepo.Create(ctx, req.File)
|
||||
if err != nil {
|
||||
return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode)
|
||||
}
|
||||
resp.File = req.File
|
||||
return
|
||||
}
|
||||
|
||||
func (u *uploadSVC) UploadFiles(ctx context.Context, req *UploadFilesRequest) (resp *UploadFilesResponse, err error) {
|
||||
resp = &UploadFilesResponse{}
|
||||
for _, file := range req.Files {
|
||||
if file.ID == 0 {
|
||||
file.ID, err = u.idgen.GenID(ctx)
|
||||
if err != nil {
|
||||
return nil, errorx.New(errno.ErrIDGenError)
|
||||
}
|
||||
}
|
||||
}
|
||||
err = u.fileRepo.BatchCreate(ctx, req.Files)
|
||||
if err != nil {
|
||||
return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode)
|
||||
}
|
||||
resp.Files = req.Files
|
||||
return
|
||||
}
|
||||
|
||||
func (u *uploadSVC) GetFiles(ctx context.Context, req *GetFilesRequest) (resp *GetFilesResponse, err error) {
|
||||
resp = &GetFilesResponse{}
|
||||
resp.Files, err = u.fileRepo.MGetByIDs(ctx, req.IDs)
|
||||
if err != nil {
|
||||
return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *uploadSVC) GetFile(ctx context.Context, req *GetFileRequest) (resp *GetFileResponse, err error) {
|
||||
resp = &GetFileResponse{}
|
||||
resp.File, err = u.fileRepo.GetByID(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode)
|
||||
}
|
||||
if resp.File != nil {
|
||||
url, err := u.oss.GetObjectUrl(ctx, resp.File.TosURI)
|
||||
if err == nil {
|
||||
resp.File.Url = url
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/config"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
@@ -55,12 +56,41 @@ type AsTool interface {
|
||||
allInterruptEvents map[string]*entity.ToolInterruptEvent) compose.Option
|
||||
}
|
||||
|
||||
type ChatFlowRole interface {
|
||||
CreateChatFlowRole(ctx context.Context, role *vo.ChatFlowRoleCreate) (int64, error)
|
||||
UpdateChatFlowRole(ctx context.Context, workflowID int64, role *vo.ChatFlowRoleUpdate) error
|
||||
GetChatFlowRole(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error)
|
||||
DeleteChatFlowRole(ctx context.Context, id int64, workflowID int64) error
|
||||
PublishChatFlowRole(ctx context.Context, policy *vo.PublishRolePolicy) error
|
||||
}
|
||||
|
||||
type Conversation interface {
|
||||
CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error)
|
||||
UpdateDraftConversationTemplateName(ctx context.Context, appID int64, userID int64, templateID int64, name string) error
|
||||
DeleteDraftConversationTemplate(ctx context.Context, templateID int64, wfID2ConversationName map[int64]string) (int64, error)
|
||||
CheckWorkflowsToReplace(ctx context.Context, appID int64, templateID int64) ([]*entity.Workflow, error)
|
||||
DeleteDynamicConversation(ctx context.Context, env vo.Env, templateID int64) (int64, error)
|
||||
ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error)
|
||||
MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
type InterruptEventStore interface {
|
||||
SaveInterruptEvents(ctx context.Context, wfExeID int64, events []*entity.InterruptEvent) error
|
||||
GetFirstInterruptEvent(ctx context.Context, wfExeID int64) (*entity.InterruptEvent, bool, error)
|
||||
UpdateFirstInterruptEvent(ctx context.Context, wfExeID int64, event *entity.InterruptEvent) error
|
||||
PopFirstInterruptEvent(ctx context.Context, wfExeID int64) (*entity.InterruptEvent, bool, error)
|
||||
ListInterruptEvents(ctx context.Context, wfExeID int64) ([]*entity.InterruptEvent, error)
|
||||
|
||||
BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error
|
||||
GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error)
|
||||
}
|
||||
|
||||
type CancelSignalStore interface {
|
||||
@@ -93,6 +123,33 @@ type ToolFromWorkflow interface {
|
||||
GetWorkflow() *entity.Workflow
|
||||
}
|
||||
|
||||
type ConversationIDGenerator func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error)
|
||||
|
||||
type ConversationRepository interface {
|
||||
CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error)
|
||||
UpdateDraftConversationTemplateName(ctx context.Context, templateID int64, name string) error
|
||||
DeleteDraftConversationTemplate(ctx context.Context, templateID int64) (int64, error)
|
||||
GetConversationTemplate(ctx context.Context, env vo.Env, policy vo.GetConversationTemplatePolicy) (*entity.ConversationTemplate, bool, error)
|
||||
DeleteDynamicConversation(ctx context.Context, env vo.Env, id int64) (int64, error)
|
||||
ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error)
|
||||
MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error)
|
||||
GetOrCreateStaticConversation(ctx context.Context, env vo.Env, idGen ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error)
|
||||
GetOrCreateDynamicConversation(ctx context.Context, env vo.Env, idGen ConversationIDGenerator, meta *vo.CreateDynamicConversation) (int64, int64, bool, error)
|
||||
GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error)
|
||||
GetStaticConversationByTemplateID(ctx context.Context, env vo.Env, userID, connectorID, templateID int64) (*entity.StaticConversation, bool, error)
|
||||
ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error)
|
||||
BatchCreateOnlineConversationTemplate(ctx context.Context, templates []*entity.ConversationTemplate, version string) error
|
||||
UpdateDynamicConversationNameByID(ctx context.Context, env vo.Env, templateID int64, name string) error
|
||||
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)
|
||||
}
|
||||
type WorkflowConfig interface {
|
||||
GetNodeOfCodeConfig() *config.NodeOfCodeConfig
|
||||
}
|
||||
|
||||
type Suggester interface {
|
||||
Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type WorkflowConfig struct {
|
||||
NodeOfCodeConfig *NodeOfCodeConfig `yaml:"NodeOfCodeConfig"`
|
||||
}
|
||||
|
||||
func (w WorkflowConfig) GetNodeOfCodeConfig() *NodeOfCodeConfig {
|
||||
func (w *WorkflowConfig) GetNodeOfCodeConfig() *NodeOfCodeConfig {
|
||||
return w.NodeOfCodeConfig
|
||||
}
|
||||
|
||||
|
||||
37
backend/domain/workflow/entity/chatflow_role.go
Normal file
37
backend/domain/workflow/entity/chatflow_role.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 entity
|
||||
|
||||
import "time"
|
||||
|
||||
type ChatFlowRole struct {
|
||||
ID int64
|
||||
WorkflowID int64
|
||||
ConnectorID int64
|
||||
Name string
|
||||
Description string
|
||||
Version string
|
||||
AvatarUri string
|
||||
BackgroundImageInfo string
|
||||
OnboardingInfo string
|
||||
SuggestReplyInfo string
|
||||
AudioConfig string
|
||||
UserInputConfig string
|
||||
CreatorID int64
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
39
backend/domain/workflow/entity/conversation.go
Normal file
39
backend/domain/workflow/entity/conversation.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 entity
|
||||
|
||||
type ConversationTemplate struct {
|
||||
SpaceID int64
|
||||
AppID int64
|
||||
Name string
|
||||
TemplateID int64
|
||||
}
|
||||
|
||||
type StaticConversation struct {
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
TemplateID int64
|
||||
ConversationID int64
|
||||
}
|
||||
|
||||
type DynamicConversation struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
ConversationID int64
|
||||
Name string
|
||||
}
|
||||
@@ -74,3 +74,9 @@ type ToolInterruptEvent struct {
|
||||
ExecuteID int64
|
||||
*InterruptEvent
|
||||
}
|
||||
|
||||
type ConvRelatedInfo struct {
|
||||
EventID int64
|
||||
ExecID int64
|
||||
NodeType NodeType
|
||||
}
|
||||
|
||||
@@ -144,8 +144,11 @@ const (
|
||||
NodeTypeCodeRunner NodeType = "CodeRunner"
|
||||
NodeTypePlugin NodeType = "Plugin"
|
||||
NodeTypeCreateConversation NodeType = "CreateConversation"
|
||||
NodeTypeConversationList NodeType = "ConversationList"
|
||||
NodeTypeMessageList NodeType = "MessageList"
|
||||
NodeTypeClearMessage NodeType = "ClearMessage"
|
||||
NodeTypeCreateMessage NodeType = "CreateMessage"
|
||||
NodeTypeEditMessage NodeType = "EditMessage"
|
||||
NodeTypeDeleteMessage NodeType = "DeleteMessage"
|
||||
NodeTypeLambda NodeType = "Lambda"
|
||||
NodeTypeLLM NodeType = "LLM"
|
||||
NodeTypeSelector NodeType = "Selector"
|
||||
@@ -153,6 +156,10 @@ const (
|
||||
NodeTypeSubWorkflow NodeType = "SubWorkflow"
|
||||
NodeTypeJsonSerialization NodeType = "JsonSerialization"
|
||||
NodeTypeJsonDeserialization NodeType = "JsonDeserialization"
|
||||
NodeTypeConversationUpdate NodeType = "ConversationUpdate"
|
||||
NodeTypeConversationDelete NodeType = "ConversationDelete"
|
||||
NodeTypeClearConversationHistory NodeType = "ClearConversationHistory"
|
||||
NodeTypeConversationHistory NodeType = "ConversationHistory"
|
||||
NodeTypeComment NodeType = "Comment"
|
||||
)
|
||||
|
||||
@@ -272,6 +279,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
PostFillNil: true,
|
||||
InputSourceAware: true,
|
||||
MayUseChatModel: true,
|
||||
UseCtxCache: true,
|
||||
},
|
||||
EnUSName: "LLM",
|
||||
EnUSDescription: "Invoke the large language model, generate responses using variables and prompt words.",
|
||||
@@ -324,6 +332,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
UseCtxCache: true,
|
||||
},
|
||||
EnUSName: "Knowledge retrieval",
|
||||
EnUSDescription: "In the selected knowledge, the best matching information is recalled based on the input variable and returned as an Array.",
|
||||
@@ -487,6 +496,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
MayUseChatModel: true,
|
||||
UseCtxCache: true,
|
||||
},
|
||||
EnUSName: "Intent recognition",
|
||||
EnUSDescription: "Used for recognizing the intent in user input and matching it with preset intent options.",
|
||||
@@ -593,7 +603,6 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-List.jpeg",
|
||||
SupportBatch: false,
|
||||
Disabled: true,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
@@ -601,16 +610,15 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
EnUSName: "Query message list",
|
||||
EnUSDescription: "Used to query the message list",
|
||||
},
|
||||
NodeTypeClearMessage: {
|
||||
NodeTypeClearConversationHistory: {
|
||||
ID: 38,
|
||||
Key: NodeTypeClearMessage,
|
||||
Name: "清除上下文",
|
||||
Category: "conversation_history",
|
||||
Key: NodeTypeClearConversationHistory,
|
||||
Name: "清空会话历史",
|
||||
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",
|
||||
SupportBatch: false,
|
||||
Disabled: true,
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
@@ -627,7 +635,6 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
SupportBatch: false,
|
||||
Disabled: true,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
@@ -730,6 +737,118 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{
|
||||
EnUSName: "Add Data",
|
||||
EnUSDescription: "Add new data records to the table, and insert them into the database after the user enters the data content",
|
||||
},
|
||||
NodeTypeConversationUpdate: {
|
||||
ID: 51,
|
||||
Name: "修改会话",
|
||||
Key: NodeTypeConversationUpdate,
|
||||
Category: "conversation_management",
|
||||
Desc: "用于修改会话的名字",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Edit Conversation",
|
||||
EnUSDescription: "Used to modify the name of a conversation.",
|
||||
},
|
||||
|
||||
NodeTypeConversationDelete: {
|
||||
ID: 52,
|
||||
Name: "删除会话",
|
||||
Key: NodeTypeConversationDelete,
|
||||
Category: "conversation_management",
|
||||
Desc: "用于删除会话",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除会话.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Delete Conversation",
|
||||
EnUSDescription: "Used to delete a conversation.",
|
||||
},
|
||||
NodeTypeConversationList: {
|
||||
ID: 53,
|
||||
Name: "查询会话列表",
|
||||
Key: NodeTypeConversationList,
|
||||
Category: "conversation_management",
|
||||
Desc: "用于查询所有会话,包含静态会话、动态会话",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话.jpg",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Query Conversation List",
|
||||
EnUSDescription: "Used to query all conversations, including static conversations and dynamic conversations",
|
||||
},
|
||||
NodeTypeConversationHistory: {
|
||||
ID: 54,
|
||||
Name: "查询会话历史",
|
||||
Key: NodeTypeConversationHistory,
|
||||
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",
|
||||
SupportBatch: false,
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Query Conversation History",
|
||||
EnUSDescription: "Used to query conversation history, returns conversation messages visible to the LLM",
|
||||
},
|
||||
NodeTypeCreateMessage: {
|
||||
ID: 55,
|
||||
Name: "创建消息",
|
||||
Key: NodeTypeCreateMessage,
|
||||
Category: "message",
|
||||
Desc: "用于创建消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Create message",
|
||||
EnUSDescription: "Used to create messages",
|
||||
},
|
||||
NodeTypeEditMessage: {
|
||||
ID: 56,
|
||||
Name: "修改消息",
|
||||
Key: NodeTypeEditMessage,
|
||||
Category: "message",
|
||||
Desc: "用于修改消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-修改消息.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Edit message",
|
||||
EnUSDescription: "Used to edit messages",
|
||||
},
|
||||
NodeTypeDeleteMessage: {
|
||||
ID: 57,
|
||||
Name: "删除消息",
|
||||
Key: NodeTypeDeleteMessage,
|
||||
Category: "message",
|
||||
Desc: "用于删除消息",
|
||||
Color: "#F2B600",
|
||||
IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除消息.jpg",
|
||||
SupportBatch: false, // supportBatch: 1
|
||||
ExecutableMeta: ExecutableMeta{
|
||||
PreFillZero: true,
|
||||
PostFillNil: true,
|
||||
},
|
||||
EnUSName: "Delete message",
|
||||
EnUSDescription: "Used to delete messages",
|
||||
},
|
||||
NodeTypeJsonSerialization: {
|
||||
// ID is the unique identifier of this node type. Used in various front-end APIs.
|
||||
ID: 58,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/i18n"
|
||||
@@ -108,7 +110,8 @@ type Data struct {
|
||||
type Inputs struct {
|
||||
// InputParameters are the fields defined by user for this particular node.
|
||||
InputParameters []*Param `json:"inputParameters"`
|
||||
|
||||
// ChatHistorySetting configures the chat history setting for this node in chatflow mode.
|
||||
ChatHistorySetting *ChatHistorySetting `json:"chatHistorySetting,omitempty"`
|
||||
// SettingOnError configures common error handling strategy for nodes.
|
||||
// NOTE: enable in frontend node's form first.
|
||||
SettingOnError *SettingOnError `json:"settingOnError,omitempty"`
|
||||
@@ -432,9 +435,8 @@ type DatabaseInfo struct {
|
||||
}
|
||||
|
||||
type IntentDetector struct {
|
||||
ChatHistorySetting *ChatHistorySetting `json:"chatHistorySetting,omitempty"`
|
||||
Intents []*Intent `json:"intents,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Intents []*Intent `json:"intents,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
}
|
||||
type ChatHistorySetting struct {
|
||||
EnableChatHistory bool `json:"enableChatHistory,omitempty"`
|
||||
@@ -826,6 +828,133 @@ const defaultEnUSInitCanvasJsonSchema = `{
|
||||
}
|
||||
}`
|
||||
|
||||
const defaultZhCNInitCanvasJsonSchemaChat = `{
|
||||
"nodes": [{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [{
|
||||
"type": "string",
|
||||
"name": "USER_INPUT",
|
||||
"required": true
|
||||
}, {
|
||||
"type": "string",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"required": false,
|
||||
"description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。",
|
||||
"defaultValue": "%s"
|
||||
}],
|
||||
"nodeMeta": {
|
||||
"title": "开始",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start.png",
|
||||
"description": "工作流的起始节点,用于设定启动工作流需要的信息",
|
||||
"subTitle": ""
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1000,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"title": "结束",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End.png",
|
||||
"description": "工作流的最终节点,用于返回工作流运行后的结果信息",
|
||||
"subTitle": ""
|
||||
},
|
||||
"inputs": {
|
||||
"terminatePlan": "useAnswerContent",
|
||||
"streamingOutput": true,
|
||||
"inputParameters": [{
|
||||
"name": "output",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}`
|
||||
const defaultEnUSInitCanvasJsonSchemaChat = `{
|
||||
"nodes": [{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [{
|
||||
"type": "string",
|
||||
"name": "USER_INPUT",
|
||||
"required": true
|
||||
}, {
|
||||
"type": "string",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"required": false,
|
||||
"description": "The conversation bound to this request will automatically write messages and read conversation history from that conversation.",
|
||||
"defaultValue": "%s"
|
||||
}],
|
||||
"nodeMeta": {
|
||||
"title": "Start",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start.png",
|
||||
"description": "The starting node of the workflow, used to set the information needed to initiate the workflow.",
|
||||
"subTitle": ""
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1000,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"title": "End",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End.png",
|
||||
"description": "The final node of the workflow, used to return the result information after the workflow runs.",
|
||||
"subTitle": ""
|
||||
},
|
||||
"inputs": {
|
||||
"terminatePlan": "useAnswerContent",
|
||||
"streamingOutput": true,
|
||||
"inputParameters": [{
|
||||
"name": "output",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}`
|
||||
|
||||
func GetDefaultInitCanvasJsonSchema(locale i18n.Locale) string {
|
||||
return ternary.IFElse(locale == i18n.LocaleEN, defaultEnUSInitCanvasJsonSchema, defaultZhCNInitCanvasJsonSchema)
|
||||
}
|
||||
|
||||
func GetDefaultInitCanvasJsonSchemaChat(locale i18n.Locale, name string) string {
|
||||
return ternary.IFElse(locale == i18n.LocaleEN, fmt.Sprintf(defaultEnUSInitCanvasJsonSchemaChat, name), fmt.Sprintf(defaultZhCNInitCanvasJsonSchemaChat, name))
|
||||
}
|
||||
|
||||
48
backend/domain/workflow/entity/vo/chat_flow_role.go
Normal file
48
backend/domain/workflow/entity/vo/chat_flow_role.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 vo
|
||||
|
||||
type ChatFlowRoleCreate struct {
|
||||
WorkflowID int64
|
||||
CreatorID int64
|
||||
Name string
|
||||
Description string
|
||||
AvatarUri string
|
||||
BackgroundImageInfo string
|
||||
OnboardingInfo string
|
||||
SuggestReplyInfo string
|
||||
AudioConfig string
|
||||
UserInputConfig string
|
||||
}
|
||||
|
||||
type ChatFlowRoleUpdate struct {
|
||||
WorkflowID int64
|
||||
Name *string
|
||||
Description *string
|
||||
AvatarUri *string
|
||||
BackgroundImageInfo *string
|
||||
OnboardingInfo *string
|
||||
SuggestReplyInfo *string
|
||||
AudioConfig *string
|
||||
UserInputConfig *string
|
||||
}
|
||||
|
||||
type PublishRolePolicy struct {
|
||||
WorkflowID int64
|
||||
CreatorID int64
|
||||
Version string
|
||||
}
|
||||
84
backend/domain/workflow/entity/vo/chatflow.go
Normal file
84
backend/domain/workflow/entity/vo/chatflow.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 vo
|
||||
|
||||
import "github.com/cloudwego/eino/schema"
|
||||
|
||||
type ChatFlowEvent string
|
||||
|
||||
const (
|
||||
ChatFlowCreated ChatFlowEvent = "conversation.chat.created"
|
||||
ChatFlowInProgress ChatFlowEvent = "conversation.chat.in_progress"
|
||||
ChatFlowCompleted ChatFlowEvent = "conversation.chat.completed"
|
||||
ChatFlowFailed ChatFlowEvent = "conversation.chat.failed"
|
||||
ChatFlowRequiresAction ChatFlowEvent = "conversation.chat.requires_action"
|
||||
ChatFlowError ChatFlowEvent = "error"
|
||||
ChatFlowDone ChatFlowEvent = "done"
|
||||
ChatFlowMessageDelta ChatFlowEvent = "conversation.message.delta"
|
||||
ChatFlowMessageCompleted ChatFlowEvent = "conversation.message.completed"
|
||||
)
|
||||
|
||||
type Usage struct {
|
||||
TokenCount *int32 `form:"token_count" json:"token_count,omitempty"`
|
||||
OutputTokens *int32 `form:"output_count" json:"output_count,omitempty"`
|
||||
InputTokens *int32 `form:"input_count" json:"input_count,omitempty"`
|
||||
}
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
Created Status = "created"
|
||||
InProgress Status = "in_progress"
|
||||
Completed Status = "completed"
|
||||
Failed Status = "failed"
|
||||
RequiresAction Status = "requires_action"
|
||||
Canceled Status = "canceled"
|
||||
)
|
||||
|
||||
type ChatFlowDetail struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
ConversationID string `json:"conversation_id,omitempty"`
|
||||
BotID string `json:"bot_id,omitempty"`
|
||||
Status Status `json:"status,omitempty"`
|
||||
Usage *Usage `json:"usage,omitempty"`
|
||||
ExecuteID string `json:"execute_id,omitempty"`
|
||||
SectionID string `json:"section_id"`
|
||||
}
|
||||
|
||||
type MessageDetail struct {
|
||||
ID string `json:"id"`
|
||||
ChatID string `json:"chat_id"`
|
||||
ConversationID string `json:"conversation_id"`
|
||||
BotID string `json:"bot_id"`
|
||||
Role string `json:"role"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
ContentType string `json:"content_type"`
|
||||
SectionID string `json:"section_id"`
|
||||
}
|
||||
|
||||
type ErrorDetail struct {
|
||||
Code string `form:"code,required" json:"code,required"`
|
||||
Msg string `form:"msg,required" json:"msg,required"`
|
||||
DebugUrl string `form:"debug_url" json:"debug_url,omitempty"`
|
||||
}
|
||||
|
||||
type SuggestInfo struct {
|
||||
UserInput *schema.Message `json:"user_input,omitempty"`
|
||||
AnswerInput *schema.Message `json:"answer,omitempty"`
|
||||
PersonaInput *string `json:"persona_input,omitempty"`
|
||||
}
|
||||
74
backend/domain/workflow/entity/vo/conversation.go
Normal file
74
backend/domain/workflow/entity/vo/conversation.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 vo
|
||||
|
||||
type Env string
|
||||
|
||||
const (
|
||||
Draft Env = "draft"
|
||||
Online Env = "online"
|
||||
)
|
||||
|
||||
type CreateConversationTemplateMeta struct {
|
||||
UserID int64
|
||||
AppID int64
|
||||
SpaceID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
type GetConversationTemplatePolicy struct {
|
||||
AppID *int64
|
||||
Name *string
|
||||
Version *string
|
||||
TemplateID *int64
|
||||
}
|
||||
|
||||
type ListConversationTemplatePolicy struct {
|
||||
AppID int64
|
||||
Page *Page
|
||||
NameLike *string
|
||||
Version *string
|
||||
}
|
||||
|
||||
type ListConversationMeta struct {
|
||||
APPID int64
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
}
|
||||
|
||||
type ListConversationPolicy struct {
|
||||
ListConversationMeta
|
||||
|
||||
Page *Page
|
||||
NameLike *string
|
||||
Version *string
|
||||
}
|
||||
|
||||
type CreateStaticConversation struct {
|
||||
AppID int64
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
|
||||
TemplateID int64
|
||||
}
|
||||
type CreateDynamicConversation struct {
|
||||
AppID int64
|
||||
UserID int64
|
||||
ConnectorID int64
|
||||
|
||||
Name string
|
||||
}
|
||||
@@ -68,6 +68,7 @@ type MetaUpdate struct {
|
||||
IconURI *string
|
||||
HasPublished *bool
|
||||
LatestPublishedVersion *string
|
||||
WorkflowMode *Mode
|
||||
}
|
||||
|
||||
type MetaQuery struct {
|
||||
@@ -80,4 +81,5 @@ type MetaQuery struct {
|
||||
LibOnly bool
|
||||
NeedTotalNumber bool
|
||||
DescByUpdate bool
|
||||
Mode *workflow.WorkflowMode
|
||||
}
|
||||
|
||||
@@ -21,4 +21,5 @@ type ReleaseWorkflowConfig struct {
|
||||
PluginIDs []int64
|
||||
|
||||
ConnectorIDs []int64
|
||||
WorkflowIDs []int64
|
||||
}
|
||||
|
||||
@@ -26,6 +26,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/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination ../../internal/mock/domain/workflow/interface.go --package mockWorkflow -source interface.go
|
||||
@@ -39,12 +40,15 @@ type Service interface {
|
||||
Publish(ctx context.Context, policy *vo.PublishPolicy) (err error)
|
||||
UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) (err error)
|
||||
CopyWorkflow(ctx context.Context, workflowID int64, policy vo.CopyWorkflowPolicy) (*entity.Workflow, error)
|
||||
WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []workflow.CheckType) ([]*workflow.CheckResult, error)
|
||||
|
||||
QueryNodeProperties(ctx context.Context, id int64) (map[string]*vo.NodeProperty, error) // only draft
|
||||
ValidateTree(ctx context.Context, id int64, validateConfig vo.ValidateTreeConfig) ([]*workflow.ValidateTreeInfo, error)
|
||||
|
||||
GetWorkflowReference(ctx context.Context, id int64) (map[int64]*vo.Meta, error)
|
||||
|
||||
GetWorkflowVersionsByConnector(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error)
|
||||
|
||||
Executable
|
||||
AsTool
|
||||
|
||||
@@ -53,17 +57,29 @@ type Service interface {
|
||||
DuplicateWorkflowsByAppID(ctx context.Context, sourceAPPID, targetAppID int64, related vo.ExternalResourceRelated) ([]*entity.Workflow, error)
|
||||
GetWorkflowDependenceResource(ctx context.Context, workflowID int64) (*vo.DependenceResource, error)
|
||||
SyncRelatedWorkflowResources(ctx context.Context, appID int64, relatedWorkflows map[int64]entity.IDVersionPair, related vo.ExternalResourceRelated) error
|
||||
|
||||
ChatFlowRole
|
||||
Conversation
|
||||
|
||||
BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error
|
||||
GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error)
|
||||
Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error)
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
CreateMeta(ctx context.Context, meta *vo.Meta) (int64, error)
|
||||
CreateVersion(ctx context.Context, id int64, info *vo.VersionInfo, newRefs map[entity.WorkflowReferenceKey]struct{}) (err error)
|
||||
CreateOrUpdateDraft(ctx context.Context, id int64, draft *vo.DraftInfo) error
|
||||
CreateChatFlowRoleConfig(ctx context.Context, chatFlowRole *entity.ChatFlowRole) (int64, error)
|
||||
UpdateChatFlowRoleConfig(ctx context.Context, workflowID int64, chatFlowRole *vo.ChatFlowRoleUpdate) error
|
||||
GetChatFlowRoleConfig(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error, bool)
|
||||
DeleteChatFlowRoleConfig(ctx context.Context, id int64, workflowID int64) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
MDelete(ctx context.Context, ids []int64) error
|
||||
GetMeta(ctx context.Context, id int64) (*vo.Meta, error)
|
||||
UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) error
|
||||
GetVersion(ctx context.Context, id int64, version string) (*vo.VersionInfo, error)
|
||||
GetVersion(ctx context.Context, id int64, version string) (*vo.VersionInfo, bool, error)
|
||||
GetVersionListByConnectorAndWorkflowID(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error)
|
||||
|
||||
GetEntity(ctx context.Context, policy *vo.GetPolicy) (*entity.Workflow, error)
|
||||
|
||||
@@ -95,12 +111,15 @@ type Repository interface {
|
||||
|
||||
IsApplicationConnectorWorkflowVersion(ctx context.Context, connectorID, workflowID int64, version string) (b bool, err error)
|
||||
|
||||
GetObjectUrl(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (string, error)
|
||||
|
||||
compose.CheckPointStore
|
||||
idgen.IDGenerator
|
||||
|
||||
GetKnowledgeRecallChatModel() model.BaseChatModel
|
||||
|
||||
ConversationRepository
|
||||
WorkflowConfig
|
||||
Suggester
|
||||
}
|
||||
|
||||
var repositorySingleton Repository
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/batch"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/code"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/conversation"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/database"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/emitter"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/entry"
|
||||
@@ -674,6 +675,36 @@ func RegisterAllNodeAdaptors() {
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeLLM, func() nodes.NodeAdaptor {
|
||||
return &llm.Config{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeCreateConversation, func() nodes.NodeAdaptor {
|
||||
return &conversation.CreateConversationConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeConversationUpdate, func() nodes.NodeAdaptor {
|
||||
return &conversation.UpdateConversationConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeConversationDelete, func() nodes.NodeAdaptor {
|
||||
return &conversation.DeleteConversationConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeConversationList, func() nodes.NodeAdaptor {
|
||||
return &conversation.ConversationListConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeConversationHistory, func() nodes.NodeAdaptor {
|
||||
return &conversation.ConversationHistoryConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeClearConversationHistory, func() nodes.NodeAdaptor {
|
||||
return &conversation.ClearConversationHistoryConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeMessageList, func() nodes.NodeAdaptor {
|
||||
return &conversation.MessageListConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeCreateMessage, func() nodes.NodeAdaptor {
|
||||
return &conversation.CreateMessageConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeEditMessage, func() nodes.NodeAdaptor {
|
||||
return &conversation.EditMessageConfig{}
|
||||
})
|
||||
nodes.RegisterNodeAdaptor(entity.NodeTypeDeleteMessage, func() nodes.NodeAdaptor {
|
||||
return &conversation.DeleteMessageConfig{}
|
||||
})
|
||||
|
||||
// register branch adaptors
|
||||
nodes.RegisterBranchAdaptor(entity.NodeTypeSelector, func() nodes.BranchAdaptor {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 13.818572856225469,
|
||||
"y": -37.20384999753011
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"nodeMeta": {
|
||||
"title": "开始",
|
||||
"description": "工作流的起始节点,用于设定启动工作流需要的信息",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg",
|
||||
"subTitle": ""
|
||||
},
|
||||
"settings": null,
|
||||
"version": "",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "USER_INPUT",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"required": false,
|
||||
"description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。",
|
||||
"defaultValue": "Default"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "input",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"trigger_parameters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 642.9671427865745,
|
||||
"y": -37.20384999753011
|
||||
}
|
||||
},
|
||||
"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": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "input"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "900001"
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"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": "100001",
|
||||
"name": "USER_INPUT",
|
||||
"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": 1000,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"type": "2"
|
||||
}],
|
||||
"edges": [{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "900001",
|
||||
"sourcePortID": ""
|
||||
}],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"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": {
|
||||
"schema": {
|
||||
"schema": [{
|
||||
"name": "conversationName",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "conversationId",
|
||||
"type": "string"
|
||||
}],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "list",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "107363",
|
||||
"name": "conversationList",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 103
|
||||
},
|
||||
"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": 1058,
|
||||
"y": -13
|
||||
}
|
||||
},
|
||||
"type": "2"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": []
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于查询所有会话,包含静态会话、动态会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话.jpg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "查询会话列表",
|
||||
"title": "查询会话列表"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "conversationList",
|
||||
"schema": {
|
||||
"schema": [{
|
||||
"name": "conversationName",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "conversationId",
|
||||
"type": "string"
|
||||
}],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "list"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "107363",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 561,
|
||||
"y": 186
|
||||
}
|
||||
},
|
||||
"type": "53"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "conversationName"
|
||||
}]
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于创建会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建会话",
|
||||
"title": "创建会话"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "isSuccess",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "isExisted",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "conversationId",
|
||||
"type": "string"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "110245",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 487,
|
||||
"y": -196
|
||||
}
|
||||
},
|
||||
"type": "39"
|
||||
}],
|
||||
"edges": [{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "110245",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "107363",
|
||||
"targetNodeID": "900001",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "110245",
|
||||
"targetNodeID": "107363",
|
||||
"sourcePortID": ""
|
||||
}],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 180,
|
||||
"y": 13.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",
|
||||
"name": "input",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"trigger_parameters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1100,
|
||||
"y": 0.7000000000000028
|
||||
}
|
||||
},
|
||||
"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": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "163698",
|
||||
"name": "conversationId"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "163698",
|
||||
"type": "39",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 640,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isExisted"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "conversationId"
|
||||
}
|
||||
],
|
||||
"nodeMeta": {
|
||||
"title": "创建会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
"description": "用于创建会话",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建会话"
|
||||
},
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "conversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "input"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "163698"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "163698",
|
||||
"targetNodeID": "900001"
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": -13.523809523809522,
|
||||
"y": -25.294372294372295
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"name": "input",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"trigger_parameters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 890.3549783549786,
|
||||
"y": -71.48917748917748
|
||||
}
|
||||
},
|
||||
"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": "boolean",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "118024",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "118024",
|
||||
"type": "52",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 423.6623376623378,
|
||||
"y": -126.39999999999999
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isSuccess"
|
||||
}
|
||||
],
|
||||
"nodeMeta": {
|
||||
"title": "删除会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除会话.jpg",
|
||||
"description": "用于删除会话",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "删除会话"
|
||||
},
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "conversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "input"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "118024"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "118024",
|
||||
"targetNodeID": "900001"
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": -243.67931247880136,
|
||||
"y": -233.598184501318
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"name": "input",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"trigger_parameters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 911.2952705396514,
|
||||
"y": -331.2250749763467
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"value": {
|
||||
"type": "object_ref"
|
||||
},
|
||||
"type": "object",
|
||||
"schema": [
|
||||
{
|
||||
"name": "isSuccess",
|
||||
"input": {
|
||||
"type": "boolean",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "122336",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isExisted",
|
||||
"input": {
|
||||
"type": "boolean",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "122336",
|
||||
"name": "isExisted"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conversationId",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "122336",
|
||||
"name": "conversationId"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "122336",
|
||||
"type": "51",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 343.08704991877585,
|
||||
"y": -462.38794621339696
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isExisted"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "conversationId"
|
||||
}
|
||||
],
|
||||
"nodeMeta": {
|
||||
"title": "修改会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg",
|
||||
"description": "用于修改会话的名字",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "修改会话"
|
||||
},
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "conversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "literal",
|
||||
"content": "template_v1",
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "newConversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "literal",
|
||||
"content": "new",
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "122336"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "122336",
|
||||
"targetNodeID": "900001"
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "100001",
|
||||
"type": "1",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 180,
|
||||
"y": 13.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",
|
||||
"name": "input",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "new_name",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"trigger_parameters": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "900001",
|
||||
"type": "2",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1560,
|
||||
"y": 0.7000000000000028
|
||||
}
|
||||
},
|
||||
"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": "obj",
|
||||
"input": {
|
||||
"value": {
|
||||
"type": "object_ref"
|
||||
},
|
||||
"type": "object",
|
||||
"schema": [
|
||||
{
|
||||
"name": "isSuccess",
|
||||
"input": {
|
||||
"type": "boolean",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "193175",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isExisted",
|
||||
"input": {
|
||||
"type": "boolean",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "193175",
|
||||
"name": "isExisted"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conversationId",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "193175",
|
||||
"name": "conversationId"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "139551",
|
||||
"type": "39",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 627.929589270746,
|
||||
"y": -36.21123218776195
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isExisted"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "conversationId"
|
||||
}
|
||||
],
|
||||
"nodeMeta": {
|
||||
"title": "创建会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
"description": "用于创建会话",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建会话"
|
||||
},
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "conversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "input"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "193175",
|
||||
"type": "51",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 1100,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"outputs": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isSuccess"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "isExisted"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "conversationId"
|
||||
}
|
||||
],
|
||||
"nodeMeta": {
|
||||
"title": "修改会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg",
|
||||
"description": "用于修改会话的名字",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "修改会话"
|
||||
},
|
||||
"inputs": {
|
||||
"inputParameters": [
|
||||
{
|
||||
"name": "conversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "input"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "newConversationName",
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"type": "ref",
|
||||
"content": {
|
||||
"source": "block-output",
|
||||
"blockID": "100001",
|
||||
"name": "new_name"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "139551"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "193175",
|
||||
"targetNodeID": "900001"
|
||||
},
|
||||
{
|
||||
"sourceNodeID": "139551",
|
||||
"targetNodeID": "193175"
|
||||
}
|
||||
],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"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": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "boolean",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "195185",
|
||||
"name": "isSuccess",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 3
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "output"
|
||||
}, {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "195185",
|
||||
"name": "message.messageId",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "mID"
|
||||
}],
|
||||
"terminatePlan": "returnVariables"
|
||||
},
|
||||
"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": 1000,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"type": "2"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "conversationName"
|
||||
}, {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": "user",
|
||||
"type": "literal"
|
||||
}
|
||||
},
|
||||
"name": "role"
|
||||
}, {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": "1",
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "literal"
|
||||
}
|
||||
},
|
||||
"name": "content"
|
||||
}]
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于创建消息",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建消息",
|
||||
"title": "创建消息"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "isSuccess",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "message",
|
||||
"schema": [{
|
||||
"name": "messageId",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "role",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "contentType",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "content",
|
||||
"type": "string"
|
||||
}],
|
||||
"type": "object"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "195185",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 482,
|
||||
"y": -13
|
||||
}
|
||||
},
|
||||
"type": "55"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "conversationName"
|
||||
}]
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于创建会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建会话",
|
||||
"title": "创建会话"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "isSuccess",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "isExisted",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "conversationId",
|
||||
"type": "string"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "121849",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 302,
|
||||
"y": -236
|
||||
}
|
||||
},
|
||||
"type": "39"
|
||||
}],
|
||||
"edges": [{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "121849",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "195185",
|
||||
"targetNodeID": "900001",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "121849",
|
||||
"targetNodeID": "195185",
|
||||
"sourcePortID": ""
|
||||
}],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
{
|
||||
"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": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"schema": {
|
||||
"schema": [{
|
||||
"name": "messageId",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "role",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "contentType",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "content",
|
||||
"type": "string"
|
||||
}],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "list",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "132703",
|
||||
"name": "messageList",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 103
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "output"
|
||||
}],
|
||||
"terminatePlan": "returnVariables"
|
||||
},
|
||||
"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": 1000,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
"type": "2"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "conversationName"
|
||||
}]
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于查询消息列表",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-List.jpeg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "查询消息列表",
|
||||
"title": "查询消息列表"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "messageList",
|
||||
"schema": {
|
||||
"schema": [{
|
||||
"name": "messageId",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "role",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "contentType",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "content",
|
||||
"type": "string"
|
||||
}],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "list"
|
||||
}, {
|
||||
"name": "firstId",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "lastId",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "hasMore",
|
||||
"type": "boolean"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "132703",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 514,
|
||||
"y": 96
|
||||
}
|
||||
},
|
||||
"type": "37"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "conversationName"
|
||||
}]
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于创建会话",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建会话",
|
||||
"title": "创建会话"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "isSuccess",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "isExisted",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "conversationId",
|
||||
"type": "string"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "166724",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 323,
|
||||
"y": -332
|
||||
}
|
||||
},
|
||||
"type": "39"
|
||||
}, {
|
||||
"blocks": [],
|
||||
"data": {
|
||||
"inputs": {
|
||||
"inputParameters": [{
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "CONVERSATION_NAME",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "conversationName"
|
||||
}, {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": "user",
|
||||
"type": "literal"
|
||||
}
|
||||
},
|
||||
"name": "role"
|
||||
}, {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"value": {
|
||||
"content": {
|
||||
"blockID": "100001",
|
||||
"name": "USER_INPUT",
|
||||
"source": "block-output"
|
||||
},
|
||||
"rawMeta": {
|
||||
"type": 1
|
||||
},
|
||||
"type": "ref"
|
||||
}
|
||||
},
|
||||
"name": "content"
|
||||
}]
|
||||
},
|
||||
"nodeMeta": {
|
||||
"description": "用于创建消息",
|
||||
"icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg",
|
||||
"mainColor": "#F2B600",
|
||||
"subTitle": "创建消息",
|
||||
"title": "创建消息"
|
||||
},
|
||||
"outputs": [{
|
||||
"name": "isSuccess",
|
||||
"type": "boolean"
|
||||
}, {
|
||||
"name": "message",
|
||||
"schema": [{
|
||||
"name": "messageId",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "role",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "contentType",
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "content",
|
||||
"type": "string"
|
||||
}],
|
||||
"type": "object"
|
||||
}]
|
||||
},
|
||||
"edges": null,
|
||||
"id": "157061",
|
||||
"meta": {
|
||||
"position": {
|
||||
"x": 479,
|
||||
"y": -127
|
||||
}
|
||||
},
|
||||
"type": "55"
|
||||
}],
|
||||
"edges": [{
|
||||
"sourceNodeID": "100001",
|
||||
"targetNodeID": "166724",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "132703",
|
||||
"targetNodeID": "900001",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "157061",
|
||||
"targetNodeID": "132703",
|
||||
"sourcePortID": ""
|
||||
}, {
|
||||
"sourceNodeID": "166724",
|
||||
"targetNodeID": "157061",
|
||||
"sourcePortID": ""
|
||||
}],
|
||||
"versions": {
|
||||
"loop": "v2"
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package validate
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"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/domain/workflow/variable"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
@@ -390,10 +392,13 @@ func (cv *CanvasValidator) CheckSubWorkFlowTerminatePlanType(ctx context.Context
|
||||
|
||||
if len(subID2SubVersion) > 0 {
|
||||
for id, version := range subID2SubVersion {
|
||||
v, err := workflow.GetRepository().GetVersion(ctx, id, version)
|
||||
v, existed, err := workflow.GetRepository().GetVersion(ctx, id, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existed {
|
||||
return nil, vo.WrapError(errno.ErrWorkflowNotFound, fmt.Errorf("workflow version %s not found for ID %d: %w", version, id, err), errorx.KV("id", strconv.FormatInt(id, 10)))
|
||||
}
|
||||
|
||||
var canvas vo.Canvas
|
||||
if err = sonic.UnmarshalString(v.Canvas, &canvas); err != nil {
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
workflow2 "github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"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/execute"
|
||||
@@ -87,6 +88,11 @@ func init() {
|
||||
_ = compose.RegisterSerializableType[workflowModel.Locator]("wf_locator")
|
||||
_ = compose.RegisterSerializableType[workflowModel.BizType]("biz_type")
|
||||
_ = compose.RegisterSerializableType[*execute.AppVariables]("app_variables")
|
||||
_ = compose.RegisterSerializableType[workflow2.WorkflowMode]("workflow_mode")
|
||||
_ = compose.RegisterSerializableType[*schema.Message]("schema_message")
|
||||
_ = compose.RegisterSerializableType[*crossmessage.WfMessage]("history_messages")
|
||||
_ = compose.RegisterSerializableType[*crossmessage.Content]("content")
|
||||
|
||||
}
|
||||
|
||||
func (s *State) AddQuestion(nodeKey vo.NodeKey, question *qa.Question) {
|
||||
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
model2 "github.com/cloudwego/eino/components/model"
|
||||
"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/domain/workflow/internal/execute"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
@@ -98,6 +100,14 @@ func TestLLM(t *testing.T) {
|
||||
|
||||
ctx := ctxcache.Init(context.Background())
|
||||
|
||||
defer mockey.Mock(execute.GetExeCtx).Return(&execute.Context{
|
||||
RootCtx: execute.RootCtx{
|
||||
ExeCfg: workflowModel.ExecuteConfig{
|
||||
WorkflowMode: 0,
|
||||
},
|
||||
},
|
||||
NodeCtx: &execute.NodeCtx{},
|
||||
}).Build().UnPatch()
|
||||
t.Run("plain text output, non-streaming mode", func(t *testing.T) {
|
||||
if openaiModel == nil {
|
||||
defer func() {
|
||||
|
||||
@@ -98,13 +98,24 @@ func TestQuestionAnswer(t *testing.T) {
|
||||
defer s.Close()
|
||||
|
||||
redisClient := redis.NewWithAddrAndPassword(s.Addr(), "")
|
||||
|
||||
var oneChatModel = chatModel
|
||||
if oneChatModel == nil {
|
||||
oneChatModel = &testutil.UTChatModel{
|
||||
InvokeResultProvider: func(_ int, in []*schema.Message) (*schema.Message, error) {
|
||||
return &schema.Message{
|
||||
Role: schema.Assistant,
|
||||
Content: "-1",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
mockIDGen := mock.NewMockIDGenerator(ctrl)
|
||||
mockIDGen.EXPECT().GenID(gomock.Any()).Return(time.Now().UnixNano(), nil).AnyTimes()
|
||||
mockTos := storageMock.NewMockStorage(ctrl)
|
||||
mockTos.EXPECT().GetObjectUrl(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes()
|
||||
repo := repo2.NewRepository(mockIDGen, db, redisClient, mockTos,
|
||||
checkpoint.NewRedisStore(redisClient), nil, nil)
|
||||
repo, _ := repo2.NewRepository(mockIDGen, db, redisClient, mockTos,
|
||||
checkpoint.NewRedisStore(redisClient), oneChatModel, nil)
|
||||
|
||||
mockey.Mock(workflow.GetRepository).Return(repo).Build()
|
||||
|
||||
t.Run("answer directly, no structured output", func(t *testing.T) {
|
||||
|
||||
@@ -380,7 +380,7 @@ func handleEvent(ctx context.Context, event *Event, repo workflow.Repository,
|
||||
}
|
||||
|
||||
if updatedRows, currentStatus, err = repo.UpdateWorkflowExecution(ctx, wfExec, []entity.WorkflowExecuteStatus{entity.WorkflowRunning,
|
||||
entity.WorkflowInterrupted}); err != nil {
|
||||
entity.WorkflowInterrupted, entity.WorkflowCancel}); err != nil {
|
||||
return noTerminate, fmt.Errorf("failed to save workflow execution when canceled: %v", err)
|
||||
} else if updatedRows == 0 {
|
||||
return noTerminate, fmt.Errorf("failed to update workflow execution to canceled for execution id %d, current status is %v", exeID, currentStatus)
|
||||
|
||||
@@ -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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation"
|
||||
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/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/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type ClearConversationHistoryConfig struct{}
|
||||
|
||||
type ClearConversationHistory struct{}
|
||||
|
||||
func (c *ClearConversationHistoryConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeClearConversationHistory,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: c,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (c *ClearConversationHistoryConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &ClearConversationHistory{}, nil
|
||||
}
|
||||
|
||||
func (c *ClearConversationHistory) Invoke(ctx context.Context, in map[string]any) (map[string]any, error) {
|
||||
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
version = execCtx.ExeCfg.Version
|
||||
)
|
||||
|
||||
if agentID != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, query conversation list is not available"))
|
||||
}
|
||||
if appID == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("query conversation list node, app id is required"))
|
||||
}
|
||||
|
||||
conversationName, ok := in["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required"))
|
||||
}
|
||||
|
||||
t, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
var conversationID int64
|
||||
if existed {
|
||||
ret, existed, err := wf.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, t.TemplateID)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if existed {
|
||||
conversationID = ret.ConversationID
|
||||
}
|
||||
} else {
|
||||
ret, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if existed {
|
||||
conversationID = ret.ConversationID
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return map[string]any{
|
||||
"isSuccess": false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
resp, err := crossconversation.DefaultSVC().ClearConversationHistory(ctx, &crossconversation.ClearConversationHistoryReq{
|
||||
ConversationID: conversationID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, fmt.Errorf("clear conversation history failed, response is nil"))
|
||||
}
|
||||
if execCtx.ExeCfg.SectionID != nil {
|
||||
atomic.StoreInt64(execCtx.ExeCfg.SectionID, resp.SectionID)
|
||||
}
|
||||
return map[string]any{
|
||||
"isSuccess": true,
|
||||
}, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
|
||||
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/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/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type ConversationHistoryConfig struct{}
|
||||
|
||||
type ConversationHistory struct{}
|
||||
|
||||
func (ch *ConversationHistoryConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeConversationHistory,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: ch,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (ch *ConversationHistoryConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &ConversationHistory{}, nil
|
||||
}
|
||||
|
||||
func (ch *ConversationHistory) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
version = execCtx.ExeCfg.Version
|
||||
initRunID = execCtx.ExeCfg.InitRoundID
|
||||
)
|
||||
if agentID != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, query conversation list is not available"))
|
||||
}
|
||||
if appID == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("query conversation list node, app id is required"))
|
||||
}
|
||||
|
||||
conversationName, ok := input["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required"))
|
||||
}
|
||||
|
||||
rounds, ok := input["rounds"].(int64)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("rounds is required"))
|
||||
}
|
||||
|
||||
template, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
var conversationID int64
|
||||
if existed {
|
||||
var sc *entity.StaticConversation
|
||||
sc, existed, err = wf.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, template.TemplateID)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if existed {
|
||||
conversationID = sc.ConversationID
|
||||
}
|
||||
|
||||
} else {
|
||||
var dc *entity.DynamicConversation
|
||||
dc, existed, err = wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if existed {
|
||||
conversationID = dc.ConversationID
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, vo.WrapError(errno.ErrConversationOfAppNotFound, fmt.Errorf("the conversation name does not exist: '%v'", conversationName))
|
||||
}
|
||||
|
||||
currentConversationID := execCtx.ExeCfg.ConversationID
|
||||
isCurrentConversation := currentConversationID != nil && *currentConversationID == conversationID
|
||||
var sectionID int64
|
||||
if isCurrentConversation {
|
||||
if execCtx.ExeCfg.SectionID == nil {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("section id is required"))
|
||||
}
|
||||
sectionID = *execCtx.ExeCfg.SectionID
|
||||
} else {
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectionID = cInfo.SectionID
|
||||
}
|
||||
|
||||
runIDs, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, &crossmessage.GetLatestRunIDsRequest{
|
||||
ConversationID: conversationID,
|
||||
UserID: userID,
|
||||
AppID: *appID,
|
||||
Rounds: rounds,
|
||||
InitRunID: initRunID,
|
||||
SectionID: sectionID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if len(runIDs) == 0 {
|
||||
return map[string]any{
|
||||
"messageList": []any{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
response, err := crossmessage.DefaultSVC().GetMessagesByRunIDs(ctx, &crossmessage.GetMessagesByRunIDsRequest{
|
||||
ConversationID: conversationID,
|
||||
RunIDs: runIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
var messageList []any
|
||||
for _, msg := range response.Messages {
|
||||
content, err := nodes.ConvertMessageToString(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
messageList = append(messageList, map[string]any{
|
||||
"role": string(msg.Role),
|
||||
"content": content,
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"messageList": messageList,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
|
||||
"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/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/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type ConversationList struct{}
|
||||
|
||||
type ConversationListConfig struct{}
|
||||
|
||||
func (c *ConversationListConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeConversationList,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: c,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (c *ConversationListConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &ConversationList{}, nil
|
||||
}
|
||||
|
||||
type conversationInfo struct {
|
||||
conversationName string
|
||||
conversationId string
|
||||
}
|
||||
|
||||
func (c *ConversationList) Invoke(ctx context.Context, _ map[string]any) (map[string]any, error) {
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
version = execCtx.ExeCfg.Version
|
||||
)
|
||||
if agentID != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, query conversation list is not available"))
|
||||
}
|
||||
if appID == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("query conversation list node, app id is required"))
|
||||
}
|
||||
|
||||
templates, err := workflow.GetRepository().ListConversationTemplate(ctx, env, &vo.ListConversationTemplatePolicy{
|
||||
AppID: *appID,
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templateIds := make([]int64, 0, len(templates))
|
||||
for _, template := range templates {
|
||||
templateIds = append(templateIds, template.TemplateID)
|
||||
}
|
||||
|
||||
staticConversations, err := workflow.GetRepository().MGetStaticConversation(ctx, env, userID, connectorID, templateIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templateIDToConvID := slices.ToMap(staticConversations, func(conv *entity.StaticConversation) (int64, int64) {
|
||||
return conv.TemplateID, conv.ConversationID
|
||||
})
|
||||
|
||||
var conversationList []conversationInfo
|
||||
|
||||
for _, template := range templates {
|
||||
convID, ok := templateIDToConvID[template.TemplateID]
|
||||
if !ok {
|
||||
convID = 0
|
||||
}
|
||||
conversationList = append(conversationList, conversationInfo{
|
||||
conversationName: template.Name,
|
||||
conversationId: strconv.FormatInt(convID, 10),
|
||||
})
|
||||
}
|
||||
|
||||
dynamicConversations, err := workflow.GetRepository().ListDynamicConversation(ctx, env, &vo.ListConversationPolicy{
|
||||
ListConversationMeta: vo.ListConversationMeta{
|
||||
APPID: *appID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, conv := range dynamicConversations {
|
||||
conversationList = append(conversationList, conversationInfo{
|
||||
conversationName: conv.Name,
|
||||
conversationId: strconv.FormatInt(conv.ConversationID, 10),
|
||||
})
|
||||
}
|
||||
|
||||
resultList := make([]any, len(conversationList))
|
||||
for i, v := range conversationList {
|
||||
resultList[i] = map[string]any{
|
||||
"conversationName": v.conversationName,
|
||||
"conversationId": v.conversationId,
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"conversationList": resultList,
|
||||
}, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/conversation/common"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation"
|
||||
conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
|
||||
|
||||
"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/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/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type CreateConversationConfig struct{}
|
||||
|
||||
type CreateConversation struct{}
|
||||
|
||||
func (c *CreateConversationConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeCreateConversation,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: c,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (c *CreateConversationConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &CreateConversation{}, nil
|
||||
}
|
||||
|
||||
func (c *CreateConversation) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
conversationIDGenerator = workflow.ConversationIDGenerator(func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) {
|
||||
return crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{
|
||||
AgentID: appID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
Scene: common.Scene_SceneWorkflow,
|
||||
})
|
||||
})
|
||||
)
|
||||
if agentID != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, create conversation is not available"))
|
||||
}
|
||||
|
||||
if appID == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, errors.New("create conversation node, app id is required"))
|
||||
}
|
||||
|
||||
conversationName, ok := input["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required"))
|
||||
}
|
||||
|
||||
template, existed, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
cID, _, existed, err := workflow.GetRepository().GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{
|
||||
AppID: ptr.From(appID),
|
||||
TemplateID: template.TemplateID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{
|
||||
"isSuccess": true,
|
||||
"conversationId": cID,
|
||||
"isExisted": existed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
cID, _, existed, err := workflow.GetRepository().GetOrCreateDynamicConversation(ctx, env, conversationIDGenerator, &vo.CreateDynamicConversation{
|
||||
AppID: ptr.From(appID),
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
Name: conversationName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"isSuccess": true,
|
||||
"conversationId": cID,
|
||||
"isExisted": existed,
|
||||
}, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/conversation/common"
|
||||
conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
|
||||
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
einoSchema "github.com/cloudwego/eino/schema"
|
||||
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun"
|
||||
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
|
||||
agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
"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/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/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type CreateMessageConfig struct{}
|
||||
|
||||
type CreateMessage struct{}
|
||||
|
||||
func (c *CreateMessageConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeCreateMessage,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: c,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (c *CreateMessageConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &CreateMessage{}, nil
|
||||
}
|
||||
|
||||
func (c *CreateMessage) getConversationIDByName(ctx context.Context, env vo.Env, appID *int64, version, conversationName string, userID, connectorID int64) (int64, error) {
|
||||
template, isExist, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
conversationIDGenerator := workflow.ConversationIDGenerator(func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) {
|
||||
return crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{
|
||||
AgentID: appID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
Scene: common.Scene_SceneWorkflow,
|
||||
})
|
||||
})
|
||||
|
||||
var conversationID int64
|
||||
if isExist {
|
||||
cID, _, _, err := workflow.GetRepository().GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{
|
||||
AppID: ptr.From(appID),
|
||||
TemplateID: template.TemplateID,
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
conversationID = cID
|
||||
} else {
|
||||
dc, _, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if dc != nil {
|
||||
conversationID = dc.ConversationID
|
||||
}
|
||||
}
|
||||
return conversationID, nil
|
||||
}
|
||||
|
||||
func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
)
|
||||
|
||||
conversationName, ok := input["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversationName is required"))
|
||||
}
|
||||
|
||||
role, ok := input["role"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("role is required"))
|
||||
}
|
||||
if role != "user" && role != "assistant" {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, fmt.Errorf("role must be user or assistant"))
|
||||
}
|
||||
|
||||
content, ok := input["content"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("content is required"))
|
||||
}
|
||||
|
||||
var conversationID int64
|
||||
var err error
|
||||
var resolvedAppID int64
|
||||
if appID == nil {
|
||||
if conversationName != "Default" {
|
||||
return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, errors.New("conversation node only allow in application"))
|
||||
}
|
||||
if agentID == nil || execCtx.ExeCfg.ConversationID == nil {
|
||||
return map[string]any{
|
||||
"isSuccess": false,
|
||||
"message": map[string]any{
|
||||
"messageId": "0",
|
||||
"role": role,
|
||||
"contentType": "text",
|
||||
"content": content,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
conversationID = *execCtx.ExeCfg.ConversationID
|
||||
resolvedAppID = *agentID
|
||||
} else {
|
||||
conversationID, err = c.getConversationIDByName(ctx, env, appID, version, conversationName, userID, connectorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAppID = *appID
|
||||
}
|
||||
|
||||
if conversationID == 0 {
|
||||
return map[string]any{
|
||||
"isSuccess": false,
|
||||
"message": map[string]any{
|
||||
"messageId": "0",
|
||||
"role": role,
|
||||
"contentType": "text",
|
||||
"content": content,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
currentConversationID := execCtx.ExeCfg.ConversationID
|
||||
isCurrentConversation := currentConversationID != nil && *currentConversationID == conversationID
|
||||
var runID int64
|
||||
var sectionID int64
|
||||
if isCurrentConversation {
|
||||
if execCtx.ExeCfg.SectionID != nil {
|
||||
sectionID = *execCtx.ExeCfg.SectionID
|
||||
} else {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("section id is required"))
|
||||
}
|
||||
} else {
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sectionID = cInfo.SectionID
|
||||
}
|
||||
|
||||
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,
|
||||
ConversationID: conversationID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
ConnectorID: connectorID,
|
||||
SectionID: sectionID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRunID := runRecord.ID
|
||||
if execCtx.ExeCfg.RoundID != nil {
|
||||
atomic.StoreInt64(execCtx.ExeCfg.RoundID, newRunID)
|
||||
}
|
||||
runID = newRunID
|
||||
} else if isCurrentConversation {
|
||||
// For assistant messages in the same conversation, reuse the runID from the context.
|
||||
if execCtx.ExeCfg.RoundID == nil {
|
||||
// This indicates an inconsistent state, as a user message should have set this.
|
||||
return map[string]any{
|
||||
"isSuccess": false,
|
||||
"message": map[string]any{
|
||||
"messageId": "0",
|
||||
"role": role,
|
||||
"contentType": "text",
|
||||
"content": content,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
runID = *execCtx.ExeCfg.RoundID
|
||||
} else {
|
||||
// For assistant messages in a different conversation or a new workflow run,
|
||||
// find the latest runID or create a new one as a fallback.
|
||||
runIDs, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, &crossmessage.GetLatestRunIDsRequest{
|
||||
ConversationID: conversationID,
|
||||
UserID: userID,
|
||||
AppID: resolvedAppID,
|
||||
Rounds: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(runIDs) > 0 && runIDs[0] != 0 {
|
||||
runID = runIDs[0]
|
||||
} else {
|
||||
runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{
|
||||
AgentID: resolvedAppID,
|
||||
ConversationID: conversationID,
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
ConnectorID: connectorID,
|
||||
SectionID: sectionID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
runID = runRecord.ID
|
||||
}
|
||||
}
|
||||
|
||||
message := &model.Message{
|
||||
ConversationID: conversationID,
|
||||
Role: einoSchema.RoleType(role),
|
||||
Content: content,
|
||||
ContentType: model.ContentType("text"),
|
||||
UserID: strconv.FormatInt(userID, 10),
|
||||
AgentID: resolvedAppID,
|
||||
RunID: runID,
|
||||
SectionID: sectionID,
|
||||
}
|
||||
if message.Role == einoSchema.User {
|
||||
message.MessageType = model.MessageTypeQuestion
|
||||
} else {
|
||||
message.MessageType = model.MessageTypeAnswer
|
||||
}
|
||||
msg, err := crossmessage.DefaultSVC().Create(ctx, message)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
messageOutput := map[string]any{
|
||||
"messageId": msg.ID,
|
||||
"role": role,
|
||||
"contentType": "text",
|
||||
"content": content,
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"isSuccess": true,
|
||||
"message": messageOutput,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
|
||||
"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/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/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type DeleteConversationConfig struct{}
|
||||
|
||||
type DeleteConversation struct{}
|
||||
|
||||
func (d *DeleteConversationConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeConversationDelete,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: d,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (d *DeleteConversationConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &DeleteConversation{}, nil
|
||||
}
|
||||
|
||||
func (d *DeleteConversation) Invoke(ctx context.Context, in map[string]any) (map[string]any, error) {
|
||||
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
)
|
||||
|
||||
if agentID != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, delete conversation is not available"))
|
||||
}
|
||||
|
||||
if appID == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, errors.New("delete conversation node, app id is required"))
|
||||
}
|
||||
|
||||
cName, ok := in["conversationName"]
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required"))
|
||||
}
|
||||
|
||||
conversationName := cName.(string)
|
||||
|
||||
_, existed, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeInvalidOperation, fmt.Errorf("only conversation created through nodes are allowed to be modified or deleted"))
|
||||
}
|
||||
|
||||
dyConversation, existed, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, vo.WrapError(errno.ErrConversationOfAppNotFound, fmt.Errorf("the conversation name does not exist: '%v'", conversationName))
|
||||
}
|
||||
|
||||
_, err = workflow.GetRepository().DeleteDynamicConversation(ctx, env, dyConversation.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"isSuccess": true,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
msgentity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
|
||||
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/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/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type DeleteMessageConfig struct{}
|
||||
|
||||
type DeleteMessage struct{}
|
||||
|
||||
func (d *DeleteMessageConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeDeleteMessage,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: d,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (d *DeleteMessageConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &DeleteMessage{}, nil
|
||||
}
|
||||
|
||||
func (d *DeleteMessage) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
|
||||
successMap = map[string]any{
|
||||
"isSuccess": true,
|
||||
}
|
||||
failedMap = map[string]any{
|
||||
"isSuccess": false,
|
||||
}
|
||||
)
|
||||
|
||||
conversationName, ok := input["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversationName is required"))
|
||||
}
|
||||
messageStr, ok := input["messageId"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("messageId is required"))
|
||||
}
|
||||
messageID, err := strconv.ParseInt(messageStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, err)
|
||||
}
|
||||
|
||||
if appID == nil {
|
||||
if conversationName != "Default" {
|
||||
return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, fmt.Errorf("only default conversation allow in agent scenario"))
|
||||
}
|
||||
|
||||
if agentID == nil || execCtx.ExeCfg.ConversationID == nil {
|
||||
return failedMap, nil
|
||||
}
|
||||
|
||||
err = crossmessage.DefaultSVC().Delete(ctx, &msgentity.DeleteMeta{MessageIDs: []int64{messageID}, ConversationID: execCtx.ExeCfg.ConversationID})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
return successMap, nil
|
||||
}
|
||||
|
||||
t, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if existed {
|
||||
sc, existed, err := wf.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, t.TemplateID)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return failedMap, nil
|
||||
}
|
||||
|
||||
err = crossmessage.DefaultSVC().Delete(ctx, &msgentity.DeleteMeta{MessageIDs: []int64{messageID}, ConversationID: ptr.Of(sc.ConversationID)})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
return successMap, nil
|
||||
|
||||
} else {
|
||||
dc, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return failedMap, nil
|
||||
}
|
||||
|
||||
err = crossmessage.DefaultSVC().Delete(ctx, &msgentity.DeleteMeta{MessageIDs: []int64{messageID}, ConversationID: ptr.Of(dc.ConversationID)})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
return successMap, nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
"strconv"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"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/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/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type EditMessageConfig struct{}
|
||||
|
||||
type EditMessage struct{}
|
||||
|
||||
func (e *EditMessageConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeEditMessage,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: e,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (e *EditMessageConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &EditMessage{}, nil
|
||||
}
|
||||
|
||||
func (e *EditMessage) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
|
||||
successMap = map[string]any{
|
||||
"isSuccess": true,
|
||||
}
|
||||
failedMap = map[string]any{
|
||||
"isSuccess": false,
|
||||
}
|
||||
)
|
||||
|
||||
conversationName, ok := input["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversationName is required"))
|
||||
}
|
||||
|
||||
messageStr, ok := input["messageId"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("messageId is required"))
|
||||
}
|
||||
|
||||
messageID, err := strconv.ParseInt(messageStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
newContent, ok := input["newContent"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("newContent is required"))
|
||||
}
|
||||
|
||||
if appID == nil {
|
||||
if conversationName != "Default" {
|
||||
return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, fmt.Errorf("only default conversation allow in agent scenario"))
|
||||
}
|
||||
|
||||
if agentID == nil || execCtx.ExeCfg.ConversationID == nil {
|
||||
return failedMap, nil
|
||||
}
|
||||
|
||||
_, err = crossmessage.DefaultSVC().Edit(ctx, &model.Message{ConversationID: *execCtx.ExeCfg.ConversationID, ID: messageID, Content: newContent})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
return successMap, err
|
||||
}
|
||||
|
||||
msg, err := message.DefaultSVC().GetMessageByID(ctx, messageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msg == nil {
|
||||
return nil, vo.NewError(errno.ErrMessageNodeOperationFail, errorx.KV("cause", "message not found"))
|
||||
}
|
||||
|
||||
if msg.Content == newContent {
|
||||
return successMap, nil
|
||||
}
|
||||
|
||||
t, existed, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if existed {
|
||||
sts, existed, err := workflow.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, t.TemplateID)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return failedMap, nil
|
||||
}
|
||||
|
||||
_, err = crossmessage.DefaultSVC().Edit(ctx, &model.Message{ConversationID: sts.ConversationID, ID: messageID, Content: newContent})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
return successMap, nil
|
||||
|
||||
} else {
|
||||
dyConversation, existed, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return failedMap, nil
|
||||
}
|
||||
|
||||
_, err = crossmessage.DefaultSVC().Edit(ctx, &model.Message{ConversationID: dyConversation.ConversationID, ID: messageID, Content: newContent})
|
||||
if err != nil {
|
||||
return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
return successMap, nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"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/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/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type MessageListConfig struct{}
|
||||
|
||||
type MessageList struct{}
|
||||
|
||||
func (m *MessageListConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeMessageList,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: m,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (m *MessageListConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &MessageList{}, nil
|
||||
}
|
||||
|
||||
func (m *MessageList) getConversationIDByName(ctx context.Context, env vo.Env, appID *int64, version, conversationName string, userID, connectorID int64) (int64, error) {
|
||||
template, isExist, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
|
||||
var conversationID int64
|
||||
if isExist {
|
||||
sc, _, err := workflow.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, template.TemplateID)
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if sc != nil {
|
||||
conversationID = sc.ConversationID
|
||||
}
|
||||
} else {
|
||||
dc, _, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
if dc != nil {
|
||||
conversationID = dc.ConversationID
|
||||
}
|
||||
}
|
||||
|
||||
return conversationID, nil
|
||||
}
|
||||
|
||||
func (m *MessageList) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
)
|
||||
|
||||
conversationName, ok := input["conversationName"].(string)
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeInvalidOperation, errors.New("ConversationName is required"))
|
||||
}
|
||||
|
||||
var conversationID int64
|
||||
var err error
|
||||
var resolvedAppID int64
|
||||
if appID == nil {
|
||||
if conversationName != "Default" {
|
||||
return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, errors.New("conversation node only allow in application"))
|
||||
}
|
||||
if agentID == nil || execCtx.ExeCfg.ConversationID == nil {
|
||||
return map[string]any{
|
||||
"messageList": []any{},
|
||||
"firstId": "0",
|
||||
"lastId": "0",
|
||||
"hasMore": false,
|
||||
}, nil
|
||||
}
|
||||
conversationID = *execCtx.ExeCfg.ConversationID
|
||||
resolvedAppID = *agentID
|
||||
} else {
|
||||
conversationID, err = m.getConversationIDByName(ctx, env, appID, version, conversationName, userID, connectorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAppID = *appID
|
||||
}
|
||||
|
||||
req := &crossmessage.MessageListRequest{
|
||||
UserID: userID,
|
||||
AppID: resolvedAppID,
|
||||
ConversationID: conversationID,
|
||||
}
|
||||
|
||||
if req.ConversationID == 0 {
|
||||
return map[string]any{
|
||||
"messageList": []any{},
|
||||
"firstId": "0",
|
||||
"lastId": "0",
|
||||
"hasMore": false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
limit, ok := input["limit"].(int64)
|
||||
if ok {
|
||||
if limit > 0 && limit <= 50 {
|
||||
req.Limit = limit
|
||||
} else {
|
||||
req.Limit = 50
|
||||
}
|
||||
} else {
|
||||
req.Limit = 50
|
||||
}
|
||||
beforeID, ok := input["beforeId"].(string)
|
||||
if ok {
|
||||
|
||||
req.BeforeID = &beforeID
|
||||
}
|
||||
afterID, ok := input["afterId"].(string)
|
||||
if ok {
|
||||
|
||||
req.AfterID = &afterID
|
||||
}
|
||||
|
||||
if beforeID != "" && afterID != "" {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, fmt.Errorf("BeforeID and AfterID cannot be set at the same time"))
|
||||
}
|
||||
|
||||
ml, err := crossmessage.DefaultSVC().MessageList(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var messageList []any
|
||||
for _, msg := range ml.Messages {
|
||||
content, err := nodes.ConvertMessageToString(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messageList = append(messageList, map[string]any{
|
||||
"messageId": strconv.FormatInt(msg.ID, 10),
|
||||
"role": string(msg.Role),
|
||||
"contentType": msg.ContentType,
|
||||
"content": content,
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"messageList": messageList,
|
||||
"firstId": ml.FirstID,
|
||||
"lastId": ml.LastID,
|
||||
"hasMore": ml.HasMore,
|
||||
}, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/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/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/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type UpdateConversationConfig struct{}
|
||||
|
||||
type UpdateConversation struct{}
|
||||
|
||||
func (c *UpdateConversationConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
ns := &schema.NodeSchema{
|
||||
Key: vo.NodeKey(n.ID),
|
||||
Type: entity.NodeTypeConversationUpdate,
|
||||
Name: n.Data.Meta.Title,
|
||||
Configs: c,
|
||||
}
|
||||
|
||||
if err := convert.SetInputsForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (c *UpdateConversationConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) {
|
||||
return &UpdateConversation{}, nil
|
||||
}
|
||||
|
||||
func (c *UpdateConversation) Invoke(ctx context.Context, in map[string]any) (map[string]any, error) {
|
||||
|
||||
var (
|
||||
execCtx = execute.GetExeCtx(ctx)
|
||||
env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft)
|
||||
appID = execCtx.ExeCfg.AppID
|
||||
agentID = execCtx.ExeCfg.AgentID
|
||||
version = execCtx.ExeCfg.Version
|
||||
connectorID = execCtx.ExeCfg.ConnectorID
|
||||
userID = execCtx.ExeCfg.Operator
|
||||
)
|
||||
cName, ok := in["conversationName"]
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required"))
|
||||
}
|
||||
|
||||
conversationName := cName.(string)
|
||||
|
||||
ncName, ok := in["newConversationName"]
|
||||
if !ok {
|
||||
return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("new conversationName name is required"))
|
||||
}
|
||||
|
||||
newConversationName := ncName.(string)
|
||||
|
||||
if agentID != nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, update conversation is not available"))
|
||||
}
|
||||
|
||||
if appID == nil {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, errors.New("conversation update node, app id is required"))
|
||||
}
|
||||
|
||||
_, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{
|
||||
AppID: appID,
|
||||
Name: ptr.Of(conversationName),
|
||||
Version: ptr.Of(version),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return nil, vo.WrapError(errno.ErrConversationNodeInvalidOperation, fmt.Errorf("only conversation created through nodes are allowed to be modified or deleted"))
|
||||
}
|
||||
|
||||
conversation, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return map[string]any{
|
||||
"conversationId": "0",
|
||||
"isSuccess": false,
|
||||
"isExisted": false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
ncConversation, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, newConversationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return map[string]any{
|
||||
"conversationId": strconv.FormatInt(ncConversation.ConversationID, 10),
|
||||
"isSuccess": false,
|
||||
"isExisted": true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err = wf.GetRepository().UpdateDynamicConversationNameByID(ctx, env, conversation.ID, newConversationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"conversationId": strconv.FormatInt(conversation.ConversationID, 10),
|
||||
"isSuccess": true,
|
||||
"isExisted": false,
|
||||
}, nil
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -29,21 +30,26 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
|
||||
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
crossmodelmgr "github.com/coze-dev/coze-studio/backend/crossdomain/contract/modelmgr"
|
||||
"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"
|
||||
schema2 "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
|
||||
"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/sonic"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Intents []string
|
||||
SystemPrompt string
|
||||
IsFastMode bool
|
||||
LLMParams *model.LLMParams
|
||||
Intents []string
|
||||
SystemPrompt string
|
||||
IsFastMode bool
|
||||
LLMParams *model.LLMParams
|
||||
ChatHistorySetting *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema2.NodeSchema, error) {
|
||||
@@ -59,6 +65,10 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*
|
||||
return nil, fmt.Errorf("intent detector node's llmParam is nil")
|
||||
}
|
||||
|
||||
if n.Data.Inputs.ChatHistorySetting != nil {
|
||||
c.ChatHistorySetting = n.Data.Inputs.ChatHistorySetting
|
||||
}
|
||||
|
||||
llmParam, ok := param.(vo.IntentDetectorLLMParam)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("llm node's llmParam must be LLMParam, got %v", llmParam)
|
||||
@@ -141,14 +151,16 @@ func (c *Config) Build(ctx context.Context, _ *schema2.NodeSchema, _ ...schema2.
|
||||
&schema.Message{Content: sptTemplate, Role: schema.System},
|
||||
&schema.Message{Content: "{{query}}", Role: schema.User})
|
||||
|
||||
r, err := chain.AppendChatTemplate(prompts).AppendChatModel(m).Compile(ctx)
|
||||
r, err := chain.AppendChatTemplate(newHistoryChatTemplate(prompts, c.ChatHistorySetting)).AppendChatModel(m).Compile(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IntentDetector{
|
||||
isFastMode: c.IsFastMode,
|
||||
systemPrompt: c.SystemPrompt,
|
||||
runner: r,
|
||||
isFastMode: c.IsFastMode,
|
||||
systemPrompt: c.SystemPrompt,
|
||||
runner: r,
|
||||
ChatHistorySetting: c.ChatHistorySetting,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -182,6 +194,10 @@ func (c *Config) ExpectPorts(ctx context.Context, n *vo.Node) []string {
|
||||
return expects
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
const chatHistoryKey contextKey = "chatHistory"
|
||||
|
||||
const SystemIntentPrompt = `
|
||||
# Role
|
||||
You are an intention classification expert, good at being able to judge which classification the user's input belongs to.
|
||||
@@ -240,9 +256,10 @@ Note:
|
||||
const classificationID = "classificationId"
|
||||
|
||||
type IntentDetector struct {
|
||||
isFastMode bool
|
||||
systemPrompt string
|
||||
runner compose.Runnable[map[string]any, *schema.Message]
|
||||
isFastMode bool
|
||||
systemPrompt string
|
||||
runner compose.Runnable[map[string]any, *schema.Message]
|
||||
ChatHistorySetting *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
func (id *IntentDetector) parseToNodeOut(content string) (map[string]any, error) {
|
||||
@@ -320,3 +337,66 @@ func toIntentString(its []string) (string, error) {
|
||||
|
||||
return sonic.MarshalString(vs)
|
||||
}
|
||||
|
||||
func (id *IntentDetector) ToCallbackInput(ctx context.Context, in map[string]any) (map[string]any, error) {
|
||||
if id.ChatHistorySetting == nil || !id.ChatHistorySetting.EnableChatHistory {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
var messages []*crossmessage.WfMessage
|
||||
var scMessages []*schema.Message
|
||||
var sectionID *int64
|
||||
execCtx := execute.GetExeCtx(ctx)
|
||||
if execCtx != nil {
|
||||
messages = execCtx.ExeCfg.ConversationHistory
|
||||
scMessages = execCtx.ExeCfg.ConversationHistorySchemaMessages
|
||||
sectionID = execCtx.ExeCfg.SectionID
|
||||
}
|
||||
|
||||
ret := map[string]any{
|
||||
"chatHistory": []any{},
|
||||
}
|
||||
maps.Copy(ret, in)
|
||||
|
||||
if len(messages) == 0 {
|
||||
return ret, nil
|
||||
}
|
||||
if sectionID != nil && messages[0].SectionID != *sectionID {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
maxRounds := int(id.ChatHistorySetting.ChatHistoryRound)
|
||||
if execCtx != nil && execCtx.ExeCfg.MaxHistoryRounds != nil {
|
||||
maxRounds = min(int(*execCtx.ExeCfg.MaxHistoryRounds), maxRounds)
|
||||
}
|
||||
|
||||
count := 0
|
||||
startIdx := 0
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
if messages[i].Role == schema.User {
|
||||
count++
|
||||
}
|
||||
if count >= maxRounds {
|
||||
startIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var historyMessages []any
|
||||
for _, msg := range messages[startIdx:] {
|
||||
content, err := nodes.ConvertMessageToString(ctx, msg)
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "failed to convert message to string: %v", err)
|
||||
continue
|
||||
}
|
||||
historyMessages = append(historyMessages, map[string]any{
|
||||
"role": string(msg.Role),
|
||||
"content": content,
|
||||
})
|
||||
}
|
||||
|
||||
ctxcache.Store(ctx, chatHistoryKey, scMessages[startIdx:])
|
||||
|
||||
ret["chatHistory"] = historyMessages
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 intentdetector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cloudwego/eino/components/prompt"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/ctxcache"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
type historyChatTemplate struct {
|
||||
basePrompt prompt.ChatTemplate
|
||||
chatHistorySetting *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
func newHistoryChatTemplate(basePrompt prompt.ChatTemplate, chatHistorySetting *vo.ChatHistorySetting) prompt.ChatTemplate {
|
||||
return &historyChatTemplate{
|
||||
basePrompt: basePrompt,
|
||||
chatHistorySetting: chatHistorySetting,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *historyChatTemplate) Format(ctx context.Context, vs map[string]any, opts ...prompt.Option) ([]*schema.Message, error) {
|
||||
baseMessages, err := t.basePrompt.Format(ctx, vs, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to format base prompt: %w", err)
|
||||
}
|
||||
if len(baseMessages) == 0 {
|
||||
return nil, fmt.Errorf("base prompt returned no messages")
|
||||
}
|
||||
|
||||
if t.chatHistorySetting == nil || !t.chatHistorySetting.EnableChatHistory {
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
exeCtx := execute.GetExeCtx(ctx)
|
||||
if exeCtx == nil {
|
||||
logs.CtxWarnf(ctx, "execute context is nil, skipping chat history")
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
if exeCtx.ExeCfg.WorkflowMode != workflow.WorkflowMode_ChatFlow {
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
historyMessages, ok := ctxcache.Get[[]*schema.Message](ctx, chatHistoryKey)
|
||||
if !ok || len(historyMessages) == 0 {
|
||||
logs.CtxWarnf(ctx, "conversation history is empty")
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
if len(historyMessages) == 0 {
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
finalMessages := make([]*schema.Message, 0, len(baseMessages)+len(historyMessages))
|
||||
finalMessages = append(finalMessages, baseMessages[0]) // System prompt
|
||||
finalMessages = append(finalMessages, historyMessages...)
|
||||
if len(baseMessages) > 1 {
|
||||
finalMessages = append(finalMessages, baseMessages[1:]...) // User prompt and any others
|
||||
}
|
||||
|
||||
return finalMessages, nil
|
||||
}
|
||||
@@ -19,24 +19,37 @@ package knowledge
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"maps"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
einoSchema "github.com/cloudwego/eino/schema"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/contract/knowledge"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"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/pkg/ctxcache"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
const outputList = "outputList"
|
||||
|
||||
type contextKey string
|
||||
|
||||
const chatHistoryKey contextKey = "chatHistory"
|
||||
|
||||
type RetrieveConfig struct {
|
||||
KnowledgeIDs []int64
|
||||
RetrievalStrategy *knowledge.RetrievalStrategy
|
||||
KnowledgeIDs []int64
|
||||
RetrievalStrategy *knowledge.RetrievalStrategy
|
||||
ChatHistorySetting *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
func (r *RetrieveConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) {
|
||||
@@ -60,6 +73,10 @@ func (r *RetrieveConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOp
|
||||
}
|
||||
r.KnowledgeIDs = knowledgeIDs
|
||||
|
||||
if inputs.ChatHistorySetting != nil {
|
||||
r.ChatHistorySetting = inputs.ChatHistorySetting
|
||||
}
|
||||
|
||||
retrievalStrategy := &knowledge.RetrievalStrategy{}
|
||||
|
||||
var getDesignatedParamContent = func(name string) (any, bool) {
|
||||
@@ -154,14 +171,16 @@ func (r *RetrieveConfig) Build(_ context.Context, _ *schema.NodeSchema, _ ...sch
|
||||
}
|
||||
|
||||
return &Retrieve{
|
||||
knowledgeIDs: r.KnowledgeIDs,
|
||||
retrievalStrategy: r.RetrievalStrategy,
|
||||
knowledgeIDs: r.KnowledgeIDs,
|
||||
retrievalStrategy: r.RetrievalStrategy,
|
||||
ChatHistorySetting: r.ChatHistorySetting,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Retrieve struct {
|
||||
knowledgeIDs []int64
|
||||
retrievalStrategy *knowledge.RetrievalStrategy
|
||||
knowledgeIDs []int64
|
||||
retrievalStrategy *knowledge.RetrievalStrategy
|
||||
ChatHistorySetting *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
func (kr *Retrieve) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
@@ -173,6 +192,7 @@ func (kr *Retrieve) Invoke(ctx context.Context, input map[string]any) (map[strin
|
||||
req := &knowledge.RetrieveRequest{
|
||||
Query: query,
|
||||
KnowledgeIDs: kr.knowledgeIDs,
|
||||
ChatHistory: kr.GetChatHistoryOrNil(ctx, kr.ChatHistorySetting),
|
||||
Strategy: kr.retrievalStrategy,
|
||||
}
|
||||
|
||||
@@ -190,3 +210,89 @@ func (kr *Retrieve) Invoke(ctx context.Context, input map[string]any) (map[strin
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (kr *Retrieve) GetChatHistoryOrNil(ctx context.Context, ChatHistorySetting *vo.ChatHistorySetting) []*einoSchema.Message {
|
||||
if ChatHistorySetting == nil || !ChatHistorySetting.EnableChatHistory {
|
||||
return nil
|
||||
}
|
||||
|
||||
exeCtx := execute.GetExeCtx(ctx)
|
||||
if exeCtx == nil {
|
||||
logs.CtxWarnf(ctx, "execute context is nil, skipping chat history")
|
||||
return nil
|
||||
}
|
||||
if exeCtx.ExeCfg.WorkflowMode != workflow.WorkflowMode_ChatFlow {
|
||||
return nil
|
||||
}
|
||||
|
||||
historyMessages, ok := ctxcache.Get[[]*einoSchema.Message](ctx, chatHistoryKey)
|
||||
|
||||
if !ok || len(historyMessages) == 0 {
|
||||
logs.CtxWarnf(ctx, "conversation history is empty")
|
||||
return nil
|
||||
}
|
||||
return historyMessages
|
||||
}
|
||||
|
||||
func (kr *Retrieve) ToCallbackInput(ctx context.Context, in map[string]any) (map[string]any, error) {
|
||||
if kr.ChatHistorySetting == nil || !kr.ChatHistorySetting.EnableChatHistory {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
var messages []*crossmessage.WfMessage
|
||||
var scMessages []*einoSchema.Message
|
||||
var sectionID *int64
|
||||
execCtx := execute.GetExeCtx(ctx)
|
||||
if execCtx != nil {
|
||||
messages = execCtx.ExeCfg.ConversationHistory
|
||||
scMessages = execCtx.ExeCfg.ConversationHistorySchemaMessages
|
||||
sectionID = execCtx.ExeCfg.SectionID
|
||||
}
|
||||
|
||||
ret := map[string]any{
|
||||
"chatHistory": []any{},
|
||||
}
|
||||
maps.Copy(ret, in)
|
||||
|
||||
if len(messages) == 0 {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
if sectionID != nil && messages[0].SectionID != *sectionID {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
maxRounds := int(kr.ChatHistorySetting.ChatHistoryRound)
|
||||
if execCtx != nil && execCtx.ExeCfg.MaxHistoryRounds != nil {
|
||||
maxRounds = min(int(*execCtx.ExeCfg.MaxHistoryRounds), maxRounds)
|
||||
}
|
||||
|
||||
count := 0
|
||||
startIdx := 0
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
if messages[i].Role == einoSchema.User {
|
||||
count++
|
||||
}
|
||||
if count >= maxRounds {
|
||||
startIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var historyMessages []any
|
||||
for _, msg := range messages[startIdx:] {
|
||||
content, err := nodes.ConvertMessageToString(ctx, msg)
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "failed to convert message to string: %v", err)
|
||||
continue
|
||||
}
|
||||
historyMessages = append(historyMessages, map[string]any{
|
||||
"role": string(msg.Role),
|
||||
"content": content,
|
||||
})
|
||||
}
|
||||
ctxcache.Store(ctx, chatHistoryKey, scMessages[startIdx:])
|
||||
|
||||
ret["chatHistory"] = historyMessages
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
|
||||
workflow3 "github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/contract/knowledge"
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
crossmodelmgr "github.com/coze-dev/coze-studio/backend/crossdomain/contract/modelmgr"
|
||||
crossplugin "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
@@ -59,6 +60,10 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const chatHistoryKey contextKey = "chatHistory"
|
||||
|
||||
type Format int
|
||||
|
||||
const (
|
||||
@@ -167,12 +172,14 @@ type KnowledgeRecallConfig struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SystemPrompt string
|
||||
UserPrompt string
|
||||
OutputFormat Format
|
||||
LLMParams *crossmodel.LLMParams
|
||||
FCParam *vo.FCParam
|
||||
BackupLLMParams *crossmodel.LLMParams
|
||||
SystemPrompt string
|
||||
UserPrompt string
|
||||
OutputFormat Format
|
||||
LLMParams *crossmodel.LLMParams
|
||||
FCParam *vo.FCParam
|
||||
BackupLLMParams *crossmodel.LLMParams
|
||||
ChatHistorySetting *vo.ChatHistorySetting
|
||||
AssociateStartNodeUserInputFields map[string]struct{}
|
||||
}
|
||||
|
||||
func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema2.NodeSchema, error) {
|
||||
@@ -202,6 +209,13 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*
|
||||
c.SystemPrompt = convertedLLMParam.SystemPrompt
|
||||
c.UserPrompt = convertedLLMParam.Prompt
|
||||
|
||||
if convertedLLMParam.EnableChatHistory {
|
||||
c.ChatHistorySetting = &vo.ChatHistorySetting{
|
||||
EnableChatHistory: true,
|
||||
ChatHistoryRound: convertedLLMParam.ChatHistoryRound,
|
||||
}
|
||||
}
|
||||
|
||||
var resFormat Format
|
||||
switch convertedLLMParam.ResponseFormat {
|
||||
case crossmodel.ResponseFormatText:
|
||||
@@ -273,6 +287,15 @@ 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")) {
|
||||
c.AssociateStartNodeUserInputFields[info.Path[0]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
@@ -320,7 +343,14 @@ func llmParamsToLLMParam(params vo.LLMParam) (*crossmodel.LLMParams, error) {
|
||||
case "systemPrompt":
|
||||
strVal := param.Input.Value.Content.(string)
|
||||
p.SystemPrompt = strVal
|
||||
case "chatHistoryRound", "generationDiversity", "frequencyPenalty", "presencePenalty":
|
||||
case "chatHistoryRound":
|
||||
strVal := param.Input.Value.Content.(string)
|
||||
int64Val, err := strconv.ParseInt(strVal, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.ChatHistoryRound = int64Val
|
||||
case "generationDiversity", "frequencyPenalty", "presencePenalty":
|
||||
// do nothing
|
||||
case "topP":
|
||||
strVal := param.Input.Value.Content.(string)
|
||||
@@ -590,11 +620,12 @@ func (c *Config) Build(ctx context.Context, ns *schema2.NodeSchema, _ ...schema2
|
||||
inputs[knowledgeUserPromptTemplateKey] = &vo.TypeInfo{
|
||||
Type: vo.DataTypeString,
|
||||
}
|
||||
sp := newPromptTpl(schema.System, c.SystemPrompt, inputs, nil)
|
||||
up := newPromptTpl(schema.User, userPrompt, inputs, []string{knowledgeUserPromptTemplateKey})
|
||||
sp := newPromptTpl(schema.System, c.SystemPrompt, inputs)
|
||||
up := newPromptTpl(schema.User, userPrompt, inputs, withReservedKeys([]string{knowledgeUserPromptTemplateKey}), withAssociateUserInputFields(c.AssociateStartNodeUserInputFields))
|
||||
template := newPrompts(sp, up, modelWithInfo)
|
||||
templateWithChatHistory := newPromptsWithChatHistory(template, c.ChatHistorySetting)
|
||||
|
||||
_ = g.AddChatTemplateNode(templateNodeKey, template,
|
||||
_ = g.AddChatTemplateNode(templateNodeKey, templateWithChatHistory,
|
||||
compose.WithStatePreHandler(func(ctx context.Context, in map[string]any, state llmState) (map[string]any, error) {
|
||||
for k, v := range state {
|
||||
in[k] = v
|
||||
@@ -604,10 +635,12 @@ func (c *Config) Build(ctx context.Context, ns *schema2.NodeSchema, _ ...schema2
|
||||
_ = g.AddEdge(knowledgeLambdaKey, templateNodeKey)
|
||||
|
||||
} else {
|
||||
sp := newPromptTpl(schema.System, c.SystemPrompt, ns.InputTypes, nil)
|
||||
up := newPromptTpl(schema.User, userPrompt, ns.InputTypes, nil)
|
||||
sp := newPromptTpl(schema.System, c.SystemPrompt, ns.InputTypes)
|
||||
up := newPromptTpl(schema.User, userPrompt, ns.InputTypes, withAssociateUserInputFields(c.AssociateStartNodeUserInputFields))
|
||||
template := newPrompts(sp, up, modelWithInfo)
|
||||
_ = g.AddChatTemplateNode(templateNodeKey, template)
|
||||
templateWithChatHistory := newPromptsWithChatHistory(template, c.ChatHistorySetting)
|
||||
|
||||
_ = g.AddChatTemplateNode(templateNodeKey, templateWithChatHistory)
|
||||
|
||||
_ = g.AddEdge(compose.START, templateNodeKey)
|
||||
}
|
||||
@@ -747,10 +780,11 @@ func (c *Config) Build(ctx context.Context, ns *schema2.NodeSchema, _ ...schema2
|
||||
}
|
||||
|
||||
llm := &LLM{
|
||||
r: r,
|
||||
outputFormat: format,
|
||||
requireCheckpoint: requireCheckpoint,
|
||||
fullSources: ns.FullSources,
|
||||
r: r,
|
||||
outputFormat: format,
|
||||
requireCheckpoint: requireCheckpoint,
|
||||
fullSources: ns.FullSources,
|
||||
chatHistorySetting: c.ChatHistorySetting,
|
||||
}
|
||||
|
||||
return llm, nil
|
||||
@@ -825,10 +859,11 @@ func toRetrievalSearchType(s int64) (knowledge.SearchType, error) {
|
||||
}
|
||||
|
||||
type LLM struct {
|
||||
r compose.Runnable[map[string]any, map[string]any]
|
||||
outputFormat Format
|
||||
requireCheckpoint bool
|
||||
fullSources map[string]*schema2.SourceInfo
|
||||
r compose.Runnable[map[string]any, map[string]any]
|
||||
outputFormat Format
|
||||
requireCheckpoint bool
|
||||
fullSources map[string]*schema2.SourceInfo
|
||||
chatHistorySetting *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -1193,6 +1228,68 @@ type ToolInterruptEventStore interface {
|
||||
ResumeToolInterruptEvent(llmNodeKey vo.NodeKey, toolCallID string) (string, error)
|
||||
}
|
||||
|
||||
func (l *LLM) ToCallbackInput(ctx context.Context, input map[string]any) (map[string]any, error) {
|
||||
if l.chatHistorySetting == nil || !l.chatHistorySetting.EnableChatHistory {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
var messages []*crossmessage.WfMessage
|
||||
var scMessages []*schema.Message
|
||||
var sectionID *int64
|
||||
execCtx := execute.GetExeCtx(ctx)
|
||||
if execCtx != nil {
|
||||
messages = execCtx.ExeCfg.ConversationHistory
|
||||
scMessages = execCtx.ExeCfg.ConversationHistorySchemaMessages
|
||||
sectionID = execCtx.ExeCfg.SectionID
|
||||
}
|
||||
|
||||
ret := map[string]any{
|
||||
"chatHistory": []any{},
|
||||
}
|
||||
maps.Copy(ret, input)
|
||||
|
||||
if len(messages) == 0 {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
if sectionID != nil && messages[0].SectionID != *sectionID {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
maxRounds := int(l.chatHistorySetting.ChatHistoryRound)
|
||||
if execCtx != nil && execCtx.ExeCfg.MaxHistoryRounds != nil {
|
||||
maxRounds = min(int(*execCtx.ExeCfg.MaxHistoryRounds), maxRounds)
|
||||
}
|
||||
count := 0
|
||||
startIdx := 0
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
if messages[i].Role == schema.User {
|
||||
count++
|
||||
}
|
||||
if count >= maxRounds {
|
||||
startIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var historyMessages []any
|
||||
for _, msg := range messages[startIdx:] {
|
||||
content, err := nodes.ConvertMessageToString(ctx, msg)
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "failed to convert message to string: %v", err)
|
||||
continue
|
||||
}
|
||||
historyMessages = append(historyMessages, map[string]any{
|
||||
"role": string(msg.Role),
|
||||
"content": content,
|
||||
})
|
||||
}
|
||||
ctxcache.Store(ctx, chatHistoryKey, scMessages[startIdx:])
|
||||
|
||||
ret["chatHistory"] = historyMessages
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (l *LLM) ToCallbackOutput(ctx context.Context, output map[string]any) (*nodes.StructuredCallbackOutput, error) {
|
||||
c := execute.GetExeCtx(ctx)
|
||||
if c == nil {
|
||||
|
||||
@@ -23,12 +23,14 @@ import (
|
||||
"github.com/cloudwego/eino/components/prompt"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes"
|
||||
schema2 "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/modelmgr"
|
||||
"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/pkg/sonic"
|
||||
)
|
||||
|
||||
@@ -38,12 +40,30 @@ type prompts struct {
|
||||
mwi ModelWithInfo
|
||||
}
|
||||
|
||||
type promptsWithChatHistory struct {
|
||||
prompts *prompts
|
||||
cfg *vo.ChatHistorySetting
|
||||
}
|
||||
|
||||
func withReservedKeys(keys []string) func(tpl *promptTpl) {
|
||||
return func(tpl *promptTpl) {
|
||||
tpl.reservedKeys = keys
|
||||
}
|
||||
}
|
||||
|
||||
func withAssociateUserInputFields(fs map[string]struct{}) func(tpl *promptTpl) {
|
||||
return func(tpl *promptTpl) {
|
||||
tpl.associateUserInputFields = fs
|
||||
}
|
||||
}
|
||||
|
||||
type promptTpl struct {
|
||||
role schema.RoleType
|
||||
tpl string
|
||||
parts []promptPart
|
||||
hasMultiModal bool
|
||||
reservedKeys []string
|
||||
role schema.RoleType
|
||||
tpl string
|
||||
parts []promptPart
|
||||
hasMultiModal bool
|
||||
reservedKeys []string
|
||||
associateUserInputFields map[string]struct{}
|
||||
}
|
||||
|
||||
type promptPart struct {
|
||||
@@ -54,12 +74,20 @@ type promptPart struct {
|
||||
func newPromptTpl(role schema.RoleType,
|
||||
tpl string,
|
||||
inputTypes map[string]*vo.TypeInfo,
|
||||
reservedKeys []string,
|
||||
opts ...func(*promptTpl),
|
||||
) *promptTpl {
|
||||
if len(tpl) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pTpl := &promptTpl{
|
||||
role: role,
|
||||
tpl: tpl,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(pTpl)
|
||||
}
|
||||
|
||||
parts := nodes.ParseTemplate(tpl)
|
||||
promptParts := make([]promptPart, 0, len(parts))
|
||||
hasMultiModal := false
|
||||
@@ -87,14 +115,10 @@ func newPromptTpl(role schema.RoleType,
|
||||
|
||||
hasMultiModal = true
|
||||
}
|
||||
pTpl.parts = promptParts
|
||||
pTpl.hasMultiModal = hasMultiModal
|
||||
|
||||
return &promptTpl{
|
||||
role: role,
|
||||
tpl: tpl,
|
||||
parts: promptParts,
|
||||
hasMultiModal: hasMultiModal,
|
||||
reservedKeys: reservedKeys,
|
||||
}
|
||||
return pTpl
|
||||
}
|
||||
|
||||
const sourceKey = "sources_%s"
|
||||
@@ -107,23 +131,53 @@ func newPrompts(sp, up *promptTpl, model ModelWithInfo) *prompts {
|
||||
}
|
||||
}
|
||||
|
||||
func newPromptsWithChatHistory(prompts *prompts, cfg *vo.ChatHistorySetting) *promptsWithChatHistory {
|
||||
return &promptsWithChatHistory{
|
||||
prompts: prompts,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *promptTpl) render(ctx context.Context, vs map[string]any,
|
||||
sources map[string]*schema2.SourceInfo,
|
||||
supportedModals map[modelmgr.Modal]bool,
|
||||
) (*schema.Message, error) {
|
||||
if !pl.hasMultiModal || len(supportedModals) == 0 {
|
||||
var opts []nodes.RenderOption
|
||||
if len(pl.reservedKeys) > 0 {
|
||||
opts = append(opts, nodes.WithReservedKey(pl.reservedKeys...))
|
||||
isChatFlow := execute.GetExeCtx(ctx).ExeCfg.WorkflowMode == workflow.WorkflowMode_ChatFlow
|
||||
userMessage := execute.GetExeCtx(ctx).ExeCfg.UserMessage
|
||||
|
||||
if !isChatFlow {
|
||||
if !pl.hasMultiModal || len(supportedModals) == 0 {
|
||||
var opts []nodes.RenderOption
|
||||
if len(pl.reservedKeys) > 0 {
|
||||
opts = append(opts, nodes.WithReservedKey(pl.reservedKeys...))
|
||||
}
|
||||
r, err := nodes.Render(ctx, pl.tpl, vs, sources, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.Message{
|
||||
Role: pl.role,
|
||||
Content: r,
|
||||
}, nil
|
||||
}
|
||||
r, err := nodes.Render(ctx, pl.tpl, vs, sources, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if (!pl.hasMultiModal || len(supportedModals) == 0) &&
|
||||
(len(pl.associateUserInputFields) == 0 ||
|
||||
(len(pl.associateUserInputFields) > 0 && userMessage != nil && userMessage.MultiContent == nil)) {
|
||||
var opts []nodes.RenderOption
|
||||
if len(pl.reservedKeys) > 0 {
|
||||
opts = append(opts, nodes.WithReservedKey(pl.reservedKeys...))
|
||||
}
|
||||
r, err := nodes.Render(ctx, pl.tpl, vs, sources, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.Message{
|
||||
Role: pl.role,
|
||||
Content: r,
|
||||
}, nil
|
||||
}
|
||||
return &schema.Message{
|
||||
Role: pl.role,
|
||||
Content: r,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
multiParts := make([]schema.ChatMessagePart, 0, len(pl.parts))
|
||||
@@ -141,6 +195,13 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any,
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := pl.associateUserInputFields[part.part.Value]; ok && userMessage != nil && isChatFlow {
|
||||
for _, p := range userMessage.MultiContent {
|
||||
multiParts = append(multiParts, transformMessagePart(p, supportedModals))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
skipped, invalid := part.part.Skipped(sources)
|
||||
if invalid {
|
||||
var reserved bool
|
||||
@@ -164,6 +225,7 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if part.fileType == nil {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
@@ -172,64 +234,38 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any,
|
||||
continue
|
||||
}
|
||||
|
||||
var originalPart schema.ChatMessagePart
|
||||
switch *part.fileType {
|
||||
case vo.FileTypeImage, vo.FileTypeSVG:
|
||||
if _, ok := supportedModals[modelmgr.ModalImage]; !ok {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: r,
|
||||
})
|
||||
} else {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeImageURL,
|
||||
ImageURL: &schema.ChatMessageImageURL{
|
||||
URL: r,
|
||||
},
|
||||
})
|
||||
originalPart = schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeImageURL,
|
||||
ImageURL: &schema.ChatMessageImageURL{
|
||||
URL: r,
|
||||
},
|
||||
}
|
||||
case vo.FileTypeAudio, vo.FileTypeVoice:
|
||||
if _, ok := supportedModals[modelmgr.ModalAudio]; !ok {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: r,
|
||||
})
|
||||
} else {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeAudioURL,
|
||||
AudioURL: &schema.ChatMessageAudioURL{
|
||||
URL: r,
|
||||
},
|
||||
})
|
||||
originalPart = schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeAudioURL,
|
||||
AudioURL: &schema.ChatMessageAudioURL{
|
||||
URL: r,
|
||||
},
|
||||
}
|
||||
case vo.FileTypeVideo:
|
||||
if _, ok := supportedModals[modelmgr.ModalVideo]; !ok {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: r,
|
||||
})
|
||||
} else {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeVideoURL,
|
||||
VideoURL: &schema.ChatMessageVideoURL{
|
||||
URL: r,
|
||||
},
|
||||
})
|
||||
originalPart = schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeVideoURL,
|
||||
VideoURL: &schema.ChatMessageVideoURL{
|
||||
URL: r,
|
||||
},
|
||||
}
|
||||
default:
|
||||
if _, ok := supportedModals[modelmgr.ModalFile]; !ok {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: r,
|
||||
})
|
||||
} else {
|
||||
multiParts = append(multiParts, schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeFileURL,
|
||||
FileURL: &schema.ChatMessageFileURL{
|
||||
URL: r,
|
||||
},
|
||||
})
|
||||
originalPart = schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeFileURL,
|
||||
FileURL: &schema.ChatMessageFileURL{
|
||||
URL: r,
|
||||
},
|
||||
}
|
||||
}
|
||||
multiParts = append(multiParts, transformMessagePart(originalPart, supportedModals))
|
||||
}
|
||||
|
||||
return &schema.Message{
|
||||
@@ -238,6 +274,40 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func transformMessagePart(part schema.ChatMessagePart, supportedModals map[modelmgr.Modal]bool) schema.ChatMessagePart {
|
||||
switch part.Type {
|
||||
case schema.ChatMessagePartTypeImageURL:
|
||||
if _, ok := supportedModals[modelmgr.ModalImage]; !ok {
|
||||
return schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: part.ImageURL.URL,
|
||||
}
|
||||
}
|
||||
case schema.ChatMessagePartTypeAudioURL:
|
||||
if _, ok := supportedModals[modelmgr.ModalAudio]; !ok {
|
||||
return schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: part.AudioURL.URL,
|
||||
}
|
||||
}
|
||||
case schema.ChatMessagePartTypeVideoURL:
|
||||
if _, ok := supportedModals[modelmgr.ModalVideo]; !ok {
|
||||
return schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: part.VideoURL.URL,
|
||||
}
|
||||
}
|
||||
case schema.ChatMessagePartTypeFileURL:
|
||||
if _, ok := supportedModals[modelmgr.ModalFile]; !ok {
|
||||
return schema.ChatMessagePart{
|
||||
Type: schema.ChatMessagePartTypeText,
|
||||
Text: part.FileURL.URL,
|
||||
}
|
||||
}
|
||||
}
|
||||
return part
|
||||
}
|
||||
|
||||
func (p *prompts) Format(ctx context.Context, vs map[string]any, _ ...prompt.Option) (
|
||||
_ []*schema.Message, err error,
|
||||
) {
|
||||
@@ -288,3 +358,45 @@ func (p *prompts) Format(ctx context.Context, vs map[string]any, _ ...prompt.Opt
|
||||
|
||||
return []*schema.Message{systemMsg, userMsg}, nil
|
||||
}
|
||||
|
||||
func (p *promptsWithChatHistory) Format(ctx context.Context, vs map[string]any, _ ...prompt.Option) (
|
||||
[]*schema.Message, error) {
|
||||
baseMessages, err := p.prompts.Format(ctx, vs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.cfg == nil || !p.cfg.EnableChatHistory {
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
exeCtx := execute.GetExeCtx(ctx)
|
||||
if exeCtx == nil {
|
||||
logs.CtxWarnf(ctx, "execute context is nil, skipping chat history")
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
if exeCtx.ExeCfg.WorkflowMode != workflow.WorkflowMode_ChatFlow {
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
historyMessages, ok := ctxcache.Get[[]*schema.Message](ctx, chatHistoryKey)
|
||||
|
||||
if !ok || len(historyMessages) == 0 {
|
||||
logs.CtxWarnf(ctx, "conversation history is empty")
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
if len(historyMessages) == 0 {
|
||||
return baseMessages, nil
|
||||
}
|
||||
|
||||
finalMessages := make([]*schema.Message, 0, len(baseMessages)+len(historyMessages))
|
||||
if len(baseMessages) > 0 && baseMessages[0].Role == schema.System {
|
||||
finalMessages = append(finalMessages, baseMessages[0])
|
||||
baseMessages = baseMessages[1:]
|
||||
}
|
||||
finalMessages = append(finalMessages, historyMessages...)
|
||||
finalMessages = append(finalMessages, baseMessages...)
|
||||
|
||||
return finalMessages, nil
|
||||
}
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
@@ -279,3 +282,30 @@ func GetConcatFunc(typ reflect.Type) func(reflect.Value) (reflect.Value, error)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ConvertMessageToString(_ context.Context, msg *crossmessage.WfMessage) (string, error) {
|
||||
if msg.MultiContent != nil {
|
||||
var textContents []string
|
||||
var otherContents []string
|
||||
for _, m := range msg.MultiContent {
|
||||
if m.Text != nil {
|
||||
textContents = append(textContents, ptr.From(m.Text))
|
||||
} else if m.Uri != nil {
|
||||
otherContents = append(otherContents, ptr.From(m.Url))
|
||||
}
|
||||
}
|
||||
|
||||
var allParts []string
|
||||
if len(textContents) > 0 {
|
||||
allParts = append(allParts, textContents...)
|
||||
}
|
||||
if len(otherContents) > 0 {
|
||||
allParts = append(allParts, otherContents...)
|
||||
}
|
||||
return strings.Join(allParts, ","), nil
|
||||
} else if msg.Text != nil {
|
||||
return ptr.From(msg.Text), nil
|
||||
} else {
|
||||
return "", vo.WrapError(errno.ErrInvalidParameter, errors.New("message is invalid"))
|
||||
}
|
||||
}
|
||||
|
||||
940
backend/domain/workflow/internal/repo/conversation_repository.go
Normal file
940
backend/domain/workflow/internal/repo/conversation_repository.go
Normal file
@@ -0,0 +1,940 @@
|
||||
/*
|
||||
* 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 repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gen"
|
||||
"gorm.io/gorm"
|
||||
|
||||
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation"
|
||||
"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/repo/dal/model"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
const batchSize = 10
|
||||
|
||||
func (r *RepositoryImpl) CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) {
|
||||
id, err := r.GenID(ctx)
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
m := &model.AppConversationTemplateDraft{
|
||||
ID: id,
|
||||
AppID: template.AppID,
|
||||
SpaceID: template.SpaceID,
|
||||
Name: template.Name,
|
||||
CreatorID: template.UserID,
|
||||
TemplateID: id,
|
||||
}
|
||||
err = r.query.AppConversationTemplateDraft.WithContext(ctx).Create(m)
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetConversationTemplate(ctx context.Context, env vo.Env, policy vo.GetConversationTemplatePolicy) (*entity.ConversationTemplate, bool, error) {
|
||||
var (
|
||||
appID = policy.AppID
|
||||
name = policy.Name
|
||||
version = policy.Version
|
||||
templateID = policy.TemplateID
|
||||
)
|
||||
|
||||
conditions := make([]gen.Condition, 0)
|
||||
if env == vo.Draft {
|
||||
if appID != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateDraft.AppID.Eq(*appID))
|
||||
}
|
||||
if name != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateDraft.Name.Eq(*name))
|
||||
}
|
||||
if templateID != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateDraft.TemplateID.Eq(*templateID))
|
||||
}
|
||||
|
||||
template, err := r.query.AppConversationTemplateDraft.WithContext(ctx).Where(conditions...).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return &entity.ConversationTemplate{
|
||||
AppID: template.AppID,
|
||||
Name: template.Name,
|
||||
TemplateID: template.TemplateID,
|
||||
}, true, nil
|
||||
|
||||
} else if env == vo.Online {
|
||||
if policy.Version != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.Version.Eq(*version))
|
||||
}
|
||||
if appID != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.AppID.Eq(*appID))
|
||||
}
|
||||
if name != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.Name.Eq(*name))
|
||||
}
|
||||
|
||||
if templateID != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.TemplateID.Eq(*templateID))
|
||||
}
|
||||
|
||||
template, err := r.query.AppConversationTemplateOnline.WithContext(ctx).Where(conditions...).Order(r.query.AppConversationTemplateOnline.CreatedAt.Desc()).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return &entity.ConversationTemplate{
|
||||
AppID: template.AppID,
|
||||
Name: template.Name,
|
||||
TemplateID: template.TemplateID,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("unknown env %v", env)
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) UpdateDraftConversationTemplateName(ctx context.Context, templateID int64, name string) error {
|
||||
_, err := r.query.AppConversationTemplateDraft.WithContext(ctx).Where(
|
||||
r.query.AppConversationTemplateDraft.TemplateID.Eq(templateID),
|
||||
).UpdateColumnSimple(r.query.AppConversationTemplateDraft.Name.Value(name))
|
||||
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) DeleteDraftConversationTemplate(ctx context.Context, templateID int64) (int64, error) {
|
||||
resultInfo, err := r.query.AppConversationTemplateDraft.WithContext(ctx).Where(
|
||||
r.query.AppConversationTemplateDraft.TemplateID.Eq(templateID),
|
||||
).Delete()
|
||||
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return resultInfo.RowsAffected, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) DeleteDynamicConversation(ctx context.Context, env vo.Env, id int64) (int64, error) {
|
||||
if env == vo.Draft {
|
||||
info, err := r.query.AppDynamicConversationDraft.WithContext(ctx).Where(r.query.AppDynamicConversationDraft.ID.Eq(id)).Delete()
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return info.RowsAffected, nil
|
||||
} else if env == vo.Online {
|
||||
info, err := r.query.AppDynamicConversationOnline.WithContext(ctx).Where(r.query.AppDynamicConversationOnline.ID.Eq(id)).Delete()
|
||||
if err != nil {
|
||||
return 0, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return info.RowsAffected, nil
|
||||
} else {
|
||||
return 0, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) {
|
||||
if env == vo.Draft {
|
||||
return r.listDraftConversationTemplate(ctx, policy)
|
||||
} else if env == vo.Online {
|
||||
return r.listOnlineConversationTemplate(ctx, policy)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) listDraftConversationTemplate(ctx context.Context, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) {
|
||||
conditions := make([]gen.Condition, 0)
|
||||
conditions = append(conditions, r.query.AppConversationTemplateDraft.AppID.Eq(policy.AppID))
|
||||
|
||||
if policy.NameLike != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateDraft.Name.Like("%%"+*policy.NameLike+"%%"))
|
||||
}
|
||||
appConversationTemplateDraftDao := r.query.AppConversationTemplateDraft.WithContext(ctx)
|
||||
var (
|
||||
templates []*model.AppConversationTemplateDraft
|
||||
err error
|
||||
)
|
||||
|
||||
if policy.Page != nil {
|
||||
templates, err = appConversationTemplateDraftDao.Where(conditions...).Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find()
|
||||
} else {
|
||||
templates, err = appConversationTemplateDraftDao.Where(conditions...).Find()
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []*entity.ConversationTemplate{}, nil
|
||||
}
|
||||
return nil, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return slices.Transform(templates, func(a *model.AppConversationTemplateDraft) *entity.ConversationTemplate {
|
||||
return &entity.ConversationTemplate{
|
||||
SpaceID: a.SpaceID,
|
||||
AppID: a.AppID,
|
||||
Name: a.Name,
|
||||
TemplateID: a.TemplateID,
|
||||
}
|
||||
}), nil
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) listOnlineConversationTemplate(ctx context.Context, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) {
|
||||
conditions := make([]gen.Condition, 0)
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.AppID.Eq(policy.AppID))
|
||||
if policy.Version == nil {
|
||||
return nil, fmt.Errorf("list online template fail, version is required")
|
||||
}
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.Version.Eq(*policy.Version))
|
||||
|
||||
if policy.NameLike != nil {
|
||||
conditions = append(conditions, r.query.AppConversationTemplateOnline.Name.Like("%%"+*policy.NameLike+"%%"))
|
||||
}
|
||||
appConversationTemplateOnlineDao := r.query.AppConversationTemplateOnline.WithContext(ctx)
|
||||
var (
|
||||
templates []*model.AppConversationTemplateOnline
|
||||
err error
|
||||
)
|
||||
if policy.Page != nil {
|
||||
templates, err = appConversationTemplateOnlineDao.Where(conditions...).Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find()
|
||||
|
||||
} else {
|
||||
templates, err = appConversationTemplateOnlineDao.Where(conditions...).Find()
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []*entity.ConversationTemplate{}, nil
|
||||
}
|
||||
return nil, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return slices.Transform(templates, func(a *model.AppConversationTemplateOnline) *entity.ConversationTemplate {
|
||||
return &entity.ConversationTemplate{
|
||||
SpaceID: a.SpaceID,
|
||||
AppID: a.AppID,
|
||||
Name: a.Name,
|
||||
TemplateID: a.TemplateID,
|
||||
}
|
||||
}), nil
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) {
|
||||
if env == vo.Draft {
|
||||
return r.mGetDraftStaticConversation(ctx, userID, connectorID, templateIDs)
|
||||
} else if env == vo.Online {
|
||||
return r.mGetOnlineStaticConversation(ctx, userID, connectorID, templateIDs)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) mGetDraftStaticConversation(ctx context.Context, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) {
|
||||
conditions := make([]gen.Condition, 0, 3)
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.UserID.Eq(userID))
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.ConnectorID.Eq(connectorID))
|
||||
if len(templateIDs) == 1 {
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.TemplateID.Eq(templateIDs[0]))
|
||||
} else {
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.TemplateID.In(templateIDs...))
|
||||
}
|
||||
|
||||
cs, err := r.query.AppStaticConversationDraft.WithContext(ctx).Where(conditions...).Find()
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []*entity.StaticConversation{}, nil
|
||||
}
|
||||
return nil, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return slices.Transform(cs, func(a *model.AppStaticConversationDraft) *entity.StaticConversation {
|
||||
return &entity.StaticConversation{
|
||||
TemplateID: a.TemplateID,
|
||||
ConversationID: a.ConversationID,
|
||||
UserID: a.UserID,
|
||||
ConnectorID: a.ConnectorID,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) mGetOnlineStaticConversation(ctx context.Context, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) {
|
||||
conditions := make([]gen.Condition, 0, 3)
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.UserID.Eq(userID))
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.ConnectorID.Eq(connectorID))
|
||||
if len(templateIDs) == 1 {
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.TemplateID.Eq(templateIDs[0]))
|
||||
} else {
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.TemplateID.In(templateIDs...))
|
||||
}
|
||||
|
||||
cs, err := r.query.AppStaticConversationOnline.WithContext(ctx).Where(conditions...).Find()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []*entity.StaticConversation{}, nil
|
||||
}
|
||||
return nil, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return slices.Transform(cs, func(a *model.AppStaticConversationOnline) *entity.StaticConversation {
|
||||
return &entity.StaticConversation{
|
||||
TemplateID: a.TemplateID,
|
||||
ConversationID: a.ConversationID,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) {
|
||||
if env == vo.Draft {
|
||||
return r.listDraftDynamicConversation(ctx, policy)
|
||||
} else if env == vo.Online {
|
||||
return r.listOnlineDynamicConversation(ctx, policy)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) listDraftDynamicConversation(ctx context.Context, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) {
|
||||
var (
|
||||
appID = policy.APPID
|
||||
userID = policy.UserID
|
||||
connectorID = policy.ConnectorID
|
||||
)
|
||||
|
||||
conditions := make([]gen.Condition, 0)
|
||||
conditions = append(conditions, r.query.AppDynamicConversationDraft.AppID.Eq(appID))
|
||||
conditions = append(conditions, r.query.AppDynamicConversationDraft.UserID.Eq(userID))
|
||||
conditions = append(conditions, r.query.AppDynamicConversationDraft.ConnectorID.Eq(connectorID))
|
||||
if policy.NameLike != nil {
|
||||
conditions = append(conditions, r.query.AppDynamicConversationDraft.Name.Like("%%"+*policy.NameLike+"%%"))
|
||||
}
|
||||
|
||||
appDynamicConversationDraftDao := r.query.AppDynamicConversationDraft.WithContext(ctx).Where(conditions...)
|
||||
var (
|
||||
dynamicConversations = make([]*model.AppDynamicConversationDraft, 0)
|
||||
err error
|
||||
)
|
||||
|
||||
if policy.Page != nil {
|
||||
dynamicConversations, err = appDynamicConversationDraftDao.Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find()
|
||||
|
||||
} else {
|
||||
dynamicConversations, err = appDynamicConversationDraftDao.Where(conditions...).Find()
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []*entity.DynamicConversation{}, nil
|
||||
}
|
||||
return nil, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return slices.Transform(dynamicConversations, func(a *model.AppDynamicConversationDraft) *entity.DynamicConversation {
|
||||
return &entity.DynamicConversation{
|
||||
ID: a.ID,
|
||||
Name: a.Name,
|
||||
UserID: a.UserID,
|
||||
ConnectorID: a.ConnectorID,
|
||||
ConversationID: a.ConversationID,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) listOnlineDynamicConversation(ctx context.Context, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) {
|
||||
var (
|
||||
appID = policy.APPID
|
||||
userID = policy.UserID
|
||||
connectorID = policy.ConnectorID
|
||||
)
|
||||
|
||||
conditions := make([]gen.Condition, 0)
|
||||
conditions = append(conditions, r.query.AppDynamicConversationOnline.AppID.Eq(appID))
|
||||
conditions = append(conditions, r.query.AppDynamicConversationOnline.UserID.Eq(userID))
|
||||
conditions = append(conditions, r.query.AppDynamicConversationOnline.AppID.Eq(appID))
|
||||
conditions = append(conditions, r.query.AppDynamicConversationOnline.ConnectorID.Eq(connectorID))
|
||||
if policy.NameLike != nil {
|
||||
conditions = append(conditions, r.query.AppDynamicConversationOnline.Name.Like("%%"+*policy.NameLike+"%%"))
|
||||
}
|
||||
|
||||
appDynamicConversationOnlineDao := r.query.AppDynamicConversationOnline.WithContext(ctx).Where(conditions...)
|
||||
var (
|
||||
dynamicConversations = make([]*model.AppDynamicConversationOnline, 0)
|
||||
err error
|
||||
)
|
||||
if policy.Page != nil {
|
||||
dynamicConversations, err = appDynamicConversationOnlineDao.Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find()
|
||||
} else {
|
||||
dynamicConversations, err = appDynamicConversationOnlineDao.Where(conditions...).Find()
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return []*entity.DynamicConversation{}, nil
|
||||
}
|
||||
return nil, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return slices.Transform(dynamicConversations, func(a *model.AppDynamicConversationOnline) *entity.DynamicConversation {
|
||||
return &entity.DynamicConversation{
|
||||
ID: a.ID,
|
||||
Name: a.Name,
|
||||
UserID: a.UserID,
|
||||
ConnectorID: a.ConnectorID,
|
||||
ConversationID: a.ConversationID,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetOrCreateStaticConversation(ctx context.Context, env vo.Env, idGen workflow.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) {
|
||||
if env == vo.Draft {
|
||||
return r.getOrCreateDraftStaticConversation(ctx, idGen, meta)
|
||||
} else if env == vo.Online {
|
||||
return r.getOrCreateOnlineStaticConversation(ctx, idGen, meta)
|
||||
} else {
|
||||
return 0, 0, false, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env vo.Env, idGen workflow.ConversationIDGenerator, meta *vo.CreateDynamicConversation) (int64, int64, bool, error) {
|
||||
if env == vo.Draft {
|
||||
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
ret, err := appDynamicConversationDraft.WithContext(ctx).Where(
|
||||
appDynamicConversationDraft.AppID.Eq(meta.AppID),
|
||||
appDynamicConversationDraft.ConnectorID.Eq(meta.ConnectorID),
|
||||
appDynamicConversationDraft.UserID.Eq(meta.UserID),
|
||||
appDynamicConversationDraft.Name.Eq(meta.Name),
|
||||
).First()
|
||||
if err == nil {
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, ret.ConversationID)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
if cInfo == nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found"))
|
||||
}
|
||||
return ret.ConversationID, cInfo.SectionID, true, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
|
||||
id, err := r.GenID(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
|
||||
err = r.query.AppDynamicConversationDraft.WithContext(ctx).Create(&model.AppDynamicConversationDraft{
|
||||
ID: id,
|
||||
AppID: meta.AppID,
|
||||
Name: meta.Name,
|
||||
UserID: meta.UserID,
|
||||
ConnectorID: meta.ConnectorID,
|
||||
ConversationID: conv.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return conv.ID, conv.SectionID, false, nil
|
||||
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
ret, err := appDynamicConversationOnline.WithContext(ctx).Where(
|
||||
appDynamicConversationOnline.AppID.Eq(meta.AppID),
|
||||
appDynamicConversationOnline.ConnectorID.Eq(meta.ConnectorID),
|
||||
appDynamicConversationOnline.UserID.Eq(meta.UserID),
|
||||
appDynamicConversationOnline.Name.Eq(meta.Name),
|
||||
).First()
|
||||
if err == nil {
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, ret.ConversationID)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
if cInfo == nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found"))
|
||||
}
|
||||
return ret.ConversationID, cInfo.SectionID, true, nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
id, err := r.GenID(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
|
||||
err = r.query.AppDynamicConversationOnline.WithContext(ctx).Create(&model.AppDynamicConversationOnline{
|
||||
ID: id,
|
||||
AppID: meta.AppID,
|
||||
Name: meta.Name,
|
||||
UserID: meta.UserID,
|
||||
ConnectorID: meta.ConnectorID,
|
||||
ConversationID: conv.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return conv.ID, conv.SectionID, false, nil
|
||||
|
||||
} else {
|
||||
return 0, 0, false, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetStaticConversationByTemplateID(ctx context.Context, env vo.Env, userID, connectorID, templateID int64) (*entity.StaticConversation, bool, error) {
|
||||
if env == vo.Draft {
|
||||
conditions := make([]gen.Condition, 0, 3)
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.UserID.Eq(userID))
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.ConnectorID.Eq(connectorID))
|
||||
conditions = append(conditions, r.query.AppStaticConversationDraft.TemplateID.Eq(templateID))
|
||||
cs, err := r.query.AppStaticConversationDraft.WithContext(ctx).Where(conditions...).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return &entity.StaticConversation{
|
||||
UserID: cs.UserID,
|
||||
ConnectorID: cs.ConnectorID,
|
||||
TemplateID: cs.TemplateID,
|
||||
ConversationID: cs.ConversationID,
|
||||
}, true, nil
|
||||
} else if env == vo.Online {
|
||||
conditions := make([]gen.Condition, 0, 3)
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.UserID.Eq(userID))
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.ConnectorID.Eq(connectorID))
|
||||
conditions = append(conditions, r.query.AppStaticConversationOnline.TemplateID.Eq(templateID))
|
||||
cs, err := r.query.AppStaticConversationOnline.WithContext(ctx).Where(conditions...).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return &entity.StaticConversation{
|
||||
UserID: cs.UserID,
|
||||
ConnectorID: cs.ConnectorID,
|
||||
TemplateID: cs.TemplateID,
|
||||
ConversationID: cs.ConversationID,
|
||||
}, true, nil
|
||||
} else {
|
||||
return nil, false, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) getOrCreateDraftStaticConversation(ctx context.Context, idGen workflow.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) {
|
||||
cs, err := r.mGetDraftStaticConversation(ctx, meta.UserID, meta.ConnectorID, []int64{meta.TemplateID})
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
if len(cs) > 0 {
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, cs[0].ConversationID)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
if cInfo == nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found"))
|
||||
}
|
||||
return cs[0].ConversationID, cInfo.SectionID, true, nil
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
|
||||
id, err := r.GenID(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
object := &model.AppStaticConversationDraft{
|
||||
ID: id,
|
||||
UserID: meta.UserID,
|
||||
ConnectorID: meta.ConnectorID,
|
||||
TemplateID: meta.TemplateID,
|
||||
ConversationID: conv.ID,
|
||||
}
|
||||
err = r.query.AppStaticConversationDraft.WithContext(ctx).Create(object)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return conv.ID, conv.SectionID, false, nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) getOrCreateOnlineStaticConversation(ctx context.Context, idGen workflow.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) {
|
||||
cs, err := r.mGetOnlineStaticConversation(ctx, meta.UserID, meta.ConnectorID, []int64{meta.TemplateID})
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
if len(cs) > 0 {
|
||||
cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, cs[0].ConversationID)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
if cInfo == nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found"))
|
||||
}
|
||||
return cs[0].ConversationID, cInfo.SectionID, true, nil
|
||||
}
|
||||
|
||||
conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID)
|
||||
if err != nil {
|
||||
return 0, 0, false, err
|
||||
}
|
||||
|
||||
id, err := r.GenID(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
object := &model.AppStaticConversationOnline{
|
||||
ID: id,
|
||||
UserID: meta.UserID,
|
||||
ConnectorID: meta.ConnectorID,
|
||||
TemplateID: meta.TemplateID,
|
||||
ConversationID: conv.ID,
|
||||
}
|
||||
err = r.query.AppStaticConversationOnline.WithContext(ctx).Create(object)
|
||||
if err != nil {
|
||||
return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
return conv.ID, conv.SectionID, false, nil
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) BatchCreateOnlineConversationTemplate(ctx context.Context, templates []*entity.ConversationTemplate, version string) error {
|
||||
ids, err := r.GenMultiIDs(ctx, len(templates))
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
|
||||
objects := make([]*model.AppConversationTemplateOnline, 0, len(templates))
|
||||
for idx := range templates {
|
||||
template := templates[idx]
|
||||
objects = append(objects, &model.AppConversationTemplateOnline{
|
||||
ID: ids[idx],
|
||||
SpaceID: template.SpaceID,
|
||||
AppID: template.AppID,
|
||||
TemplateID: template.TemplateID,
|
||||
Name: template.Name,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
|
||||
err = r.query.AppConversationTemplateOnline.WithContext(ctx).CreateInBatches(objects, batchSize)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) {
|
||||
if env == vo.Draft {
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
ret, err := appDynamicConversationDraft.WithContext(ctx).Where(
|
||||
appDynamicConversationDraft.AppID.Eq(appID),
|
||||
appDynamicConversationDraft.ConnectorID.Eq(connectorID),
|
||||
appDynamicConversationDraft.UserID.Eq(userID),
|
||||
appDynamicConversationDraft.Name.Eq(name)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return &entity.DynamicConversation{
|
||||
ID: ret.ID,
|
||||
UserID: ret.UserID,
|
||||
ConnectorID: ret.ConnectorID,
|
||||
ConversationID: ret.ConversationID,
|
||||
Name: ret.Name,
|
||||
}, true, nil
|
||||
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
ret, err := appDynamicConversationOnline.WithContext(ctx).Where(
|
||||
appDynamicConversationOnline.AppID.Eq(appID),
|
||||
appDynamicConversationOnline.ConnectorID.Eq(connectorID),
|
||||
appDynamicConversationOnline.UserID.Eq(userID),
|
||||
appDynamicConversationOnline.Name.Eq(name)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return &entity.DynamicConversation{
|
||||
ID: ret.ID,
|
||||
UserID: ret.UserID,
|
||||
ConnectorID: ret.ConnectorID,
|
||||
ConversationID: ret.ConversationID,
|
||||
Name: ret.Name,
|
||||
}, true, nil
|
||||
|
||||
} else {
|
||||
return nil, false, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) UpdateDynamicConversationNameByID(ctx context.Context, env vo.Env, templateID int64, name string) error {
|
||||
if env == vo.Draft {
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
_, err := appDynamicConversationDraft.WithContext(ctx).Where(
|
||||
appDynamicConversationDraft.ID.Eq(templateID),
|
||||
).UpdateColumnSimple(appDynamicConversationDraft.Name.Value(name))
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return nil
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
_, err := appDynamicConversationOnline.WithContext(ctx).Where(
|
||||
appDynamicConversationOnline.ID.Eq(templateID),
|
||||
).UpdateColumnSimple(appDynamicConversationOnline.Name.Value(name))
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) UpdateStaticConversation(ctx context.Context, env vo.Env, templateID int64, connectorID int64, userID int64, newConversationID int64) error {
|
||||
|
||||
if env == vo.Draft {
|
||||
appStaticConversationDraft := r.query.AppStaticConversationDraft
|
||||
_, err := appStaticConversationDraft.WithContext(ctx).Where(
|
||||
appStaticConversationDraft.TemplateID.Eq(templateID),
|
||||
appStaticConversationDraft.ConnectorID.Eq(connectorID),
|
||||
appStaticConversationDraft.UserID.Eq(userID),
|
||||
).UpdateColumn(appStaticConversationDraft.ConversationID, newConversationID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
|
||||
} else if env == vo.Online {
|
||||
appStaticConversationOnline := r.query.AppStaticConversationOnline
|
||||
_, err := appStaticConversationOnline.WithContext(ctx).Where(
|
||||
appStaticConversationOnline.TemplateID.Eq(templateID),
|
||||
appStaticConversationOnline.ConnectorID.Eq(connectorID),
|
||||
appStaticConversationOnline.UserID.Eq(userID),
|
||||
).UpdateColumn(appStaticConversationOnline.ConversationID, newConversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) UpdateDynamicConversation(ctx context.Context, env vo.Env, conversationID, newConversationID int64) error {
|
||||
if env == vo.Draft {
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
_, err := appDynamicConversationDraft.WithContext(ctx).Where(appDynamicConversationDraft.ConversationID.Eq(conversationID)).
|
||||
UpdateColumn(appDynamicConversationDraft.ConversationID, newConversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
_, err := appDynamicConversationOnline.WithContext(ctx).Where(appDynamicConversationOnline.ConversationID.Eq(conversationID)).
|
||||
UpdateColumn(appDynamicConversationOnline.ConversationID, newConversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) CopyTemplateConversationByAppID(ctx context.Context, appID int64, toAppID int64) error {
|
||||
appConversationTemplateDraft := r.query.AppConversationTemplateDraft
|
||||
templates, err := appConversationTemplateDraft.WithContext(ctx).Where(appConversationTemplateDraft.AppID.Eq(appID), appConversationTemplateDraft.Name.Neq("Default")).Find()
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
|
||||
if len(templates) == 0 {
|
||||
return nil
|
||||
}
|
||||
templateTemplates := make([]*model.AppConversationTemplateDraft, 0, len(templates))
|
||||
ids, err := r.GenMultiIDs(ctx, len(templates))
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrIDGenError, err)
|
||||
}
|
||||
for i := range templates {
|
||||
copiedTemplate := templates[i]
|
||||
copiedTemplate.ID = ids[i]
|
||||
copiedTemplate.TemplateID = ids[i]
|
||||
copiedTemplate.AppID = toAppID
|
||||
templateTemplates = append(templateTemplates, copiedTemplate)
|
||||
}
|
||||
err = appConversationTemplateDraft.WithContext(ctx).CreateInBatches(templateTemplates, batchSize)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrDatabaseError, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) {
|
||||
if env == vo.Draft {
|
||||
appStaticConversationDraft := r.query.AppStaticConversationDraft
|
||||
ret, err := appStaticConversationDraft.WithContext(ctx).Where(
|
||||
appStaticConversationDraft.ConnectorID.Eq(connectorID),
|
||||
appStaticConversationDraft.ConversationID.Eq(conversationID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", false, nil
|
||||
}
|
||||
return "", false, err
|
||||
}
|
||||
appConversationTemplateDraft := r.query.AppConversationTemplateDraft
|
||||
template, err := appConversationTemplateDraft.WithContext(ctx).Where(
|
||||
appConversationTemplateDraft.TemplateID.Eq(ret.TemplateID),
|
||||
appConversationTemplateDraft.AppID.Eq(appID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", false, nil
|
||||
}
|
||||
return "", false, err
|
||||
}
|
||||
return template.Name, true, nil
|
||||
} else if env == vo.Online {
|
||||
appStaticConversationOnline := r.query.AppStaticConversationOnline
|
||||
ret, err := appStaticConversationOnline.WithContext(ctx).Where(
|
||||
appStaticConversationOnline.ConnectorID.Eq(connectorID),
|
||||
appStaticConversationOnline.ConversationID.Eq(conversationID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", false, nil
|
||||
}
|
||||
return "", false, err
|
||||
}
|
||||
appConversationTemplateOnline := r.query.AppConversationTemplateOnline
|
||||
template, err := appConversationTemplateOnline.WithContext(ctx).Where(
|
||||
appConversationTemplateOnline.TemplateID.Eq(ret.TemplateID),
|
||||
appConversationTemplateOnline.AppID.Eq(appID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", false, nil
|
||||
}
|
||||
return "", false, err
|
||||
}
|
||||
return template.Name, true, nil
|
||||
}
|
||||
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) {
|
||||
if env == vo.Draft {
|
||||
appDynamicConversationDraft := r.query.AppDynamicConversationDraft
|
||||
ret, err := appDynamicConversationDraft.WithContext(ctx).Where(
|
||||
appDynamicConversationDraft.AppID.Eq(appID),
|
||||
appDynamicConversationDraft.ConnectorID.Eq(connectorID),
|
||||
appDynamicConversationDraft.ConversationID.Eq(conversationID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return &entity.DynamicConversation{
|
||||
ID: ret.ID,
|
||||
UserID: ret.UserID,
|
||||
ConnectorID: ret.ConnectorID,
|
||||
ConversationID: ret.ConversationID,
|
||||
Name: ret.Name,
|
||||
}, true, nil
|
||||
} else if env == vo.Online {
|
||||
appDynamicConversationOnline := r.query.AppDynamicConversationOnline
|
||||
ret, err := appDynamicConversationOnline.WithContext(ctx).Where(
|
||||
appDynamicConversationOnline.AppID.Eq(appID),
|
||||
appDynamicConversationOnline.ConnectorID.Eq(connectorID),
|
||||
appDynamicConversationOnline.ConversationID.Eq(conversationID),
|
||||
).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return &entity.DynamicConversation{
|
||||
ID: ret.ID,
|
||||
UserID: ret.UserID,
|
||||
ConnectorID: ret.ConnectorID,
|
||||
ConversationID: ret.ConversationID,
|
||||
Name: ret.Name,
|
||||
}, true, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("unknown env %v", env)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const TableNameAppConversationTemplateDraft = "app_conversation_template_draft"
|
||||
|
||||
// AppConversationTemplateDraft mapped from table <app_conversation_template_draft>
|
||||
type AppConversationTemplateDraft struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id
|
||||
SpaceID int64 `gorm:"column:space_id;not null;comment:space id" json:"space_id"` // space id
|
||||
Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name
|
||||
TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id
|
||||
CreatorID int64 `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond
|
||||
UpdatedAt int64 `gorm:"column:updated_at;autoUpdateTime:milli;comment:update time in millisecond" json:"updated_at"` // update time in millisecond
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond
|
||||
}
|
||||
|
||||
// TableName AppConversationTemplateDraft's table name
|
||||
func (*AppConversationTemplateDraft) TableName() string {
|
||||
return TableNameAppConversationTemplateDraft
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
const TableNameAppConversationTemplateOnline = "app_conversation_template_online"
|
||||
|
||||
// AppConversationTemplateOnline mapped from table <app_conversation_template_online>
|
||||
type AppConversationTemplateOnline struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id
|
||||
SpaceID int64 `gorm:"column:space_id;not null;comment:space id" json:"space_id"` // space id
|
||||
Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name
|
||||
TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id
|
||||
Version string `gorm:"column:version;not null;comment:version name" json:"version"` // version name
|
||||
CreatorID int64 `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond
|
||||
}
|
||||
|
||||
// TableName AppConversationTemplateOnline's table name
|
||||
func (*AppConversationTemplateOnline) TableName() string {
|
||||
return TableNameAppConversationTemplateOnline
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const TableNameAppDynamicConversationDraft = "app_dynamic_conversation_draft"
|
||||
|
||||
// AppDynamicConversationDraft mapped from table <app_dynamic_conversation_draft>
|
||||
type AppDynamicConversationDraft struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id
|
||||
Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name
|
||||
UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id
|
||||
ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id
|
||||
ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond
|
||||
}
|
||||
|
||||
// TableName AppDynamicConversationDraft's table name
|
||||
func (*AppDynamicConversationDraft) TableName() string {
|
||||
return TableNameAppDynamicConversationDraft
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const TableNameAppDynamicConversationOnline = "app_dynamic_conversation_online"
|
||||
|
||||
// AppDynamicConversationOnline mapped from table <app_dynamic_conversation_online>
|
||||
type AppDynamicConversationOnline struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id
|
||||
Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name
|
||||
UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id
|
||||
ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id
|
||||
ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond
|
||||
}
|
||||
|
||||
// TableName AppDynamicConversationOnline's table name
|
||||
func (*AppDynamicConversationOnline) TableName() string {
|
||||
return TableNameAppDynamicConversationOnline
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const TableNameAppStaticConversationDraft = "app_static_conversation_draft"
|
||||
|
||||
// AppStaticConversationDraft mapped from table <app_static_conversation_draft>
|
||||
type AppStaticConversationDraft struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id
|
||||
TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id
|
||||
UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id
|
||||
ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id
|
||||
ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond
|
||||
}
|
||||
|
||||
// TableName AppStaticConversationDraft's table name
|
||||
func (*AppStaticConversationDraft) TableName() string {
|
||||
return TableNameAppStaticConversationDraft
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user