coze-studio/backend/application/singleagent/get.go

589 lines
19 KiB
Go

/*
* 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 singleagent
import (
"context"
"fmt"
"github.com/getkin/kin-openapi/openapi3"
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
knowledgeModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow"
"github.com/coze-dev/coze-studio/backend/api/model/playground"
"github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
plugin_develop_common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
knowledge "github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
pluginEntity "github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
"github.com/coze-dev/coze-studio/backend/domain/plugin/service"
shortcutCMDEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
workflowEntity "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/modelmgr"
"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"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func (s *SingleAgentApplicationService) GetAgentBotInfo(ctx context.Context, req *playground.GetDraftBotInfoAgwRequest) (*playground.GetDraftBotInfoAgwResponse, error) {
agentInfo, err := s.DomainSVC.GetSingleAgent(ctx, req.GetBotID(), req.GetVersion())
if err != nil {
return nil, err
}
if agentInfo == nil {
return nil, errorx.New(errno.ErrAgentInvalidParamCode, errorx.KVf("msg", "agent %d not found", req.GetBotID()))
}
vo, err := s.singleAgentDraftDo2Vo(ctx, agentInfo)
if err != nil {
return nil, err
}
klInfos, err := s.fetchKnowledgeDetails(ctx, agentInfo)
if err != nil {
return nil, err
}
modelInfos, err := s.fetchModelDetails(ctx, agentInfo)
if err != nil {
return nil, err
}
toolInfos, err := s.fetchToolDetails(ctx, agentInfo, req)
if err != nil {
return nil, err
}
pluginInfos, err := s.fetchPluginDetails(ctx, agentInfo, toolInfos)
if err != nil {
return nil, err
}
workflowInfos, err := s.fetchWorkflowDetails(ctx, agentInfo)
if err != nil {
return nil, err
}
shortCutCmdResp, err := s.fetchShortcutCMD(ctx, agentInfo)
if err != nil {
return nil, err
}
workflowDetailMap, err := workflowDo2Vo(workflowInfos)
if err != nil {
return nil, err
}
return &playground.GetDraftBotInfoAgwResponse{
Data: &playground.GetDraftBotInfoAgwData{
BotInfo: vo,
BotOptionData: &playground.BotOptionData{
ModelDetailMap: modelInfoDo2Vo(modelInfos),
KnowledgeDetailMap: knowledgeInfoDo2Vo(klInfos),
PluginAPIDetailMap: toolInfoDo2Vo(toolInfos),
PluginDetailMap: s.pluginInfoDo2Vo(ctx, pluginInfos),
WorkflowDetailMap: workflowDetailMap,
ShortcutCommandList: shortCutCmdResp,
},
SpaceID: agentInfo.SpaceID,
Editable: ptr.Of(true),
Deletable: ptr.Of(true),
},
}, nil
}
func (s *SingleAgentApplicationService) fetchShortcutCMD(ctx context.Context, agentInfo *entity.SingleAgent) ([]*playground.ShortcutCommand, error) {
var cmdVOs []*playground.ShortcutCommand
if len(agentInfo.ShortcutCommand) == 0 {
return cmdVOs, nil
}
cmdDOs, err := s.appContext.ShortcutCMDDomainSVC.ListCMD(ctx, &shortcutCMDEntity.ListMeta{
SpaceID: agentInfo.SpaceID,
ObjectID: agentInfo.AgentID,
CommandIDs: slices.Transform(agentInfo.ShortcutCommand, func(a string) int64 {
return conv.StrToInt64D(a, 0)
}),
})
logs.CtxInfof(ctx, "fetchShortcutCMD cmdDOs = %v, err = %v", conv.DebugJsonToStr(cmdDOs), err)
if err != nil {
return nil, err
}
cmdVOs = s.shortcutCMDDo2Vo(cmdDOs)
return cmdVOs, nil
}
func (s *SingleAgentApplicationService) shortcutCMDDo2Vo(cmdDOs []*shortcutCMDEntity.ShortcutCmd) []*playground.ShortcutCommand {
return slices.Transform(cmdDOs, func(cmdDO *shortcutCMDEntity.ShortcutCmd) *playground.ShortcutCommand {
return &playground.ShortcutCommand{
ObjectID: cmdDO.ObjectID,
CommandID: cmdDO.CommandID,
CommandName: cmdDO.CommandName,
ShortcutCommand: cmdDO.ShortcutCommand,
Description: cmdDO.Description,
SendType: playground.SendType(cmdDO.SendType),
ToolType: playground.ToolType(cmdDO.ToolType),
WorkFlowID: conv.Int64ToStr(cmdDO.WorkFlowID),
PluginID: conv.Int64ToStr(cmdDO.PluginID),
PluginAPIName: cmdDO.PluginToolName,
PluginAPIID: cmdDO.PluginToolID,
ShortcutIcon: cmdDO.ShortcutIcon,
TemplateQuery: cmdDO.TemplateQuery,
ComponentsList: cmdDO.Components,
CardSchema: cmdDO.CardSchema,
ToolInfo: cmdDO.ToolInfo,
}
})
}
func (s *SingleAgentApplicationService) fetchModelDetails(ctx context.Context, agentInfo *entity.SingleAgent) ([]*modelmgr.Model, error) {
if agentInfo.ModelInfo.ModelId == nil {
return nil, nil
}
modelID := agentInfo.ModelInfo.GetModelId()
modelInfos, err := s.appContext.ModelMgr.MGetModelByID(ctx, &modelmgr.MGetModelRequest{
IDs: []int64{modelID},
})
if err != nil {
return nil, fmt.Errorf("fetch model(%d) details failed: %v", modelID, err)
}
return modelInfos, nil
}
func (s *SingleAgentApplicationService) fetchKnowledgeDetails(ctx context.Context, agentInfo *entity.SingleAgent) ([]*knowledgeModel.Knowledge, error) {
knowledgeIDs := make([]int64, 0, len(agentInfo.Knowledge.KnowledgeInfo))
for _, v := range agentInfo.Knowledge.KnowledgeInfo {
id, err := conv.StrToInt64(v.GetId())
if err != nil {
return nil, fmt.Errorf("invalid knowledge id: %s", v.GetId())
}
knowledgeIDs = append(knowledgeIDs, id)
}
if len(knowledgeIDs) == 0 {
return nil, nil
}
listResp, err := s.appContext.KnowledgeDomainSVC.ListKnowledge(ctx, &knowledge.ListKnowledgeRequest{
IDs: knowledgeIDs,
})
if err != nil {
return nil, fmt.Errorf("fetch knowledge details failed: %v", err)
}
return listResp.KnowledgeList, err
}
func (s *SingleAgentApplicationService) fetchToolDetails(ctx context.Context, agentInfo *entity.SingleAgent, req *playground.GetDraftBotInfoAgwRequest) ([]*pluginEntity.ToolInfo, error) {
return s.appContext.PluginDomainSVC.MGetAgentTools(ctx, &service.MGetAgentToolsRequest{
SpaceID: agentInfo.SpaceID,
AgentID: req.GetBotID(),
IsDraft: true,
VersionAgentTools: slices.Transform(agentInfo.Plugin, func(a *bot_common.PluginInfo) pluginEntity.VersionAgentTool {
return pluginEntity.VersionAgentTool{
ToolID: a.GetApiId(),
}
}),
})
}
func (s *SingleAgentApplicationService) fetchPluginDetails(ctx context.Context, agentInfo *entity.SingleAgent, toolInfos []*pluginEntity.ToolInfo) ([]*pluginEntity.PluginInfo, error) {
vPlugins := make([]pluginEntity.VersionPlugin, 0, len(agentInfo.Plugin))
vPluginMap := make(map[string]bool, len(agentInfo.Plugin))
for _, v := range toolInfos {
k := fmt.Sprintf("%d:%s", v.PluginID, v.GetVersion())
if vPluginMap[k] {
continue
}
vPluginMap[k] = true
vPlugins = append(vPlugins, pluginEntity.VersionPlugin{
PluginID: v.PluginID,
Version: v.GetVersion(),
})
}
return s.appContext.PluginDomainSVC.MGetVersionPlugins(ctx, vPlugins)
}
func (s *SingleAgentApplicationService) fetchWorkflowDetails(ctx context.Context, agentInfo *entity.SingleAgent) ([]*workflowEntity.Workflow, error) {
if len(agentInfo.Workflow) == 0 {
return nil, nil
}
policy := &vo.MGetPolicy{
MetaQuery: vo.MetaQuery{
IDs: slices.Transform(agentInfo.Workflow, func(a *bot_common.WorkflowInfo) int64 {
return a.GetWorkflowId()
}),
},
QType: workflowModel.FromLatestVersion,
}
ret, _, err := s.appContext.WorkflowDomainSVC.MGet(ctx, policy)
if err != nil {
return nil, fmt.Errorf("fetch workflow details failed: %v", err)
}
return ret, nil
}
func modelInfoDo2Vo(modelInfos []*modelmgr.Model) map[int64]*playground.ModelDetail {
return slices.ToMap(modelInfos, func(e *modelmgr.Model) (int64, *playground.ModelDetail) {
return e.ID, toModelDetail(e)
})
}
func toModelDetail(m *modelmgr.Model) *playground.ModelDetail {
mm := m.Meta
return &playground.ModelDetail{
Name: ptr.Of(m.Name),
ModelName: ptr.Of(m.Name),
ModelID: ptr.Of(m.ID),
ModelFamily: ptr.Of(int64(mm.Protocol.TOModelClass())),
ModelIconURL: ptr.Of(m.IconURL),
}
}
func knowledgeInfoDo2Vo(klInfos []*knowledgeModel.Knowledge) map[string]*playground.KnowledgeDetail {
return slices.ToMap(klInfos, func(e *knowledgeModel.Knowledge) (string, *playground.KnowledgeDetail) {
return fmt.Sprintf("%v", e.ID), &playground.KnowledgeDetail{
ID: ptr.Of(fmt.Sprintf("%d", e.ID)),
Name: ptr.Of(e.Name),
IconURL: ptr.Of(e.IconURL),
FormatType: func() playground.DataSetType {
switch e.Type {
case knowledgeModel.DocumentTypeText:
return playground.DataSetType_Text
case knowledgeModel.DocumentTypeTable:
return playground.DataSetType_Table
case knowledgeModel.DocumentTypeImage:
return playground.DataSetType_Image
}
return playground.DataSetType_Text
}(),
}
})
}
func toolInfoDo2Vo(toolInfos []*pluginEntity.ToolInfo) map[int64]*playground.PluginAPIDetal {
return slices.ToMap(toolInfos, func(e *pluginEntity.ToolInfo) (int64, *playground.PluginAPIDetal) {
return e.ID, &playground.PluginAPIDetal{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.GetName()),
Description: ptr.Of(e.GetDesc()),
PluginID: ptr.Of(e.PluginID),
Parameters: parametersDo2Vo(e.Operation),
}
})
}
func (s *SingleAgentApplicationService) pluginInfoDo2Vo(ctx context.Context, pluginInfos []*pluginEntity.PluginInfo) map[int64]*playground.PluginDetal {
return slices.ToMap(pluginInfos, func(v *pluginEntity.PluginInfo) (int64, *playground.PluginDetal) {
e := v.PluginInfo
var iconURL string
if e.GetIconURI() != "" {
var err error
iconURL, err = s.appContext.TosClient.GetObjectUrl(ctx, e.GetIconURI())
if err != nil {
logs.CtxErrorf(ctx, "get icon url failed, err = %v", err)
}
}
return e.ID, &playground.PluginDetal{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.GetName()),
Description: ptr.Of(e.GetDesc()),
PluginType: (*int64)(&e.PluginType),
IconURL: &iconURL,
PluginStatus: (*int64)(ptr.Of(common.PluginStatus_PUBLISHED)),
IsOfficial: func() *bool {
if e.SpaceID == 0 {
return ptr.Of(true)
}
return ptr.Of(false)
}(),
}
})
}
func parametersDo2Vo(op *plugin.Openapi3Operation) []*playground.PluginParameter {
var convertReqBody func(paramName string, isRequired bool, sc *openapi3.Schema) *playground.PluginParameter
convertReqBody = func(paramName string, isRequired bool, sc *openapi3.Schema) *playground.PluginParameter {
if disabledParam(sc) {
return nil
}
var assistType *int64
if v, ok := sc.Extensions[plugin.APISchemaExtendAssistType]; ok {
if _v, ok := v.(string); ok {
assistType = toParameterAssistType(_v)
}
}
paramInfo := &playground.PluginParameter{
Name: ptr.Of(paramName),
Type: ptr.Of(sc.Type),
Description: ptr.Of(sc.Description),
IsRequired: ptr.Of(isRequired),
AssistType: assistType,
}
switch sc.Type {
case openapi3.TypeObject:
required := slices.ToMap(sc.Required, func(e string) (string, bool) {
return e, true
})
subParams := make([]*playground.PluginParameter, 0, len(sc.Properties))
for subParamName, prop := range sc.Properties {
subParamInfo := convertReqBody(subParamName, required[subParamName], prop.Value)
if subParamInfo != nil {
subParams = append(subParams, subParamInfo)
}
}
paramInfo.SubParameters = subParams
return paramInfo
case openapi3.TypeArray:
paramInfo.SubType = ptr.Of(sc.Items.Value.Type)
if sc.Items.Value.Type != openapi3.TypeObject {
return paramInfo
}
required := slices.ToMap(sc.Required, func(e string) (string, bool) {
return e, true
})
subParams := make([]*playground.PluginParameter, 0, len(sc.Items.Value.Properties))
for subParamName, prop := range sc.Items.Value.Properties {
subParamInfo := convertReqBody(subParamName, required[subParamName], prop.Value)
if subParamInfo != nil {
subParams = append(subParams, subParamInfo)
}
}
paramInfo.SubParameters = subParams
return paramInfo
default:
return paramInfo
}
}
var params []*playground.PluginParameter
for _, prop := range op.Parameters {
paramVal := prop.Value
schemaVal := paramVal.Schema.Value
if schemaVal.Type == openapi3.TypeObject || schemaVal.Type == openapi3.TypeArray {
continue
}
if disabledParam(prop.Value.Schema.Value) {
continue
}
var assistType *int64
if v, ok := schemaVal.Extensions[plugin.APISchemaExtendAssistType]; ok {
if _v, ok := v.(string); ok {
assistType = toParameterAssistType(_v)
}
}
params = append(params, &playground.PluginParameter{
Name: ptr.Of(paramVal.Name),
Description: ptr.Of(paramVal.Description),
IsRequired: ptr.Of(paramVal.Required),
Type: ptr.Of(schemaVal.Type),
AssistType: assistType,
})
}
if op.RequestBody == nil || op.RequestBody.Value == nil || len(op.RequestBody.Value.Content) == 0 {
return params
}
for _, mType := range op.RequestBody.Value.Content {
schemaVal := mType.Schema.Value
if len(schemaVal.Properties) == 0 {
continue
}
required := slices.ToMap(schemaVal.Required, func(e string) (string, bool) {
return e, true
})
for paramName, prop := range schemaVal.Properties {
paramInfo := convertReqBody(paramName, required[paramName], prop.Value)
if paramInfo != nil {
params = append(params, paramInfo)
}
}
break // Take only one MIME.
}
return params
}
func toParameterAssistType(assistType string) *int64 {
if assistType == "" {
return nil
}
switch plugin.APIFileAssistType(assistType) {
case plugin.AssistTypeFile:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_CODE))
case plugin.AssistTypeImage:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_IMAGE))
case plugin.AssistTypeDoc:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_DOC))
case plugin.AssistTypePPT:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_PPT))
case plugin.AssistTypeCode:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_CODE))
case plugin.AssistTypeExcel:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_EXCEL))
case plugin.AssistTypeZIP:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_ZIP))
case plugin.AssistTypeVideo:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_VIDEO))
case plugin.AssistTypeAudio:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_AUDIO))
case plugin.AssistTypeTXT:
return ptr.Of(int64(plugin_develop_common.AssistParameterType_TXT))
default:
return nil
}
}
func workflowDo2Vo(wfInfos []*workflowEntity.Workflow) (map[int64]*playground.WorkflowDetail, error) {
result := make(map[int64]*playground.WorkflowDetail, len(wfInfos))
for _, e := range wfInfos {
parameters, err := slices.TransformWithErrorCheck(e.InputParams, toPluginParameter)
if err != nil {
return nil, err
}
result[e.ID] = &playground.WorkflowDetail{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.Name),
Description: ptr.Of(e.Desc),
IconURL: ptr.Of(e.IconURL),
PluginID: ptr.Of(e.ID),
APIDetail: &playground.PluginAPIDetal{
ID: ptr.Of(e.ID),
Name: ptr.Of(e.Name),
Description: ptr.Of(e.Desc),
PluginID: ptr.Of(e.ID),
Parameters: parameters,
},
}
}
return result, nil
}
func toPluginParameter(info *vo.NamedTypeInfo) (*playground.PluginParameter, error) {
if info == nil {
return nil, fmt.Errorf("named type info is nil")
}
p := &playground.PluginParameter{
Name: ptr.Of(info.Name),
Description: ptr.Of(info.Desc),
IsRequired: ptr.Of(info.Required),
}
switch info.Type {
case vo.DataTypeString, vo.DataTypeFile, vo.DataTypeTime:
p.Type = ptr.Of("string")
if info.Type == vo.DataTypeFile {
p.AssistType = toWorkflowParameterAssistType(string(*info.FileType))
}
case vo.DataTypeInteger:
p.Type = ptr.Of("integer")
case vo.DataTypeNumber:
p.Type = ptr.Of("number")
case vo.DataTypeBoolean:
p.Type = ptr.Of("boolean")
case vo.DataTypeObject:
p.Type = ptr.Of("object")
p.SubParameters = make([]*playground.PluginParameter, 0, len(info.Properties))
for _, sub := range info.Properties {
subParameter, err := toPluginParameter(sub)
if err != nil {
return nil, err
}
p.SubParameters = append(p.SubParameters, subParameter)
}
case vo.DataTypeArray:
p.Type = ptr.Of("array")
eleParameter, err := toPluginParameter(info.ElemTypeInfo)
if err != nil {
return nil, err
}
p.SubType = eleParameter.Type
p.SubParameters = []*playground.PluginParameter{eleParameter}
default:
return nil, fmt.Errorf("unknown named type info type: %s", info.Type)
}
return p, nil
}
func toWorkflowParameterAssistType(assistType string) *int64 {
if assistType == "" {
return nil
}
switch vo.FileSubType(assistType) {
case vo.FileTypeDefault:
return ptr.Of(int64(workflow.AssistParameterType_DEFAULT))
case vo.FileTypeImage:
return ptr.Of(int64(workflow.AssistParameterType_IMAGE))
case vo.FileTypeDocument:
return ptr.Of(int64(workflow.AssistParameterType_DOC))
case vo.FileTypePPT:
return ptr.Of(int64(workflow.AssistParameterType_PPT))
case vo.FileTypeCode:
return ptr.Of(int64(workflow.AssistParameterType_CODE))
case vo.FileTypeExcel:
return ptr.Of(int64(workflow.AssistParameterType_EXCEL))
case vo.FileTypeZip:
return ptr.Of(int64(workflow.AssistParameterType_ZIP))
case vo.FileTypeVideo:
return ptr.Of(int64(workflow.AssistParameterType_VIDEO))
case vo.FileTypeAudio:
return ptr.Of(int64(workflow.AssistParameterType_AUDIO))
case vo.FileTypeTxt:
return ptr.Of(int64(workflow.AssistParameterType_TXT))
default:
return nil
}
}