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 }