212 lines
6.0 KiB
Go
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
|
|
}
|