Files
ai-agent/workflow/service/node/node_library_service.go
qhd 03c95c3601 feat: 新增主动拉取与多类型回调功能
- 新增 ActivePull 实体、DAO、DTO 及 Service,支持主动拉取任务管理
- 新增 ComposeCallback、VideoCallback、HttpNodeCallback 多类型回调接口
- FlowExecution 增加 NodeGroupId 和 TotalTokens 字段,支持节点组追踪与 Token 统计
- ExecutedNodes 结构由字符串列表改为包含执行状态的节点对象列表
- 重构回调通知机制,统一 Notify 函数调用
- 优化输出项类型判断逻辑,新增文件类型标识
2026-06-10 14:23:55 +08:00

402 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package node
import (
"ai-agent/workflow/consts/node"
nodeDto "ai-agent/workflow/model/dto/node"
"context"
"fmt"
"gitea.com/red-future/common/beans"
commonHttp "gitea.com/red-future/common/http"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
)
var NodeLibraryService = &nodeLibraryService{}
type nodeLibraryService struct{}
func GetModelType(ctx context.Context) (mainTypeMap map[int]string, err error) {
headers := make(map[string]string)
if r := g.RequestFromCtx(ctx); r != nil {
for k, v := range r.Request.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
}
res := new(nodeDto.ModelTypeResponse)
err = commonHttp.Get(ctx, "model-gateway/model/listType", headers, res, nil)
// 通用过滤:只保留 能被 100 整除的主类型100/200/300...
mainTypeMap = make(map[int]string)
for typ, name := range res.Type {
if typ%100 == 0 {
mainTypeMap[typ] = name
}
}
return
}
func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.WorkflowNodeTreeReq) (*nodeDto.WorkflowNodeTreeRes, error) {
WorkflowNodeGroups := []node.NodeGroupItem{
{
Group: node.NodeGroupComponent,
Label: node.NodeGroupNameComponent,
Items: []node.NodeItem{
{
NodeCode: node.NodeTypeTextModel,
NodeName: node.NodeNameTextModel,
ModelType: node.ModelTypeText,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeImageModel,
NodeName: node.NodeNameImageModel,
ModelType: node.ModelTypeImage,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeVideoModel,
NodeName: node.NodeNameVideoModel,
ModelType: node.ModelTypeVideo,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeAudioModel,
NodeName: node.NodeNameAudioModel,
ModelType: node.ModelTypeAudio,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeBatchModel,
NodeName: node.NodeNameBatchModel,
ModelType: node.ModelTypeText,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
//{
// NodeCode: node.NodeTypeSenseOptimizeModel,
// NodeName: node.NodeNameSenseOptimizeModel,
// ModelType: node.ModelTypeText,
// SkillOption: false,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
//{
// NodeCode: node.NodeTypeStoryOptimizeModel,
// NodeName: node.NodeNameStoryOptimizeModel,
// ModelType: node.ModelTypeText,
// SkillOption: false,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
//{
// NodeCode: node.NodeTypeScriptOptimizeModel,
// NodeName: node.NodeNameScriptOptimizeModel,
// ModelType: node.ModelTypeText,
// SkillOption: false,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
},
},
{
Group: node.NodeGroupBase,
Label: node.NodeGroupNameBase,
Items: []node.NodeItem{
{
NodeCode: node.NodeTypeDataConversionModel,
NodeName: node.NodeNameDataConversionModel,
ModelType: node.ModelTypeText,
SkillOption: false,
PromptOption: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeMerge,
NodeName: node.NodeNameMerge,
SkillOption: false,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeDataMerge,
NodeName: node.NodeNameDataMerge,
SkillOption: false,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeJudge,
NodeName: node.NodeNameJudge,
SkillOption: false,
FormConfig: []node.NodeFormField{
{Field: "condition", Label: node.FormLabelCondition, Type: "input", Required: true},
},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeForm,
NodeName: node.NodeNameForm,
SkillOption: false,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeHttp,
NodeName: node.NodeNameHttp,
SkillOption: false,
IsSaveFile: true,
FormConfig: []node.NodeFormField{
{
Field: "method",
Label: "请求方式",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "GET", Value: "GET"},
{Label: "POST", Value: "POST"},
{Label: "PUT", Value: "PUT"},
{Label: "DELETE", Value: "DELETE"},
},
},
{
Field: "url",
Label: "请求地址",
Type: "input",
Required: true,
},
{
Field: "headers",
Label: "请求头支持Authorization鉴权",
Type: "keyValue",
Required: false,
},
{
Field: "bodyType",
Label: "请求体类型",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "无", Value: "None"},
{Label: "JSON", Value: "JSON"},
//{Label: "表单", Value: "FormUrlEncoded"},
//{Label: "文件上传", Value: "FormData"},
//{Label: "原生文本", Value: "Raw"},
},
},
{
Field: "body",
Label: "请求体内容",
Type: "keyValue",
Required: false,
},
{
Field: "response",
Label: "结果返回结构",
Type: "keyValue",
Required: false,
},
{
Field: "responseType",
Label: "结果返回方式",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "同步返回", Value: "sync"},
{Label: "等候回调", Value: "callback"},
{Label: "主动拉取", Value: "pull"},
},
Expand: []node.NodeFormField{
{
Field: "method",
Label: "请求方式",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "GET", Value: "GET"},
{Label: "POST", Value: "POST"},
{Label: "PUT", Value: "PUT"},
{Label: "DELETE", Value: "DELETE"},
},
},
{
Field: "url",
Label: "请求地址",
Type: "input",
Required: true,
},
{
Field: "headers",
Label: "请求头支持Authorization鉴权",
Type: "keyValue",
Required: false,
},
{
Field: "bodyType",
Label: "请求体类型",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "无", Value: "None"},
{Label: "JSON", Value: "JSON"},
//{Label: "表单", Value: "FormUrlEncoded"},
//{Label: "文件上传", Value: "FormData"},
//{Label: "原生文本", Value: "Raw"},
},
},
{
Field: "body",
Label: "请求体内容",
Type: "keyValue",
Required: false,
},
{
Field: "response",
Label: "结果返回结构",
Type: "keyValue",
Required: false,
},
{
Field: "timeout",
Label: "超时时间(秒)",
Type: "inputNumber",
Required: false,
Default: 30,
},
{
Field: "insecureSkipVerify",
Label: "跳过HTTPS证书校验",
Type: "switch",
Required: false,
Default: false,
},
},
},
{
Field: "callbackUrl",
Label: "回调地址(只需要填写字段名称)",
Type: "input",
Required: false,
Default: "",
},
{
Field: "timeout",
Label: "超时时间(秒)",
Type: "inputNumber",
Required: false,
Default: 30,
},
{
Field: "insecureSkipVerify",
Label: "跳过HTTPS证书校验",
Type: "switch",
Required: false,
Default: false,
},
},
ModelConfig: []node.ModelItem{},
},
//{
// NodeCode: node.NodeTypeModel,
// NodeName: node.NodeNameModel,
// SkillOption: true,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
},
},
//{
// Group: node.NodeGroupCustom,
// Label: node.NodeGroupNameCustom,
// Items: []node.NodeItem{
// {
// NodeCode: node.NodeTypeCustomNode,
// NodeName: node.NodeNameCustomNode,
// SkillOption: true,
// FormConfig: []node.NodeFormField{
// {Field: "nodeName", Label: node.FormLabelApiKey, Type: "input", Required: true},
// {Field: "nodeType", Label: node.FormLabelModel, Type: "input", Required: true},
// },
// ModelConfig: []node.ModelItem{},
// },
// },
//},
}
tree := &nodeDto.WorkflowNodeTreeRes{
Groups: WorkflowNodeGroups,
}
// 3. 遍历分组,根据 typeId=1 给【文本模型节点】追加固定表单
for gIdx := range tree.Groups {
group := &tree.Groups[gIdx]
// 遍历分组下的每个节点
for itemIdx := range group.Items {
item := &group.Items[itemIdx]
if item.NodeCode == node.NodeTypeTextModel ||
item.NodeCode == node.NodeTypeImageModel ||
item.NodeCode == node.NodeTypeVideoModel ||
item.NodeCode == node.NodeTypeAudioModel ||
item.NodeCode == node.NodeTypeBatchModel ||
item.NodeCode == node.NodeTypeDataConversionModel ||
item.NodeCode == node.NodeTypeSenseOptimizeModel ||
item.NodeCode == node.NodeTypeStoryOptimizeModel ||
item.NodeCode == node.NodeTypeScriptOptimizeModel {
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
ModelName: "自定义",
})
}
}
}
return tree, nil
}
// SetUserInfo 设置用户信息
func (s *nodeLibraryService) SetUserInfo(ctx context.Context, creator string, tenantId uint64) (headers map[string]string, err error) {
// 创建完整的用户信息
userInfo := &beans.User{
UserName: creator,
TenantId: tenantId,
}
ctx = context.WithValue(ctx, "user", *userInfo)
// 提取并保存请求头(在连接升级前)
headers = make(map[string]string)
// 提取其他headers
if r := g.RequestFromCtx(ctx); r != nil {
for k, v := range r.Request.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
}
// 将完整用户信息序列化为JSON放到X-User-Info请求头
userInfoJson, err := gjson.Encode(userInfo)
if err != nil {
return nil, fmt.Errorf("用户信息序列化失败: %w", err)
}
headers["X-User-Info"] = string(userInfoJson)
return
}