Files
ai-agent/workflow/service/flow/lambda_node_util.go
T

212 lines
6.0 KiB
Go

package flow
import (
"ai-agent/gateway"
"ai-agent/workflow/consts/node"
flowDto "ai-agent/workflow/model/dto/flow"
"context"
"fmt"
"sync"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
"github.com/google/uuid"
)
// 全局等待任务回调的工具
var (
asyncMu sync.Mutex
asyncTasks = make(map[string]chan any)
)
// Wait 阻塞等待回调结果
// 调用后会一直卡住,直到 Notify 唤醒 或 超时/取消
func Wait(ctx context.Context, taskId string) (any, error) {
asyncMu.Lock()
ch := make(chan any, 1)
asyncTasks[taskId] = ch
asyncMu.Unlock()
defer close(ch)
for {
select {
case result := <-ch:
return result, nil
case <-ctx.Done():
asyncMu.Lock()
delete(asyncTasks, taskId)
asyncMu.Unlock()
return nil, ctx.Err()
}
}
}
// Notify 回调时调用,唤醒等待的任务
func Notify(taskId string, result any) {
asyncMu.Lock()
defer asyncMu.Unlock()
ch, exist := asyncTasks[taskId]
if !exist {
return
}
ch <- result
delete(asyncTasks, taskId)
}
// ModelCallResultLambda 调用模型并返回输出内容列表,同时回传本次调用的 token/费用(*gateway.ModelCallRes),
// 供调用方(ModelLambda)累计写入节点执行记录 token_info,最后由汇总节点聚合到 exec_workflow。
func ModelCallResultLambda(ctx context.Context, modelId int64, sessionId string, modelRequestParams map[string]any) ([]map[string]any, *gateway.ModelCallRes, error) {
modelInfo, err := gateway.GetModelInfoById(ctx, &gateway.GetModelInfoByIdReq{ModelId: modelId})
if err != nil {
return nil, nil, fmt.Errorf("获取模型配置失败: %w", err)
}
// 异步模型 msgTopic 由 gateway.ModelCallResult 在为空时自动生成(唯一、带业务标识),调用方无需管理
responseParams, err := gateway.ModelCallResult(ctx, modelId, modelInfo.ModelManage.ResponseType, sessionId, modelRequestParams, nil)
if err != nil {
return nil, nil, err
}
if g.IsEmpty(responseParams) {
return nil, nil, fmt.Errorf("生成内容为空")
}
outputRes := make([]map[string]any, 0)
for key, val := range responseParams.Content {
outputRes = append(outputRes, map[string]any{
key: val,
})
}
return outputRes, responseParams, nil
}
func HttpCallResultLambda(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]map[string]any, error) {
var method, url, responseType, callbackUrl string
var headers map[string]string
var body map[string]any
var responseMapping map[string]any
n := new([]node.NodePresetField)
err := gconv.Structs(nodeInput.Config.OutputConfig, n)
if err != nil {
return nil, err
}
for _, item := range *n {
switch item.Field {
case "method":
method = gconv.String(item.Value)
case "url":
url = gconv.String(item.Value)
case "headers":
headers = gconv.MapStrStr(item.Value)
case "body":
body = gconv.Map(item.Value)
case "response":
// 先剥掉 {type, value/attrs} 包裹层,得到干净的输出结构模板
responseMapping = gconv.Map(UnwrapSchemaWrapper(gconv.Map(item.Value)))
case "responseType":
responseType = gconv.String(item.Value)
if responseType == "callback" {
callbackUrl = item.Options[0].Config[0].Value
}
}
}
if method == "" {
return nil, fmt.Errorf("method为空")
}
if url == "" {
return nil, fmt.Errorf("url为空")
}
if headers == nil {
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]
}
}
}
}
// 构建请求参数
ProcessValueSourceRecursive(body, nodeInput.Global)
// 递归剥掉 {type, value/attrs} 包裹层,只保留 key/value
wrapper := UnwrapSchemaWrapper(body)
newBody := gconv.Map(wrapper)
// 1. 自己生成唯一 taskId(不用前端给)
taskId := "my_task_" + uuid.New().String() // 自己生成唯一ID
if responseType == "callback" {
newBody[callbackUrl] = utils.GetCallbackURL(ctx, "/httpNodeCallback?task_id="+taskId)
}
// ====================== 核心改动 ======================
// 1. 定义一个空map接收原始HTTP返回结果
var rawHttpResult map[string]any
// 2. 发送请求(不变)
if method == "GET" {
err = commonHttp.Get(ctx, url, headers, &rawHttpResult, newBody)
} else if method == "POST" {
err = commonHttp.Post(ctx, url, headers, &rawHttpResult, newBody)
} else if method == "PUT" {
err = commonHttp.Put(ctx, url, headers, &rawHttpResult, newBody)
} else if method == "DELETE" {
err = commonHttp.Delete(ctx, url, headers, &rawHttpResult, newBody)
} else {
return nil, fmt.Errorf("method 不支持")
}
if err != nil {
return nil, err
}
var e = ""
finalResult := make(map[string]any)
if responseType == "sync" {
httpResultJson := gconv.String(rawHttpResult)
// 按 responseMapping 定义的结构,从 http 返回结果中拷贝对应字段
finalResult = MapResultByTemplate(responseMapping, rawHttpResult)
e = httpResultJson
}
if responseType == "callback" {
var waitResult any
waitResult, err = Wait(ctx, taskId)
if err != nil {
return nil, err
}
request, ok := waitResult.(*ghttp.Request)
if !ok {
return nil, fmt.Errorf("入参类型错误")
}
bodyStr := request.GetBodyString()
// 按 responseMapping 定义的结构,从回调结果中拷贝对应字段
finalResult = MapResultByTemplate(responseMapping, gconv.Map(bodyStr))
e = bodyStr
}
if responseType == "pull" {
return nil, fmt.Errorf("pull 暂不支持")
}
if g.IsEmpty(finalResult) {
return nil, fmt.Errorf("http请求异常,返回结果为空:%v", e)
}
outputRes := make([]map[string]any, 0)
for i, item := range finalResult {
if nodeInput.Config.IsSaveFile {
outputRes = append(outputRes, map[string]any{
fmt.Sprintf("http_file_url:%v", i): item,
})
}
outputRes = append(outputRes, map[string]any{
fmt.Sprintf("%v", i): item,
})
}
return outputRes, nil
}