feat(singleagent): chatflow as agent

This commit is contained in:
lijunwen.gigoo
2025-08-05 17:07:25 +08:00
parent efc6e55fe5
commit 6dae6bd42d
16 changed files with 315 additions and 172 deletions

View File

@ -26,14 +26,6 @@ import (
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossworkflow"
)
type AgentRuntime struct {
AgentVersion string
IsDraft bool
SpaceID int64
ConnectorID int64
PreRetrieveTools []*agentrun.Tool
}
type EventType string
const (
@ -84,6 +76,8 @@ type SingleAgent struct {
JumpConfig *bot_common.JumpConfig
BackgroundImageInfoList []*bot_common.BackgroundImageInfo
Database []*bot_common.Database
BotMode bot_common.BotMode
LayoutInfo *bot_common.LayoutInfo
ShortcutCommand []string
}

View File

@ -136,7 +136,7 @@ func Init(ctx context.Context) (err error) {
crossconversation.SetDefaultSVC(conversationImpl.InitDomainService(complexServices.conversationSVC.ConversationDomainSVC))
crossmessage.SetDefaultSVC(messageImpl.InitDomainService(complexServices.conversationSVC.MessageDomainSVC))
crossagentrun.SetDefaultSVC(agentrunImpl.InitDomainService(complexServices.conversationSVC.AgentRunDomainSVC))
crossagent.SetDefaultSVC(singleagentImpl.InitDomainService(complexServices.singleAgentSVC.DomainSVC, infra.ImageXClient))
crossagent.SetDefaultSVC(singleagentImpl.InitDomainService(complexServices.singleAgentSVC.DomainSVC))
crossuser.SetDefaultSVC(crossuserImpl.InitDomainService(basicServices.userSVC.DomainSVC))
crossdatacopy.SetDefaultSVC(dataCopyImpl.InitDomainService(basicServices.infra))
crosssearch.SetDefaultSVC(searchImpl.InitDomainService(complexServices.searchSVC.DomainSVC))

View File

@ -56,6 +56,7 @@ func InitService(s *ServiceComponents) *ConversationApplicationService {
arDomainComponents := &agentrun.Components{
RunRecordRepo: repository.NewRunRecordRepo(s.DB, s.IDGen),
ImagexSVC: s.ImageX,
}
agentRunDomainSVC := agentrun.NewService(arDomainComponents)

View File

@ -370,6 +370,12 @@ func (s *SingleAgentApplicationService) applyAgentUpdates(target *entity.SingleA
}
target.Database = patch.DatabaseList
}
if patch.BotMode != nil {
target.BotMode = ptr.From(patch.BotMode)
}
if patch.LayoutInfo != nil {
target.LayoutInfo = patch.LayoutInfo
}
return target, nil
}
@ -419,11 +425,12 @@ func (s *SingleAgentApplicationService) singleAgentDraftDo2Vo(ctx context.Contex
TaskInfo: &bot_common.TaskInfo{},
CreateTime: do.CreatedAt / 1000,
UpdateTime: do.UpdatedAt / 1000,
BotMode: bot_common.BotMode_SingleMode,
BotMode: do.BotMode,
BackgroundImageInfoList: do.BackgroundImageInfoList,
Status: bot_common.BotStatus_Using,
DatabaseList: do.Database,
ShortcutSort: do.ShortcutCommand,
LayoutInfo: do.LayoutInfo,
}
if do.VariablesMetaID != nil {

View File

@ -21,17 +21,31 @@ import (
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
)
// Requests and responses must not reference domain entities and can only use models under api/model/crossdomain.
type SingleAgent interface {
StreamExecute(ctx context.Context, historyMsg []*message.Message, query *message.Message,
agentRuntime *singleagent.AgentRuntime) (*schema.StreamReader[*singleagent.AgentEvent], error)
StreamExecute(ctx context.Context,
agentRuntime *AgentRuntime) (*schema.StreamReader[*singleagent.AgentEvent], error)
ObtainAgentByIdentity(ctx context.Context, identity *singleagent.AgentIdentity) (*singleagent.SingleAgent, error)
}
type AgentRuntime struct {
AgentVersion string
UserID string
AgentID int64
IsDraft bool
SpaceID int64
ConnectorID int64
PreRetrieveTools []*agentrun.Tool
HistoryMsg []*schema.Message
Input *schema.Message
ResumeInfo *ResumeInfo
}
type ResumeInfo = singleagent.InterruptInfo
type AgentEvent = singleagent.AgentEvent

View File

@ -39,11 +39,13 @@ type Workflow interface {
ReleaseApplicationWorkflows(ctx context.Context, appID int64, config *ReleaseWorkflowConfig) ([]*vo.ValidateIssue, error)
GetWorkflowIDsByAppID(ctx context.Context, appID int64) ([]int64, error)
SyncExecuteWorkflow(ctx context.Context, config vo.ExecuteConfig, input map[string]any) (*workflowEntity.WorkflowExecution, vo.TerminatePlan, error)
StreamExecute(ctx context.Context, config vo.ExecuteConfig, input map[string]any) (*schema.StreamReader[*workflowEntity.Message], error)
WithExecuteConfig(cfg vo.ExecuteConfig) einoCompose.Option
WithMessagePipe() (compose.Option, *schema.StreamReader[*entity.Message])
}
type ExecuteConfig = vo.ExecuteConfig
type WorkflowMessage = workflowEntity.Message
type ExecuteMode = vo.ExecuteMode
type NodeType = entity.NodeType
@ -59,6 +61,14 @@ const (
ExecuteModeNodeDebug ExecuteMode = "node_debug"
)
type SyncPattern = vo.SyncPattern
const (
SyncPatternSync SyncPattern = "sync"
SyncPatternAsync SyncPattern = "async"
SyncPatternStream SyncPattern = "stream"
)
type TaskType = vo.TaskType
const (

View File

@ -18,17 +18,13 @@ package agent
import (
"context"
"encoding/json"
"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"
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossagent"
singleagent "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/service"
"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/lang/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
@ -38,49 +34,34 @@ var defaultSVC crossagent.SingleAgent
type impl struct {
DomainSVC singleagent.SingleAgent
ImagexSVC imagex.ImageX
}
func InitDomainService(c singleagent.SingleAgent, imagexClient imagex.ImageX) crossagent.SingleAgent {
func InitDomainService(c singleagent.SingleAgent) crossagent.SingleAgent {
defaultSVC = &impl{
DomainSVC: c,
ImagexSVC: imagexClient,
}
return defaultSVC
}
func (c *impl) StreamExecute(ctx context.Context, historyMsg []*message.Message,
query *message.Message, agentRuntime *model.AgentRuntime,
func (c *impl) StreamExecute(ctx context.Context, agentRuntime *crossagent.AgentRuntime,
) (*schema.StreamReader[*model.AgentEvent], error) {
historyMsg = c.historyPairs(historyMsg)
singleAgentStreamExecReq := c.buildSingleAgentStreamExecuteReq(ctx, historyMsg, query, agentRuntime)
singleAgentStreamExecReq := c.buildSingleAgentStreamExecuteReq(ctx, agentRuntime)
streamEvent, err := c.DomainSVC.StreamExecute(ctx, singleAgentStreamExecReq)
logs.CtxInfof(ctx, "agent StreamExecute req:%v, streamEvent:%v, err:%v", conv.DebugJsonToStr(singleAgentStreamExecReq), streamEvent, err)
return streamEvent, err
}
func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, historyMsg []*message.Message,
input *message.Message, agentRuntime *model.AgentRuntime,
func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, agentRuntime *crossagent.AgentRuntime,
) *model.ExecuteRequest {
identity := c.buildIdentity(input, agentRuntime)
inputBuild := c.buildSchemaMessage(ctx, []*message.Message{input})
var inputSM *schema.Message
if len(inputBuild) > 0 {
inputSM = inputBuild[0]
}
history := c.buildSchemaMessage(ctx, historyMsg)
resumeInfo := c.checkResumeInfo(ctx, historyMsg)
return &model.ExecuteRequest{
Identity: identity,
Input: inputSM,
History: history,
UserID: input.UserID,
Identity: c.buildIdentity(agentRuntime),
Input: agentRuntime.Input,
History: agentRuntime.HistoryMsg,
UserID: agentRuntime.UserID,
PreCallTools: slices.Transform(agentRuntime.PreRetrieveTools, func(tool *agentrun.Tool) *agentrun.ToolsRetriever {
return &agentrun.ToolsRetriever{
PluginID: tool.PluginID,
@ -98,141 +79,19 @@ func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, historyMsg
}(tool.Type),
}
}),
ResumeInfo: resumeInfo,
ResumeInfo: agentRuntime.ResumeInfo,
}
}
func (c *impl) 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 (c *impl) checkResumeInfo(_ 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(entity.ExtKeyResumeInfo)] != "" {
err := json.Unmarshal([]byte(historyMsg[i].Ext[string(entity.ExtKeyResumeInfo)]), &resumeInfo)
if err != nil {
return nil
}
}
}
}
return resumeInfo
}
func (c *impl) buildSchemaMessage(ctx context.Context, msgs []*message.Message) []*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, c.parseMessageURI(ctx, sm))
}
return schemaMessage
}
func (c *impl) parseMessageURI(ctx context.Context, mcMsg *schema.Message) *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 := c.ImagexSVC.GetResourceURL(ctx, one.ImageURL.URI)
if err == nil {
mcMsg.MultiContent[k].ImageURL.URL = url.URL
}
}
case schema.ChatMessagePartTypeFileURL:
if one.FileURL.URI != "" {
url, err := c.ImagexSVC.GetResourceURL(ctx, one.FileURL.URI)
if err == nil {
mcMsg.MultiContent[k].FileURL.URL = url.URL
}
}
case schema.ChatMessagePartTypeAudioURL:
if one.AudioURL.URI != "" {
url, err := c.ImagexSVC.GetResourceURL(ctx, one.AudioURL.URI)
if err == nil {
mcMsg.MultiContent[k].AudioURL.URL = url.URL
}
}
case schema.ChatMessagePartTypeVideoURL:
if one.VideoURL.URI != "" {
url, err := c.ImagexSVC.GetResourceURL(ctx, one.VideoURL.URI)
if err == nil {
mcMsg.MultiContent[k].VideoURL.URL = url.URL
}
}
}
}
return mcMsg
}
func (c *impl) buildIdentity(input *message.Message, agentRuntime *model.AgentRuntime) *model.AgentIdentity {
func (c *impl) buildIdentity(agentRuntime *crossagent.AgentRuntime) *model.AgentIdentity {
return &model.AgentIdentity{
AgentID: input.AgentID,
AgentID: agentRuntime.AgentID,
Version: agentRuntime.AgentVersion,
IsDraft: agentRuntime.IsDraft,
ConnectorID: agentRuntime.ConnectorID,
}
}
func (c *impl) GetSingleAgent(ctx context.Context, agentID int64, version string) (agent *model.SingleAgent, err error) {
agentInfo, err := c.DomainSVC.GetSingleAgent(ctx, agentID, version)
if err != nil {
return nil, err
}
return agentInfo.SingleAgent, nil
}
func (c *impl) ObtainAgentByIdentity(ctx context.Context, identity *model.AgentIdentity) (*model.SingleAgent, error) {
agentInfo, err := c.DomainSVC.ObtainAgentByIdentity(ctx, identity)
if err != nil {

View File

@ -70,7 +70,9 @@ func (i *impl) WithResumeToolWorkflow(resumingEvent *workflowEntity.ToolInterrup
func (i *impl) SyncExecuteWorkflow(ctx context.Context, config vo.ExecuteConfig, input map[string]any) (*workflowEntity.WorkflowExecution, vo.TerminatePlan, error) {
return i.DomainSVC.SyncExecute(ctx, config, input)
}
func (i *impl) StreamExecute(ctx context.Context, config vo.ExecuteConfig, input map[string]any) (*schema.StreamReader[*workflowEntity.Message], error) {
return i.DomainSVC.StreamExecute(ctx, config, input)
}
func (i *impl) WithExecuteConfig(cfg vo.ExecuteConfig) einoCompose.Option {
return i.DomainSVC.WithExecuteConfig(cfg)
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -23,6 +23,7 @@ import (
"gorm.io/gorm"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_common"
"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"
@ -144,6 +145,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,
},
}
}
@ -171,5 +174,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,
}
}

View File

@ -0,0 +1,140 @@
/*
* 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"
"github.com/cloudwego/eino/schema"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossagent"
"github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
)
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(entity.ExtKeyResumeInfo)] != "" {
err := json.Unmarshal([]byte(historyMsg[i].Ext[string(entity.ExtKeyResumeInfo)]), &resumeInfo)
if err != nil {
return nil
}
}
}
}
return resumeInfo
}

View File

@ -37,13 +37,16 @@ import (
"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"
"github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_common"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossagent"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossmessage"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossworkflow"
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal"
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model"
"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/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
@ -72,6 +75,7 @@ type runtimeDependence struct {
type Components struct {
RunRecordRepo repository.RunRecordRepo
ImagexSVC imagex.ImageX
}
func NewService(c *Components) Run {
@ -145,7 +149,11 @@ func (c *runImpl) run(ctx context.Context, sw *schema.StreamWriter[*entity.Agent
rtDependence.questionMsgID = input.ID
err = c.handlerStreamExecute(ctx, sw, history, input, rtDependence)
if rtDependence.agentInfo.BotMode == bot_common.BotMode_WorkflowMode {
err = c.handlerWfAsAgentStreamExecute(ctx, sw, rtDependence)
} else {
err = c.handlerAgentStreamExecute(ctx, sw, history, input, rtDependence)
}
return
}
@ -161,18 +169,51 @@ func (c *runImpl) handlerAgent(ctx context.Context, rtDependence *runtimeDepende
return agentInfo, nil
}
func (c *runImpl) handlerStreamExecute(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], historyMsg []*msgEntity.Message, input *msgEntity.Message, rtDependence *runtimeDependence) (err error) {
func (c *runImpl) handlerWfAsAgentStreamExecute(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (err error) {
wfID, _ := strconv.ParseInt(rtDependence.agentInfo.LayoutInfo.WorkflowId, 10, 64)
wfStreamer, err := crossworkflow.DefaultSVC().StreamExecute(ctx, crossworkflow.ExecuteConfig{
ID: wfID,
ConnectorID: rtDependence.runMeta.ConnectorID,
ConnectorUID: rtDependence.runMeta.UserID,
AgentID: ptr.Of(rtDependence.runMeta.AgentID),
Mode: crossworkflow.ExecuteModeRelease,
BizType: crossworkflow.BizTypeAgent,
SyncPattern: crossworkflow.SyncPatternStream,
}, map[string]any{
"input": "你有什么功能?",
})
mainChan := make(chan *entity.AgentRespEvent, 100)
ar := &singleagent.AgentRuntime{
var wg sync.WaitGroup
wg.Add(2)
safego.Go(ctx, func() {
defer wg.Done()
c.pullWfStream(ctx, mainChan, wfStreamer)
})
safego.Go(ctx, func() {
defer wg.Done()
c.push(ctx, mainChan, sw, rtDependence)
})
wg.Wait()
return err
}
func (c *runImpl) handlerAgentStreamExecute(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], historyMsg []*msgEntity.Message, input *msgEntity.Message, rtDependence *runtimeDependence) (err error) {
mainChan := make(chan *entity.AgentRespEvent, 100)
ar := &crossagent.AgentRuntime{
AgentVersion: rtDependence.runMeta.Version,
SpaceID: rtDependence.runMeta.SpaceID,
AgentID: rtDependence.runMeta.AgentID,
IsDraft: rtDependence.runMeta.IsDraft,
ConnectorID: rtDependence.runMeta.ConnectorID,
PreRetrieveTools: rtDependence.runMeta.PreRetrieveTools,
Input: internal.TransMessageToSchemaMessage(ctx, []*msgEntity.Message{input}, c.ImagexSVC)[0],
HistoryMsg: internal.TransMessageToSchemaMessage(ctx, internal.HistoryPairs(historyMsg), c.ImagexSVC),
ResumeInfo: internal.ParseResumeInfo(ctx, historyMsg),
}
streamer, err := crossagent.DefaultSVC().StreamExecute(ctx, historyMsg, input, ar)
streamer, err := crossagent.DefaultSVC().StreamExecute(ctx, ar)
if err != nil {
return errors.New(errorx.ErrorWithoutStack(err))
}
@ -431,7 +472,23 @@ func (c *runImpl) handlerInput(ctx context.Context, sw *schema.StreamWriter[*ent
}
return cm, nil
}
func (c *runImpl) pullWfStream(ctx context.Context, mainChan chan *entity.AgentRespEvent, events *schema.StreamReader[*crossworkflow.WorkflowMessage]) {
defer func() {
close(mainChan)
}()
for {
st, re := events.Recv()
logs.CtxInfof(ctx, "pullWfStream Recv:%v,err:%v", conv.DebugJsonToStr(st), re)
if re != nil {
errChunk := &entity.AgentRespEvent{
Err: re,
}
mainChan <- errChunk
return
}
}
}
func (c *runImpl) pull(_ context.Context, mainChan chan *entity.AgentRespEvent, events *schema.StreamReader[*crossagent.AgentEvent]) {
defer func() {
close(mainChan)

View File

@ -57,6 +57,7 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{
"background_image_info_list": []*bot_common.BackgroundImageInfo{},
"database_config": []*bot_common.Database{},
"shortcut_command": []string{},
"layout_info": &bot_common.LayoutInfo{},
},
"single_agent_version": {
// "variable": []*bot_common.Variable{},
@ -71,6 +72,7 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{
"background_image_info_list": []*bot_common.BackgroundImageInfo{},
"database_config": []*bot_common.Database{},
"shortcut_command": []string{},
"layout_info": &bot_common.LayoutInfo{},
},
"single_agent_publish": {
"connector_ids": []int64{},