diff --git a/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator.go b/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator.go index 5e22b17d2a..e1d2339a34 100644 --- a/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator.go +++ b/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator.go @@ -3019,12 +3019,16 @@ type CustomRPCEvaluator struct { Cluster *string `thrift:"cluster,4,optional" frugal:"4,optional,string" form:"cluster" json:"cluster,omitempty" query:"cluster"` // 执行http信息 InvokeHTTPInfo *EvaluatorHTTPInfo `thrift:"invoke_http_info,5,optional" frugal:"5,optional,EvaluatorHTTPInfo" form:"invoke_http_info" json:"invoke_http_info,omitempty" query:"invoke_http_info"` + // 异步执行http信息 + AsyncInvokeHTTPInfo *EvaluatorHTTPInfo `thrift:"async_invoke_http_info,6,optional" frugal:"6,optional,EvaluatorHTTPInfo" form:"async_invoke_http_info" json:"async_invoke_http_info,omitempty" query:"async_invoke_http_info"` // ms Timeout *int64 `thrift:"timeout,10,optional" frugal:"10,optional,i64" form:"timeout" json:"timeout,omitempty" query:"timeout"` // 自定义评估器的限流配置 RateLimit *common.RateLimit `thrift:"rate_limit,11,optional" frugal:"11,optional,common.RateLimit" form:"rate_limit" json:"rate_limit,omitempty" query:"rate_limit"` // extra fields Ext map[string]string `thrift:"ext,12,optional" frugal:"12,optional,map" form:"ext" json:"ext,omitempty" query:"ext"` + // 是否异步执行;true 时调用 AsyncInvokeEvaluator + IsAsync *bool `thrift:"is_async,13,optional" frugal:"13,optional,bool" form:"is_async" json:"is_async,omitempty" query:"is_async"` } func NewCustomRPCEvaluator() *CustomRPCEvaluator { @@ -3089,6 +3093,18 @@ func (p *CustomRPCEvaluator) GetInvokeHTTPInfo() (v *EvaluatorHTTPInfo) { return p.InvokeHTTPInfo } +var CustomRPCEvaluator_AsyncInvokeHTTPInfo_DEFAULT *EvaluatorHTTPInfo + +func (p *CustomRPCEvaluator) GetAsyncInvokeHTTPInfo() (v *EvaluatorHTTPInfo) { + if p == nil { + return + } + if !p.IsSetAsyncInvokeHTTPInfo() { + return CustomRPCEvaluator_AsyncInvokeHTTPInfo_DEFAULT + } + return p.AsyncInvokeHTTPInfo +} + var CustomRPCEvaluator_Timeout_DEFAULT int64 func (p *CustomRPCEvaluator) GetTimeout() (v int64) { @@ -3124,6 +3140,18 @@ func (p *CustomRPCEvaluator) GetExt() (v map[string]string) { } return p.Ext } + +var CustomRPCEvaluator_IsAsync_DEFAULT bool + +func (p *CustomRPCEvaluator) GetIsAsync() (v bool) { + if p == nil { + return + } + if !p.IsSetIsAsync() { + return CustomRPCEvaluator_IsAsync_DEFAULT + } + return *p.IsAsync +} func (p *CustomRPCEvaluator) SetProviderEvaluatorCode(val *string) { p.ProviderEvaluatorCode = val } @@ -3139,6 +3167,9 @@ func (p *CustomRPCEvaluator) SetCluster(val *string) { func (p *CustomRPCEvaluator) SetInvokeHTTPInfo(val *EvaluatorHTTPInfo) { p.InvokeHTTPInfo = val } +func (p *CustomRPCEvaluator) SetAsyncInvokeHTTPInfo(val *EvaluatorHTTPInfo) { + p.AsyncInvokeHTTPInfo = val +} func (p *CustomRPCEvaluator) SetTimeout(val *int64) { p.Timeout = val } @@ -3148,6 +3179,9 @@ func (p *CustomRPCEvaluator) SetRateLimit(val *common.RateLimit) { func (p *CustomRPCEvaluator) SetExt(val map[string]string) { p.Ext = val } +func (p *CustomRPCEvaluator) SetIsAsync(val *bool) { + p.IsAsync = val +} var fieldIDToName_CustomRPCEvaluator = map[int16]string{ 1: "provider_evaluator_code", @@ -3155,9 +3189,11 @@ var fieldIDToName_CustomRPCEvaluator = map[int16]string{ 3: "service_name", 4: "cluster", 5: "invoke_http_info", + 6: "async_invoke_http_info", 10: "timeout", 11: "rate_limit", 12: "ext", + 13: "is_async", } func (p *CustomRPCEvaluator) IsSetProviderEvaluatorCode() bool { @@ -3176,6 +3212,10 @@ func (p *CustomRPCEvaluator) IsSetInvokeHTTPInfo() bool { return p.InvokeHTTPInfo != nil } +func (p *CustomRPCEvaluator) IsSetAsyncInvokeHTTPInfo() bool { + return p.AsyncInvokeHTTPInfo != nil +} + func (p *CustomRPCEvaluator) IsSetTimeout() bool { return p.Timeout != nil } @@ -3188,6 +3228,10 @@ func (p *CustomRPCEvaluator) IsSetExt() bool { return p.Ext != nil } +func (p *CustomRPCEvaluator) IsSetIsAsync() bool { + return p.IsAsync != nil +} + func (p *CustomRPCEvaluator) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -3248,6 +3292,14 @@ func (p *CustomRPCEvaluator) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 6: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField6(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } case 10: if fieldTypeId == thrift.I64 { if err = p.ReadField10(iprot); err != nil { @@ -3272,6 +3324,14 @@ func (p *CustomRPCEvaluator) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 13: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField13(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } default: if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError @@ -3359,6 +3419,14 @@ func (p *CustomRPCEvaluator) ReadField5(iprot thrift.TProtocol) error { p.InvokeHTTPInfo = _field return nil } +func (p *CustomRPCEvaluator) ReadField6(iprot thrift.TProtocol) error { + _field := NewEvaluatorHTTPInfo() + if err := _field.Read(iprot); err != nil { + return err + } + p.AsyncInvokeHTTPInfo = _field + return nil +} func (p *CustomRPCEvaluator) ReadField10(iprot thrift.TProtocol) error { var _field *int64 @@ -3407,6 +3475,17 @@ func (p *CustomRPCEvaluator) ReadField12(iprot thrift.TProtocol) error { p.Ext = _field return nil } +func (p *CustomRPCEvaluator) ReadField13(iprot thrift.TProtocol) error { + + var _field *bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = &v + } + p.IsAsync = _field + return nil +} func (p *CustomRPCEvaluator) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 @@ -3434,6 +3513,10 @@ func (p *CustomRPCEvaluator) Write(oprot thrift.TProtocol) (err error) { fieldId = 5 goto WriteFieldError } + if err = p.writeField6(oprot); err != nil { + fieldId = 6 + goto WriteFieldError + } if err = p.writeField10(oprot); err != nil { fieldId = 10 goto WriteFieldError @@ -3446,6 +3529,10 @@ func (p *CustomRPCEvaluator) Write(oprot thrift.TProtocol) (err error) { fieldId = 12 goto WriteFieldError } + if err = p.writeField13(oprot); err != nil { + fieldId = 13 + goto WriteFieldError + } } if err = oprot.WriteFieldStop(); err != nil { goto WriteFieldStopError @@ -3552,6 +3639,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) } +func (p *CustomRPCEvaluator) writeField6(oprot thrift.TProtocol) (err error) { + if p.IsSetAsyncInvokeHTTPInfo() { + if err = oprot.WriteFieldBegin("async_invoke_http_info", thrift.STRUCT, 6); err != nil { + goto WriteFieldBeginError + } + if err := p.AsyncInvokeHTTPInfo.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 end error: ", p), err) +} func (p *CustomRPCEvaluator) writeField10(oprot thrift.TProtocol) (err error) { if p.IsSetTimeout() { if err = oprot.WriteFieldBegin("timeout", thrift.I64, 10); err != nil { @@ -3617,6 +3722,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 12 end error: ", p), err) } +func (p *CustomRPCEvaluator) writeField13(oprot thrift.TProtocol) (err error) { + if p.IsSetIsAsync() { + if err = oprot.WriteFieldBegin("is_async", thrift.BOOL, 13); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(*p.IsAsync); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 13 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 13 end error: ", p), err) +} func (p *CustomRPCEvaluator) String() string { if p == nil { @@ -3647,6 +3770,9 @@ func (p *CustomRPCEvaluator) DeepEqual(ano *CustomRPCEvaluator) bool { if !p.Field5DeepEqual(ano.InvokeHTTPInfo) { return false } + if !p.Field6DeepEqual(ano.AsyncInvokeHTTPInfo) { + return false + } if !p.Field10DeepEqual(ano.Timeout) { return false } @@ -3656,6 +3782,9 @@ func (p *CustomRPCEvaluator) DeepEqual(ano *CustomRPCEvaluator) bool { if !p.Field12DeepEqual(ano.Ext) { return false } + if !p.Field13DeepEqual(ano.IsAsync) { + return false + } return true } @@ -3709,6 +3838,13 @@ func (p *CustomRPCEvaluator) Field5DeepEqual(src *EvaluatorHTTPInfo) bool { } return true } +func (p *CustomRPCEvaluator) Field6DeepEqual(src *EvaluatorHTTPInfo) bool { + + if !p.AsyncInvokeHTTPInfo.DeepEqual(src) { + return false + } + return true +} func (p *CustomRPCEvaluator) Field10DeepEqual(src *int64) bool { if p.Timeout == src { @@ -3741,6 +3877,18 @@ func (p *CustomRPCEvaluator) Field12DeepEqual(src map[string]string) bool { } return true } +func (p *CustomRPCEvaluator) Field13DeepEqual(src *bool) bool { + + if p.IsAsync == src { + return true + } else if p.IsAsync == nil || src == nil { + return false + } + if *p.IsAsync != *src { + return false + } + return true +} type EvaluatorVersion struct { // 版本id diff --git a/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator_validator.go b/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator_validator.go index cd922a567b..671949d65f 100644 --- a/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator_validator.go +++ b/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/evaluator_validator.go @@ -93,6 +93,11 @@ func (p *CustomRPCEvaluator) IsValid() error { return fmt.Errorf("field InvokeHTTPInfo not valid, %w", err) } } + if p.AsyncInvokeHTTPInfo != nil { + if err := p.AsyncInvokeHTTPInfo.IsValid(); err != nil { + return fmt.Errorf("field AsyncInvokeHTTPInfo not valid, %w", err) + } + } if p.RateLimit != nil { if err := p.RateLimit.IsValid(); err != nil { return fmt.Errorf("field RateLimit not valid, %w", err) diff --git a/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/k-evaluator.go b/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/k-evaluator.go index 50ab60fef0..1f69d99c79 100644 --- a/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/k-evaluator.go +++ b/backend/kitex_gen/coze/loop/evaluation/domain/evaluator/k-evaluator.go @@ -2087,6 +2087,20 @@ func (p *CustomRPCEvaluator) FastRead(buf []byte) (int, error) { goto SkipFieldError } } + case 6: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField6(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } case 10: if fieldTypeId == thrift.I64 { l, err = p.FastReadField10(buf[offset:]) @@ -2129,6 +2143,20 @@ func (p *CustomRPCEvaluator) FastRead(buf []byte) (int, error) { goto SkipFieldError } } + case 13: + if fieldTypeId == thrift.BOOL { + l, err = p.FastReadField13(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } default: l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) offset += l @@ -2221,6 +2249,18 @@ func (p *CustomRPCEvaluator) FastReadField5(buf []byte) (int, error) { return offset, nil } +func (p *CustomRPCEvaluator) FastReadField6(buf []byte) (int, error) { + offset := 0 + _field := NewEvaluatorHTTPInfo() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.AsyncInvokeHTTPInfo = _field + return offset, nil +} + func (p *CustomRPCEvaluator) FastReadField10(buf []byte) (int, error) { offset := 0 @@ -2279,6 +2319,20 @@ func (p *CustomRPCEvaluator) FastReadField12(buf []byte) (int, error) { return offset, nil } +func (p *CustomRPCEvaluator) FastReadField13(buf []byte) (int, error) { + offset := 0 + + var _field *bool + if v, l, err := thrift.Binary.ReadBool(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + _field = &v + } + p.IsAsync = _field + return offset, nil +} + func (p *CustomRPCEvaluator) FastWrite(buf []byte) int { return p.FastWriteNocopy(buf, nil) } @@ -2287,11 +2341,13 @@ func (p *CustomRPCEvaluator) FastWriteNocopy(buf []byte, w thrift.NocopyWriter) offset := 0 if p != nil { offset += p.fastWriteField10(buf[offset:], w) + offset += p.fastWriteField13(buf[offset:], w) offset += p.fastWriteField1(buf[offset:], w) offset += p.fastWriteField2(buf[offset:], w) offset += p.fastWriteField3(buf[offset:], w) offset += p.fastWriteField4(buf[offset:], w) offset += p.fastWriteField5(buf[offset:], w) + offset += p.fastWriteField6(buf[offset:], w) offset += p.fastWriteField11(buf[offset:], w) offset += p.fastWriteField12(buf[offset:], w) } @@ -2307,9 +2363,11 @@ func (p *CustomRPCEvaluator) BLength() int { l += p.field3Length() l += p.field4Length() l += p.field5Length() + l += p.field6Length() l += p.field10Length() l += p.field11Length() l += p.field12Length() + l += p.field13Length() } l += thrift.Binary.FieldStopLength() return l @@ -2358,6 +2416,15 @@ func (p *CustomRPCEvaluator) fastWriteField5(buf []byte, w thrift.NocopyWriter) return offset } +func (p *CustomRPCEvaluator) fastWriteField6(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetAsyncInvokeHTTPInfo() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 6) + offset += p.AsyncInvokeHTTPInfo.FastWriteNocopy(buf[offset:], w) + } + return offset +} + func (p *CustomRPCEvaluator) fastWriteField10(buf []byte, w thrift.NocopyWriter) int { offset := 0 if p.IsSetTimeout() { @@ -2393,6 +2460,15 @@ func (p *CustomRPCEvaluator) fastWriteField12(buf []byte, w thrift.NocopyWriter) return offset } +func (p *CustomRPCEvaluator) fastWriteField13(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetIsAsync() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.BOOL, 13) + offset += thrift.Binary.WriteBool(buf[offset:], *p.IsAsync) + } + return offset +} + func (p *CustomRPCEvaluator) field1Length() int { l := 0 if p.IsSetProviderEvaluatorCode() { @@ -2436,6 +2512,15 @@ func (p *CustomRPCEvaluator) field5Length() int { return l } +func (p *CustomRPCEvaluator) field6Length() int { + l := 0 + if p.IsSetAsyncInvokeHTTPInfo() { + l += thrift.Binary.FieldBeginLength() + l += p.AsyncInvokeHTTPInfo.BLength() + } + return l +} + func (p *CustomRPCEvaluator) field10Length() int { l := 0 if p.IsSetTimeout() { @@ -2469,6 +2554,15 @@ func (p *CustomRPCEvaluator) field12Length() int { return l } +func (p *CustomRPCEvaluator) field13Length() int { + l := 0 + if p.IsSetIsAsync() { + l += thrift.Binary.FieldBeginLength() + l += thrift.Binary.BoolLength() + } + return l +} + func (p *CustomRPCEvaluator) DeepCopy(s interface{}) error { src, ok := s.(*CustomRPCEvaluator) if !ok { @@ -2510,6 +2604,15 @@ func (p *CustomRPCEvaluator) DeepCopy(s interface{}) error { } p.InvokeHTTPInfo = _invokeHTTPInfo + var _asyncInvokeHTTPInfo *EvaluatorHTTPInfo + if src.AsyncInvokeHTTPInfo != nil { + _asyncInvokeHTTPInfo = &EvaluatorHTTPInfo{} + if err := _asyncInvokeHTTPInfo.DeepCopy(src.AsyncInvokeHTTPInfo); err != nil { + return err + } + } + p.AsyncInvokeHTTPInfo = _asyncInvokeHTTPInfo + if src.Timeout != nil { tmp := *src.Timeout p.Timeout = &tmp @@ -2541,6 +2644,11 @@ func (p *CustomRPCEvaluator) DeepCopy(s interface{}) error { } } + if src.IsAsync != nil { + tmp := *src.IsAsync + p.IsAsync = &tmp + } + return nil } diff --git a/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator.go b/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator.go index 542816fc21..5d8d8fb2e6 100644 --- a/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator.go +++ b/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator.go @@ -858,14 +858,16 @@ type CustomRPCEvaluator struct { // 自定义评估器编码 ProviderEvaluatorCode *string `thrift:"provider_evaluator_code,1,optional" frugal:"1,optional,string" form:"provider_evaluator_code" json:"provider_evaluator_code,omitempty" query:"provider_evaluator_code"` // rpc / faas_http - AccessProtocol *EvaluatorAccessProtocol `thrift:"access_protocol,2,optional" frugal:"2,optional,string" form:"access_protocol" json:"access_protocol,omitempty" query:"access_protocol"` - ServiceName *string `thrift:"service_name,3,optional" frugal:"3,optional,string" form:"service_name" json:"service_name,omitempty" query:"service_name"` - Cluster *string `thrift:"cluster,4,optional" frugal:"4,optional,string" form:"cluster" json:"cluster,omitempty" query:"cluster"` - InvokeHTTPInfo *EvaluatorHTTPInfo `thrift:"invoke_http_info,5,optional" frugal:"5,optional,EvaluatorHTTPInfo" form:"invoke_http_info" json:"invoke_http_info,omitempty" query:"invoke_http_info"` + AccessProtocol *EvaluatorAccessProtocol `thrift:"access_protocol,2,optional" frugal:"2,optional,string" form:"access_protocol" json:"access_protocol,omitempty" query:"access_protocol"` + ServiceName *string `thrift:"service_name,3,optional" frugal:"3,optional,string" form:"service_name" json:"service_name,omitempty" query:"service_name"` + Cluster *string `thrift:"cluster,4,optional" frugal:"4,optional,string" form:"cluster" json:"cluster,omitempty" query:"cluster"` + InvokeHTTPInfo *EvaluatorHTTPInfo `thrift:"invoke_http_info,5,optional" frugal:"5,optional,EvaluatorHTTPInfo" form:"invoke_http_info" json:"invoke_http_info,omitempty" query:"invoke_http_info"` + AsyncInvokeHTTPInfo *EvaluatorHTTPInfo `thrift:"async_invoke_http_info,6,optional" frugal:"6,optional,EvaluatorHTTPInfo" form:"async_invoke_http_info" json:"async_invoke_http_info,omitempty" query:"async_invoke_http_info"` // ms Timeout *int64 `thrift:"timeout,10,optional" frugal:"10,optional,i64" form:"timeout" json:"timeout,omitempty" query:"timeout"` RateLimit *common.RateLimit `thrift:"rate_limit,11,optional" frugal:"11,optional,common.RateLimit" form:"rate_limit" json:"rate_limit,omitempty" query:"rate_limit"` Ext map[string]string `thrift:"ext,12,optional" frugal:"12,optional,map" form:"ext" json:"ext,omitempty" query:"ext"` + IsAsync *bool `thrift:"is_async,13,optional" frugal:"13,optional,bool" form:"is_async" json:"is_async,omitempty" query:"is_async"` } func NewCustomRPCEvaluator() *CustomRPCEvaluator { @@ -935,6 +937,18 @@ func (p *CustomRPCEvaluator) GetInvokeHTTPInfo() (v *EvaluatorHTTPInfo) { return p.InvokeHTTPInfo } +var CustomRPCEvaluator_AsyncInvokeHTTPInfo_DEFAULT *EvaluatorHTTPInfo + +func (p *CustomRPCEvaluator) GetAsyncInvokeHTTPInfo() (v *EvaluatorHTTPInfo) { + if p == nil { + return + } + if !p.IsSetAsyncInvokeHTTPInfo() { + return CustomRPCEvaluator_AsyncInvokeHTTPInfo_DEFAULT + } + return p.AsyncInvokeHTTPInfo +} + var CustomRPCEvaluator_Timeout_DEFAULT int64 func (p *CustomRPCEvaluator) GetTimeout() (v int64) { @@ -970,6 +984,18 @@ func (p *CustomRPCEvaluator) GetExt() (v map[string]string) { } return p.Ext } + +var CustomRPCEvaluator_IsAsync_DEFAULT bool + +func (p *CustomRPCEvaluator) GetIsAsync() (v bool) { + if p == nil { + return + } + if !p.IsSetIsAsync() { + return CustomRPCEvaluator_IsAsync_DEFAULT + } + return *p.IsAsync +} func (p *CustomRPCEvaluator) SetProviderEvaluatorCode(val *string) { p.ProviderEvaluatorCode = val } @@ -985,6 +1011,9 @@ func (p *CustomRPCEvaluator) SetCluster(val *string) { func (p *CustomRPCEvaluator) SetInvokeHTTPInfo(val *EvaluatorHTTPInfo) { p.InvokeHTTPInfo = val } +func (p *CustomRPCEvaluator) SetAsyncInvokeHTTPInfo(val *EvaluatorHTTPInfo) { + p.AsyncInvokeHTTPInfo = val +} func (p *CustomRPCEvaluator) SetTimeout(val *int64) { p.Timeout = val } @@ -994,6 +1023,9 @@ func (p *CustomRPCEvaluator) SetRateLimit(val *common.RateLimit) { func (p *CustomRPCEvaluator) SetExt(val map[string]string) { p.Ext = val } +func (p *CustomRPCEvaluator) SetIsAsync(val *bool) { + p.IsAsync = val +} var fieldIDToName_CustomRPCEvaluator = map[int16]string{ 1: "provider_evaluator_code", @@ -1001,9 +1033,11 @@ var fieldIDToName_CustomRPCEvaluator = map[int16]string{ 3: "service_name", 4: "cluster", 5: "invoke_http_info", + 6: "async_invoke_http_info", 10: "timeout", 11: "rate_limit", 12: "ext", + 13: "is_async", } func (p *CustomRPCEvaluator) IsSetProviderEvaluatorCode() bool { @@ -1026,6 +1060,10 @@ func (p *CustomRPCEvaluator) IsSetInvokeHTTPInfo() bool { return p.InvokeHTTPInfo != nil } +func (p *CustomRPCEvaluator) IsSetAsyncInvokeHTTPInfo() bool { + return p.AsyncInvokeHTTPInfo != nil +} + func (p *CustomRPCEvaluator) IsSetTimeout() bool { return p.Timeout != nil } @@ -1038,6 +1076,10 @@ func (p *CustomRPCEvaluator) IsSetExt() bool { return p.Ext != nil } +func (p *CustomRPCEvaluator) IsSetIsAsync() bool { + return p.IsAsync != nil +} + func (p *CustomRPCEvaluator) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -1096,6 +1138,14 @@ func (p *CustomRPCEvaluator) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 6: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField6(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } case 10: if fieldTypeId == thrift.I64 { if err = p.ReadField10(iprot); err != nil { @@ -1120,6 +1170,14 @@ func (p *CustomRPCEvaluator) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 13: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField13(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } default: if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError @@ -1201,6 +1259,14 @@ func (p *CustomRPCEvaluator) ReadField5(iprot thrift.TProtocol) error { p.InvokeHTTPInfo = _field return nil } +func (p *CustomRPCEvaluator) ReadField6(iprot thrift.TProtocol) error { + _field := NewEvaluatorHTTPInfo() + if err := _field.Read(iprot); err != nil { + return err + } + p.AsyncInvokeHTTPInfo = _field + return nil +} func (p *CustomRPCEvaluator) ReadField10(iprot thrift.TProtocol) error { var _field *int64 @@ -1249,6 +1315,17 @@ func (p *CustomRPCEvaluator) ReadField12(iprot thrift.TProtocol) error { p.Ext = _field return nil } +func (p *CustomRPCEvaluator) ReadField13(iprot thrift.TProtocol) error { + + var _field *bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = &v + } + p.IsAsync = _field + return nil +} func (p *CustomRPCEvaluator) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 @@ -1276,6 +1353,10 @@ func (p *CustomRPCEvaluator) Write(oprot thrift.TProtocol) (err error) { fieldId = 5 goto WriteFieldError } + if err = p.writeField6(oprot); err != nil { + fieldId = 6 + goto WriteFieldError + } if err = p.writeField10(oprot); err != nil { fieldId = 10 goto WriteFieldError @@ -1288,6 +1369,10 @@ func (p *CustomRPCEvaluator) Write(oprot thrift.TProtocol) (err error) { fieldId = 12 goto WriteFieldError } + if err = p.writeField13(oprot); err != nil { + fieldId = 13 + goto WriteFieldError + } } if err = oprot.WriteFieldStop(); err != nil { goto WriteFieldStopError @@ -1396,6 +1481,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) } +func (p *CustomRPCEvaluator) writeField6(oprot thrift.TProtocol) (err error) { + if p.IsSetAsyncInvokeHTTPInfo() { + if err = oprot.WriteFieldBegin("async_invoke_http_info", thrift.STRUCT, 6); err != nil { + goto WriteFieldBeginError + } + if err := p.AsyncInvokeHTTPInfo.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 end error: ", p), err) +} func (p *CustomRPCEvaluator) writeField10(oprot thrift.TProtocol) (err error) { if p.IsSetTimeout() { if err = oprot.WriteFieldBegin("timeout", thrift.I64, 10); err != nil { @@ -1461,6 +1564,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 12 end error: ", p), err) } +func (p *CustomRPCEvaluator) writeField13(oprot thrift.TProtocol) (err error) { + if p.IsSetIsAsync() { + if err = oprot.WriteFieldBegin("is_async", thrift.BOOL, 13); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(*p.IsAsync); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 13 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 13 end error: ", p), err) +} func (p *CustomRPCEvaluator) String() string { if p == nil { @@ -1491,6 +1612,9 @@ func (p *CustomRPCEvaluator) DeepEqual(ano *CustomRPCEvaluator) bool { if !p.Field5DeepEqual(ano.InvokeHTTPInfo) { return false } + if !p.Field6DeepEqual(ano.AsyncInvokeHTTPInfo) { + return false + } if !p.Field10DeepEqual(ano.Timeout) { return false } @@ -1500,6 +1624,9 @@ func (p *CustomRPCEvaluator) DeepEqual(ano *CustomRPCEvaluator) bool { if !p.Field12DeepEqual(ano.Ext) { return false } + if !p.Field13DeepEqual(ano.IsAsync) { + return false + } return true } @@ -1558,6 +1685,13 @@ func (p *CustomRPCEvaluator) Field5DeepEqual(src *EvaluatorHTTPInfo) bool { } return true } +func (p *CustomRPCEvaluator) Field6DeepEqual(src *EvaluatorHTTPInfo) bool { + + if !p.AsyncInvokeHTTPInfo.DeepEqual(src) { + return false + } + return true +} func (p *CustomRPCEvaluator) Field10DeepEqual(src *int64) bool { if p.Timeout == src { @@ -1590,6 +1724,18 @@ func (p *CustomRPCEvaluator) Field12DeepEqual(src map[string]string) bool { } return true } +func (p *CustomRPCEvaluator) Field13DeepEqual(src *bool) bool { + + if p.IsAsync == src { + return true + } else if p.IsAsync == nil || src == nil { + return false + } + if *p.IsAsync != *src { + return false + } + return true +} // Agent评估器Prompt配置输出规则 type AgentEvaluatorPromptConfigOutputRules struct { diff --git a/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator_validator.go b/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator_validator.go index d6a5859832..61284f796f 100644 --- a/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator_validator.go +++ b/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/evaluator_validator.go @@ -41,6 +41,11 @@ func (p *CustomRPCEvaluator) IsValid() error { return fmt.Errorf("field InvokeHTTPInfo not valid, %w", err) } } + if p.AsyncInvokeHTTPInfo != nil { + if err := p.AsyncInvokeHTTPInfo.IsValid(); err != nil { + return fmt.Errorf("field AsyncInvokeHTTPInfo not valid, %w", err) + } + } if p.RateLimit != nil { if err := p.RateLimit.IsValid(); err != nil { return fmt.Errorf("field RateLimit not valid, %w", err) diff --git a/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/k-evaluator.go b/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/k-evaluator.go index f9ec501820..136e75e1cf 100644 --- a/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/k-evaluator.go +++ b/backend/kitex_gen/coze/loop/evaluation/domain_openapi/evaluator/k-evaluator.go @@ -664,6 +664,20 @@ func (p *CustomRPCEvaluator) FastRead(buf []byte) (int, error) { goto SkipFieldError } } + case 6: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField6(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } case 10: if fieldTypeId == thrift.I64 { l, err = p.FastReadField10(buf[offset:]) @@ -706,6 +720,20 @@ func (p *CustomRPCEvaluator) FastRead(buf []byte) (int, error) { goto SkipFieldError } } + case 13: + if fieldTypeId == thrift.BOOL { + l, err = p.FastReadField13(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } default: l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) offset += l @@ -792,6 +820,18 @@ func (p *CustomRPCEvaluator) FastReadField5(buf []byte) (int, error) { return offset, nil } +func (p *CustomRPCEvaluator) FastReadField6(buf []byte) (int, error) { + offset := 0 + _field := NewEvaluatorHTTPInfo() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.AsyncInvokeHTTPInfo = _field + return offset, nil +} + func (p *CustomRPCEvaluator) FastReadField10(buf []byte) (int, error) { offset := 0 @@ -850,6 +890,20 @@ func (p *CustomRPCEvaluator) FastReadField12(buf []byte) (int, error) { return offset, nil } +func (p *CustomRPCEvaluator) FastReadField13(buf []byte) (int, error) { + offset := 0 + + var _field *bool + if v, l, err := thrift.Binary.ReadBool(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + _field = &v + } + p.IsAsync = _field + return offset, nil +} + func (p *CustomRPCEvaluator) FastWrite(buf []byte) int { return p.FastWriteNocopy(buf, nil) } @@ -858,11 +912,13 @@ func (p *CustomRPCEvaluator) FastWriteNocopy(buf []byte, w thrift.NocopyWriter) offset := 0 if p != nil { offset += p.fastWriteField10(buf[offset:], w) + offset += p.fastWriteField13(buf[offset:], w) offset += p.fastWriteField1(buf[offset:], w) offset += p.fastWriteField2(buf[offset:], w) offset += p.fastWriteField3(buf[offset:], w) offset += p.fastWriteField4(buf[offset:], w) offset += p.fastWriteField5(buf[offset:], w) + offset += p.fastWriteField6(buf[offset:], w) offset += p.fastWriteField11(buf[offset:], w) offset += p.fastWriteField12(buf[offset:], w) } @@ -878,9 +934,11 @@ func (p *CustomRPCEvaluator) BLength() int { l += p.field3Length() l += p.field4Length() l += p.field5Length() + l += p.field6Length() l += p.field10Length() l += p.field11Length() l += p.field12Length() + l += p.field13Length() } l += thrift.Binary.FieldStopLength() return l @@ -931,6 +989,15 @@ func (p *CustomRPCEvaluator) fastWriteField5(buf []byte, w thrift.NocopyWriter) return offset } +func (p *CustomRPCEvaluator) fastWriteField6(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetAsyncInvokeHTTPInfo() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 6) + offset += p.AsyncInvokeHTTPInfo.FastWriteNocopy(buf[offset:], w) + } + return offset +} + func (p *CustomRPCEvaluator) fastWriteField10(buf []byte, w thrift.NocopyWriter) int { offset := 0 if p.IsSetTimeout() { @@ -966,6 +1033,15 @@ func (p *CustomRPCEvaluator) fastWriteField12(buf []byte, w thrift.NocopyWriter) return offset } +func (p *CustomRPCEvaluator) fastWriteField13(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetIsAsync() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.BOOL, 13) + offset += thrift.Binary.WriteBool(buf[offset:], *p.IsAsync) + } + return offset +} + func (p *CustomRPCEvaluator) field1Length() int { l := 0 if p.IsSetProviderEvaluatorCode() { @@ -1011,6 +1087,15 @@ func (p *CustomRPCEvaluator) field5Length() int { return l } +func (p *CustomRPCEvaluator) field6Length() int { + l := 0 + if p.IsSetAsyncInvokeHTTPInfo() { + l += thrift.Binary.FieldBeginLength() + l += p.AsyncInvokeHTTPInfo.BLength() + } + return l +} + func (p *CustomRPCEvaluator) field10Length() int { l := 0 if p.IsSetTimeout() { @@ -1044,6 +1129,15 @@ func (p *CustomRPCEvaluator) field12Length() int { return l } +func (p *CustomRPCEvaluator) field13Length() int { + l := 0 + if p.IsSetIsAsync() { + l += thrift.Binary.FieldBeginLength() + l += thrift.Binary.BoolLength() + } + return l +} + func (p *CustomRPCEvaluator) DeepCopy(s interface{}) error { src, ok := s.(*CustomRPCEvaluator) if !ok { @@ -1088,6 +1182,15 @@ func (p *CustomRPCEvaluator) DeepCopy(s interface{}) error { } p.InvokeHTTPInfo = _invokeHTTPInfo + var _asyncInvokeHTTPInfo *EvaluatorHTTPInfo + if src.AsyncInvokeHTTPInfo != nil { + _asyncInvokeHTTPInfo = &EvaluatorHTTPInfo{} + if err := _asyncInvokeHTTPInfo.DeepCopy(src.AsyncInvokeHTTPInfo); err != nil { + return err + } + } + p.AsyncInvokeHTTPInfo = _asyncInvokeHTTPInfo + if src.Timeout != nil { tmp := *src.Timeout p.Timeout = &tmp @@ -1119,6 +1222,11 @@ func (p *CustomRPCEvaluator) DeepCopy(s interface{}) error { } } + if src.IsAsync != nil { + tmp := *src.IsAsync + p.IsAsync = &tmp + } + return nil } diff --git a/backend/kitex_gen/coze/loop/evaluation/evalspiservice/client.go b/backend/kitex_gen/coze/loop/evaluation/evalspiservice/client.go index 2e942e8f8b..9011a7434c 100644 --- a/backend/kitex_gen/coze/loop/evaluation/evalspiservice/client.go +++ b/backend/kitex_gen/coze/loop/evaluation/evalspiservice/client.go @@ -15,6 +15,7 @@ type Client interface { InvokeEvalTarget(ctx context.Context, req *spi.InvokeEvalTargetRequest, callOptions ...callopt.Option) (r *spi.InvokeEvalTargetResponse, err error) AsyncInvokeEvalTarget(ctx context.Context, req *spi.AsyncInvokeEvalTargetRequest, callOptions ...callopt.Option) (r *spi.AsyncInvokeEvalTargetResponse, err error) InvokeEvaluator(ctx context.Context, req *spi.InvokeEvaluatorRequest, callOptions ...callopt.Option) (r *spi.InvokeEvaluatorResponse, err error) + AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest, callOptions ...callopt.Option) (r *spi.AsyncInvokeEvaluatorResponse, err error) } // NewClient creates a client for the service defined in IDL. @@ -65,3 +66,8 @@ func (p *kEvalSPIServiceClient) InvokeEvaluator(ctx context.Context, req *spi.In ctx = client.NewCtxWithCallOptions(ctx, callOptions) return p.kClient.InvokeEvaluator(ctx, req) } + +func (p *kEvalSPIServiceClient) AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest, callOptions ...callopt.Option) (r *spi.AsyncInvokeEvaluatorResponse, err error) { + ctx = client.NewCtxWithCallOptions(ctx, callOptions) + return p.kClient.AsyncInvokeEvaluator(ctx, req) +} diff --git a/backend/kitex_gen/coze/loop/evaluation/evalspiservice/evalspiservice.go b/backend/kitex_gen/coze/loop/evaluation/evalspiservice/evalspiservice.go index bb533a1387..6e5fa9c27b 100644 --- a/backend/kitex_gen/coze/loop/evaluation/evalspiservice/evalspiservice.go +++ b/backend/kitex_gen/coze/loop/evaluation/evalspiservice/evalspiservice.go @@ -42,6 +42,13 @@ var serviceMethods = map[string]kitex.MethodInfo{ false, kitex.WithStreamingMode(kitex.StreamingNone), ), + "AsyncInvokeEvaluator": kitex.NewMethodInfo( + asyncInvokeEvaluatorHandler, + newEvaluationSPIServiceAsyncInvokeEvaluatorArgs, + newEvaluationSPIServiceAsyncInvokeEvaluatorResult, + false, + kitex.WithStreamingMode(kitex.StreamingNone), + ), } var ( @@ -151,6 +158,25 @@ func newEvaluationSPIServiceInvokeEvaluatorResult() interface{} { return spi.NewEvaluationSPIServiceInvokeEvaluatorResult() } +func asyncInvokeEvaluatorHandler(ctx context.Context, handler interface{}, arg, result interface{}) error { + realArg := arg.(*spi.EvaluationSPIServiceAsyncInvokeEvaluatorArgs) + realResult := result.(*spi.EvaluationSPIServiceAsyncInvokeEvaluatorResult) + success, err := handler.(spi.EvaluationSPIService).AsyncInvokeEvaluator(ctx, realArg.Req) + if err != nil { + return err + } + realResult.Success = success + return nil +} + +func newEvaluationSPIServiceAsyncInvokeEvaluatorArgs() interface{} { + return spi.NewEvaluationSPIServiceAsyncInvokeEvaluatorArgs() +} + +func newEvaluationSPIServiceAsyncInvokeEvaluatorResult() interface{} { + return spi.NewEvaluationSPIServiceAsyncInvokeEvaluatorResult() +} + type kClient struct { c client.Client sc client.Streaming @@ -202,3 +228,13 @@ func (p *kClient) InvokeEvaluator(ctx context.Context, req *spi.InvokeEvaluatorR } return _result.GetSuccess(), nil } + +func (p *kClient) AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest) (r *spi.AsyncInvokeEvaluatorResponse, err error) { + var _args spi.EvaluationSPIServiceAsyncInvokeEvaluatorArgs + _args.Req = req + var _result spi.EvaluationSPIServiceAsyncInvokeEvaluatorResult + if err = p.c.Call(ctx, "AsyncInvokeEvaluator", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} diff --git a/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi.go b/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi.go index c345ce4a04..7496522663 100644 --- a/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi.go +++ b/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi.go @@ -7977,6 +7977,644 @@ func (p *InvokeEvaluatorResponse) Field255DeepEqual(src *base.BaseResp) bool { return true } +// asynchronously invoke custom evaluator request +type AsyncInvokeEvaluatorRequest struct { + WorkspaceID *int64 `thrift:"workspace_id,1,optional" frugal:"1,optional,i64" form:"workspace_id" json:"workspace_id,string,omitempty" query:"workspace_id"` + // execution id, report result with the same id + InvokeID *int64 `thrift:"invoke_id,2,optional" frugal:"2,optional,i64" form:"invoke_id" json:"invoke_id,string,omitempty" query:"invoke_id"` + Evaluator *InvokeCustomEvaluator `thrift:"evaluator,3,optional" frugal:"3,optional,InvokeCustomEvaluator" form:"evaluator" json:"evaluator,omitempty" query:"evaluator"` + InputData *InvokeEvaluatorInputData `thrift:"input_data,4,optional" frugal:"4,optional,InvokeEvaluatorInputData" form:"input_data" json:"input_data,omitempty" query:"input_data"` + Base *base.Base `thrift:"Base,255,optional" frugal:"255,optional,base.Base" form:"Base" json:"Base,omitempty" query:"Base"` +} + +func NewAsyncInvokeEvaluatorRequest() *AsyncInvokeEvaluatorRequest { + return &AsyncInvokeEvaluatorRequest{} +} + +func (p *AsyncInvokeEvaluatorRequest) InitDefault() { +} + +var AsyncInvokeEvaluatorRequest_WorkspaceID_DEFAULT int64 + +func (p *AsyncInvokeEvaluatorRequest) GetWorkspaceID() (v int64) { + if p == nil { + return + } + if !p.IsSetWorkspaceID() { + return AsyncInvokeEvaluatorRequest_WorkspaceID_DEFAULT + } + return *p.WorkspaceID +} + +var AsyncInvokeEvaluatorRequest_InvokeID_DEFAULT int64 + +func (p *AsyncInvokeEvaluatorRequest) GetInvokeID() (v int64) { + if p == nil { + return + } + if !p.IsSetInvokeID() { + return AsyncInvokeEvaluatorRequest_InvokeID_DEFAULT + } + return *p.InvokeID +} + +var AsyncInvokeEvaluatorRequest_Evaluator_DEFAULT *InvokeCustomEvaluator + +func (p *AsyncInvokeEvaluatorRequest) GetEvaluator() (v *InvokeCustomEvaluator) { + if p == nil { + return + } + if !p.IsSetEvaluator() { + return AsyncInvokeEvaluatorRequest_Evaluator_DEFAULT + } + return p.Evaluator +} + +var AsyncInvokeEvaluatorRequest_InputData_DEFAULT *InvokeEvaluatorInputData + +func (p *AsyncInvokeEvaluatorRequest) GetInputData() (v *InvokeEvaluatorInputData) { + if p == nil { + return + } + if !p.IsSetInputData() { + return AsyncInvokeEvaluatorRequest_InputData_DEFAULT + } + return p.InputData +} + +var AsyncInvokeEvaluatorRequest_Base_DEFAULT *base.Base + +func (p *AsyncInvokeEvaluatorRequest) GetBase() (v *base.Base) { + if p == nil { + return + } + if !p.IsSetBase() { + return AsyncInvokeEvaluatorRequest_Base_DEFAULT + } + return p.Base +} +func (p *AsyncInvokeEvaluatorRequest) SetWorkspaceID(val *int64) { + p.WorkspaceID = val +} +func (p *AsyncInvokeEvaluatorRequest) SetInvokeID(val *int64) { + p.InvokeID = val +} +func (p *AsyncInvokeEvaluatorRequest) SetEvaluator(val *InvokeCustomEvaluator) { + p.Evaluator = val +} +func (p *AsyncInvokeEvaluatorRequest) SetInputData(val *InvokeEvaluatorInputData) { + p.InputData = val +} +func (p *AsyncInvokeEvaluatorRequest) SetBase(val *base.Base) { + p.Base = val +} + +var fieldIDToName_AsyncInvokeEvaluatorRequest = map[int16]string{ + 1: "workspace_id", + 2: "invoke_id", + 3: "evaluator", + 4: "input_data", + 255: "Base", +} + +func (p *AsyncInvokeEvaluatorRequest) IsSetWorkspaceID() bool { + return p.WorkspaceID != nil +} + +func (p *AsyncInvokeEvaluatorRequest) IsSetInvokeID() bool { + return p.InvokeID != nil +} + +func (p *AsyncInvokeEvaluatorRequest) IsSetEvaluator() bool { + return p.Evaluator != nil +} + +func (p *AsyncInvokeEvaluatorRequest) IsSetInputData() bool { + return p.InputData != nil +} + +func (p *AsyncInvokeEvaluatorRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *AsyncInvokeEvaluatorRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.I64 { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 4: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField4(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AsyncInvokeEvaluatorRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *AsyncInvokeEvaluatorRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.WorkspaceID = _field + return nil +} +func (p *AsyncInvokeEvaluatorRequest) ReadField2(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.InvokeID = _field + return nil +} +func (p *AsyncInvokeEvaluatorRequest) ReadField3(iprot thrift.TProtocol) error { + _field := NewInvokeCustomEvaluator() + if err := _field.Read(iprot); err != nil { + return err + } + p.Evaluator = _field + return nil +} +func (p *AsyncInvokeEvaluatorRequest) ReadField4(iprot thrift.TProtocol) error { + _field := NewInvokeEvaluatorInputData() + if err := _field.Read(iprot); err != nil { + return err + } + p.InputData = _field + return nil +} +func (p *AsyncInvokeEvaluatorRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *AsyncInvokeEvaluatorRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("AsyncInvokeEvaluatorRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField4(oprot); err != nil { + fieldId = 4 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *AsyncInvokeEvaluatorRequest) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetWorkspaceID() { + if err = oprot.WriteFieldBegin("workspace_id", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.WorkspaceID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *AsyncInvokeEvaluatorRequest) writeField2(oprot thrift.TProtocol) (err error) { + if p.IsSetInvokeID() { + if err = oprot.WriteFieldBegin("invoke_id", thrift.I64, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.InvokeID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *AsyncInvokeEvaluatorRequest) writeField3(oprot thrift.TProtocol) (err error) { + if p.IsSetEvaluator() { + if err = oprot.WriteFieldBegin("evaluator", thrift.STRUCT, 3); err != nil { + goto WriteFieldBeginError + } + if err := p.Evaluator.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *AsyncInvokeEvaluatorRequest) writeField4(oprot thrift.TProtocol) (err error) { + if p.IsSetInputData() { + if err = oprot.WriteFieldBegin("input_data", thrift.STRUCT, 4); err != nil { + goto WriteFieldBeginError + } + if err := p.InputData.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) +} +func (p *AsyncInvokeEvaluatorRequest) writeField255(oprot thrift.TProtocol) (err error) { + if p.IsSetBase() { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *AsyncInvokeEvaluatorRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("AsyncInvokeEvaluatorRequest(%+v)", *p) + +} + +func (p *AsyncInvokeEvaluatorRequest) DeepEqual(ano *AsyncInvokeEvaluatorRequest) bool { + if p == ano { + return true + } else if p == nil || ano == nil { + return false + } + if !p.Field1DeepEqual(ano.WorkspaceID) { + return false + } + if !p.Field2DeepEqual(ano.InvokeID) { + return false + } + if !p.Field3DeepEqual(ano.Evaluator) { + return false + } + if !p.Field4DeepEqual(ano.InputData) { + return false + } + if !p.Field255DeepEqual(ano.Base) { + return false + } + return true +} + +func (p *AsyncInvokeEvaluatorRequest) Field1DeepEqual(src *int64) bool { + + if p.WorkspaceID == src { + return true + } else if p.WorkspaceID == nil || src == nil { + return false + } + if *p.WorkspaceID != *src { + return false + } + return true +} +func (p *AsyncInvokeEvaluatorRequest) Field2DeepEqual(src *int64) bool { + + if p.InvokeID == src { + return true + } else if p.InvokeID == nil || src == nil { + return false + } + if *p.InvokeID != *src { + return false + } + return true +} +func (p *AsyncInvokeEvaluatorRequest) Field3DeepEqual(src *InvokeCustomEvaluator) bool { + + if !p.Evaluator.DeepEqual(src) { + return false + } + return true +} +func (p *AsyncInvokeEvaluatorRequest) Field4DeepEqual(src *InvokeEvaluatorInputData) bool { + + if !p.InputData.DeepEqual(src) { + return false + } + return true +} +func (p *AsyncInvokeEvaluatorRequest) Field255DeepEqual(src *base.Base) bool { + + if !p.Base.DeepEqual(src) { + return false + } + return true +} + +type AsyncInvokeEvaluatorResponse struct { + BaseResp *base.BaseResp `thrift:"BaseResp,255" frugal:"255,default,base.BaseResp" form:"BaseResp" json:"BaseResp" query:"BaseResp"` +} + +func NewAsyncInvokeEvaluatorResponse() *AsyncInvokeEvaluatorResponse { + return &AsyncInvokeEvaluatorResponse{} +} + +func (p *AsyncInvokeEvaluatorResponse) InitDefault() { +} + +var AsyncInvokeEvaluatorResponse_BaseResp_DEFAULT *base.BaseResp + +func (p *AsyncInvokeEvaluatorResponse) GetBaseResp() (v *base.BaseResp) { + if p == nil { + return + } + if !p.IsSetBaseResp() { + return AsyncInvokeEvaluatorResponse_BaseResp_DEFAULT + } + return p.BaseResp +} +func (p *AsyncInvokeEvaluatorResponse) SetBaseResp(val *base.BaseResp) { + p.BaseResp = val +} + +var fieldIDToName_AsyncInvokeEvaluatorResponse = map[int16]string{ + 255: "BaseResp", +} + +func (p *AsyncInvokeEvaluatorResponse) IsSetBaseResp() bool { + return p.BaseResp != nil +} + +func (p *AsyncInvokeEvaluatorResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AsyncInvokeEvaluatorResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *AsyncInvokeEvaluatorResponse) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBaseResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.BaseResp = _field + return nil +} + +func (p *AsyncInvokeEvaluatorResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("AsyncInvokeEvaluatorResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *AsyncInvokeEvaluatorResponse) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.BaseResp.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *AsyncInvokeEvaluatorResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("AsyncInvokeEvaluatorResponse(%+v)", *p) + +} + +func (p *AsyncInvokeEvaluatorResponse) DeepEqual(ano *AsyncInvokeEvaluatorResponse) bool { + if p == ano { + return true + } else if p == nil || ano == nil { + return false + } + if !p.Field255DeepEqual(ano.BaseResp) { + return false + } + return true +} + +func (p *AsyncInvokeEvaluatorResponse) Field255DeepEqual(src *base.BaseResp) bool { + + if !p.BaseResp.DeepEqual(src) { + return false + } + return true +} + type EvaluationSPIService interface { SearchEvalTarget(ctx context.Context, req *SearchEvalTargetRequest) (r *SearchEvalTargetResponse, err error) @@ -7985,6 +8623,8 @@ type EvaluationSPIService interface { AsyncInvokeEvalTarget(ctx context.Context, req *AsyncInvokeEvalTargetRequest) (r *AsyncInvokeEvalTargetResponse, err error) // invoke custom evaluator InvokeEvaluator(ctx context.Context, req *InvokeEvaluatorRequest) (r *InvokeEvaluatorResponse, err error) + + AsyncInvokeEvaluator(ctx context.Context, req *AsyncInvokeEvaluatorRequest) (r *AsyncInvokeEvaluatorResponse, err error) } type EvaluationSPIServiceClient struct { @@ -8049,6 +8689,15 @@ func (p *EvaluationSPIServiceClient) InvokeEvaluator(ctx context.Context, req *I } return _result.GetSuccess(), nil } +func (p *EvaluationSPIServiceClient) AsyncInvokeEvaluator(ctx context.Context, req *AsyncInvokeEvaluatorRequest) (r *AsyncInvokeEvaluatorResponse, err error) { + var _args EvaluationSPIServiceAsyncInvokeEvaluatorArgs + _args.Req = req + var _result EvaluationSPIServiceAsyncInvokeEvaluatorResult + if err = p.Client_().Call(ctx, "AsyncInvokeEvaluator", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} type EvaluationSPIServiceProcessor struct { processorMap map[string]thrift.TProcessorFunction @@ -8074,6 +8723,7 @@ func NewEvaluationSPIServiceProcessor(handler EvaluationSPIService) *EvaluationS self.AddToProcessorMap("InvokeEvalTarget", &evaluationSPIServiceProcessorInvokeEvalTarget{handler: handler}) self.AddToProcessorMap("AsyncInvokeEvalTarget", &evaluationSPIServiceProcessorAsyncInvokeEvalTarget{handler: handler}) self.AddToProcessorMap("InvokeEvaluator", &evaluationSPIServiceProcessorInvokeEvaluator{handler: handler}) + self.AddToProcessorMap("AsyncInvokeEvaluator", &evaluationSPIServiceProcessorAsyncInvokeEvaluator{handler: handler}) return self } func (p *EvaluationSPIServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { @@ -8081,29 +8731,173 @@ func (p *EvaluationSPIServiceProcessor) Process(ctx context.Context, iprot, opro if err != nil { return false, err } - if processor, ok := p.GetProcessorFunction(name); ok { - return processor.Process(ctx, seqId, iprot, oprot) + if processor, ok := p.GetProcessorFunction(name); ok { + return processor.Process(ctx, seqId, iprot, oprot) + } + iprot.Skip(thrift.STRUCT) + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name) + oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, x +} + +type evaluationSPIServiceProcessorSearchEvalTarget struct { + handler EvaluationSPIService +} + +func (p *evaluationSPIServiceProcessorSearchEvalTarget) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := EvaluationSPIServiceSearchEvalTargetArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("SearchEvalTarget", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := EvaluationSPIServiceSearchEvalTargetResult{} + var retval *SearchEvalTargetResponse + if retval, err2 = p.handler.SearchEvalTarget(ctx, args.Req); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing SearchEvalTarget: "+err2.Error()) + oprot.WriteMessageBegin("SearchEvalTarget", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("SearchEvalTarget", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type evaluationSPIServiceProcessorInvokeEvalTarget struct { + handler EvaluationSPIService +} + +func (p *evaluationSPIServiceProcessorInvokeEvalTarget) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := EvaluationSPIServiceInvokeEvalTargetArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("InvokeEvalTarget", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := EvaluationSPIServiceInvokeEvalTargetResult{} + var retval *InvokeEvalTargetResponse + if retval, err2 = p.handler.InvokeEvalTarget(ctx, args.Req); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing InvokeEvalTarget: "+err2.Error()) + oprot.WriteMessageBegin("InvokeEvalTarget", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("InvokeEvalTarget", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type evaluationSPIServiceProcessorAsyncInvokeEvalTarget struct { + handler EvaluationSPIService +} + +func (p *evaluationSPIServiceProcessorAsyncInvokeEvalTarget) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := EvaluationSPIServiceAsyncInvokeEvalTargetArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("AsyncInvokeEvalTarget", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := EvaluationSPIServiceAsyncInvokeEvalTargetResult{} + var retval *AsyncInvokeEvalTargetResponse + if retval, err2 = p.handler.AsyncInvokeEvalTarget(ctx, args.Req); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing AsyncInvokeEvalTarget: "+err2.Error()) + oprot.WriteMessageBegin("AsyncInvokeEvalTarget", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("AsyncInvokeEvalTarget", thrift.REPLY, seqId); err2 != nil { + err = err2 } - iprot.Skip(thrift.STRUCT) - iprot.ReadMessageEnd() - x := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name) - oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId) - x.Write(oprot) - oprot.WriteMessageEnd() - oprot.Flush(ctx) - return false, x + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err } -type evaluationSPIServiceProcessorSearchEvalTarget struct { +type evaluationSPIServiceProcessorInvokeEvaluator struct { handler EvaluationSPIService } -func (p *evaluationSPIServiceProcessorSearchEvalTarget) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { - args := EvaluationSPIServiceSearchEvalTargetArgs{} +func (p *evaluationSPIServiceProcessorInvokeEvaluator) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := EvaluationSPIServiceInvokeEvaluatorArgs{} if err = args.Read(iprot); err != nil { iprot.ReadMessageEnd() x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) - oprot.WriteMessageBegin("SearchEvalTarget", thrift.EXCEPTION, seqId) + oprot.WriteMessageBegin("InvokeEvaluator", thrift.EXCEPTION, seqId) x.Write(oprot) oprot.WriteMessageEnd() oprot.Flush(ctx) @@ -8112,11 +8906,11 @@ func (p *evaluationSPIServiceProcessorSearchEvalTarget) Process(ctx context.Cont iprot.ReadMessageEnd() var err2 error - result := EvaluationSPIServiceSearchEvalTargetResult{} - var retval *SearchEvalTargetResponse - if retval, err2 = p.handler.SearchEvalTarget(ctx, args.Req); err2 != nil { - x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing SearchEvalTarget: "+err2.Error()) - oprot.WriteMessageBegin("SearchEvalTarget", thrift.EXCEPTION, seqId) + result := EvaluationSPIServiceInvokeEvaluatorResult{} + var retval *InvokeEvaluatorResponse + if retval, err2 = p.handler.InvokeEvaluator(ctx, args.Req); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing InvokeEvaluator: "+err2.Error()) + oprot.WriteMessageBegin("InvokeEvaluator", thrift.EXCEPTION, seqId) x.Write(oprot) oprot.WriteMessageEnd() oprot.Flush(ctx) @@ -8124,7 +8918,7 @@ func (p *evaluationSPIServiceProcessorSearchEvalTarget) Process(ctx context.Cont } else { result.Success = retval } - if err2 = oprot.WriteMessageBegin("SearchEvalTarget", thrift.REPLY, seqId); err2 != nil { + if err2 = oprot.WriteMessageBegin("InvokeEvaluator", thrift.REPLY, seqId); err2 != nil { err = err2 } if err2 = result.Write(oprot); err == nil && err2 != nil { @@ -8142,16 +8936,16 @@ func (p *evaluationSPIServiceProcessorSearchEvalTarget) Process(ctx context.Cont return true, err } -type evaluationSPIServiceProcessorInvokeEvalTarget struct { +type evaluationSPIServiceProcessorAsyncInvokeEvaluator struct { handler EvaluationSPIService } -func (p *evaluationSPIServiceProcessorInvokeEvalTarget) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { - args := EvaluationSPIServiceInvokeEvalTargetArgs{} +func (p *evaluationSPIServiceProcessorAsyncInvokeEvaluator) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := EvaluationSPIServiceAsyncInvokeEvaluatorArgs{} if err = args.Read(iprot); err != nil { iprot.ReadMessageEnd() x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) - oprot.WriteMessageBegin("InvokeEvalTarget", thrift.EXCEPTION, seqId) + oprot.WriteMessageBegin("AsyncInvokeEvaluator", thrift.EXCEPTION, seqId) x.Write(oprot) oprot.WriteMessageEnd() oprot.Flush(ctx) @@ -8160,11 +8954,11 @@ func (p *evaluationSPIServiceProcessorInvokeEvalTarget) Process(ctx context.Cont iprot.ReadMessageEnd() var err2 error - result := EvaluationSPIServiceInvokeEvalTargetResult{} - var retval *InvokeEvalTargetResponse - if retval, err2 = p.handler.InvokeEvalTarget(ctx, args.Req); err2 != nil { - x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing InvokeEvalTarget: "+err2.Error()) - oprot.WriteMessageBegin("InvokeEvalTarget", thrift.EXCEPTION, seqId) + result := EvaluationSPIServiceAsyncInvokeEvaluatorResult{} + var retval *AsyncInvokeEvaluatorResponse + if retval, err2 = p.handler.AsyncInvokeEvaluator(ctx, args.Req); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing AsyncInvokeEvaluator: "+err2.Error()) + oprot.WriteMessageBegin("AsyncInvokeEvaluator", thrift.EXCEPTION, seqId) x.Write(oprot) oprot.WriteMessageEnd() oprot.Flush(ctx) @@ -8172,7 +8966,7 @@ func (p *evaluationSPIServiceProcessorInvokeEvalTarget) Process(ctx context.Cont } else { result.Success = retval } - if err2 = oprot.WriteMessageBegin("InvokeEvalTarget", thrift.REPLY, seqId); err2 != nil { + if err2 = oprot.WriteMessageBegin("AsyncInvokeEvaluator", thrift.REPLY, seqId); err2 != nil { err = err2 } if err2 = result.Write(oprot); err == nil && err2 != nil { @@ -8190,137 +8984,385 @@ func (p *evaluationSPIServiceProcessorInvokeEvalTarget) Process(ctx context.Cont return true, err } -type evaluationSPIServiceProcessorAsyncInvokeEvalTarget struct { - handler EvaluationSPIService +type EvaluationSPIServiceSearchEvalTargetArgs struct { + Req *SearchEvalTargetRequest `thrift:"req,1" frugal:"1,default,SearchEvalTargetRequest"` +} + +func NewEvaluationSPIServiceSearchEvalTargetArgs() *EvaluationSPIServiceSearchEvalTargetArgs { + return &EvaluationSPIServiceSearchEvalTargetArgs{} +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) InitDefault() { +} + +var EvaluationSPIServiceSearchEvalTargetArgs_Req_DEFAULT *SearchEvalTargetRequest + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) GetReq() (v *SearchEvalTargetRequest) { + if p == nil { + return + } + if !p.IsSetReq() { + return EvaluationSPIServiceSearchEvalTargetArgs_Req_DEFAULT + } + return p.Req +} +func (p *EvaluationSPIServiceSearchEvalTargetArgs) SetReq(val *SearchEvalTargetRequest) { + p.Req = val +} + +var fieldIDToName_EvaluationSPIServiceSearchEvalTargetArgs = map[int16]string{ + 1: "req", +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) IsSetReq() bool { + return p.Req != nil +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceSearchEvalTargetArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewSearchEvalTargetRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Req = _field + return nil +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("SearchEvalTarget_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("req", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Req.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("EvaluationSPIServiceSearchEvalTargetArgs(%+v)", *p) + +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) DeepEqual(ano *EvaluationSPIServiceSearchEvalTargetArgs) bool { + if p == ano { + return true + } else if p == nil || ano == nil { + return false + } + if !p.Field1DeepEqual(ano.Req) { + return false + } + return true +} + +func (p *EvaluationSPIServiceSearchEvalTargetArgs) Field1DeepEqual(src *SearchEvalTargetRequest) bool { + + if !p.Req.DeepEqual(src) { + return false + } + return true +} + +type EvaluationSPIServiceSearchEvalTargetResult struct { + Success *SearchEvalTargetResponse `thrift:"success,0,optional" frugal:"0,optional,SearchEvalTargetResponse"` +} + +func NewEvaluationSPIServiceSearchEvalTargetResult() *EvaluationSPIServiceSearchEvalTargetResult { + return &EvaluationSPIServiceSearchEvalTargetResult{} +} + +func (p *EvaluationSPIServiceSearchEvalTargetResult) InitDefault() { +} + +var EvaluationSPIServiceSearchEvalTargetResult_Success_DEFAULT *SearchEvalTargetResponse + +func (p *EvaluationSPIServiceSearchEvalTargetResult) GetSuccess() (v *SearchEvalTargetResponse) { + if p == nil { + return + } + if !p.IsSetSuccess() { + return EvaluationSPIServiceSearchEvalTargetResult_Success_DEFAULT + } + return p.Success +} +func (p *EvaluationSPIServiceSearchEvalTargetResult) SetSuccess(x interface{}) { + p.Success = x.(*SearchEvalTargetResponse) +} + +var fieldIDToName_EvaluationSPIServiceSearchEvalTargetResult = map[int16]string{ + 0: "success", +} + +func (p *EvaluationSPIServiceSearchEvalTargetResult) IsSetSuccess() bool { + return p.Success != nil } -func (p *evaluationSPIServiceProcessorAsyncInvokeEvalTarget) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { - args := EvaluationSPIServiceAsyncInvokeEvalTargetArgs{} - if err = args.Read(iprot); err != nil { - iprot.ReadMessageEnd() - x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) - oprot.WriteMessageBegin("AsyncInvokeEvalTarget", thrift.EXCEPTION, seqId) - x.Write(oprot) - oprot.WriteMessageEnd() - oprot.Flush(ctx) - return false, err +func (p *EvaluationSPIServiceSearchEvalTargetResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError } - iprot.ReadMessageEnd() - var err2 error - result := EvaluationSPIServiceAsyncInvokeEvalTargetResult{} - var retval *AsyncInvokeEvalTargetResponse - if retval, err2 = p.handler.AsyncInvokeEvalTarget(ctx, args.Req); err2 != nil { - x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing AsyncInvokeEvalTarget: "+err2.Error()) - oprot.WriteMessageBegin("AsyncInvokeEvalTarget", thrift.EXCEPTION, seqId) - x.Write(oprot) - oprot.WriteMessageEnd() - oprot.Flush(ctx) - return true, err2 - } else { - result.Success = retval + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } } - if err2 = oprot.WriteMessageBegin("AsyncInvokeEvalTarget", thrift.REPLY, seqId); err2 != nil { - err = err2 + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError } - if err2 = result.Write(oprot); err == nil && err2 != nil { - err = err2 + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceSearchEvalTargetResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *EvaluationSPIServiceSearchEvalTargetResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewSearchEvalTargetResponse() + if err := _field.Read(iprot); err != nil { + return err } - if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { - err = err2 + p.Success = _field + return nil +} + +func (p *EvaluationSPIServiceSearchEvalTargetResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("SearchEvalTarget_result"); err != nil { + goto WriteStructBeginError } - if err2 = oprot.Flush(ctx); err == nil && err2 != nil { - err = err2 + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } } - if err != nil { - return + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError } - return true, err + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -type evaluationSPIServiceProcessorInvokeEvaluator struct { - handler EvaluationSPIService +func (p *EvaluationSPIServiceSearchEvalTargetResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) } -func (p *evaluationSPIServiceProcessorInvokeEvaluator) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { - args := EvaluationSPIServiceInvokeEvaluatorArgs{} - if err = args.Read(iprot); err != nil { - iprot.ReadMessageEnd() - x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) - oprot.WriteMessageBegin("InvokeEvaluator", thrift.EXCEPTION, seqId) - x.Write(oprot) - oprot.WriteMessageEnd() - oprot.Flush(ctx) - return false, err +func (p *EvaluationSPIServiceSearchEvalTargetResult) String() string { + if p == nil { + return "" } + return fmt.Sprintf("EvaluationSPIServiceSearchEvalTargetResult(%+v)", *p) - iprot.ReadMessageEnd() - var err2 error - result := EvaluationSPIServiceInvokeEvaluatorResult{} - var retval *InvokeEvaluatorResponse - if retval, err2 = p.handler.InvokeEvaluator(ctx, args.Req); err2 != nil { - x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing InvokeEvaluator: "+err2.Error()) - oprot.WriteMessageBegin("InvokeEvaluator", thrift.EXCEPTION, seqId) - x.Write(oprot) - oprot.WriteMessageEnd() - oprot.Flush(ctx) - return true, err2 - } else { - result.Success = retval - } - if err2 = oprot.WriteMessageBegin("InvokeEvaluator", thrift.REPLY, seqId); err2 != nil { - err = err2 - } - if err2 = result.Write(oprot); err == nil && err2 != nil { - err = err2 - } - if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { - err = err2 +} + +func (p *EvaluationSPIServiceSearchEvalTargetResult) DeepEqual(ano *EvaluationSPIServiceSearchEvalTargetResult) bool { + if p == ano { + return true + } else if p == nil || ano == nil { + return false } - if err2 = oprot.Flush(ctx); err == nil && err2 != nil { - err = err2 + if !p.Field0DeepEqual(ano.Success) { + return false } - if err != nil { - return + return true +} + +func (p *EvaluationSPIServiceSearchEvalTargetResult) Field0DeepEqual(src *SearchEvalTargetResponse) bool { + + if !p.Success.DeepEqual(src) { + return false } - return true, err + return true } -type EvaluationSPIServiceSearchEvalTargetArgs struct { - Req *SearchEvalTargetRequest `thrift:"req,1" frugal:"1,default,SearchEvalTargetRequest"` +type EvaluationSPIServiceInvokeEvalTargetArgs struct { + Req *InvokeEvalTargetRequest `thrift:"req,1" frugal:"1,default,InvokeEvalTargetRequest"` } -func NewEvaluationSPIServiceSearchEvalTargetArgs() *EvaluationSPIServiceSearchEvalTargetArgs { - return &EvaluationSPIServiceSearchEvalTargetArgs{} +func NewEvaluationSPIServiceInvokeEvalTargetArgs() *EvaluationSPIServiceInvokeEvalTargetArgs { + return &EvaluationSPIServiceInvokeEvalTargetArgs{} } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) InitDefault() { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) InitDefault() { } -var EvaluationSPIServiceSearchEvalTargetArgs_Req_DEFAULT *SearchEvalTargetRequest +var EvaluationSPIServiceInvokeEvalTargetArgs_Req_DEFAULT *InvokeEvalTargetRequest -func (p *EvaluationSPIServiceSearchEvalTargetArgs) GetReq() (v *SearchEvalTargetRequest) { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) GetReq() (v *InvokeEvalTargetRequest) { if p == nil { return } if !p.IsSetReq() { - return EvaluationSPIServiceSearchEvalTargetArgs_Req_DEFAULT + return EvaluationSPIServiceInvokeEvalTargetArgs_Req_DEFAULT } return p.Req } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) SetReq(val *SearchEvalTargetRequest) { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) SetReq(val *InvokeEvalTargetRequest) { p.Req = val } -var fieldIDToName_EvaluationSPIServiceSearchEvalTargetArgs = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceInvokeEvalTargetArgs = map[int16]string{ 1: "req", } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) IsSetReq() bool { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) IsSetReq() bool { return p.Req != nil } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -8365,7 +9407,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceSearchEvalTargetArgs[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvalTargetArgs[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -8375,8 +9417,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) ReadField1(iprot thrift.TProtocol) error { - _field := NewSearchEvalTargetRequest() +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewInvokeEvalTargetRequest() if err := _field.Read(iprot); err != nil { return err } @@ -8384,9 +9426,9 @@ func (p *EvaluationSPIServiceSearchEvalTargetArgs) ReadField1(iprot thrift.TProt return nil } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("SearchEvalTarget_args"); err != nil { + if err = oprot.WriteStructBegin("InvokeEvalTarget_args"); err != nil { goto WriteStructBeginError } if p != nil { @@ -8412,7 +9454,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) writeField1(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) writeField1(oprot thrift.TProtocol) (err error) { if err = oprot.WriteFieldBegin("req", thrift.STRUCT, 1); err != nil { goto WriteFieldBeginError } @@ -8429,15 +9471,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) String() string { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceSearchEvalTargetArgs(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceInvokeEvalTargetArgs(%+v)", *p) } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) DeepEqual(ano *EvaluationSPIServiceSearchEvalTargetArgs) bool { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) DeepEqual(ano *EvaluationSPIServiceInvokeEvalTargetArgs) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -8449,7 +9491,7 @@ func (p *EvaluationSPIServiceSearchEvalTargetArgs) DeepEqual(ano *EvaluationSPIS return true } -func (p *EvaluationSPIServiceSearchEvalTargetArgs) Field1DeepEqual(src *SearchEvalTargetRequest) bool { +func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Field1DeepEqual(src *InvokeEvalTargetRequest) bool { if !p.Req.DeepEqual(src) { return false @@ -8457,41 +9499,41 @@ func (p *EvaluationSPIServiceSearchEvalTargetArgs) Field1DeepEqual(src *SearchEv return true } -type EvaluationSPIServiceSearchEvalTargetResult struct { - Success *SearchEvalTargetResponse `thrift:"success,0,optional" frugal:"0,optional,SearchEvalTargetResponse"` +type EvaluationSPIServiceInvokeEvalTargetResult struct { + Success *InvokeEvalTargetResponse `thrift:"success,0,optional" frugal:"0,optional,InvokeEvalTargetResponse"` } -func NewEvaluationSPIServiceSearchEvalTargetResult() *EvaluationSPIServiceSearchEvalTargetResult { - return &EvaluationSPIServiceSearchEvalTargetResult{} +func NewEvaluationSPIServiceInvokeEvalTargetResult() *EvaluationSPIServiceInvokeEvalTargetResult { + return &EvaluationSPIServiceInvokeEvalTargetResult{} } -func (p *EvaluationSPIServiceSearchEvalTargetResult) InitDefault() { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) InitDefault() { } -var EvaluationSPIServiceSearchEvalTargetResult_Success_DEFAULT *SearchEvalTargetResponse +var EvaluationSPIServiceInvokeEvalTargetResult_Success_DEFAULT *InvokeEvalTargetResponse -func (p *EvaluationSPIServiceSearchEvalTargetResult) GetSuccess() (v *SearchEvalTargetResponse) { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) GetSuccess() (v *InvokeEvalTargetResponse) { if p == nil { return } if !p.IsSetSuccess() { - return EvaluationSPIServiceSearchEvalTargetResult_Success_DEFAULT + return EvaluationSPIServiceInvokeEvalTargetResult_Success_DEFAULT } return p.Success } -func (p *EvaluationSPIServiceSearchEvalTargetResult) SetSuccess(x interface{}) { - p.Success = x.(*SearchEvalTargetResponse) +func (p *EvaluationSPIServiceInvokeEvalTargetResult) SetSuccess(x interface{}) { + p.Success = x.(*InvokeEvalTargetResponse) } -var fieldIDToName_EvaluationSPIServiceSearchEvalTargetResult = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceInvokeEvalTargetResult = map[int16]string{ 0: "success", } -func (p *EvaluationSPIServiceSearchEvalTargetResult) IsSetSuccess() bool { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) IsSetSuccess() bool { return p.Success != nil } -func (p *EvaluationSPIServiceSearchEvalTargetResult) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -8536,7 +9578,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceSearchEvalTargetResult[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvalTargetResult[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -8546,8 +9588,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceSearchEvalTargetResult) ReadField0(iprot thrift.TProtocol) error { - _field := NewSearchEvalTargetResponse() +func (p *EvaluationSPIServiceInvokeEvalTargetResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewInvokeEvalTargetResponse() if err := _field.Read(iprot); err != nil { return err } @@ -8555,9 +9597,9 @@ func (p *EvaluationSPIServiceSearchEvalTargetResult) ReadField0(iprot thrift.TPr return nil } -func (p *EvaluationSPIServiceSearchEvalTargetResult) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("SearchEvalTarget_result"); err != nil { + if err = oprot.WriteStructBegin("InvokeEvalTarget_result"); err != nil { goto WriteStructBeginError } if p != nil { @@ -8583,7 +9625,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceSearchEvalTargetResult) writeField0(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) writeField0(oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { goto WriteFieldBeginError @@ -8602,15 +9644,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) } -func (p *EvaluationSPIServiceSearchEvalTargetResult) String() string { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceSearchEvalTargetResult(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceInvokeEvalTargetResult(%+v)", *p) } -func (p *EvaluationSPIServiceSearchEvalTargetResult) DeepEqual(ano *EvaluationSPIServiceSearchEvalTargetResult) bool { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) DeepEqual(ano *EvaluationSPIServiceInvokeEvalTargetResult) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -8622,7 +9664,7 @@ func (p *EvaluationSPIServiceSearchEvalTargetResult) DeepEqual(ano *EvaluationSP return true } -func (p *EvaluationSPIServiceSearchEvalTargetResult) Field0DeepEqual(src *SearchEvalTargetResponse) bool { +func (p *EvaluationSPIServiceInvokeEvalTargetResult) Field0DeepEqual(src *InvokeEvalTargetResponse) bool { if !p.Success.DeepEqual(src) { return false @@ -8630,41 +9672,41 @@ func (p *EvaluationSPIServiceSearchEvalTargetResult) Field0DeepEqual(src *Search return true } -type EvaluationSPIServiceInvokeEvalTargetArgs struct { - Req *InvokeEvalTargetRequest `thrift:"req,1" frugal:"1,default,InvokeEvalTargetRequest"` +type EvaluationSPIServiceAsyncInvokeEvalTargetArgs struct { + Req *AsyncInvokeEvalTargetRequest `thrift:"req,1" frugal:"1,default,AsyncInvokeEvalTargetRequest"` } -func NewEvaluationSPIServiceInvokeEvalTargetArgs() *EvaluationSPIServiceInvokeEvalTargetArgs { - return &EvaluationSPIServiceInvokeEvalTargetArgs{} +func NewEvaluationSPIServiceAsyncInvokeEvalTargetArgs() *EvaluationSPIServiceAsyncInvokeEvalTargetArgs { + return &EvaluationSPIServiceAsyncInvokeEvalTargetArgs{} } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) InitDefault() { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) InitDefault() { } -var EvaluationSPIServiceInvokeEvalTargetArgs_Req_DEFAULT *InvokeEvalTargetRequest +var EvaluationSPIServiceAsyncInvokeEvalTargetArgs_Req_DEFAULT *AsyncInvokeEvalTargetRequest -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) GetReq() (v *InvokeEvalTargetRequest) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) GetReq() (v *AsyncInvokeEvalTargetRequest) { if p == nil { return } if !p.IsSetReq() { - return EvaluationSPIServiceInvokeEvalTargetArgs_Req_DEFAULT + return EvaluationSPIServiceAsyncInvokeEvalTargetArgs_Req_DEFAULT } return p.Req } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) SetReq(val *InvokeEvalTargetRequest) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) SetReq(val *AsyncInvokeEvalTargetRequest) { p.Req = val } -var fieldIDToName_EvaluationSPIServiceInvokeEvalTargetArgs = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetArgs = map[int16]string{ 1: "req", } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) IsSetReq() bool { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) IsSetReq() bool { return p.Req != nil } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -8709,7 +9751,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvalTargetArgs[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetArgs[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -8719,8 +9761,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) ReadField1(iprot thrift.TProtocol) error { - _field := NewInvokeEvalTargetRequest() +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewAsyncInvokeEvalTargetRequest() if err := _field.Read(iprot); err != nil { return err } @@ -8728,9 +9770,9 @@ func (p *EvaluationSPIServiceInvokeEvalTargetArgs) ReadField1(iprot thrift.TProt return nil } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("InvokeEvalTarget_args"); err != nil { + if err = oprot.WriteStructBegin("AsyncInvokeEvalTarget_args"); err != nil { goto WriteStructBeginError } if p != nil { @@ -8756,7 +9798,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) writeField1(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) writeField1(oprot thrift.TProtocol) (err error) { if err = oprot.WriteFieldBegin("req", thrift.STRUCT, 1); err != nil { goto WriteFieldBeginError } @@ -8773,15 +9815,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) String() string { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceInvokeEvalTargetArgs(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceAsyncInvokeEvalTargetArgs(%+v)", *p) } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) DeepEqual(ano *EvaluationSPIServiceInvokeEvalTargetArgs) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) DeepEqual(ano *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -8793,7 +9835,7 @@ func (p *EvaluationSPIServiceInvokeEvalTargetArgs) DeepEqual(ano *EvaluationSPIS return true } -func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Field1DeepEqual(src *InvokeEvalTargetRequest) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Field1DeepEqual(src *AsyncInvokeEvalTargetRequest) bool { if !p.Req.DeepEqual(src) { return false @@ -8801,41 +9843,41 @@ func (p *EvaluationSPIServiceInvokeEvalTargetArgs) Field1DeepEqual(src *InvokeEv return true } -type EvaluationSPIServiceInvokeEvalTargetResult struct { - Success *InvokeEvalTargetResponse `thrift:"success,0,optional" frugal:"0,optional,InvokeEvalTargetResponse"` +type EvaluationSPIServiceAsyncInvokeEvalTargetResult struct { + Success *AsyncInvokeEvalTargetResponse `thrift:"success,0,optional" frugal:"0,optional,AsyncInvokeEvalTargetResponse"` } -func NewEvaluationSPIServiceInvokeEvalTargetResult() *EvaluationSPIServiceInvokeEvalTargetResult { - return &EvaluationSPIServiceInvokeEvalTargetResult{} +func NewEvaluationSPIServiceAsyncInvokeEvalTargetResult() *EvaluationSPIServiceAsyncInvokeEvalTargetResult { + return &EvaluationSPIServiceAsyncInvokeEvalTargetResult{} } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) InitDefault() { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) InitDefault() { } -var EvaluationSPIServiceInvokeEvalTargetResult_Success_DEFAULT *InvokeEvalTargetResponse +var EvaluationSPIServiceAsyncInvokeEvalTargetResult_Success_DEFAULT *AsyncInvokeEvalTargetResponse -func (p *EvaluationSPIServiceInvokeEvalTargetResult) GetSuccess() (v *InvokeEvalTargetResponse) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) GetSuccess() (v *AsyncInvokeEvalTargetResponse) { if p == nil { return } if !p.IsSetSuccess() { - return EvaluationSPIServiceInvokeEvalTargetResult_Success_DEFAULT + return EvaluationSPIServiceAsyncInvokeEvalTargetResult_Success_DEFAULT } return p.Success } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) SetSuccess(x interface{}) { - p.Success = x.(*InvokeEvalTargetResponse) +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) SetSuccess(x interface{}) { + p.Success = x.(*AsyncInvokeEvalTargetResponse) } -var fieldIDToName_EvaluationSPIServiceInvokeEvalTargetResult = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetResult = map[int16]string{ 0: "success", } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) IsSetSuccess() bool { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) IsSetSuccess() bool { return p.Success != nil } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -8880,7 +9922,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvalTargetResult[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetResult[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -8890,8 +9932,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) ReadField0(iprot thrift.TProtocol) error { - _field := NewInvokeEvalTargetResponse() +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewAsyncInvokeEvalTargetResponse() if err := _field.Read(iprot); err != nil { return err } @@ -8899,9 +9941,9 @@ func (p *EvaluationSPIServiceInvokeEvalTargetResult) ReadField0(iprot thrift.TPr return nil } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("InvokeEvalTarget_result"); err != nil { + if err = oprot.WriteStructBegin("AsyncInvokeEvalTarget_result"); err != nil { goto WriteStructBeginError } if p != nil { @@ -8927,7 +9969,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) writeField0(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) writeField0(oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { goto WriteFieldBeginError @@ -8946,15 +9988,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) String() string { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceInvokeEvalTargetResult(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceAsyncInvokeEvalTargetResult(%+v)", *p) } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) DeepEqual(ano *EvaluationSPIServiceInvokeEvalTargetResult) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) DeepEqual(ano *EvaluationSPIServiceAsyncInvokeEvalTargetResult) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -8966,7 +10008,7 @@ func (p *EvaluationSPIServiceInvokeEvalTargetResult) DeepEqual(ano *EvaluationSP return true } -func (p *EvaluationSPIServiceInvokeEvalTargetResult) Field0DeepEqual(src *InvokeEvalTargetResponse) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Field0DeepEqual(src *AsyncInvokeEvalTargetResponse) bool { if !p.Success.DeepEqual(src) { return false @@ -8974,41 +10016,41 @@ func (p *EvaluationSPIServiceInvokeEvalTargetResult) Field0DeepEqual(src *Invoke return true } -type EvaluationSPIServiceAsyncInvokeEvalTargetArgs struct { - Req *AsyncInvokeEvalTargetRequest `thrift:"req,1" frugal:"1,default,AsyncInvokeEvalTargetRequest"` +type EvaluationSPIServiceInvokeEvaluatorArgs struct { + Req *InvokeEvaluatorRequest `thrift:"req,1" frugal:"1,default,InvokeEvaluatorRequest"` } -func NewEvaluationSPIServiceAsyncInvokeEvalTargetArgs() *EvaluationSPIServiceAsyncInvokeEvalTargetArgs { - return &EvaluationSPIServiceAsyncInvokeEvalTargetArgs{} +func NewEvaluationSPIServiceInvokeEvaluatorArgs() *EvaluationSPIServiceInvokeEvaluatorArgs { + return &EvaluationSPIServiceInvokeEvaluatorArgs{} } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) InitDefault() { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) InitDefault() { } -var EvaluationSPIServiceAsyncInvokeEvalTargetArgs_Req_DEFAULT *AsyncInvokeEvalTargetRequest +var EvaluationSPIServiceInvokeEvaluatorArgs_Req_DEFAULT *InvokeEvaluatorRequest -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) GetReq() (v *AsyncInvokeEvalTargetRequest) { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) GetReq() (v *InvokeEvaluatorRequest) { if p == nil { return } if !p.IsSetReq() { - return EvaluationSPIServiceAsyncInvokeEvalTargetArgs_Req_DEFAULT + return EvaluationSPIServiceInvokeEvaluatorArgs_Req_DEFAULT } return p.Req } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) SetReq(val *AsyncInvokeEvalTargetRequest) { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) SetReq(val *InvokeEvaluatorRequest) { p.Req = val } -var fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetArgs = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceInvokeEvaluatorArgs = map[int16]string{ 1: "req", } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) IsSetReq() bool { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) IsSetReq() bool { return p.Req != nil } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -9053,7 +10095,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetArgs[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvaluatorArgs[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -9063,8 +10105,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) ReadField1(iprot thrift.TProtocol) error { - _field := NewAsyncInvokeEvalTargetRequest() +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewInvokeEvaluatorRequest() if err := _field.Read(iprot); err != nil { return err } @@ -9072,9 +10114,9 @@ func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) ReadField1(iprot thrift. return nil } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("AsyncInvokeEvalTarget_args"); err != nil { + if err = oprot.WriteStructBegin("InvokeEvaluator_args"); err != nil { goto WriteStructBeginError } if p != nil { @@ -9100,7 +10142,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) writeField1(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) writeField1(oprot thrift.TProtocol) (err error) { if err = oprot.WriteFieldBegin("req", thrift.STRUCT, 1); err != nil { goto WriteFieldBeginError } @@ -9117,15 +10159,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) String() string { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceAsyncInvokeEvalTargetArgs(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceInvokeEvaluatorArgs(%+v)", *p) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) DeepEqual(ano *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) bool { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) DeepEqual(ano *EvaluationSPIServiceInvokeEvaluatorArgs) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -9137,7 +10179,7 @@ func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) DeepEqual(ano *Evaluatio return true } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Field1DeepEqual(src *AsyncInvokeEvalTargetRequest) bool { +func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Field1DeepEqual(src *InvokeEvaluatorRequest) bool { if !p.Req.DeepEqual(src) { return false @@ -9145,41 +10187,41 @@ func (p *EvaluationSPIServiceAsyncInvokeEvalTargetArgs) Field1DeepEqual(src *Asy return true } -type EvaluationSPIServiceAsyncInvokeEvalTargetResult struct { - Success *AsyncInvokeEvalTargetResponse `thrift:"success,0,optional" frugal:"0,optional,AsyncInvokeEvalTargetResponse"` +type EvaluationSPIServiceInvokeEvaluatorResult struct { + Success *InvokeEvaluatorResponse `thrift:"success,0,optional" frugal:"0,optional,InvokeEvaluatorResponse"` } -func NewEvaluationSPIServiceAsyncInvokeEvalTargetResult() *EvaluationSPIServiceAsyncInvokeEvalTargetResult { - return &EvaluationSPIServiceAsyncInvokeEvalTargetResult{} +func NewEvaluationSPIServiceInvokeEvaluatorResult() *EvaluationSPIServiceInvokeEvaluatorResult { + return &EvaluationSPIServiceInvokeEvaluatorResult{} } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) InitDefault() { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) InitDefault() { } -var EvaluationSPIServiceAsyncInvokeEvalTargetResult_Success_DEFAULT *AsyncInvokeEvalTargetResponse +var EvaluationSPIServiceInvokeEvaluatorResult_Success_DEFAULT *InvokeEvaluatorResponse -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) GetSuccess() (v *AsyncInvokeEvalTargetResponse) { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) GetSuccess() (v *InvokeEvaluatorResponse) { if p == nil { return } if !p.IsSetSuccess() { - return EvaluationSPIServiceAsyncInvokeEvalTargetResult_Success_DEFAULT + return EvaluationSPIServiceInvokeEvaluatorResult_Success_DEFAULT } return p.Success } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) SetSuccess(x interface{}) { - p.Success = x.(*AsyncInvokeEvalTargetResponse) +func (p *EvaluationSPIServiceInvokeEvaluatorResult) SetSuccess(x interface{}) { + p.Success = x.(*InvokeEvaluatorResponse) } -var fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetResult = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceInvokeEvaluatorResult = map[int16]string{ 0: "success", } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) IsSetSuccess() bool { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) IsSetSuccess() bool { return p.Success != nil } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -9224,7 +10266,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvalTargetResult[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvaluatorResult[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -9234,8 +10276,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) ReadField0(iprot thrift.TProtocol) error { - _field := NewAsyncInvokeEvalTargetResponse() +func (p *EvaluationSPIServiceInvokeEvaluatorResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewInvokeEvaluatorResponse() if err := _field.Read(iprot); err != nil { return err } @@ -9243,9 +10285,9 @@ func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) ReadField0(iprot thrif return nil } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("AsyncInvokeEvalTarget_result"); err != nil { + if err = oprot.WriteStructBegin("InvokeEvaluator_result"); err != nil { goto WriteStructBeginError } if p != nil { @@ -9271,7 +10313,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) writeField0(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) writeField0(oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { goto WriteFieldBeginError @@ -9290,15 +10332,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) String() string { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceAsyncInvokeEvalTargetResult(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceInvokeEvaluatorResult(%+v)", *p) } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) DeepEqual(ano *EvaluationSPIServiceAsyncInvokeEvalTargetResult) bool { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) DeepEqual(ano *EvaluationSPIServiceInvokeEvaluatorResult) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -9310,7 +10352,7 @@ func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) DeepEqual(ano *Evaluat return true } -func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Field0DeepEqual(src *AsyncInvokeEvalTargetResponse) bool { +func (p *EvaluationSPIServiceInvokeEvaluatorResult) Field0DeepEqual(src *InvokeEvaluatorResponse) bool { if !p.Success.DeepEqual(src) { return false @@ -9318,41 +10360,41 @@ func (p *EvaluationSPIServiceAsyncInvokeEvalTargetResult) Field0DeepEqual(src *A return true } -type EvaluationSPIServiceInvokeEvaluatorArgs struct { - Req *InvokeEvaluatorRequest `thrift:"req,1" frugal:"1,default,InvokeEvaluatorRequest"` +type EvaluationSPIServiceAsyncInvokeEvaluatorArgs struct { + Req *AsyncInvokeEvaluatorRequest `thrift:"req,1" frugal:"1,default,AsyncInvokeEvaluatorRequest"` } -func NewEvaluationSPIServiceInvokeEvaluatorArgs() *EvaluationSPIServiceInvokeEvaluatorArgs { - return &EvaluationSPIServiceInvokeEvaluatorArgs{} +func NewEvaluationSPIServiceAsyncInvokeEvaluatorArgs() *EvaluationSPIServiceAsyncInvokeEvaluatorArgs { + return &EvaluationSPIServiceAsyncInvokeEvaluatorArgs{} } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) InitDefault() { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) InitDefault() { } -var EvaluationSPIServiceInvokeEvaluatorArgs_Req_DEFAULT *InvokeEvaluatorRequest +var EvaluationSPIServiceAsyncInvokeEvaluatorArgs_Req_DEFAULT *AsyncInvokeEvaluatorRequest -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) GetReq() (v *InvokeEvaluatorRequest) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) GetReq() (v *AsyncInvokeEvaluatorRequest) { if p == nil { return } if !p.IsSetReq() { - return EvaluationSPIServiceInvokeEvaluatorArgs_Req_DEFAULT + return EvaluationSPIServiceAsyncInvokeEvaluatorArgs_Req_DEFAULT } return p.Req } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) SetReq(val *InvokeEvaluatorRequest) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) SetReq(val *AsyncInvokeEvaluatorRequest) { p.Req = val } -var fieldIDToName_EvaluationSPIServiceInvokeEvaluatorArgs = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceAsyncInvokeEvaluatorArgs = map[int16]string{ 1: "req", } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) IsSetReq() bool { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) IsSetReq() bool { return p.Req != nil } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -9397,7 +10439,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvaluatorArgs[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvaluatorArgs[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -9407,8 +10449,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) ReadField1(iprot thrift.TProtocol) error { - _field := NewInvokeEvaluatorRequest() +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewAsyncInvokeEvaluatorRequest() if err := _field.Read(iprot); err != nil { return err } @@ -9416,9 +10458,9 @@ func (p *EvaluationSPIServiceInvokeEvaluatorArgs) ReadField1(iprot thrift.TProto return nil } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("InvokeEvaluator_args"); err != nil { + if err = oprot.WriteStructBegin("AsyncInvokeEvaluator_args"); err != nil { goto WriteStructBeginError } if p != nil { @@ -9444,7 +10486,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) writeField1(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) writeField1(oprot thrift.TProtocol) (err error) { if err = oprot.WriteFieldBegin("req", thrift.STRUCT, 1); err != nil { goto WriteFieldBeginError } @@ -9461,15 +10503,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) String() string { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceInvokeEvaluatorArgs(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceAsyncInvokeEvaluatorArgs(%+v)", *p) } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) DeepEqual(ano *EvaluationSPIServiceInvokeEvaluatorArgs) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) DeepEqual(ano *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -9481,7 +10523,7 @@ func (p *EvaluationSPIServiceInvokeEvaluatorArgs) DeepEqual(ano *EvaluationSPISe return true } -func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Field1DeepEqual(src *InvokeEvaluatorRequest) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) Field1DeepEqual(src *AsyncInvokeEvaluatorRequest) bool { if !p.Req.DeepEqual(src) { return false @@ -9489,41 +10531,41 @@ func (p *EvaluationSPIServiceInvokeEvaluatorArgs) Field1DeepEqual(src *InvokeEva return true } -type EvaluationSPIServiceInvokeEvaluatorResult struct { - Success *InvokeEvaluatorResponse `thrift:"success,0,optional" frugal:"0,optional,InvokeEvaluatorResponse"` +type EvaluationSPIServiceAsyncInvokeEvaluatorResult struct { + Success *AsyncInvokeEvaluatorResponse `thrift:"success,0,optional" frugal:"0,optional,AsyncInvokeEvaluatorResponse"` } -func NewEvaluationSPIServiceInvokeEvaluatorResult() *EvaluationSPIServiceInvokeEvaluatorResult { - return &EvaluationSPIServiceInvokeEvaluatorResult{} +func NewEvaluationSPIServiceAsyncInvokeEvaluatorResult() *EvaluationSPIServiceAsyncInvokeEvaluatorResult { + return &EvaluationSPIServiceAsyncInvokeEvaluatorResult{} } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) InitDefault() { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) InitDefault() { } -var EvaluationSPIServiceInvokeEvaluatorResult_Success_DEFAULT *InvokeEvaluatorResponse +var EvaluationSPIServiceAsyncInvokeEvaluatorResult_Success_DEFAULT *AsyncInvokeEvaluatorResponse -func (p *EvaluationSPIServiceInvokeEvaluatorResult) GetSuccess() (v *InvokeEvaluatorResponse) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) GetSuccess() (v *AsyncInvokeEvaluatorResponse) { if p == nil { return } if !p.IsSetSuccess() { - return EvaluationSPIServiceInvokeEvaluatorResult_Success_DEFAULT + return EvaluationSPIServiceAsyncInvokeEvaluatorResult_Success_DEFAULT } return p.Success } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) SetSuccess(x interface{}) { - p.Success = x.(*InvokeEvaluatorResponse) +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) SetSuccess(x interface{}) { + p.Success = x.(*AsyncInvokeEvaluatorResponse) } -var fieldIDToName_EvaluationSPIServiceInvokeEvaluatorResult = map[int16]string{ +var fieldIDToName_EvaluationSPIServiceAsyncInvokeEvaluatorResult = map[int16]string{ 0: "success", } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) IsSetSuccess() bool { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) IsSetSuccess() bool { return p.Success != nil } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) Read(iprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -9568,7 +10610,7 @@ ReadStructBeginError: ReadFieldBeginError: return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceInvokeEvaluatorResult[fieldId]), err) + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvaluatorResult[fieldId]), err) SkipFieldError: return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) @@ -9578,8 +10620,8 @@ ReadStructEndError: return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) ReadField0(iprot thrift.TProtocol) error { - _field := NewInvokeEvaluatorResponse() +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewAsyncInvokeEvaluatorResponse() if err := _field.Read(iprot); err != nil { return err } @@ -9587,9 +10629,9 @@ func (p *EvaluationSPIServiceInvokeEvaluatorResult) ReadField0(iprot thrift.TPro return nil } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) Write(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 - if err = oprot.WriteStructBegin("InvokeEvaluator_result"); err != nil { + if err = oprot.WriteStructBegin("AsyncInvokeEvaluator_result"); err != nil { goto WriteStructBeginError } if p != nil { @@ -9615,7 +10657,7 @@ WriteStructEndError: return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) writeField0(oprot thrift.TProtocol) (err error) { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) writeField0(oprot thrift.TProtocol) (err error) { if p.IsSetSuccess() { if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { goto WriteFieldBeginError @@ -9634,15 +10676,15 @@ WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) String() string { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) String() string { if p == nil { return "" } - return fmt.Sprintf("EvaluationSPIServiceInvokeEvaluatorResult(%+v)", *p) + return fmt.Sprintf("EvaluationSPIServiceAsyncInvokeEvaluatorResult(%+v)", *p) } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) DeepEqual(ano *EvaluationSPIServiceInvokeEvaluatorResult) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) DeepEqual(ano *EvaluationSPIServiceAsyncInvokeEvaluatorResult) bool { if p == ano { return true } else if p == nil || ano == nil { @@ -9654,7 +10696,7 @@ func (p *EvaluationSPIServiceInvokeEvaluatorResult) DeepEqual(ano *EvaluationSPI return true } -func (p *EvaluationSPIServiceInvokeEvaluatorResult) Field0DeepEqual(src *InvokeEvaluatorResponse) bool { +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) Field0DeepEqual(src *AsyncInvokeEvaluatorResponse) bool { if !p.Success.DeepEqual(src) { return false diff --git a/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi_validator.go b/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi_validator.go index 71c67f0875..9aea1586a6 100644 --- a/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi_validator.go +++ b/backend/kitex_gen/coze/loop/evaluation/spi/coze.loop.evaluation.spi_validator.go @@ -215,3 +215,29 @@ func (p *InvokeEvaluatorResponse) IsValid() error { } return nil } +func (p *AsyncInvokeEvaluatorRequest) IsValid() error { + if p.Evaluator != nil { + if err := p.Evaluator.IsValid(); err != nil { + return fmt.Errorf("field Evaluator not valid, %w", err) + } + } + if p.InputData != nil { + if err := p.InputData.IsValid(); err != nil { + return fmt.Errorf("field InputData not valid, %w", err) + } + } + if p.Base != nil { + if err := p.Base.IsValid(); err != nil { + return fmt.Errorf("field Base not valid, %w", err) + } + } + return nil +} +func (p *AsyncInvokeEvaluatorResponse) IsValid() error { + if p.BaseResp != nil { + if err := p.BaseResp.IsValid(); err != nil { + return fmt.Errorf("field BaseResp not valid, %w", err) + } + } + return nil +} diff --git a/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/client.go b/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/client.go index 955c4f6486..09a2f7832a 100644 --- a/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/client.go +++ b/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/client.go @@ -15,6 +15,7 @@ type Client interface { InvokeEvalTarget(ctx context.Context, req *spi.InvokeEvalTargetRequest, callOptions ...callopt.Option) (r *spi.InvokeEvalTargetResponse, err error) AsyncInvokeEvalTarget(ctx context.Context, req *spi.AsyncInvokeEvalTargetRequest, callOptions ...callopt.Option) (r *spi.AsyncInvokeEvalTargetResponse, err error) InvokeEvaluator(ctx context.Context, req *spi.InvokeEvaluatorRequest, callOptions ...callopt.Option) (r *spi.InvokeEvaluatorResponse, err error) + AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest, callOptions ...callopt.Option) (r *spi.AsyncInvokeEvaluatorResponse, err error) } // NewClient creates a client for the service defined in IDL. @@ -65,3 +66,8 @@ func (p *kEvaluationSPIServiceClient) InvokeEvaluator(ctx context.Context, req * ctx = client.NewCtxWithCallOptions(ctx, callOptions) return p.kClient.InvokeEvaluator(ctx, req) } + +func (p *kEvaluationSPIServiceClient) AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest, callOptions ...callopt.Option) (r *spi.AsyncInvokeEvaluatorResponse, err error) { + ctx = client.NewCtxWithCallOptions(ctx, callOptions) + return p.kClient.AsyncInvokeEvaluator(ctx, req) +} diff --git a/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/evaluationspiservice.go b/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/evaluationspiservice.go index da3aadc192..96a301cfe2 100644 --- a/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/evaluationspiservice.go +++ b/backend/kitex_gen/coze/loop/evaluation/spi/evaluationspiservice/evaluationspiservice.go @@ -41,6 +41,13 @@ var serviceMethods = map[string]kitex.MethodInfo{ false, kitex.WithStreamingMode(kitex.StreamingNone), ), + "AsyncInvokeEvaluator": kitex.NewMethodInfo( + asyncInvokeEvaluatorHandler, + newEvaluationSPIServiceAsyncInvokeEvaluatorArgs, + newEvaluationSPIServiceAsyncInvokeEvaluatorResult, + false, + kitex.WithStreamingMode(kitex.StreamingNone), + ), } var ( @@ -150,6 +157,25 @@ func newEvaluationSPIServiceInvokeEvaluatorResult() interface{} { return spi.NewEvaluationSPIServiceInvokeEvaluatorResult() } +func asyncInvokeEvaluatorHandler(ctx context.Context, handler interface{}, arg, result interface{}) error { + realArg := arg.(*spi.EvaluationSPIServiceAsyncInvokeEvaluatorArgs) + realResult := result.(*spi.EvaluationSPIServiceAsyncInvokeEvaluatorResult) + success, err := handler.(spi.EvaluationSPIService).AsyncInvokeEvaluator(ctx, realArg.Req) + if err != nil { + return err + } + realResult.Success = success + return nil +} + +func newEvaluationSPIServiceAsyncInvokeEvaluatorArgs() interface{} { + return spi.NewEvaluationSPIServiceAsyncInvokeEvaluatorArgs() +} + +func newEvaluationSPIServiceAsyncInvokeEvaluatorResult() interface{} { + return spi.NewEvaluationSPIServiceAsyncInvokeEvaluatorResult() +} + type kClient struct { c client.Client sc client.Streaming @@ -201,3 +227,13 @@ func (p *kClient) InvokeEvaluator(ctx context.Context, req *spi.InvokeEvaluatorR } return _result.GetSuccess(), nil } + +func (p *kClient) AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest) (r *spi.AsyncInvokeEvaluatorResponse, err error) { + var _args spi.EvaluationSPIServiceAsyncInvokeEvaluatorArgs + _args.Req = req + var _result spi.EvaluationSPIServiceAsyncInvokeEvaluatorResult + if err = p.c.Call(ctx, "AsyncInvokeEvaluator", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} diff --git a/backend/kitex_gen/coze/loop/evaluation/spi/k-coze.loop.evaluation.spi.go b/backend/kitex_gen/coze/loop/evaluation/spi/k-coze.loop.evaluation.spi.go index 514485dd13..f9c0ec33b2 100644 --- a/backend/kitex_gen/coze/loop/evaluation/spi/k-coze.loop.evaluation.spi.go +++ b/backend/kitex_gen/coze/loop/evaluation/spi/k-coze.loop.evaluation.spi.go @@ -5722,6 +5722,456 @@ func (p *InvokeEvaluatorResponse) DeepCopy(s interface{}) error { return nil } +func (p *AsyncInvokeEvaluatorRequest) FastRead(buf []byte) (int, error) { + + var err error + var offset int + var l int + var fieldTypeId thrift.TType + var fieldId int16 + for { + fieldTypeId, fieldId, l, err = thrift.Binary.ReadFieldBegin(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + l, err = p.FastReadField1(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + case 2: + if fieldTypeId == thrift.I64 { + l, err = p.FastReadField2(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + case 3: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField3(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + case 4: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField4(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + case 255: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField255(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + default: + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + } + + return offset, nil +ReadFieldBeginError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AsyncInvokeEvaluatorRequest[fieldId]), err) +SkipFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) +} + +func (p *AsyncInvokeEvaluatorRequest) FastReadField1(buf []byte) (int, error) { + offset := 0 + + var _field *int64 + if v, l, err := thrift.Binary.ReadI64(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + _field = &v + } + p.WorkspaceID = _field + return offset, nil +} + +func (p *AsyncInvokeEvaluatorRequest) FastReadField2(buf []byte) (int, error) { + offset := 0 + + var _field *int64 + if v, l, err := thrift.Binary.ReadI64(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + _field = &v + } + p.InvokeID = _field + return offset, nil +} + +func (p *AsyncInvokeEvaluatorRequest) FastReadField3(buf []byte) (int, error) { + offset := 0 + _field := NewInvokeCustomEvaluator() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.Evaluator = _field + return offset, nil +} + +func (p *AsyncInvokeEvaluatorRequest) FastReadField4(buf []byte) (int, error) { + offset := 0 + _field := NewInvokeEvaluatorInputData() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.InputData = _field + return offset, nil +} + +func (p *AsyncInvokeEvaluatorRequest) FastReadField255(buf []byte) (int, error) { + offset := 0 + _field := base.NewBase() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.Base = _field + return offset, nil +} + +func (p *AsyncInvokeEvaluatorRequest) FastWrite(buf []byte) int { + return p.FastWriteNocopy(buf, nil) +} + +func (p *AsyncInvokeEvaluatorRequest) FastWriteNocopy(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p != nil { + offset += p.fastWriteField1(buf[offset:], w) + offset += p.fastWriteField2(buf[offset:], w) + offset += p.fastWriteField3(buf[offset:], w) + offset += p.fastWriteField4(buf[offset:], w) + offset += p.fastWriteField255(buf[offset:], w) + } + offset += thrift.Binary.WriteFieldStop(buf[offset:]) + return offset +} + +func (p *AsyncInvokeEvaluatorRequest) BLength() int { + l := 0 + if p != nil { + l += p.field1Length() + l += p.field2Length() + l += p.field3Length() + l += p.field4Length() + l += p.field255Length() + } + l += thrift.Binary.FieldStopLength() + return l +} + +func (p *AsyncInvokeEvaluatorRequest) fastWriteField1(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetWorkspaceID() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.I64, 1) + offset += thrift.Binary.WriteI64(buf[offset:], *p.WorkspaceID) + } + return offset +} + +func (p *AsyncInvokeEvaluatorRequest) fastWriteField2(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetInvokeID() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.I64, 2) + offset += thrift.Binary.WriteI64(buf[offset:], *p.InvokeID) + } + return offset +} + +func (p *AsyncInvokeEvaluatorRequest) fastWriteField3(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetEvaluator() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 3) + offset += p.Evaluator.FastWriteNocopy(buf[offset:], w) + } + return offset +} + +func (p *AsyncInvokeEvaluatorRequest) fastWriteField4(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetInputData() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 4) + offset += p.InputData.FastWriteNocopy(buf[offset:], w) + } + return offset +} + +func (p *AsyncInvokeEvaluatorRequest) fastWriteField255(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetBase() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 255) + offset += p.Base.FastWriteNocopy(buf[offset:], w) + } + return offset +} + +func (p *AsyncInvokeEvaluatorRequest) field1Length() int { + l := 0 + if p.IsSetWorkspaceID() { + l += thrift.Binary.FieldBeginLength() + l += thrift.Binary.I64Length() + } + return l +} + +func (p *AsyncInvokeEvaluatorRequest) field2Length() int { + l := 0 + if p.IsSetInvokeID() { + l += thrift.Binary.FieldBeginLength() + l += thrift.Binary.I64Length() + } + return l +} + +func (p *AsyncInvokeEvaluatorRequest) field3Length() int { + l := 0 + if p.IsSetEvaluator() { + l += thrift.Binary.FieldBeginLength() + l += p.Evaluator.BLength() + } + return l +} + +func (p *AsyncInvokeEvaluatorRequest) field4Length() int { + l := 0 + if p.IsSetInputData() { + l += thrift.Binary.FieldBeginLength() + l += p.InputData.BLength() + } + return l +} + +func (p *AsyncInvokeEvaluatorRequest) field255Length() int { + l := 0 + if p.IsSetBase() { + l += thrift.Binary.FieldBeginLength() + l += p.Base.BLength() + } + return l +} + +func (p *AsyncInvokeEvaluatorRequest) DeepCopy(s interface{}) error { + src, ok := s.(*AsyncInvokeEvaluatorRequest) + if !ok { + return fmt.Errorf("%T's type not matched %T", s, p) + } + + if src.WorkspaceID != nil { + tmp := *src.WorkspaceID + p.WorkspaceID = &tmp + } + + if src.InvokeID != nil { + tmp := *src.InvokeID + p.InvokeID = &tmp + } + + var _evaluator *InvokeCustomEvaluator + if src.Evaluator != nil { + _evaluator = &InvokeCustomEvaluator{} + if err := _evaluator.DeepCopy(src.Evaluator); err != nil { + return err + } + } + p.Evaluator = _evaluator + + var _inputData *InvokeEvaluatorInputData + if src.InputData != nil { + _inputData = &InvokeEvaluatorInputData{} + if err := _inputData.DeepCopy(src.InputData); err != nil { + return err + } + } + p.InputData = _inputData + + var _base *base.Base + if src.Base != nil { + _base = &base.Base{} + if err := _base.DeepCopy(src.Base); err != nil { + return err + } + } + p.Base = _base + + return nil +} + +func (p *AsyncInvokeEvaluatorResponse) FastRead(buf []byte) (int, error) { + + var err error + var offset int + var l int + var fieldTypeId thrift.TType + var fieldId int16 + for { + fieldTypeId, fieldId, l, err = thrift.Binary.ReadFieldBegin(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + switch fieldId { + case 255: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField255(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + default: + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + } + + return offset, nil +ReadFieldBeginError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AsyncInvokeEvaluatorResponse[fieldId]), err) +SkipFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) +} + +func (p *AsyncInvokeEvaluatorResponse) FastReadField255(buf []byte) (int, error) { + offset := 0 + _field := base.NewBaseResp() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.BaseResp = _field + return offset, nil +} + +func (p *AsyncInvokeEvaluatorResponse) FastWrite(buf []byte) int { + return p.FastWriteNocopy(buf, nil) +} + +func (p *AsyncInvokeEvaluatorResponse) FastWriteNocopy(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p != nil { + offset += p.fastWriteField255(buf[offset:], w) + } + offset += thrift.Binary.WriteFieldStop(buf[offset:]) + return offset +} + +func (p *AsyncInvokeEvaluatorResponse) BLength() int { + l := 0 + if p != nil { + l += p.field255Length() + } + l += thrift.Binary.FieldStopLength() + return l +} + +func (p *AsyncInvokeEvaluatorResponse) fastWriteField255(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 255) + offset += p.BaseResp.FastWriteNocopy(buf[offset:], w) + return offset +} + +func (p *AsyncInvokeEvaluatorResponse) field255Length() int { + l := 0 + l += thrift.Binary.FieldBeginLength() + l += p.BaseResp.BLength() + return l +} + +func (p *AsyncInvokeEvaluatorResponse) DeepCopy(s interface{}) error { + src, ok := s.(*AsyncInvokeEvaluatorResponse) + if !ok { + return fmt.Errorf("%T's type not matched %T", s, p) + } + + var _baseResp *base.BaseResp + if src.BaseResp != nil { + _baseResp = &base.BaseResp{} + if err := _baseResp.DeepCopy(src.BaseResp); err != nil { + return err + } + } + p.BaseResp = _baseResp + + return nil +} + func (p *EvaluationSPIServiceSearchEvalTargetArgs) FastRead(buf []byte) (int, error) { var err error @@ -6658,6 +7108,240 @@ func (p *EvaluationSPIServiceInvokeEvaluatorResult) DeepCopy(s interface{}) erro return nil } +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) FastRead(buf []byte) (int, error) { + + var err error + var offset int + var l int + var fieldTypeId thrift.TType + var fieldId int16 + for { + fieldTypeId, fieldId, l, err = thrift.Binary.ReadFieldBegin(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField1(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + default: + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + } + + return offset, nil +ReadFieldBeginError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvaluatorArgs[fieldId]), err) +SkipFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) FastReadField1(buf []byte) (int, error) { + offset := 0 + _field := NewAsyncInvokeEvaluatorRequest() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.Req = _field + return offset, nil +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) FastWrite(buf []byte) int { + return p.FastWriteNocopy(buf, nil) +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) FastWriteNocopy(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p != nil { + offset += p.fastWriteField1(buf[offset:], w) + } + offset += thrift.Binary.WriteFieldStop(buf[offset:]) + return offset +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) BLength() int { + l := 0 + if p != nil { + l += p.field1Length() + } + l += thrift.Binary.FieldStopLength() + return l +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) fastWriteField1(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 1) + offset += p.Req.FastWriteNocopy(buf[offset:], w) + return offset +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) field1Length() int { + l := 0 + l += thrift.Binary.FieldBeginLength() + l += p.Req.BLength() + return l +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) DeepCopy(s interface{}) error { + src, ok := s.(*EvaluationSPIServiceAsyncInvokeEvaluatorArgs) + if !ok { + return fmt.Errorf("%T's type not matched %T", s, p) + } + + var _req *AsyncInvokeEvaluatorRequest + if src.Req != nil { + _req = &AsyncInvokeEvaluatorRequest{} + if err := _req.DeepCopy(src.Req); err != nil { + return err + } + } + p.Req = _req + + return nil +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) FastRead(buf []byte) (int, error) { + + var err error + var offset int + var l int + var fieldTypeId thrift.TType + var fieldId int16 + for { + fieldTypeId, fieldId, l, err = thrift.Binary.ReadFieldBegin(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + l, err = p.FastReadField0(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + default: + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } + } + + return offset, nil +ReadFieldBeginError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_EvaluationSPIServiceAsyncInvokeEvaluatorResult[fieldId]), err) +SkipFieldError: + return offset, thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) FastReadField0(buf []byte) (int, error) { + offset := 0 + _field := NewAsyncInvokeEvaluatorResponse() + if l, err := _field.FastRead(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + } + p.Success = _field + return offset, nil +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) FastWrite(buf []byte) int { + return p.FastWriteNocopy(buf, nil) +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) FastWriteNocopy(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p != nil { + offset += p.fastWriteField0(buf[offset:], w) + } + offset += thrift.Binary.WriteFieldStop(buf[offset:]) + return offset +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) BLength() int { + l := 0 + if p != nil { + l += p.field0Length() + } + l += thrift.Binary.FieldStopLength() + return l +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) fastWriteField0(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetSuccess() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRUCT, 0) + offset += p.Success.FastWriteNocopy(buf[offset:], w) + } + return offset +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) field0Length() int { + l := 0 + if p.IsSetSuccess() { + l += thrift.Binary.FieldBeginLength() + l += p.Success.BLength() + } + return l +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) DeepCopy(s interface{}) error { + src, ok := s.(*EvaluationSPIServiceAsyncInvokeEvaluatorResult) + if !ok { + return fmt.Errorf("%T's type not matched %T", s, p) + } + + var _success *AsyncInvokeEvaluatorResponse + if src.Success != nil { + _success = &AsyncInvokeEvaluatorResponse{} + if err := _success.DeepCopy(src.Success); err != nil { + return err + } + } + p.Success = _success + + return nil +} + func (p *EvaluationSPIServiceSearchEvalTargetArgs) GetFirstArgument() interface{} { return p.Req } @@ -6689,3 +7373,11 @@ func (p *EvaluationSPIServiceInvokeEvaluatorArgs) GetFirstArgument() interface{} func (p *EvaluationSPIServiceInvokeEvaluatorResult) GetResult() interface{} { return p.Success } + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorArgs) GetFirstArgument() interface{} { + return p.Req +} + +func (p *EvaluationSPIServiceAsyncInvokeEvaluatorResult) GetResult() interface{} { + return p.Success +} diff --git a/backend/loop_gen/coze/loop/evaluation/lospi/local_evaluationspiservice.go b/backend/loop_gen/coze/loop/evaluation/lospi/local_evaluationspiservice.go index d803d4a568..e1fe9fed39 100644 --- a/backend/loop_gen/coze/loop/evaluation/lospi/local_evaluationspiservice.go +++ b/backend/loop_gen/coze/loop/evaluation/lospi/local_evaluationspiservice.go @@ -108,6 +108,27 @@ func (l *LocalEvaluationSPIService) InvokeEvaluator(ctx context.Context, req *sp return result.GetSuccess(), nil } +func (l *LocalEvaluationSPIService) AsyncInvokeEvaluator(ctx context.Context, req *spi.AsyncInvokeEvaluatorRequest, callOptions ...callopt.Option) (*spi.AsyncInvokeEvaluatorResponse, error) { + chain := l.mds(func(ctx context.Context, in, out interface{}) error { + arg := in.(*spi.EvaluationSPIServiceAsyncInvokeEvaluatorArgs) + result := out.(*spi.EvaluationSPIServiceAsyncInvokeEvaluatorResult) + resp, err := l.impl.AsyncInvokeEvaluator(ctx, arg.Req) + if err != nil { + return err + } + result.SetSuccess(resp) + return nil + }) + + arg := &spi.EvaluationSPIServiceAsyncInvokeEvaluatorArgs{Req: req} + result := &spi.EvaluationSPIServiceAsyncInvokeEvaluatorResult{} + ctx = l.injectRPCInfo(ctx, "AsyncInvokeEvaluator") + if err := chain(ctx, arg, result); err != nil { + return nil, err + } + return result.GetSuccess(), nil +} + func (l *LocalEvaluationSPIService) injectRPCInfo(ctx context.Context, method string) context.Context { rpcStats := rpcinfo.AsMutableRPCStats(rpcinfo.NewRPCStats()) ri := rpcinfo.NewRPCInfo( diff --git a/backend/modules/evaluation/application/convertor/evaluator/evaluator.go b/backend/modules/evaluation/application/convertor/evaluator/evaluator.go index 6a67053bf9..82341a50f0 100644 --- a/backend/modules/evaluation/application/convertor/evaluator/evaluator.go +++ b/backend/modules/evaluation/application/convertor/evaluator/evaluator.go @@ -327,7 +327,10 @@ func ConvertEvaluatorContent2DO(content *evaluatordto.EvaluatorContent, evaluato AccessProtocol: content.CustomRPCEvaluator.AccessProtocol, ServiceName: content.CustomRPCEvaluator.ServiceName, Cluster: content.CustomRPCEvaluator.Cluster, + InvokeHTTPInfo: ConvertEvaluatorHTTPInfoDTO2DO(content.CustomRPCEvaluator.InvokeHTTPInfo), + AsyncInvokeHTTPInfo: ConvertEvaluatorHTTPInfoDTO2DO(content.CustomRPCEvaluator.AsyncInvokeHTTPInfo), Timeout: content.CustomRPCEvaluator.Timeout, + IsAsync: content.CustomRPCEvaluator.GetIsAsync(), } if content.CustomRPCEvaluator.RateLimit != nil { rateLimit, err := commonconvertor.ConvertRateLimitDTO2DO(content.CustomRPCEvaluator.RateLimit) @@ -551,7 +554,9 @@ func ConvertCustomRPCEvaluatorVersionDTO2DO(evaluatorID, spaceID int64, dto *eva customRPCEvaluatorVersion.ServiceName = dto.EvaluatorContent.CustomRPCEvaluator.ServiceName customRPCEvaluatorVersion.Cluster = dto.EvaluatorContent.CustomRPCEvaluator.Cluster customRPCEvaluatorVersion.InvokeHTTPInfo = ConvertEvaluatorHTTPInfoDTO2DO(dto.EvaluatorContent.CustomRPCEvaluator.InvokeHTTPInfo) + customRPCEvaluatorVersion.AsyncInvokeHTTPInfo = ConvertEvaluatorHTTPInfoDTO2DO(dto.EvaluatorContent.CustomRPCEvaluator.AsyncInvokeHTTPInfo) customRPCEvaluatorVersion.Timeout = dto.EvaluatorContent.CustomRPCEvaluator.Timeout + customRPCEvaluatorVersion.IsAsync = dto.EvaluatorContent.CustomRPCEvaluator.GetIsAsync() if dto.EvaluatorContent.CustomRPCEvaluator.RateLimit != nil { rateLimit, err := commonconvertor.ConvertRateLimitDTO2DO(dto.EvaluatorContent.CustomRPCEvaluator.RateLimit) if err != nil { @@ -585,8 +590,10 @@ func ConvertCustomRPCEvaluatorVersionDO2DTO(do *evaluatordo.CustomRPCEvaluatorVe Cluster: do.Cluster, Timeout: do.Timeout, InvokeHTTPInfo: ConvertEvaluatorHTTPInfoDO2DTO(do.InvokeHTTPInfo), + AsyncInvokeHTTPInfo: ConvertEvaluatorHTTPInfoDO2DTO(do.AsyncInvokeHTTPInfo), RateLimit: commonconvertor.ConvertRateLimitDO2DTO(do.RateLimit), Ext: do.Ext, + IsAsync: gptr.Of(do.IsAsync), }, }, } diff --git a/backend/modules/evaluation/application/convertor/evaluator/evaluator_test.go b/backend/modules/evaluation/application/convertor/evaluator/evaluator_test.go index 685094ca8d..e4085b56b8 100755 --- a/backend/modules/evaluation/application/convertor/evaluator/evaluator_test.go +++ b/backend/modules/evaluation/application/convertor/evaluator/evaluator_test.go @@ -1362,6 +1362,8 @@ func TestConvertEvaluatorLangTags_SkipNilInnerMap(t *testing.T) { // TestConvertCustomRPCEvaluatorVersionDTO2DO 测试将 CustomRPC EvaluatorVersion DTO 转换为 DO func TestConvertCustomRPCEvaluatorVersionDTO2DO(t *testing.T) { t.Parallel() + post := evaluatordo.EvaluatorHTTPMethodPost + asyncPath := "/async_invoke_evaluator" tests := []struct { name string @@ -1397,6 +1399,11 @@ func TestConvertCustomRPCEvaluatorVersionDTO2DO(t *testing.T) { ServiceName: gptr.Of("test_service"), Cluster: gptr.Of("test_cluster"), Timeout: gptr.Of(int64(5000)), + AsyncInvokeHTTPInfo: &evaluatordto.EvaluatorHTTPInfo{ + Method: gptr.Of(post), + Path: gptr.Of(asyncPath), + }, + IsAsync: gptr.Of(true), }, InputSchemas: []*commondto.ArgsSchema{ { @@ -1432,6 +1439,10 @@ func TestConvertCustomRPCEvaluatorVersionDTO2DO(t *testing.T) { assert.Equal(t, "test_cluster", *result.Cluster) assert.NotNil(t, result.Timeout) assert.Equal(t, int64(5000), *result.Timeout) + assert.True(t, result.IsAsync) + if assert.NotNil(t, result.AsyncInvokeHTTPInfo) { + assert.Equal(t, asyncPath, gptr.Indirect(result.AsyncInvokeHTTPInfo.Path)) + } assert.NotNil(t, result.InputSchemas) assert.Len(t, result.InputSchemas, 1) assert.NotNil(t, result.OutputSchemas) @@ -1476,6 +1487,8 @@ func TestConvertCustomRPCEvaluatorVersionDTO2DO(t *testing.T) { // TestConvertCustomRPCEvaluatorVersionDO2DTO 测试将 CustomRPC EvaluatorVersion DO 转换为 DTO func TestConvertCustomRPCEvaluatorVersionDO2DTO(t *testing.T) { t.Parallel() + post := evaluatordo.EvaluatorHTTPMethodPost + asyncPath := "/async_invoke_evaluator" tests := []struct { name string @@ -1505,6 +1518,11 @@ func TestConvertCustomRPCEvaluatorVersionDO2DTO(t *testing.T) { ServiceName: gptr.Of("test_service"), Cluster: gptr.Of("test_cluster"), Timeout: gptr.Of(int64(5000)), + AsyncInvokeHTTPInfo: &evaluatordo.EvaluatorHTTPInfo{ + Method: &post, + Path: &asyncPath, + }, + IsAsync: true, InputSchemas: []*evaluatordo.ArgsSchema{ { Key: gptr.Of("input1"), @@ -1532,6 +1550,8 @@ func TestConvertCustomRPCEvaluatorVersionDO2DTO(t *testing.T) { assert.Equal(t, "test_service", *result.EvaluatorContent.CustomRPCEvaluator.ServiceName) assert.Equal(t, "test_cluster", *result.EvaluatorContent.CustomRPCEvaluator.Cluster) assert.Equal(t, int64(5000), *result.EvaluatorContent.CustomRPCEvaluator.Timeout) + assert.True(t, result.EvaluatorContent.CustomRPCEvaluator.GetIsAsync()) + assert.Equal(t, asyncPath, result.EvaluatorContent.CustomRPCEvaluator.GetAsyncInvokeHTTPInfo().GetPath()) assert.NotNil(t, result.EvaluatorContent.InputSchemas) assert.Len(t, result.EvaluatorContent.InputSchemas, 1) assert.NotNil(t, result.EvaluatorContent.OutputSchemas) diff --git a/backend/modules/evaluation/application/convertor/evaluator/openapi.go b/backend/modules/evaluation/application/convertor/evaluator/openapi.go index 5848e6ffa5..aad147d40f 100644 --- a/backend/modules/evaluation/application/convertor/evaluator/openapi.go +++ b/backend/modules/evaluation/application/convertor/evaluator/openapi.go @@ -170,9 +170,11 @@ func OpenAPIEvaluatorContentDO2DTO(do *entity.Evaluator) *openapiEvaluator.Evalu ServiceName: v.ServiceName, Cluster: v.Cluster, InvokeHTTPInfo: OpenAPIEvaluatorHTTPInfoDO2DTO(v.InvokeHTTPInfo), + AsyncInvokeHTTPInfo: OpenAPIEvaluatorHTTPInfoDO2DTO(v.AsyncInvokeHTTPInfo), Timeout: v.Timeout, RateLimit: common_convertor.OpenAPIRateLimitDO2DTO(v.RateLimit), Ext: v.Ext, + IsAsync: gptr.Of(v.IsAsync), } } case entity.EvaluatorTypeAgent: @@ -529,9 +531,11 @@ func OpenAPIEvaluatorContentDTO2DO(dto *openapiEvaluator.EvaluatorContent, evalT res.CustomRPCEvaluatorVersion.ServiceName = gptr.Of(c.GetServiceName()) res.CustomRPCEvaluatorVersion.Cluster = gptr.Of(c.GetCluster()) res.CustomRPCEvaluatorVersion.InvokeHTTPInfo = OpenAPIEvaluatorHTTPInfoDTO2DO(c.GetInvokeHTTPInfo()) + res.CustomRPCEvaluatorVersion.AsyncInvokeHTTPInfo = OpenAPIEvaluatorHTTPInfoDTO2DO(c.GetAsyncInvokeHTTPInfo()) if c.IsSetTimeout() { res.CustomRPCEvaluatorVersion.Timeout = gptr.Of(c.GetTimeout()) } + res.CustomRPCEvaluatorVersion.IsAsync = c.GetIsAsync() if c.IsSetExt() && len(c.GetExt()) > 0 { res.CustomRPCEvaluatorVersion.Ext = c.GetExt() } diff --git a/backend/modules/evaluation/application/convertor/evaluator/openapi_test.go b/backend/modules/evaluation/application/convertor/evaluator/openapi_test.go index ceee0950fb..127de66202 100644 --- a/backend/modules/evaluation/application/convertor/evaluator/openapi_test.go +++ b/backend/modules/evaluation/application/convertor/evaluator/openapi_test.go @@ -468,10 +468,17 @@ func TestOpenAPIEvaluatorContentDTO2DO(t *testing.T) { }) t.Run("custom rpc type", func(t *testing.T) { + post := openapiEvaluator.EvaluatorHTTPMethodPost + asyncPath := "/async_invoke_evaluator" dto := &openapiEvaluator.EvaluatorContent{ CustomRPCEvaluator: &openapiEvaluator.CustomRPCEvaluator{ ServiceName: gptr.Of("svc"), Cluster: gptr.Of("cls"), + IsAsync: gptr.Of(true), + AsyncInvokeHTTPInfo: &openapiEvaluator.EvaluatorHTTPInfo{ + Method: gptr.Of(post), + Path: gptr.Of(asyncPath), + }, }, } do, err := OpenAPIEvaluatorContentDTO2DO(dto, entity.EvaluatorTypeCustomRPC) @@ -479,6 +486,8 @@ func TestOpenAPIEvaluatorContentDTO2DO(t *testing.T) { assert.NotNil(t, do) assert.Equal(t, "svc", *do.CustomRPCEvaluatorVersion.ServiceName) assert.Equal(t, "cls", *do.CustomRPCEvaluatorVersion.Cluster) + assert.True(t, do.CustomRPCEvaluatorVersion.IsAsync) + assert.Equal(t, asyncPath, gptr.Indirect(do.CustomRPCEvaluatorVersion.AsyncInvokeHTTPInfo.Path)) }) t.Run("agent type", func(t *testing.T) { @@ -637,15 +646,24 @@ func TestOpenAPIEvaluatorContentDO2DTO(t *testing.T) { }) t.Run("custom rpc type", func(t *testing.T) { + post := entity.EvaluatorHTTPMethodPost + asyncPath := "/async_invoke_evaluator" do := &entity.Evaluator{ EvaluatorType: entity.EvaluatorTypeCustomRPC, CustomRPCEvaluatorVersion: &entity.CustomRPCEvaluatorVersion{ ServiceName: gptr.Of("svc"), + IsAsync: true, + AsyncInvokeHTTPInfo: &entity.EvaluatorHTTPInfo{ + Method: gptr.Of(post), + Path: gptr.Of(asyncPath), + }, }, } dto := OpenAPIEvaluatorContentDO2DTO(do) assert.NotNil(t, dto) assert.Equal(t, "svc", dto.CustomRPCEvaluator.GetServiceName()) + assert.True(t, dto.CustomRPCEvaluator.GetIsAsync()) + assert.Equal(t, asyncPath, dto.CustomRPCEvaluator.GetAsyncInvokeHTTPInfo().GetPath()) }) t.Run("agent type", func(t *testing.T) { diff --git a/backend/modules/evaluation/application/eval_openapi_app.go b/backend/modules/evaluation/application/eval_openapi_app.go index e676070807..25791cb5d8 100644 --- a/backend/modules/evaluation/application/eval_openapi_app.go +++ b/backend/modules/evaluation/application/eval_openapi_app.go @@ -3066,24 +3066,17 @@ func (e *EvalOpenAPIApplication) AsyncRunEvaluatorOApi(ctx context.Context, req InputData: inputData, EvaluatorRunConf: runConf, Ext: req.Ext, + AsyncCtx: &entity.EvalAsyncCtx{ + Session: &entity.Session{UserID: usersession.UserIDInCtxOrEmpty(ctx)}, + CallbackURL: req.GetCallbackURL(), + ResumeReady: true, + AsyncUnixMS: startTime.UnixMilli(), + }, }) if err != nil { return nil, err } - // 写入异步上下文供 ReportEvaluatorInvokeResult 回调读取;独立调用 Event 留空 - asyncCtxKey := fmt.Sprintf("evaluator:%d", record.ID) - if err = e.asyncRepo.SetEvalAsyncCtx(ctx, asyncCtxKey, &entity.EvalAsyncCtx{ - RecordID: record.ID, - AsyncUnixMS: startTime.UnixMilli(), - Session: &entity.Session{UserID: usersession.UserIDInCtxOrEmpty(ctx)}, - EvaluatorVersionID: req.GetEvaluatorVersionID(), - CallbackURL: req.GetCallbackURL(), - }); err != nil { - logs.CtxError(ctx, "[AsyncRunEvaluatorOApi] SetEvalAsyncCtx fail, invokeID: %d, err: %v", record.ID, err) - return nil, err - } - return &openapi.AsyncRunEvaluatorOApiResponse{ Data: &openapi.AsyncRunEvaluatorOpenAPIData{ InvokeID: gptr.Of(record.ID), @@ -3595,7 +3588,26 @@ func (e *EvalOpenAPIApplication) ListExptTemplatesOApi(ctx context.Context, req } func (e *EvalOpenAPIApplication) ReportEvaluatorInvokeResult_(ctx context.Context, req *openapi.ReportEvaluatorInvokeResultRequest) (r *openapi.ReportEvaluatorInvokeResultResponse, err error) { - logs.CtxInfo(ctx, "ReportEvaluatorInvokeResult receive req: %v", json.Jsonify(req)) + if req == nil { + return nil, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("req is nil")) + } + if req.GetWorkspaceID() <= 0 || req.GetInvokeID() <= 0 { + return nil, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("workspace_id and invoke_id are required")) + } + if req.GetStatus() != spi.InvokeEvaluatorRunStatus_SUCCESS && req.GetStatus() != spi.InvokeEvaluatorRunStatus_FAILED { + return nil, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("status must be SUCCESS or FAILED")) + } + if req.GetStatus() == spi.InvokeEvaluatorRunStatus_SUCCESS { + if req.Output == nil || req.Output.EvaluatorResult_ == nil || req.Output.EvaluatorResult_.Score == nil { + return nil, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("evaluator_result.score is required for SUCCESS")) + } + } + if req.GetStatus() == spi.InvokeEvaluatorRunStatus_FAILED { + if req.Output == nil || req.Output.EvaluatorRunError == nil || strings.TrimSpace(req.Output.EvaluatorRunError.GetMessage()) == "" { + return nil, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("evaluator_run_error.message is required for FAILED")) + } + } + logs.CtxInfo(ctx, "ReportEvaluatorInvokeResult receive, workspace_id: %d, invoke_id: %d, status: %v", req.GetWorkspaceID(), req.GetInvokeID(), req.GetStatus()) err = e.auth.Authorization(ctx, &rpc.AuthorizationParam{ ObjectID: strconv.FormatInt(req.GetWorkspaceID(), 10), @@ -3607,7 +3619,7 @@ func (e *EvalOpenAPIApplication) ReportEvaluatorInvokeResult_(ctx context.Contex } asyncCtxKey := fmt.Sprintf("evaluator:%d", req.GetInvokeID()) - actx, err := e.asyncRepo.GetEvalAsyncCtx(ctx, asyncCtxKey) + actx, err := e.asyncRepo.GetEvalAsyncCtxStrong(ctx, asyncCtxKey) if err != nil { return nil, err } @@ -3620,29 +3632,43 @@ func (e *EvalOpenAPIApplication) ReportEvaluatorInvokeResult_(ctx context.Contex req.GetInvokeID(), actx.EvaluatorVersionID, req.GetWorkspaceID(), actx.Event.GetExptID(), actx.Event.GetExptRunID(), actx.Event.GetEvalSetItemID(), req.GetStatus()) outputData := evaluator_convertor.ToInvokeEvaluatorOutputDataDO(req.GetOutput(), req.GetStatus()) - if outputData != nil { - outputData.TimeConsumingMS = time.Now().UnixMilli() - actx.AsyncUnixMS - } runStatus := evaluator_convertor.ToEvaluatorRunStatusDO(req.GetStatus()) - if err := e.evaluatorService.ReportEvaluatorInvokeResult(ctx, &entity.ReportEvaluatorRecordParam{ + outcome, err := e.evaluatorService.ReportEvaluatorInvokeResult(ctx, &entity.ReportEvaluatorRecordParam{ SpaceID: req.GetWorkspaceID(), RecordID: req.GetInvokeID(), OutputData: outputData, Status: runStatus, - }); err != nil { + }) + if err != nil { return nil, err } - + if outcome == entity.ReportEvaluatorResultConflict { + return &openapi.ReportEvaluatorInvokeResultResponse{BaseResp: base.NewBaseResp()}, nil + } + var resumeErr error if actx.Event != nil { - if err := e.publisher.PublishExptRecordEvalEvent(ctx, actx.Event, gptr.Of(time.Second*3), func(event *entity.ExptItemEvalEvent) { - event.AsyncEvaluatorReportTrigger = true - }); err != nil { - return nil, err + if !actx.ResumeReady { + latestCtx, ctxErr := e.asyncRepo.GetEvalAsyncCtxStrong(ctx, asyncCtxKey) + if ctxErr != nil { + return nil, ctxErr + } + if latestCtx != nil { + actx = latestCtx + } + } + if !actx.ResumeReady { + resumeErr = errorx.New("evaluator resume is not ready, invoke_id: %v", req.GetInvokeID()) + } else { + if err := e.publisher.PublishExptRecordEvalEvent(ctx, actx.Event, gptr.Of(time.Second*3), func(event *entity.ExptItemEvalEvent) { + event.AsyncEvaluatorReportTrigger = true + }); err != nil { + return nil, err + } } } - if actx.CallbackURL != "" { + if outcome == entity.ReportEvaluatorResultApplied && actx.CallbackURL != "" { payload := &openapi.EvaluatorCallbackPayloadOApi{ InvokeID: gptr.Of(req.GetInvokeID()), WorkspaceID: gptr.Of(req.GetWorkspaceID()), @@ -3659,6 +3685,9 @@ func (e *EvalOpenAPIApplication) ReportEvaluatorInvokeResult_(ctx context.Contex // 不返回错误:回调失败不影响运行时回报接口成功 } } + if resumeErr != nil { + return nil, resumeErr + } return &openapi.ReportEvaluatorInvokeResultResponse{BaseResp: base.NewBaseResp()}, nil } diff --git a/backend/modules/evaluation/application/eval_openapi_app_test.go b/backend/modules/evaluation/application/eval_openapi_app_test.go index e8b52b747b..0728a8557f 100755 --- a/backend/modules/evaluation/application/eval_openapi_app_test.go +++ b/backend/modules/evaluation/application/eval_openapi_app_test.go @@ -6571,12 +6571,66 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { setup func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) wantErr int32 }{ + { + name: "nil request", + req: nil, + setup: func(*rpcmocks.MockIAuthProvider, *repomocks.MockIEvalAsyncRepo, *servicemocks.MockEvaluatorService, *eventmocks.MockExptEventPublisher, *servicemocks.MockIEvaluatorCallbackDispatcher) { + }, + wantErr: errno.CommonInvalidParamCode, + }, + { + name: "unknown status rejected before auth", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_UNKNOWN), + }, + setup: func(*rpcmocks.MockIAuthProvider, *repomocks.MockIEvalAsyncRepo, *servicemocks.MockEvaluatorService, *eventmocks.MockExptEventPublisher, *servicemocks.MockIEvaluatorCallbackDispatcher) { + }, + wantErr: errno.CommonInvalidParamCode, + }, + { + name: "missing workspace id rejected before auth", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + }, + setup: func(*rpcmocks.MockIAuthProvider, *repomocks.MockIEvalAsyncRepo, *servicemocks.MockEvaluatorService, *eventmocks.MockExptEventPublisher, *servicemocks.MockIEvaluatorCallbackDispatcher) { + }, + wantErr: errno.CommonInvalidParamCode, + }, + + { + name: "success without score rejected", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + }, + setup: func(*rpcmocks.MockIAuthProvider, *repomocks.MockIEvalAsyncRepo, *servicemocks.MockEvaluatorService, *eventmocks.MockExptEventPublisher, *servicemocks.MockIEvaluatorCallbackDispatcher) { + }, + wantErr: errno.CommonInvalidParamCode, + }, + { + name: "failed without error message rejected", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_FAILED), + Output: &spi.InvokeEvaluatorOutputData{EvaluatorRunError: &spi.InvokeEvaluatorRunError{}}, + }, + setup: func(*rpcmocks.MockIAuthProvider, *repomocks.MockIEvalAsyncRepo, *servicemocks.MockEvaluatorService, *eventmocks.MockExptEventPublisher, *servicemocks.MockIEvaluatorCallbackDispatcher) { + }, + wantErr: errno.CommonInvalidParamCode, + }, + { name: "auth failed", req: &openapi.ReportEvaluatorInvokeResultRequest{ WorkspaceID: gptr.Of(workspaceID), InvokeID: gptr.Of(invokeID), Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}}, }, setup: func(auth *rpcmocks.MockIAuthProvider, _ *repomocks.MockIEvalAsyncRepo, _ *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(errorx.NewByCode(errno.CommonNoPermissionCode)) @@ -6589,10 +6643,11 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { WorkspaceID: gptr.Of(workspaceID), InvokeID: gptr.Of(invokeID), Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}}, }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, _ *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(nil, errors.New("get failed")) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(nil, errors.New("get failed")) }, wantErr: -1, }, @@ -6602,10 +6657,11 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { WorkspaceID: gptr.Of(workspaceID), InvokeID: gptr.Of(invokeID), Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}}, }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, _ *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(nil, nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(nil, nil) }, wantErr: -1, }, @@ -6621,12 +6677,13 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: event, + ResumeReady: true, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(errors.New("report failed")) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultConflict, errors.New("report failed")) }, wantErr: -1, }, @@ -6643,18 +6700,19 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: event, + ResumeReady: true, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, param *entity.ReportEvaluatorRecordParam) error { + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, param *entity.ReportEvaluatorRecordParam) (entity.ReportEvaluatorResultOutcome, error) { assert.Equal(t, entity.EvaluatorRunStatusFail, param.Status) if assert.NotNil(t, param.OutputData) && assert.NotNil(t, param.OutputData.EvaluatorUsage) { assert.Equal(t, int64(105119), param.OutputData.EvaluatorUsage.InputTokens) assert.Equal(t, int64(1938), param.OutputData.EvaluatorUsage.OutputTokens) } - return nil + return entity.ReportEvaluatorResultApplied, nil }) publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).Return(errors.New("pub failed")) }, @@ -6672,12 +6730,179 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: nil, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) + }, + }, + { + name: "success before resume ready asks provider to retry", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{ + EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}, + }, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: false, + EvaluatorVersionID: 9, + }, nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: false, + EvaluatorVersionID: 9, + }, nil) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + wantErr: -1, + }, + { + name: "duplicate terminal callback before resume ready asks provider to retry", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{ + EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}, + }, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: false, + EvaluatorVersionID: 9, + }, nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultDuplicate, nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: false, + EvaluatorVersionID: 9, + }, nil) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + wantErr: -1, + }, + { + name: "applied callback before resume ready dispatches external callback once then asks provider to retry", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{ + EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}, + }, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, ResumeReady: false, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", + }, nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, ResumeReady: false, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", + }, nil) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + dispatcher.EXPECT().Dispatch(gomock.Any(), workspaceID, "https://cb.example.com/hook", gomock.Any()).Times(1) + }, + wantErr: -1, + }, + { + name: "resume becomes ready between initial read and terminal CAS", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{ + EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}, + }, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + gomock.InOrder( + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: false, + EvaluatorVersionID: 9, + }, nil), + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil), + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: true, + EvaluatorVersionID: 9, + }, nil), + ) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Not(gomock.Nil()), gomock.Any()).Return(nil) + }, + }, + { + name: "resume readiness reread failure is returned", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{ + EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}, + }, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + gomock.InOrder( + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, + ResumeReady: false, + EvaluatorVersionID: 9, + }, nil), + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil), + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(nil, errors.New("redis reread failed")), + ) + }, + wantErr: -1, + }, + + { + name: "duplicate terminal callback republishes recovery but does not redispatch external callback", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), InvokeID: gptr.Of(invokeID), Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}}, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, ResumeReady: true, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", + }, nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultDuplicate, nil) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Any(), gomock.Any()).Return(nil) + dispatcher.EXPECT().Dispatch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + }, + { + name: "conflicting terminal callback does not republish or redispatch", + req: &openapi.ReportEvaluatorInvokeResultRequest{ + WorkspaceID: gptr.Of(workspaceID), + InvokeID: gptr.Of(invokeID), + Status: gptr.Of(spi.InvokeEvaluatorRunStatus_SUCCESS), + Output: &spi.InvokeEvaluatorOutputData{ + EvaluatorResult_: &spi.InvokeEvaluatorResult_{Score: gptr.Of(float64(0.9))}, + }, + }, + setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { + auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + Event: event, ResumeReady: true, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", + }, nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultConflict, nil) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + dispatcher.EXPECT().Dispatch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) }, }, { @@ -6693,18 +6918,19 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, publisher *eventmocks.MockExptEventPublisher, _ *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: event, + ResumeReady: true, AsyncUnixMS: time.Now().UnixMilli() - 50, EvaluatorVersionID: 9, }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, param *entity.ReportEvaluatorRecordParam) error { + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, param *entity.ReportEvaluatorRecordParam) (entity.ReportEvaluatorResultOutcome, error) { assert.Equal(t, workspaceID, param.SpaceID) assert.Equal(t, invokeID, param.RecordID) assert.Equal(t, entity.EvaluatorRunStatusSuccess, param.Status) assert.NotNil(t, param.OutputData) assert.GreaterOrEqual(t, param.OutputData.TimeConsumingMS, int64(0)) - return nil + return entity.ReportEvaluatorResultApplied, nil }) publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).DoAndReturn( func(_ context.Context, ev *entity.ExptItemEvalEvent, _ *time.Duration, modifyFunc func(*entity.ExptItemEvalEvent)) error { @@ -6729,13 +6955,13 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: nil, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) dispatcher.EXPECT().Dispatch(gomock.Any(), workspaceID, "https://cb.example.com/hook", gomock.Any()). DoAndReturn(func(_ context.Context, _ int64, _ string, p *openapi.EvaluatorCallbackPayloadOApi) error { assert.Equal(t, invokeID, p.GetInvokeID()) @@ -6758,13 +6984,13 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: nil, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) dispatcher.EXPECT().Dispatch(gomock.Any(), workspaceID, "https://cb.example.com/hook", gomock.Any()). DoAndReturn(func(_ context.Context, _ int64, _ string, p *openapi.EvaluatorCallbackPayloadOApi) error { assert.Equal(t, "fail", p.GetStatus()) @@ -6784,13 +7010,13 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: nil, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, CallbackURL: "", }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) dispatcher.EXPECT().Dispatch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) }, }, @@ -6806,13 +7032,13 @@ func TestEvalOpenAPIApplication_ReportEvaluatorInvokeResult(t *testing.T) { }, setup: func(auth *rpcmocks.MockIAuthProvider, asyncRepo *repomocks.MockIEvalAsyncRepo, evaluatorSvc *servicemocks.MockEvaluatorService, _ *eventmocks.MockExptEventPublisher, dispatcher *servicemocks.MockIEvaluatorCallbackDispatcher) { auth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - asyncRepo.EXPECT().GetEvalAsyncCtx(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ + asyncRepo.EXPECT().GetEvalAsyncCtxStrong(gomock.Any(), "evaluator:2002").Return(&entity.EvalAsyncCtx{ Event: nil, AsyncUnixMS: time.Now().UnixMilli() - 10, EvaluatorVersionID: 9, CallbackURL: "https://cb.example.com/hook", }, nil) - evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ReportEvaluatorInvokeResult(gomock.Any(), gomock.Any()).Return(entity.ReportEvaluatorResultApplied, nil) dispatcher.EXPECT().Dispatch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("boom")) }, // wantErr stays 0 (zero value): report must still succeed despite dispatch error @@ -8622,21 +8848,20 @@ func TestEvalOpenAPIApplication_AsyncRunEvaluatorOApi(t *testing.T) { wantErr: -1, }, { - name: "set async ctx failed", + name: "coordinator context setup failed", req: &openapi.AsyncRunEvaluatorOApiRequest{ WorkspaceID: gptr.Of(workspaceID), EvaluatorVersionID: gptr.Of(evaluatorVersionID), }, - setup: func(auth *rpcmocks.MockIAuthProvider, evaluatorSvc *servicemocks.MockEvaluatorService, asyncRepo *repomocks.MockIEvalAsyncRepo) { + setup: func(auth *rpcmocks.MockIAuthProvider, evaluatorSvc *servicemocks.MockEvaluatorService, _ *repomocks.MockIEvalAsyncRepo) { evaluator := &entity.Evaluator{ ID: evaluatorVersionID, SpaceID: workspaceID, BaseInfo: &entity.BaseInfo{CreatedBy: &entity.UserInfo{UserID: gptr.Of("owner")}}, } - record := &entity.EvaluatorRecord{ID: invokeID, Status: entity.EvaluatorRunStatusAsyncInvoking} + record := &entity.EvaluatorRecord{ID: invokeID, Status: entity.EvaluatorRunStatusFail} evaluatorSvc.EXPECT().GetEvaluatorVersion(gomock.Any(), gomock.Any(), evaluatorVersionID, false, false).Return(evaluator, nil) auth.EXPECT().AuthorizationWithoutSPI(gomock.Any(), gomock.Any()).Return(nil) - evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(record, nil) - asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("redis error")) + evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(record, errors.New("redis error")) }, wantErr: -1, }, @@ -8647,7 +8872,7 @@ func TestEvalOpenAPIApplication_AsyncRunEvaluatorOApi(t *testing.T) { EvaluatorVersionID: gptr.Of(evaluatorVersionID), CallbackURL: gptr.Of("https://example.com/hook"), }, - setup: func(auth *rpcmocks.MockIAuthProvider, evaluatorSvc *servicemocks.MockEvaluatorService, asyncRepo *repomocks.MockIEvalAsyncRepo) { + setup: func(auth *rpcmocks.MockIAuthProvider, evaluatorSvc *servicemocks.MockEvaluatorService, _ *repomocks.MockIEvalAsyncRepo) { evaluator := &entity.Evaluator{ ID: evaluatorVersionID, SpaceID: workspaceID, Name: "agent-eval", BaseInfo: &entity.BaseInfo{CreatedBy: &entity.UserInfo{UserID: gptr.Of("owner")}}, @@ -8655,15 +8880,12 @@ func TestEvalOpenAPIApplication_AsyncRunEvaluatorOApi(t *testing.T) { record := &entity.EvaluatorRecord{ID: invokeID, Status: entity.EvaluatorRunStatusAsyncInvoking} evaluatorSvc.EXPECT().GetEvaluatorVersion(gomock.Any(), gomock.Any(), evaluatorVersionID, false, false).Return(evaluator, nil) auth.EXPECT().AuthorizationWithoutSPI(gomock.Any(), gomock.Any()).Return(nil) - evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(record, nil) - asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:4004", gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, actx *entity.EvalAsyncCtx) error { - assert.Equal(t, invokeID, actx.RecordID) - assert.Equal(t, evaluatorVersionID, actx.EvaluatorVersionID) - assert.Nil(t, actx.Event) - assert.Equal(t, "https://example.com/hook", actx.CallbackURL) - return nil - }) + evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, req *entity.AsyncRunEvaluatorRequest) (*entity.EvaluatorRecord, error) { + require.NotNil(t, req.AsyncCtx) + assert.True(t, req.AsyncCtx.ResumeReady) + assert.Nil(t, req.AsyncCtx.Event) + return record, nil + }) }, }, { @@ -8672,19 +8894,16 @@ func TestEvalOpenAPIApplication_AsyncRunEvaluatorOApi(t *testing.T) { WorkspaceID: gptr.Of(workspaceID), EvaluatorVersionID: gptr.Of(evaluatorVersionID), }, - setup: func(_ *rpcmocks.MockIAuthProvider, evaluatorSvc *servicemocks.MockEvaluatorService, asyncRepo *repomocks.MockIEvalAsyncRepo) { + setup: func(_ *rpcmocks.MockIAuthProvider, evaluatorSvc *servicemocks.MockEvaluatorService, _ *repomocks.MockIEvalAsyncRepo) { evaluator := &entity.Evaluator{ID: evaluatorVersionID, SpaceID: workspaceID + 999, Builtin: true, Name: "builtin-agent"} record := &entity.EvaluatorRecord{ID: invokeID, Status: entity.EvaluatorRunStatusAsyncInvoking} evaluatorSvc.EXPECT().GetEvaluatorVersion(gomock.Any(), gomock.Any(), evaluatorVersionID, false, false).Return(evaluator, nil) - evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(record, nil) - asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:4004", gomock.Any()). - DoAndReturn(func(_ context.Context, _ string, actx *entity.EvalAsyncCtx) error { - assert.Equal(t, invokeID, actx.RecordID) - assert.Equal(t, evaluatorVersionID, actx.EvaluatorVersionID) - assert.Nil(t, actx.Event) - assert.Equal(t, "", actx.CallbackURL) - return nil - }) + evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, req *entity.AsyncRunEvaluatorRequest) (*entity.EvaluatorRecord, error) { + require.NotNil(t, req.AsyncCtx) + assert.True(t, req.AsyncCtx.ResumeReady) + assert.Nil(t, req.AsyncCtx.Event) + return record, nil + }) }, }, } diff --git a/backend/modules/evaluation/application/evaluator_app.go b/backend/modules/evaluation/application/evaluator_app.go index 05e0486765..a636635026 100644 --- a/backend/modules/evaluation/application/evaluator_app.go +++ b/backend/modules/evaluation/application/evaluator_app.go @@ -2085,7 +2085,6 @@ func (e *EvaluatorHandlerImpl) ListEvaluatorTags(ctx context.Context, request *e } func (e *EvaluatorHandlerImpl) AsyncRunEvaluator(ctx context.Context, req *evaluatorservice.AsyncRunEvaluatorRequest) (r *evaluatorservice.AsyncRunEvaluatorResponse, err error) { - startTime := time.Now() evaluatorDO, err := e.evaluatorService.GetEvaluatorVersion(ctx, nil, req.GetEvaluatorVersionID(), false, false) if err != nil { return nil, err @@ -2103,19 +2102,13 @@ func (e *EvaluatorHandlerImpl) AsyncRunEvaluator(ctx context.Context, req *evalu return nil, err } } - resp, err := e.evaluatorService.AsyncRunEvaluator(ctx, buildAsyncRunEvaluatorRequest(evaluatorDO.Name, req)) - if err != nil { - return nil, err + asyncReq := buildAsyncRunEvaluatorRequest(evaluatorDO.Name, req) + asyncReq.AsyncCtx = &entity.EvalAsyncCtx{ + Session: &entity.Session{UserID: session.UserIDInCtxOrEmpty(ctx)}, + ResumeReady: true, } - - asyncCtxKey := fmt.Sprintf("evaluator:%d", resp.ID) - if err := e.evalAsyncRepo.SetEvalAsyncCtx(ctx, asyncCtxKey, &entity.EvalAsyncCtx{ - RecordID: resp.ID, - AsyncUnixMS: startTime.UnixMilli(), - Session: &entity.Session{UserID: session.UserIDInCtxOrEmpty(ctx)}, - EvaluatorVersionID: req.GetEvaluatorVersionID(), - }); err != nil { - logs.CtxError(ctx, "[AsyncRunEvaluator] SetEvalAsyncCtx fail, invokeID: %d, err: %v", resp.ID, err) + resp, err := e.evaluatorService.AsyncRunEvaluator(ctx, asyncReq) + if err != nil { return nil, err } diff --git a/backend/modules/evaluation/application/evaluator_app_test.go b/backend/modules/evaluation/application/evaluator_app_test.go index b2113359c8..14fc2e5d65 100644 --- a/backend/modules/evaluation/application/evaluator_app_test.go +++ b/backend/modules/evaluation/application/evaluator_app_test.go @@ -9028,7 +9028,6 @@ func TestEvaluatorHandlerImpl_AsyncRunEvaluator(t *testing.T) { mockEvaluatorService.EXPECT().GetEvaluatorVersion(gomock.Any(), nil, int64(101), false, false).Return(evaluatorDO, nil) mockAuth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ID: 999}, nil) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) }, wantErr: false, }, @@ -9058,12 +9057,11 @@ func TestEvaluatorHandlerImpl_AsyncRunEvaluator(t *testing.T) { wantErr: true, }, { - name: "失败 - SetEvalAsyncCtx失败", + name: "失败 - coordinator context setup失败", setupMocks: func() { mockEvaluatorService.EXPECT().GetEvaluatorVersion(gomock.Any(), nil, int64(101), false, false).Return(evaluatorDO, nil) mockAuth.EXPECT().Authorization(gomock.Any(), gomock.Any()).Return(nil) - mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ID: 999}, nil) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(errors.New("set ctx failed")) + mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ID: 999, Status: entity.EvaluatorRunStatusFail}, errors.New("set ctx failed")) }, wantErr: true, }, @@ -9257,7 +9255,6 @@ func TestEvaluatorHandlerImpl_AsyncRunEvaluator_Builtin_Agent(t *testing.T) { mockEvaluatorService.EXPECT().GetEvaluatorVersion(gomock.Any(), nil, int64(101), false, false).Return(evaluatorDO, nil) // 不调用 Authorization mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ID: 999}, nil) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) resp, err := handler.AsyncRunEvaluator(ctx, req) assert.NoError(t, err) diff --git a/backend/modules/evaluation/application/wire_gen.go b/backend/modules/evaluation/application/wire_gen.go index 062d1e6a13..284c1dfa88 100644 --- a/backend/modules/evaluation/application/wire_gen.go +++ b/backend/modules/evaluation/application/wire_gen.go @@ -108,11 +108,13 @@ func InitExperimentApplication(ctx context.Context, idgen2 idgen.IIDGenerator, d codeBuilderFactory := service.NewCodeBuilderFactory() v := service.NewEvaluatorSourceServices(illmProvider, evaluatorExecMetrics, iConfiger, iRuntimeManager, codeBuilderFactory) iPlainRateLimiter := evaluator.NewPlainRateLimiterImpl(plainLimiterFactory) - serviceEvaluatorService := service.NewEvaluatorServiceImpl(idgen2, rateLimiter, rmqFactory, iEvaluatorRepo, iEvaluatorRecordRepo, idempotentService, iConfiger, v, iPlainRateLimiter, componentIConfiger) + iEvalAsyncDAO := dao.NewEvalAsyncDAO(cmdable) + iEvalAsyncRepo := experiment.NewEvalAsyncRepo(iEvalAsyncDAO) exptEventPublisher, err := producer.NewExptEventPublisher(ctx, configFactory, rmqFactory) if err != nil { return nil, err } + serviceEvaluatorService := service.NewEvaluatorServiceImpl(idgen2, rateLimiter, rmqFactory, iEvaluatorRepo, iEvaluatorRecordRepo, idempotentService, iConfiger, v, iPlainRateLimiter, componentIConfiger, iEvalAsyncRepo, exptEventPublisher) evaluatorEventPublisher, err := producer.NewEvaluatorEventPublisher(ctx, configFactory, rmqFactory) if err != nil { return nil, err @@ -173,12 +175,12 @@ func InitExperimentApplication(ctx context.Context, idgen2 idgen.IIDGenerator, d } resourceAccessAuthorizer := service.NewResourceAccessAuthorizer(iAuthProvider, sharedResourceConfigProvider) iExptManager := service.NewExptManager(exptResultService, iExperimentRepo, iExptRunLogRepo, iExptStatsRepo, iExptItemResultRepo, iExptItemRefRepo, iExptTurnResultRepo, componentIConfiger, quotaRepo, iLocker, idempotentService, exptEventPublisher, auditClient, idgen2, exptMetric, iLatestWriteTracker, evaluationSetVersionService, iEvaluationSetService, iEvalTargetService, serviceEvaluatorService, benefitSvc, exptAggrResultService, iExptTemplateRepo, iExptTemplateManager, iNotifyRPCAdapter, iUserProvider, pipelineListAdapter, resourceAccessAuthorizer, sandboxAgentMetrics) - schedulerModeFactory := service.NewSchedulerModeFactory(iExptManager, iExptItemResultRepo, iExptStatsRepo, iExptTurnResultRepo, idgen2, evaluationSetItemService, iExperimentRepo, iExptItemRefRepo, idempotentService, componentIConfiger, exptEventPublisher, evaluatorRecordService, exptResultService, iExptTemplateManager, iExptRunLogRepo, iLocker) + iSandboxAgentNotifier := service.NewSandboxAgentNotifier(iNotifyRPCAdapter, iUserProvider, iExptStatsRepo, iLocker, componentIConfiger) + v3 := service.ProvideSandboxAgentNotifiers(iSandboxAgentNotifier) + schedulerModeFactory := service.NewSchedulerModeFactory(iExptManager, iExptItemResultRepo, iExptStatsRepo, iExptTurnResultRepo, idgen2, evaluationSetItemService, iExperimentRepo, iExptItemRefRepo, idempotentService, componentIConfiger, exptEventPublisher, evaluatorRecordService, exptResultService, iExptTemplateManager, iExptRunLogRepo, iLocker, v3...) iItemCompletePublisher := service.ProvideNilItemCompletePublisher() - exptSchedulerEvent := service.NewExptSchedulerSvc(iExptManager, iExperimentRepo, iExptItemResultRepo, iExptTurnResultRepo, iEvaluatorRecordRepo, iExptStatsRepo, iExptRunLogRepo, idempotentService, componentIConfiger, quotaRepo, iLocker, exptEventPublisher, auditClient, exptMetric, exptResultService, idgen2, evaluationSetItemService, schedulerModeFactory, iEvalTargetService, iItemCompletePublisher, iExptItemRefRepo, sandboxAgentMetrics) - iEvalAsyncDAO := dao.NewEvalAsyncDAO(cmdable) - iEvalAsyncRepo := experiment.NewEvalAsyncRepo(iEvalAsyncDAO) - exptItemEvalEvent := service.NewExptRecordEvalService(iExptManager, componentIConfiger, exptEventPublisher, iExptItemResultRepo, iExptTurnResultRepo, iExptStatsRepo, iExperimentRepo, iExptItemRefRepo, quotaRepo, iLocker, idempotentService, auditClient, exptMetric, exptResultService, iEvalTargetService, evaluationSetItemService, evaluatorRecordService, serviceEvaluatorService, idgen2, benefitSvc, iEvalAsyncRepo, iItemCompletePublisher) + exptSchedulerEvent := service.NewExptSchedulerSvc(iExptManager, iExperimentRepo, iExptItemResultRepo, iExptTurnResultRepo, iEvaluatorRecordRepo, iExptStatsRepo, iExptRunLogRepo, idempotentService, componentIConfiger, quotaRepo, iLocker, exptEventPublisher, auditClient, exptMetric, exptResultService, idgen2, evaluationSetItemService, schedulerModeFactory, iEvalTargetService, iItemCompletePublisher, iExptItemRefRepo, sandboxAgentMetrics, v3...) + exptItemEvalEvent := service.NewExptRecordEvalService(iExptManager, componentIConfiger, exptEventPublisher, iExptItemResultRepo, iExptTurnResultRepo, iExptStatsRepo, iExperimentRepo, iExptItemRefRepo, quotaRepo, iLocker, idempotentService, auditClient, exptMetric, exptResultService, iEvalTargetService, evaluationSetItemService, evaluatorRecordService, serviceEvaluatorService, idgen2, benefitSvc, iEvalAsyncRepo, iItemCompletePublisher, v3...) iExptAnnotateService := service.NewExptAnnotateService(db2, iExptAnnotateRepo, iExptTurnResultRepo, exptEventPublisher, evaluationSetItemService, iExperimentRepo, exptResultService, iExptTurnResultFilterRepo, iExptAggrResultRepo) exptResultExportRecordDAO := mysql.NewExptResultExportRecordDAO(db2) iExptResultExportRecordRepo := experiment.NewExptResultExportRecordRepo(exptResultExportRecordDAO, idgen2) @@ -225,11 +227,13 @@ func InitEvaluatorApplication(ctx context.Context, idgen2 idgen.IIDGenerator, au codeBuilderFactory := service.NewCodeBuilderFactory() v := service.NewEvaluatorSourceServices(illmProvider, evaluatorExecMetrics, iConfiger, iRuntimeManager, codeBuilderFactory) iPlainRateLimiter := evaluator.NewPlainRateLimiterImpl(plainLimiterFactory) - evaluatorService := service.NewEvaluatorServiceImpl(idgen2, rateLimiter, rmqFactory, iEvaluatorRepo, iEvaluatorRecordRepo, idempotentService, iConfiger, v, iPlainRateLimiter, componentIConfiger) + iEvalAsyncDAO := dao.NewEvalAsyncDAO(cmdable) + iEvalAsyncRepo := experiment.NewEvalAsyncRepo(iEvalAsyncDAO) exptEventPublisher, err := producer.NewExptEventPublisher(ctx, configFactory, rmqFactory) if err != nil { return nil, err } + evaluatorService := service.NewEvaluatorServiceImpl(idgen2, rateLimiter, rmqFactory, iEvaluatorRepo, iEvaluatorRecordRepo, idempotentService, iConfiger, v, iPlainRateLimiter, componentIConfiger, iEvalAsyncRepo, exptEventPublisher) evaluatorEventPublisher, err := producer.NewEvaluatorEventPublisher(ctx, configFactory, rmqFactory) if err != nil { return nil, err @@ -280,8 +284,6 @@ func InitEvaluatorApplication(ctx context.Context, idgen2 idgen.IIDGenerator, au iTagRPCAdapter := tag.NewTagRPCProvider(tagClient) iEvaluationAnalysisService := service.NewEvaluationAnalysisService() exptResultService := service.NewExptResultService(iExptItemResultRepo, iExptTurnResultRepo, iExptAnnotateRepo, iExptStatsRepo, iExperimentRepo, exptMetric, iLatestWriteTracker, idgen2, iExptTurnResultFilterRepo, evaluatorService, iEvalTargetService, evaluationSetVersionService, iEvaluationSetService, evaluatorRecordService, evaluationSetItemService, exptEventPublisher, iTagRPCAdapter, iEvaluationAnalysisService, iFileProvider, iEvaluatorScoreCalculator) - iEvalAsyncDAO := dao.NewEvalAsyncDAO(cmdable) - iEvalAsyncRepo := experiment.NewEvalAsyncRepo(iEvalAsyncDAO) evaluationEvaluatorService := NewEvaluatorHandlerImpl(idgen2, iConfiger, iAuthProvider, evaluatorService, evaluatorRecordService, evaluatorTemplateService, evaluatorExecMetrics, userInfoService, auditClient, benefitSvc, iFileProvider, v, exptResultService, iEvalAsyncRepo) return evaluationEvaluatorService, nil } @@ -336,7 +338,7 @@ func InitEvalTargetApplication(ctx context.Context, idgen2 idgen.IIDGenerator, d return evalTargetService, nil } -func InitEvalOpenAPIApplication(ctx context.Context, configFactory conf.IConfigLoaderFactory, rmqFactory mq.IFactory, cmdable redis.Cmdable, idgen2 idgen.IIDGenerator, db2 db.Provider, client promptmanageservice.Client, executeClient promptexecuteservice.Client, authClient authservice.Client, meter metrics.Meter, dataClient datasetservice.Client, userClient userservice.Client, llmClient llmruntimeservice.Client, tagClient tagservice.Client, limiterFactory limiter.IRateLimiterFactory, objectStorage fileserver.ObjectStorage, batchObjectStorage fileserver.BatchObjectStorage, auditClient audit.IAuditService, benefitService benefit.IBenefitService, ckProvider ck.Provider, plainLimiterFactory limiter.IPlainRateLimiterFactory, trajectoryAdapter rpc.ITrajectoryAdapter, fileClient fileservice.Client, taskClientFactory func() taskservice.Client, scheduleAdapter rpc.IExptScheduleAdapter) (IEvalOpenAPIApplication, error) { +func InitEvalOpenAPIApplication(ctx context.Context, configFactory conf.IConfigLoaderFactory, rmqFactory mq.IFactory, cmdable redis.Cmdable, idgen2 idgen.IIDGenerator, db2 db.Provider, client promptmanageservice.Client, executeClient promptexecuteservice.Client, authClient authservice.Client, meter metrics.Meter, dataClient datasetservice.Client, userClient userservice.Client, llmClient llmruntimeservice.Client, tagClient tagservice.Client, limiterFactory limiter.IRateLimiterFactory, objectStorage fileserver.ObjectStorage, batchObjectStorage fileserver.BatchObjectStorage, auditClient audit.IAuditService, benefitService benefit.IBenefitService, ckProvider ck.Provider, plainLimiterFactory limiter.IPlainRateLimiterFactory, trajectoryAdapter rpc.ITrajectoryAdapter, fileClient fileservice.Client, taskClientFactory func() taskservice.Client, scheduleAdapter rpc.IExptScheduleAdapter) (evaluation.EvalOpenAPIService, error) { iEvalAsyncDAO := dao.NewEvalAsyncDAO(cmdable) iEvalAsyncRepo := experiment.NewEvalAsyncRepo(iEvalAsyncDAO) exptEventPublisher, err := producer.NewExptEventPublisher(ctx, configFactory, rmqFactory) @@ -399,7 +401,7 @@ func InitEvalOpenAPIApplication(ctx context.Context, configFactory conf.IConfigL codeBuilderFactory := service.NewCodeBuilderFactory() v2 := service.NewEvaluatorSourceServices(illmProvider, evaluatorExecMetrics, confIConfiger, iRuntimeManager, codeBuilderFactory) iPlainRateLimiter := evaluator.NewPlainRateLimiterImpl(plainLimiterFactory) - evaluatorService := service.NewEvaluatorServiceImpl(idgen2, rateLimiter, rmqFactory, iEvaluatorRepo, iEvaluatorRecordRepo, idempotentService, confIConfiger, v2, iPlainRateLimiter, iConfiger) + evaluatorService := service.NewEvaluatorServiceImpl(idgen2, rateLimiter, rmqFactory, iEvaluatorRepo, iEvaluatorRecordRepo, idempotentService, confIConfiger, v2, iPlainRateLimiter, iConfiger, iEvalAsyncRepo, exptEventPublisher) evaluatorEventPublisher, err := producer.NewEvaluatorEventPublisher(ctx, configFactory, rmqFactory) if err != nil { return nil, err @@ -441,10 +443,12 @@ func InitEvalOpenAPIApplication(ctx context.Context, configFactory conf.IConfigL } resourceAccessAuthorizer := service.NewResourceAccessAuthorizer(iAuthProvider, sharedResourceConfigProvider) iExptManager := service.NewExptManager(exptResultService, iExperimentRepo, iExptRunLogRepo, iExptStatsRepo, iExptItemResultRepo, iExptItemRefRepo, iExptTurnResultRepo, iConfiger, quotaRepo, iLocker, idempotentService, exptEventPublisher, auditClient, idgen2, exptMetric, iLatestWriteTracker, evaluationSetVersionService, iEvaluationSetService, iEvalTargetService, evaluatorService, benefitService, exptAggrResultService, iExptTemplateRepo, iExptTemplateManager, iNotifyRPCAdapter, iUserProvider, pipelineListAdapter, resourceAccessAuthorizer, sandboxAgentMetrics) - schedulerModeFactory := service.NewSchedulerModeFactory(iExptManager, iExptItemResultRepo, iExptStatsRepo, iExptTurnResultRepo, idgen2, evaluationSetItemService, iExperimentRepo, iExptItemRefRepo, idempotentService, iConfiger, exptEventPublisher, evaluatorRecordService, exptResultService, iExptTemplateManager, iExptRunLogRepo, iLocker) + iSandboxAgentNotifier := service.NewSandboxAgentNotifier(iNotifyRPCAdapter, iUserProvider, iExptStatsRepo, iLocker, iConfiger) + v3 := service.ProvideSandboxAgentNotifiers(iSandboxAgentNotifier) + schedulerModeFactory := service.NewSchedulerModeFactory(iExptManager, iExptItemResultRepo, iExptStatsRepo, iExptTurnResultRepo, idgen2, evaluationSetItemService, iExperimentRepo, iExptItemRefRepo, idempotentService, iConfiger, exptEventPublisher, evaluatorRecordService, exptResultService, iExptTemplateManager, iExptRunLogRepo, iLocker, v3...) iItemCompletePublisher := service.ProvideNilItemCompletePublisher() - exptSchedulerEvent := service.NewExptSchedulerSvc(iExptManager, iExperimentRepo, iExptItemResultRepo, iExptTurnResultRepo, iEvaluatorRecordRepo, iExptStatsRepo, iExptRunLogRepo, idempotentService, iConfiger, quotaRepo, iLocker, exptEventPublisher, auditClient, exptMetric, exptResultService, idgen2, evaluationSetItemService, schedulerModeFactory, iEvalTargetService, iItemCompletePublisher, iExptItemRefRepo, sandboxAgentMetrics) - exptItemEvalEvent := service.NewExptRecordEvalService(iExptManager, iConfiger, exptEventPublisher, iExptItemResultRepo, iExptTurnResultRepo, iExptStatsRepo, iExperimentRepo, iExptItemRefRepo, quotaRepo, iLocker, idempotentService, auditClient, exptMetric, exptResultService, iEvalTargetService, evaluationSetItemService, evaluatorRecordService, evaluatorService, idgen2, benefitService, iEvalAsyncRepo, iItemCompletePublisher) + exptSchedulerEvent := service.NewExptSchedulerSvc(iExptManager, iExperimentRepo, iExptItemResultRepo, iExptTurnResultRepo, iEvaluatorRecordRepo, iExptStatsRepo, iExptRunLogRepo, idempotentService, iConfiger, quotaRepo, iLocker, exptEventPublisher, auditClient, exptMetric, exptResultService, idgen2, evaluationSetItemService, schedulerModeFactory, iEvalTargetService, iItemCompletePublisher, iExptItemRefRepo, sandboxAgentMetrics, v3...) + exptItemEvalEvent := service.NewExptRecordEvalService(iExptManager, iConfiger, exptEventPublisher, iExptItemResultRepo, iExptTurnResultRepo, iExptStatsRepo, iExperimentRepo, iExptItemRefRepo, quotaRepo, iLocker, idempotentService, auditClient, exptMetric, exptResultService, iEvalTargetService, evaluationSetItemService, evaluatorRecordService, evaluatorService, idgen2, benefitService, iEvalAsyncRepo, iItemCompletePublisher, v3...) iExptAnnotateService := service.NewExptAnnotateService(db2, iExptAnnotateRepo, iExptTurnResultRepo, exptEventPublisher, evaluationSetItemService, iExperimentRepo, exptResultService, iExptTurnResultFilterRepo, iExptAggrResultRepo) exptResultExportRecordDAO := mysql.NewExptResultExportRecordDAO(db2) iExptResultExportRecordRepo := experiment.NewExptResultExportRecordRepo(exptResultExportRecordDAO, idgen2) @@ -461,8 +465,8 @@ func InitEvalOpenAPIApplication(ctx context.Context, configFactory conf.IConfigL exptLifecycleEventHandler := service.NewExptLifecycleEventHandler(iExperimentRepo, iNotifyRPCAdapter, iUserProvider, webhookDispatcher) iExperimentApplication := NewExperimentApplication(exptAggrResultService, exptResultService, iExptManager, exptSchedulerEvent, exptItemEvalEvent, idgen2, iConfiger, iAuthProvider, userInfoService, iEvalTargetService, evaluationSetItemService, iExptAnnotateService, iTagRPCAdapter, iExptResultExportService, iExptInsightAnalysisService, evaluatorService, iExptTemplateManager, iFileProvider, exptLifecycleEventHandler, sandboxSchedulerAdapter, sandboxAgentMetrics) evaluatorCallbackDispatcher := service.NewEvaluatorCallbackDispatcher(noopWebhookSecretProvider) - v3 := NewEvalOpenAPIApplication(iEvalAsyncRepo, exptEventPublisher, iEvalTargetService, iEvalTargetRepo, iAuthProvider, iEvaluationSetService, evaluationSetVersionService, evaluationSetItemService, evaluationSetSchemaService, openAPIEvaluationMetrics, sandboxAgentMetrics, userInfoService, iExperimentApplication, iExptManager, exptResultService, exptAggrResultService, evaluatorService, evaluatorRecordService, iExptTemplateManager, iConfiger, sandboxSchedulerAdapter, iFileProvider, evaluatorCallbackDispatcher, resourceAccessAuthorizer) - return v3, nil + evalOpenAPIService := NewEvalOpenAPIApplication(iEvalAsyncRepo, exptEventPublisher, iEvalTargetService, iEvalTargetRepo, iAuthProvider, iEvaluationSetService, evaluationSetVersionService, evaluationSetItemService, evaluationSetSchemaService, openAPIEvaluationMetrics, sandboxAgentMetrics, userInfoService, iExperimentApplication, iExptManager, exptResultService, exptAggrResultService, evaluatorService, evaluatorRecordService, iExptTemplateManager, iConfiger, sandboxSchedulerAdapter, iFileProvider, evaluatorCallbackDispatcher, resourceAccessAuthorizer) + return evalOpenAPIService, nil } // wire.go: diff --git a/backend/modules/evaluation/domain/entity/evaluator.go b/backend/modules/evaluation/domain/entity/evaluator.go index c52edd7303..e3ce7a6731 100644 --- a/backend/modules/evaluation/domain/entity/evaluator.go +++ b/backend/modules/evaluation/domain/entity/evaluator.go @@ -65,7 +65,17 @@ var EvaluatorTypeSet = map[EvaluatorType]struct{}{ } func (e *Evaluator) IsAsync() bool { - return e.EvaluatorType == EvaluatorTypeAgent + if e == nil { + return false + } + switch e.EvaluatorType { + case EvaluatorTypeAgent: + return true + case EvaluatorTypeCustomRPC: + return e.CustomRPCEvaluatorVersion != nil && e.CustomRPCEvaluatorVersion.IsAsync + default: + return false + } } // UpdateEvaluatorMetaRequest 用于更新评估器元信息的参数 diff --git a/backend/modules/evaluation/domain/entity/evaluator_test.go b/backend/modules/evaluation/domain/entity/evaluator_test.go index f9a66f9cdf..aee7513e25 100644 --- a/backend/modules/evaluation/domain/entity/evaluator_test.go +++ b/backend/modules/evaluation/domain/entity/evaluator_test.go @@ -2378,3 +2378,28 @@ func TestEvaluator_SetEvaluatorVersion_CustomRPCAndAgent(t *testing.T) { }) } } + +func TestEvaluator_IsAsync_CustomRPC(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + eval *Evaluator + want bool + }{ + {name: "nil evaluator remains sync", eval: nil, want: false}, + {name: "agent remains async", eval: &Evaluator{EvaluatorType: EvaluatorTypeAgent}, want: true}, + {name: "custom rpc async version", eval: &Evaluator{EvaluatorType: EvaluatorTypeCustomRPC, CustomRPCEvaluatorVersion: &CustomRPCEvaluatorVersion{IsAsync: true}}, want: true}, + {name: "custom rpc sync version", eval: &Evaluator{EvaluatorType: EvaluatorTypeCustomRPC, CustomRPCEvaluatorVersion: &CustomRPCEvaluatorVersion{}}, want: false}, + {name: "custom rpc missing version", eval: &Evaluator{EvaluatorType: EvaluatorTypeCustomRPC}, want: false}, + {name: "prompt remains sync", eval: &Evaluator{EvaluatorType: EvaluatorTypePrompt}, want: false}, + {name: "code remains sync", eval: &Evaluator{EvaluatorType: EvaluatorTypeCode}, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, tt.eval.IsAsync()) + }) + } +} diff --git a/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc.go b/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc.go index 4e99e30a7b..5aadce290a 100644 --- a/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc.go +++ b/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc.go @@ -57,8 +57,10 @@ type CustomRPCEvaluatorVersion struct { ServiceName *string `json:"service_name"` Cluster *string `json:"cluster"` InvokeHTTPInfo *EvaluatorHTTPInfo `json:"invoke_http_info,omitempty"` // invoke http info - Timeout *int64 `json:"timeout"` // timeout duration in milliseconds(ms) + AsyncInvokeHTTPInfo *EvaluatorHTTPInfo `json:"async_invoke_http_info,omitempty"` + Timeout *int64 `json:"timeout"` // timeout duration in milliseconds(ms) RateLimit *RateLimit `json:"rate_limit,omitempty"` + IsAsync bool `json:"is_async,omitempty"` // extra fields Ext map[string]string `json:"ext,omitempty"` @@ -150,6 +152,21 @@ func (do *CustomRPCEvaluatorVersion) ValidateBaseInfo() error { if do.ServiceName == nil || lo.IsEmpty(*do.ServiceName) { return errorx.NewByCode(errno.InvalidServiceNameCode, errorx.WithExtraMsg("service_name is empty")) } + if do.IsAsync { + switch do.AccessProtocol { + case EvaluatorAccessProtocolRPC: + return nil + case EvaluatorAccessProtocolFaasHTTP: + if do.AsyncInvokeHTTPInfo == nil || do.AsyncInvokeHTTPInfo.Path == nil || lo.IsEmpty(*do.AsyncInvokeHTTPInfo.Path) { + return errorx.NewByCode(errno.InvalidEvaluatorConfigurationCode, errorx.WithExtraMsg("async_invoke_http_info.path is empty")) + } + return nil + case EvaluatorAccessProtocolRPCOld, EvaluatorAccessProtocolFaasHTTPOld: + return errorx.NewByCode(errno.InvalidAccessProtocolCode, errorx.WithExtraMsg("legacy access protocol does not support async evaluator")) + default: + return errorx.NewByCode(errno.InvalidAccessProtocolCode, errorx.WithExtraMsg("access protocol does not support async evaluator")) + } + } return nil } diff --git a/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc_test.go b/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc_test.go index 88ee330b8b..03c34eee37 100644 --- a/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc_test.go +++ b/backend/modules/evaluation/domain/entity/evaluator_version_custom_rpc_test.go @@ -382,3 +382,95 @@ func TestCustomRPCEvaluatorVersion_GettersAndSetters(t *testing.T) { evaluator.SetBaseInfo(baseInfo) assert.Equal(t, baseInfo, evaluator.GetBaseInfo()) } + +func TestCustomRPCEvaluatorVersion_ValidateBaseInfo_Async(t *testing.T) { + t.Parallel() + + post := EvaluatorHTTPMethodPost + path := "/async_invoke_evaluator" + tests := []struct { + name string + version *CustomRPCEvaluatorVersion + wantErr bool + }{ + { + name: "rpc async does not require http info", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: EvaluatorAccessProtocolRPC, + ServiceName: gptr.Of("trae.work.evaluator"), + IsAsync: true, + }, + }, + { + name: "faas http async requires async http info", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: EvaluatorAccessProtocolFaasHTTP, + ServiceName: gptr.Of("trae.work.evaluator"), + IsAsync: true, + }, + wantErr: true, + }, + { + name: "faas http async accepts async http info", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: EvaluatorAccessProtocolFaasHTTP, + ServiceName: gptr.Of("trae.work.evaluator"), + IsAsync: true, + AsyncInvokeHTTPInfo: &EvaluatorHTTPInfo{ + Method: &post, + Path: &path, + }, + }, + }, + { + name: "old rpc async is rejected", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: EvaluatorAccessProtocolRPCOld, + ServiceName: gptr.Of("trae.work.evaluator"), + IsAsync: true, + }, + wantErr: true, + }, + { + name: "old http async is rejected", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: EvaluatorAccessProtocolFaasHTTPOld, + ServiceName: gptr.Of("trae.work.evaluator"), + IsAsync: true, + AsyncInvokeHTTPInfo: &EvaluatorHTTPInfo{ + Method: &post, + Path: &path, + }, + }, + wantErr: true, + }, + { + name: "unknown async protocol is rejected", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: "unsupported", + ServiceName: gptr.Of("trae.work.evaluator"), + IsAsync: true, + }, + wantErr: true, + }, + { + name: "sync faas http keeps existing behavior", + version: &CustomRPCEvaluatorVersion{ + AccessProtocol: EvaluatorAccessProtocolFaasHTTP, + ServiceName: gptr.Of("trae.work.evaluator"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.version.ValidateBaseInfo() + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/backend/modules/evaluation/domain/entity/expt_run.go b/backend/modules/evaluation/domain/entity/expt_run.go index 85021a03eb..a5bbb2f558 100644 --- a/backend/modules/evaluation/domain/entity/expt_run.go +++ b/backend/modules/evaluation/domain/entity/expt_run.go @@ -649,6 +649,7 @@ type EvalAsyncCtx struct { Callee string EvaluatorVersionID int64 // evaluator version id, used for evaluator async scenario EnableExtractTrajectory *bool + ResumeReady bool `json:"resume_ready,omitempty"` // experiment turn refs are durable and callback may resume scheduling CallbackURL string `json:"callback_url,omitempty"` // 异步执行完成后回调通知的 URL,为空则不回调 // 下述字段用于沙箱内部 step 上报的 tag 反查, 由 target async 写入位点从 etec 填充, // 调试场景 (无实验上下文) 保留零值, 由上报侧回退为占位符. diff --git a/backend/modules/evaluation/domain/entity/param.go b/backend/modules/evaluation/domain/entity/param.go index e1094fe9e6..f672e9846b 100644 --- a/backend/modules/evaluation/domain/entity/param.go +++ b/backend/modules/evaluation/domain/entity/param.go @@ -326,6 +326,9 @@ type AsyncRunEvaluatorRequest struct { // ★ alias 多实例: 同步与 RunEvaluatorRequest Alias string `json:"alias,omitempty"` SourceType EvaluatorRecordSourceType `json:"source_type,omitempty"` + // AsyncCtx is persisted after the AsyncInvoking record is created and before the provider is dispatched. + // Experiment calls set ResumeReady=false; direct calls set it true. + AsyncCtx *EvalAsyncCtx `json:"-"` } type AsyncRunEvaluatorResponse struct { @@ -357,6 +360,14 @@ type GetAsyncDebugEvaluatorInvokeResultResponse struct { InputData *EvaluatorInputData `json:"input_data,omitempty"` } +type ReportEvaluatorResultOutcome int + +const ( + ReportEvaluatorResultApplied ReportEvaluatorResultOutcome = iota + 1 + ReportEvaluatorResultDuplicate + ReportEvaluatorResultConflict +) + type ReportEvaluatorRecordParam struct { SpaceID int64 `json:"space_id"` RecordID int64 `json:"record_id"` diff --git a/backend/modules/evaluation/domain/repo/evaluator_record.go b/backend/modules/evaluation/domain/repo/evaluator_record.go index 3c3285541c..d3a4bc6e3e 100644 --- a/backend/modules/evaluation/domain/repo/evaluator_record.go +++ b/backend/modules/evaluation/domain/repo/evaluator_record.go @@ -20,4 +20,8 @@ type IEvaluatorRecordRepo interface { // 三个大字段的查询与反序列化,只返回 status=Success 且 score 非 NULL 的行(与内存聚合 contributing 集一致)。 BatchGetEvaluatorRecordForAggr(ctx context.Context, evaluatorRecordIDs []int64) ([]*entity.EvaluatorRecordAggr, error) UpdateEvaluatorRecordResult(ctx context.Context, recordID int64, status entity.EvaluatorRunStatus, outputData *entity.EvaluatorOutputData) error + // CompareAndSwapEvaluatorRecordResult updates a record only when id/space/status still match. + // It is the terminal-state guard for async callbacks and dispatch compensation. + CompareAndSwapEvaluatorRecordResult(ctx context.Context, recordID, spaceID int64, fromStatus, toStatus entity.EvaluatorRunStatus, outputData *entity.EvaluatorOutputData) (bool, error) + UpdateEvaluatorRecordAsyncDispatch(ctx context.Context, recordID, spaceID int64, traceID string, outputData *entity.EvaluatorOutputData) error } diff --git a/backend/modules/evaluation/domain/repo/expt.go b/backend/modules/evaluation/domain/repo/expt.go index 8f13418abb..d8b3b9b3c0 100644 --- a/backend/modules/evaluation/domain/repo/expt.go +++ b/backend/modules/evaluation/domain/repo/expt.go @@ -147,7 +147,11 @@ type IExptResultExportRecordRepo interface { type IEvalAsyncRepo interface { GetEvalAsyncCtx(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) + // GetEvalAsyncCtxStrong uses the shared direct Redis client and retries a transient missing key at 50/100/200ms. + GetEvalAsyncCtxStrong(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) SetEvalAsyncCtx(ctx context.Context, invokeID string, actx *entity.EvalAsyncCtx) error + // MarkEvalAsyncResumeReady atomically persists ResumeReady=true and returns the latest context. + MarkEvalAsyncResumeReady(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) } type IExptInsightAnalysisRecordRepo interface { diff --git a/backend/modules/evaluation/domain/repo/mocks/evaluator_record_mock.go b/backend/modules/evaluation/domain/repo/mocks/evaluator_record_mock.go index 0a93e30bfc..552d3a59e0 100644 --- a/backend/modules/evaluation/domain/repo/mocks/evaluator_record_mock.go +++ b/backend/modules/evaluation/domain/repo/mocks/evaluator_record_mock.go @@ -75,6 +75,33 @@ func (mr *MockIEvaluatorRecordRepoMockRecorder) BatchGetEvaluatorRecordForAggr(a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetEvaluatorRecordForAggr", reflect.TypeOf((*MockIEvaluatorRecordRepo)(nil).BatchGetEvaluatorRecordForAggr), arg0, arg1) } +// CompareAndSwapEvaluatorRecordResult mocks base method. +func (m *MockIEvaluatorRecordRepo) CompareAndSwapEvaluatorRecordResult(arg0 context.Context, arg1, arg2 int64, arg3, arg4 entity.EvaluatorRunStatus, arg5 *entity.EvaluatorOutputData) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CompareAndSwapEvaluatorRecordResult", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (mr *MockIEvaluatorRecordRepoMockRecorder) CompareAndSwapEvaluatorRecordResult(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareAndSwapEvaluatorRecordResult", reflect.TypeOf((*MockIEvaluatorRecordRepo)(nil).CompareAndSwapEvaluatorRecordResult), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// UpdateEvaluatorRecordAsyncDispatch mocks base method. +func (m *MockIEvaluatorRecordRepo) UpdateEvaluatorRecordAsyncDispatch(arg0 context.Context, arg1, arg2 int64, arg3 string, arg4 *entity.EvaluatorOutputData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateEvaluatorRecordAsyncDispatch", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +func (mr *MockIEvaluatorRecordRepoMockRecorder) UpdateEvaluatorRecordAsyncDispatch(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEvaluatorRecordAsyncDispatch", reflect.TypeOf((*MockIEvaluatorRecordRepo)(nil).UpdateEvaluatorRecordAsyncDispatch), arg0, arg1, arg2, arg3, arg4) +} + // CorrectEvaluatorRecord mocks base method. func (m *MockIEvaluatorRecordRepo) CorrectEvaluatorRecord(arg0 context.Context, arg1 *entity.EvaluatorRecord) error { m.ctrl.T.Helper() diff --git a/backend/modules/evaluation/domain/repo/mocks/expt.go b/backend/modules/evaluation/domain/repo/mocks/expt.go index 817edee9fe..1523e8dcde 100644 --- a/backend/modules/evaluation/domain/repo/mocks/expt.go +++ b/backend/modules/evaluation/domain/repo/mocks/expt.go @@ -1738,6 +1738,36 @@ func (mr *MockIEvalAsyncRepoMockRecorder) GetEvalAsyncCtx(arg0, arg1 any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvalAsyncCtx", reflect.TypeOf((*MockIEvalAsyncRepo)(nil).GetEvalAsyncCtx), arg0, arg1) } +// GetEvalAsyncCtxStrong mocks base method. +func (m *MockIEvalAsyncRepo) GetEvalAsyncCtxStrong(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEvalAsyncCtxStrong", ctx, invokeID) + ret0, _ := ret[0].(*entity.EvalAsyncCtx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEvalAsyncCtxStrong indicates an expected call of GetEvalAsyncCtxStrong. +func (mr *MockIEvalAsyncRepoMockRecorder) GetEvalAsyncCtxStrong(ctx, invokeID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvalAsyncCtxStrong", reflect.TypeOf((*MockIEvalAsyncRepo)(nil).GetEvalAsyncCtxStrong), ctx, invokeID) +} + +// MarkEvalAsyncResumeReady mocks base method. +func (m *MockIEvalAsyncRepo) MarkEvalAsyncResumeReady(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarkEvalAsyncResumeReady", ctx, invokeID) + ret0, _ := ret[0].(*entity.EvalAsyncCtx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarkEvalAsyncResumeReady indicates an expected call of MarkEvalAsyncResumeReady. +func (mr *MockIEvalAsyncRepoMockRecorder) MarkEvalAsyncResumeReady(ctx, invokeID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkEvalAsyncResumeReady", reflect.TypeOf((*MockIEvalAsyncRepo)(nil).MarkEvalAsyncResumeReady), ctx, invokeID) +} + // SetEvalAsyncCtx mocks base method. func (m *MockIEvalAsyncRepo) SetEvalAsyncCtx(arg0 context.Context, arg1 string, arg2 *entity.EvalAsyncCtx) error { m.ctrl.T.Helper() diff --git a/backend/modules/evaluation/domain/service/evaluator.go b/backend/modules/evaluation/domain/service/evaluator.go index 1fa36fcb84..87b580639b 100644 --- a/backend/modules/evaluation/domain/service/evaluator.go +++ b/backend/modules/evaluation/domain/service/evaluator.go @@ -65,8 +65,10 @@ type EvaluatorService interface { CheckNameExist(ctx context.Context, spaceID, evaluatorID int64, name string) (bool, error) // ListEvaluatorTags 根据 tagType 聚合标签,并按字母序返回 ListEvaluatorTags(ctx context.Context, tagType entity.EvaluatorTagKeyType) (map[entity.EvaluatorTagKey][]string, error) - // ReportEvaluatorInvokeResult 上报评估器异步执行结果 - ReportEvaluatorInvokeResult(ctx context.Context, param *entity.ReportEvaluatorRecordParam) error + // ReportEvaluatorInvokeResult 上报评估器异步执行结果. Duplicate/conflicting terminal callbacks are ignored. + ReportEvaluatorInvokeResult(ctx context.Context, param *entity.ReportEvaluatorRecordParam) (entity.ReportEvaluatorResultOutcome, error) + // ArmEvaluatorResume marks ResumeReady after turn refs are durable and republishes recovery when the record is already terminal. + ArmEvaluatorResume(ctx context.Context, recordID int64) error } //go:generate mockgen -destination mocks/evaluator_record_service_mock.go -package mocks . EvaluatorRecordService diff --git a/backend/modules/evaluation/domain/service/evaluator_impl.go b/backend/modules/evaluation/domain/service/evaluator_impl.go index 9d411cfa99..e8ee9a0a59 100644 --- a/backend/modules/evaluation/domain/service/evaluator_impl.go +++ b/backend/modules/evaluation/domain/service/evaluator_impl.go @@ -20,8 +20,10 @@ import ( "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/component" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/component/idem" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/entity" + "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/events" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/repo" "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/conf" + "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/contexts" "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/errno" "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/utils" "github.com/coze-dev/coze-loop/backend/pkg/errorx" @@ -46,6 +48,8 @@ func NewEvaluatorServiceImpl( evaluatorSourceServices map[entity.EvaluatorType]EvaluatorSourceService, plainRateLimiter repo.IPlainRateLimiter, cConfiger component.IConfiger, + evalAsyncRepo repo.IEvalAsyncRepo, + exptEventPublisher events.ExptEventPublisher, ) EvaluatorService { onceEvaluatorService.Do(func() { singletonEvaluatorService = &EvaluatorServiceImpl{ @@ -58,6 +62,8 @@ func NewEvaluatorServiceImpl( configer: configer, evaluatorSourceServices: evaluatorSourceServices, plainRateLimiter: plainRateLimiter, + evalAsyncRepo: evalAsyncRepo, + exptEventPublisher: exptEventPublisher, cConfiger: cConfiger, } }) @@ -75,6 +81,8 @@ type EvaluatorServiceImpl struct { configer conf.IConfiger evaluatorSourceServices map[entity.EvaluatorType]EvaluatorSourceService plainRateLimiter repo.IPlainRateLimiter + evalAsyncRepo repo.IEvalAsyncRepo + exptEventPublisher events.ExptEventPublisher cConfiger component.IConfiger } @@ -956,7 +964,7 @@ func (e *EvaluatorServiceImpl) CreateEvaluatorRunFailRecord(ctx context.Context, return recordDO, nil } -// AsyncRunEvaluator Agent evaluator_version 异步运行 +// AsyncRunEvaluator coordinates evaluator async kickoff in the strict order Record -> Context -> Provider. func (e *EvaluatorServiceImpl) AsyncRunEvaluator(ctx context.Context, request *entity.AsyncRunEvaluatorRequest) (*entity.EvaluatorRecord, error) { evaluatorDOList, err := e.evaluatorRepo.BatchGetEvaluatorByVersionID(ctx, nil, []int64{request.EvaluatorVersionID}, false, false) if err != nil { @@ -966,13 +974,14 @@ func (e *EvaluatorServiceImpl) AsyncRunEvaluator(ctx context.Context, request *e return nil, errorx.NewByCode(errno.EvaluatorVersionNotFoundCode, errorx.WithExtraMsg("evaluator_version version not found")) } evaluatorDO := evaluatorDOList[0] - if evaluatorDO.EvaluatorType != entity.EvaluatorTypeAgent { - return nil, errorx.NewByCode(errno.InvalidEvaluatorTypeCode, errorx.WithExtraMsg("async run only supports Agent evaluator type")) + if evaluatorDO.EvaluatorType == entity.EvaluatorTypeCustomRPC && evaluatorDO.Builtin { + return nil, errorx.NewByCode(errno.InvalidEvaluatorTypeCode, errorx.WithExtraMsg("builtin CustomRPC evaluator does not support async run")) } - if !evaluatorDO.Builtin { - if evaluatorDO.SpaceID != request.SpaceID { - return nil, errorx.NewByCode(errno.EvaluatorVersionNotFoundCode, errorx.WithExtraMsg("evaluator_version not found in current space")) - } + if !evaluatorDO.IsAsync() { + return nil, errorx.NewByCode(errno.InvalidEvaluatorTypeCode, errorx.WithExtraMsg("evaluator does not support async run")) + } + if !evaluatorDO.Builtin && evaluatorDO.SpaceID != request.SpaceID { + return nil, errorx.NewByCode(errno.EvaluatorVersionNotFoundCode, errorx.WithExtraMsg("evaluator_version not found in current space")) } if allow := e.limiter.AllowInvoke(ctx, request.SpaceID); !allow { return nil, errorx.NewByCode(errno.EvaluatorQPSLimitCode, errorx.WithExtraMsg("evaluator throttled due to space-level rate limit")) @@ -986,20 +995,11 @@ func (e *EvaluatorServiceImpl) AsyncRunEvaluator(ctx context.Context, request *e } evaluatorSourceService, ok := e.evaluatorSourceServices[evaluatorDO.EvaluatorType] if !ok { - return nil, errorx.NewByCode(errno.InvalidEvaluatorTypeCode, errorx.WithExtraMsg("evaluator source service not found for agent type")) - } - asyncRunExt, traceID, err := evaluatorSourceService.AsyncRun(ctx, evaluatorDO, request.InputData, request.EvaluatorRunConf, request.SpaceID, invokeID) - if err != nil { - logs.CtxError(ctx, "[AsyncRunEvaluator] AsyncRun fail, invokeID: %d, err: %v", invokeID, err) - return nil, err + return nil, errorx.NewByCode(errno.InvalidEvaluatorTypeCode, errorx.WithExtraMsg("evaluator source service not found for async type")) } + now := time.Now().UnixMilli() userIDInContext := session.UserIDInCtxOrEmpty(ctx) - logID := logs.GetLogID(ctx) - status := entity.EvaluatorRunStatusAsyncInvoking - outputData := &entity.EvaluatorOutputData{ - Ext: asyncRunExt, - } recordDO := &entity.EvaluatorRecord{ ID: invokeID, SpaceID: request.SpaceID, @@ -1010,33 +1010,105 @@ func (e *EvaluatorServiceImpl) AsyncRunEvaluator(ctx context.Context, request *e EvaluatorVersionID: request.EvaluatorVersionID, Alias: request.Alias, SourceType: normalizeEvaluatorRecordSourceType(request.SourceType), - TraceID: traceID, - LogID: logID, + LogID: logs.GetLogID(ctx), EvaluatorInputData: request.InputData, - EvaluatorOutputData: outputData, - Status: status, + EvaluatorOutputData: &entity.EvaluatorOutputData{}, + Status: entity.EvaluatorRunStatusAsyncInvoking, Ext: request.Ext, BaseInfo: &entity.BaseInfo{ - CreatedBy: &entity.UserInfo{ - UserID: gptr.Of(userIDInContext), - }, - UpdatedBy: &entity.UserInfo{ - UserID: gptr.Of(userIDInContext), - }, - CreatedAt: gptr.Of(time.Now().UnixMilli()), - UpdatedAt: gptr.Of(time.Now().UnixMilli()), + CreatedBy: &entity.UserInfo{UserID: gptr.Of(userIDInContext)}, + UpdatedBy: &entity.UserInfo{UserID: gptr.Of(userIDInContext)}, + CreatedAt: gptr.Of(now), + UpdatedAt: gptr.Of(now), }, } - if err := e.evaluatorRecordRepo.CreateEvaluatorRecord(ctx, recordDO); err != nil { + // CreateEvaluatorRecord may truncate oversized Content fields in-place before persisting them. + // Persist a deep copy so the provider still receives the original, complete input. + persistedRecord := *recordDO + persistedRecord.EvaluatorInputData = deepCopyEvaluatorInputData(request.InputData) + if persistedRecord.EvaluatorInputData == request.InputData && request.InputData != nil { + return nil, errorx.New("deep copy evaluator input data failed") + } + if err := e.evaluatorRecordRepo.CreateEvaluatorRecord(ctx, &persistedRecord); err != nil { logs.CtxError(ctx, "[AsyncRunEvaluator] CreateEvaluatorRecord fail, invokeID: %d, err: %v", invokeID, err) return nil, err } + asyncCtx := request.AsyncCtx + if asyncCtx == nil { + asyncCtx = &entity.EvalAsyncCtx{ResumeReady: true} + } + asyncCtx.RecordID = invokeID + asyncCtx.EvaluatorVersionID = request.EvaluatorVersionID + if asyncCtx.AsyncUnixMS == 0 { + asyncCtx.AsyncUnixMS = now + } + if asyncCtx.Session == nil { + asyncCtx.Session = &entity.Session{UserID: userIDInContext} + } + if e.evalAsyncRepo == nil { + return e.failAsyncEvaluatorRecord(ctx, recordDO, errorx.New("eval async repo is nil")) + } + asyncCtxKey := fmt.Sprintf("evaluator:%d", invokeID) + if err := e.evalAsyncRepo.SetEvalAsyncCtx(ctx, asyncCtxKey, asyncCtx); err != nil { + return e.failAsyncEvaluatorRecord(ctx, recordDO, err) + } + + asyncRunExt, traceID, err := evaluatorSourceService.AsyncRun(ctx, evaluatorDO, request.InputData, request.EvaluatorRunConf, request.SpaceID, invokeID) + if err != nil { + logs.CtxError(ctx, "[AsyncRunEvaluator] AsyncRun fail, invokeID: %d, err: %v", invokeID, err) + return e.failAsyncEvaluatorRecord(ctx, recordDO, err) + } + recordDO.TraceID = traceID + recordDO.EvaluatorOutputData.Ext = asyncRunExt + if traceID != "" || len(asyncRunExt) > 0 { + if err := e.evaluatorRecordRepo.UpdateEvaluatorRecordAsyncDispatch(ctx, recordDO.ID, recordDO.SpaceID, traceID, recordDO.EvaluatorOutputData); err != nil { + // The provider has already accepted the work. Treat dispatch metadata as best-effort; + // marking the record failed here would create an uncertain-state split brain and reject a later valid callback. + logs.CtxError(ctx, "[AsyncRunEvaluator] persist dispatch metadata fail, keep record async invoking, invokeID: %d, err: %v", invokeID, err) + } + } logs.CtxInfo(ctx, "[AsyncRunEvaluator] invokeID: %d, evaluatorVersionID: %d, spaceID: %d, record_ext: %v", invokeID, request.EvaluatorVersionID, request.SpaceID, json.Jsonify(recordDO.Ext)) return recordDO, nil } +func (e *EvaluatorServiceImpl) failAsyncEvaluatorRecord(ctx context.Context, record *entity.EvaluatorRecord, runErr error) (*entity.EvaluatorRecord, error) { + if record == nil { + return nil, runErr + } + errMsg := "evaluator async run failed" + if runErr != nil { + errMsg = errorx.ErrorWithoutStack(runErr) + } + output := &entity.EvaluatorOutputData{EvaluatorRunError: &entity.EvaluatorRunError{ + Code: int32(errno.CommonInternalErrorCode), + Message: errMsg, + }} + updated, casErr := e.evaluatorRecordRepo.CompareAndSwapEvaluatorRecordResult(ctx, record.ID, record.SpaceID, entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, output) + if casErr != nil { + return record, errorx.Wrapf(casErr, "mark evaluator async kickoff failed, cause: %v", runErr) + } + if updated { + record.Status = entity.EvaluatorRunStatusFail + record.EvaluatorOutputData = output + return record, runErr + } + + // The provider call can return an ACK error after it has already accepted the task and + // synchronously reported a terminal result. In that race the terminal CAS above loses by + // design; re-read the primary and let the callback outcome win instead of failing the turn + // with a stale dispatch error. + latest, getErr := e.evaluatorRecordRepo.GetEvaluatorRecord(contexts.WithCtxWriteDB(ctx), record.ID, false, entity.WithoutLoadStorageData()) + if getErr != nil { + return record, errorx.Wrapf(getErr, "reload evaluator record after async kickoff failure, cause: %v", runErr) + } + if latest != nil && (latest.Status == entity.EvaluatorRunStatusSuccess || latest.Status == entity.EvaluatorRunStatusFail) { + return latest, nil + } + return record, runErr +} + // AsyncDebugEvaluator Agent evaluator_version 异步调试 func (e *EvaluatorServiceImpl) AsyncDebugEvaluator(ctx context.Context, request *entity.AsyncDebugEvaluatorRequest) (*entity.AsyncDebugEvaluatorResponse, error) { evaluatorDO := request.EvaluatorDO @@ -1101,35 +1173,46 @@ func (e *EvaluatorServiceImpl) AsyncDebugEvaluator(ctx context.Context, request }, nil } -// ReportEvaluatorInvokeResult 上报评估器异步执行结果 -func (e *EvaluatorServiceImpl) ReportEvaluatorInvokeResult(ctx context.Context, param *entity.ReportEvaluatorRecordParam) error { +// ReportEvaluatorInvokeResult 上报评估器异步执行结果 using a terminal CAS. +func (e *EvaluatorServiceImpl) ReportEvaluatorInvokeResult(ctx context.Context, param *entity.ReportEvaluatorRecordParam) (entity.ReportEvaluatorResultOutcome, error) { + if param == nil { + return 0, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("report evaluator result param is nil")) + } + if param.Status != entity.EvaluatorRunStatusSuccess && param.Status != entity.EvaluatorRunStatusFail { + return 0, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("report evaluator status must be success or fail")) + } logs.CtxInfo(ctx, "[ReportEvaluatorInvokeResult] recordID: %d, spaceID: %d, status: %v", param.RecordID, param.SpaceID, param.Status) - existingRecord, err := e.evaluatorRecordRepo.GetEvaluatorRecord(ctx, param.RecordID, false) + existingRecord, err := e.evaluatorRecordRepo.GetEvaluatorRecord(contexts.WithCtxWriteDB(ctx), param.RecordID, false) if err != nil { logs.CtxError(ctx, "[ReportEvaluatorInvokeResult] GetEvaluatorRecord fail, recordID: %d, err: %v", param.RecordID, err) - return err + return 0, err } if existingRecord == nil { - return errorx.NewByCode(errno.EvaluatorRecordNotFoundCode, errorx.WithExtraMsg("evaluator record not found")) + return 0, errorx.NewByCode(errno.EvaluatorRecordNotFoundCode, errorx.WithExtraMsg("evaluator record not found")) } - if existingRecord.SpaceID != param.SpaceID { logs.CtxWarn(ctx, "[ReportEvaluatorInvokeResult] spaceID mismatch, recordID: %d, requestSpaceID: %d, recordSpaceID: %d", param.RecordID, param.SpaceID, existingRecord.SpaceID) - return errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("spaceID mismatch")) + return 0, errorx.NewByCode(errno.CommonInvalidParamCode, errorx.WithExtraMsg("spaceID mismatch")) } if existingRecord.Status != entity.EvaluatorRunStatusAsyncInvoking { - logs.CtxWarn(ctx, "[ReportEvaluatorInvokeResult] skip stale callback, recordID: %d, dbStatus: %v, reportStatus: %v", - param.RecordID, existingRecord.Status, param.Status) - return nil + if existingRecord.Status == param.Status { + return entity.ReportEvaluatorResultDuplicate, nil + } + return entity.ReportEvaluatorResultConflict, nil } mergedOutputData := param.OutputData if mergedOutputData == nil { mergedOutputData = &entity.EvaluatorOutputData{} } + if mergedOutputData.TimeConsumingMS == 0 && existingRecord.BaseInfo != nil && existingRecord.BaseInfo.CreatedAt != nil { + if elapsedMS := time.Now().UnixMilli() - gptr.Indirect(existingRecord.BaseInfo.CreatedAt); elapsedMS > 0 { + mergedOutputData.TimeConsumingMS = elapsedMS + } + } if existingRecord.EvaluatorOutputData != nil && existingRecord.EvaluatorOutputData.Ext != nil { if mergedOutputData.Ext == nil { mergedOutputData.Ext = make(map[string]string) @@ -1141,7 +1224,93 @@ func (e *EvaluatorServiceImpl) ReportEvaluatorInvokeResult(ctx context.Context, } } - return e.evaluatorRecordRepo.UpdateEvaluatorRecordResult(ctx, param.RecordID, param.Status, mergedOutputData) + updated, err := e.evaluatorRecordRepo.CompareAndSwapEvaluatorRecordResult(ctx, param.RecordID, param.SpaceID, entity.EvaluatorRunStatusAsyncInvoking, param.Status, mergedOutputData) + if err != nil { + return 0, err + } + if updated { + return entity.ReportEvaluatorResultApplied, nil + } + + latestRecord, err := e.evaluatorRecordRepo.GetEvaluatorRecord(contexts.WithCtxWriteDB(ctx), param.RecordID, false) + if err != nil { + return 0, err + } + if latestRecord != nil && latestRecord.Status == param.Status { + logs.CtxWarn(ctx, "[ReportEvaluatorInvokeResult] duplicate terminal callback, recordID: %d, status: %v", param.RecordID, param.Status) + return entity.ReportEvaluatorResultDuplicate, nil + } + logs.CtxWarn(ctx, "[ReportEvaluatorInvokeResult] conflicting terminal callback, recordID: %d, reportStatus: %v", param.RecordID, param.Status) + return entity.ReportEvaluatorResultConflict, nil +} + +func (e *EvaluatorServiceImpl) ArmEvaluatorResume(ctx context.Context, recordID int64) error { + if e.evalAsyncRepo == nil { + return errorx.New("eval async repo is nil") + } + asyncCtxKey := fmt.Sprintf("evaluator:%d", recordID) + var ( + actx *entity.EvalAsyncCtx + err error + ) + for _, delay := range []time.Duration{0, 50 * time.Millisecond, 100 * time.Millisecond} { + if delay > 0 { + timer := time.NewTimer(delay) + select { + case <-ctx.Done(): + timer.Stop() + return ctx.Err() + case <-timer.C: + } + } + actx, err = e.evalAsyncRepo.MarkEvalAsyncResumeReady(ctx, asyncCtxKey) + if err == nil { + break + } + } + if err != nil { + return err + } + if actx == nil || actx.Event == nil { + return nil + } + record, err := e.evaluatorRecordRepo.GetEvaluatorRecord(contexts.WithCtxWriteDB(ctx), recordID, false) + if err != nil { + return err + } + if record == nil { + return errorx.NewByCode(errno.EvaluatorRecordNotFoundCode, errorx.WithExtraMsg("evaluator record not found")) + } + if record.Status == entity.EvaluatorRunStatusAsyncInvoking { + return nil + } + return e.publishEvaluatorResumeEvent(ctx, actx.Event) +} + +func (e *EvaluatorServiceImpl) publishEvaluatorResumeEvent(ctx context.Context, event *entity.ExptItemEvalEvent) error { + if event == nil || e.exptEventPublisher == nil { + return nil + } + delays := []time.Duration{0, 50 * time.Millisecond, 100 * time.Millisecond} + var lastErr error + for _, delay := range delays { + if delay > 0 { + timer := time.NewTimer(delay) + select { + case <-ctx.Done(): + timer.Stop() + return ctx.Err() + case <-timer.C: + } + } + lastErr = e.exptEventPublisher.PublishExptRecordEvalEvent(ctx, event, gptr.Of(time.Second*3), func(event *entity.ExptItemEvalEvent) { + event.AsyncEvaluatorReportTrigger = true + }) + if lastErr == nil { + return nil + } + } + return lastErr } // DebugEvaluator 调试 evaluator_version diff --git a/backend/modules/evaluation/domain/service/evaluator_impl_test.go b/backend/modules/evaluation/domain/service/evaluator_impl_test.go index 8781a1fed0..e11a30fb78 100644 --- a/backend/modules/evaluation/domain/service/evaluator_impl_test.go +++ b/backend/modules/evaluation/domain/service/evaluator_impl_test.go @@ -6,6 +6,7 @@ package service import ( "context" "errors" + "fmt" "testing" "time" @@ -21,11 +22,13 @@ import ( idemmocks "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/component/idem/mocks" componentMocks "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/component/mocks" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/entity" + eventmocks "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/events/mocks" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/service/mocks" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/repo" repomocks "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/repo/mocks" confmocks "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/conf/mocks" + "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/contexts" "github.com/coze-dev/coze-loop/backend/modules/evaluation/pkg/errno" "github.com/coze-dev/coze-loop/backend/pkg/errorx" "github.com/coze-dev/coze-loop/backend/pkg/lang/ptr" @@ -59,6 +62,8 @@ func TestNewEvaluatorServiceImpl(t *testing.T) { }, mockPlainLimiter, mockErrConfiger, + repomocks.NewMockIEvalAsyncRepo(ctrl), + nil, ) assert.IsType(t, &EvaluatorServiceImpl{}, service) @@ -1137,6 +1142,7 @@ func TestEvaluatorServiceImpl_CreateEvaluator(t *testing.T) { expectedID int64 expectedErr error expectedErrCode int32 + expectedOutcome entity.ReportEvaluatorResultOutcome }{ { name: "失败 - validateCreateEvaluatorRequest - CheckNameExist 返回错误", @@ -3262,6 +3268,7 @@ func TestEvaluatorServiceImpl_AsyncRunEvaluator(t *testing.T) { mockEvaluatorRecordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) mockEvaluatorSourceService := mocks.NewMockEvaluatorSourceService(ctrl) mockPlainLimiter := repomocks.NewMockIPlainRateLimiter(ctrl) + mockEvalAsyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) mockCConfiger := componentMocks.NewMockIConfiger(ctrl) mockCConfiger.EXPECT().BuildEvalExt(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() @@ -3275,6 +3282,7 @@ func TestEvaluatorServiceImpl_AsyncRunEvaluator(t *testing.T) { entity.EvaluatorTypeAgent: mockEvaluatorSourceService, }, plainRateLimiter: mockPlainLimiter, + evalAsyncRepo: mockEvalAsyncRepo, cConfiger: mockCConfiger, } @@ -3315,6 +3323,7 @@ func TestEvaluatorServiceImpl_AsyncRunEvaluator(t *testing.T) { name string setupMocks func() expectedErrCode int32 + expectedOutcome entity.ReportEvaluatorResultOutcome }{ { name: "成功 - 异步运行 Agent 评估器", @@ -3323,6 +3332,7 @@ func TestEvaluatorServiceImpl_AsyncRunEvaluator(t *testing.T) { mockLimiter.EXPECT().AllowInvoke(gomock.Any(), req.SpaceID).Return(true) mockPlainLimiter.EXPECT().AllowInvokeWithKeyLimit(gomock.Any(), "async_run_evaluator:100", gomock.Any()).Return(true) mockIDGen.EXPECT().GenID(gomock.Any()).Return(int64(999), nil) + mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) mockEvaluatorSourceService.EXPECT().AsyncRun(gomock.Any(), agentEvaluatorDO, req.InputData, req.EvaluatorRunConf, req.SpaceID, int64(999)). Return(map[string]string{"async": "1"}, "trace-1", nil) mockEvaluatorRecordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).DoAndReturn( @@ -3332,9 +3342,13 @@ func TestEvaluatorServiceImpl_AsyncRunEvaluator(t *testing.T) { assert.Equal(t, req.EvaluatorVersionID, record.EvaluatorVersionID) assert.Equal(t, entity.EvaluatorRunStatusAsyncInvoking, record.Status) assert.Equal(t, req.Ext, record.Ext) - if assert.NotNil(t, record.EvaluatorOutputData) { - assert.Equal(t, map[string]string{"async": "1"}, record.EvaluatorOutputData.Ext) - } + return nil + }, + ) + mockEvaluatorRecordRepo.EXPECT().UpdateEvaluatorRecordAsyncDispatch(gomock.Any(), int64(999), req.SpaceID, "trace-1", gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ int64, traceID string, out *entity.EvaluatorOutputData) error { + assert.Equal(t, "trace-1", traceID) + assert.Equal(t, map[string]string{"async": "1"}, out.Ext) return nil }, ) @@ -3528,6 +3542,7 @@ func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult(t *testing.T) { param *entity.ReportEvaluatorRecordParam setupMocks func() expectedErrCode int32 + expectedOutcome entity.ReportEvaluatorResultOutcome }{ { name: "成功 - 合并 Ext 并更新记录", @@ -3550,16 +3565,17 @@ func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult(t *testing.T) { }, }, nil, ) - mockEvaluatorRecordRepo.EXPECT().UpdateEvaluatorRecordResult(gomock.Any(), int64(100), entity.EvaluatorRunStatusSuccess, gomock.Any()). - DoAndReturn(func(_ context.Context, _ int64, _ entity.EvaluatorRunStatus, out *entity.EvaluatorOutputData) error { + mockEvaluatorRecordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(100), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, gomock.Any()). + DoAndReturn(func(_ context.Context, _, _ int64, _, _ entity.EvaluatorRunStatus, out *entity.EvaluatorOutputData) (bool, error) { if assert.NotNil(t, out) { assert.Equal(t, "1", out.Ext["new"]) assert.Equal(t, "1", out.Ext["old"]) } - return nil + return true, nil }) }, expectedErrCode: 0, + expectedOutcome: entity.ReportEvaluatorResultApplied, }, { name: "失败 - record 不存在", @@ -3601,16 +3617,18 @@ func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult(t *testing.T) { }, nil) }, expectedErrCode: 0, + expectedOutcome: entity.ReportEvaluatorResultConflict, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setupMocks() - err := s.ReportEvaluatorInvokeResult(ctx, tt.param) + outcome, err := s.ReportEvaluatorInvokeResult(ctx, tt.param) if tt.expectedErrCode == 0 { assert.NoError(t, err) + assert.Equal(t, tt.expectedOutcome, outcome) return } assert.Error(t, err) @@ -3666,15 +3684,15 @@ func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult_OutputDataNilOrExtNil( }, }, nil, ) - mockEvaluatorRecordRepo.EXPECT().UpdateEvaluatorRecordResult(gomock.Any(), int64(100), entity.EvaluatorRunStatusSuccess, gomock.Any()). - DoAndReturn(func(_ context.Context, _ int64, _ entity.EvaluatorRunStatus, out *entity.EvaluatorOutputData) error { + mockEvaluatorRecordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(100), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, gomock.Any()). + DoAndReturn(func(_ context.Context, _, _ int64, _, _ entity.EvaluatorRunStatus, out *entity.EvaluatorOutputData) (bool, error) { if assert.NotNil(t, out) && assert.NotNil(t, out.Ext) { assert.Equal(t, "1", out.Ext["old"]) } - return nil + return true, nil }) - err := s.ReportEvaluatorInvokeResult(ctx, param) + _, err := s.ReportEvaluatorInvokeResult(ctx, param) assert.NoError(t, err) }) } @@ -4548,3 +4566,533 @@ func TestEvaluatorServiceImpl_CreateEvaluatorRunFailRecord_Errors(t *testing.T) assert.Nil(t, record) }) } + +func TestEvaluatorServiceImpl_AsyncRunEvaluator_RejectsBuiltinCustomRPC(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + evaluatorRepo := repomocks.NewMockIEvaluatorRepo(ctrl) + s := &EvaluatorServiceImpl{evaluatorRepo: evaluatorRepo} + evaluatorRepo.EXPECT().BatchGetEvaluatorByVersionID(gomock.Any(), nil, []int64{int64(101)}, false, false).Return([]*entity.Evaluator{{ + Builtin: true, + EvaluatorType: entity.EvaluatorTypeCustomRPC, + CustomRPCEvaluatorVersion: &entity.CustomRPCEvaluatorVersion{ + ID: 101, + IsAsync: true, + }, + }}, nil) + + record, err := s.AsyncRunEvaluator(context.Background(), &entity.AsyncRunEvaluatorRequest{SpaceID: 2, EvaluatorVersionID: 101}) + require.Error(t, err) + assert.Nil(t, record) +} + +func TestEvaluatorServiceImpl_AsyncRunEvaluator_PersistsCopyWithoutMutatingProviderInput(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + evaluatorRepo := repomocks.NewMockIEvaluatorRepo(ctrl) + limiter := repomocks.NewMockRateLimiter(ctrl) + plainLimiter := repomocks.NewMockIPlainRateLimiter(ctrl) + idGenerator := idgenmocks.NewMockIIDGenerator(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + source := mocks.NewMockEvaluatorSourceService(ctrl) + + originalText := "full evaluator input that must reach provider" + input := &entity.EvaluatorInputData{InputFields: map[string]*entity.Content{ + "actual_output": {ContentType: gptr.Of(entity.ContentTypeText), Text: gptr.Of(originalText)}, + }} + evaluatorDO := &entity.Evaluator{ + ID: 100, SpaceID: 2, EvaluatorType: entity.EvaluatorTypeAgent, + AgentEvaluatorVersion: &entity.AgentEvaluatorVersion{ID: 101}, + } + evaluatorRepo.EXPECT().BatchGetEvaluatorByVersionID(gomock.Any(), nil, []int64{int64(101)}, false, false).Return([]*entity.Evaluator{evaluatorDO}, nil) + limiter.EXPECT().AllowInvoke(gomock.Any(), int64(2)).Return(true) + plainLimiter.EXPECT().AllowInvokeWithKeyLimit(gomock.Any(), "async_run_evaluator:100", gomock.Any()).Return(true) + idGenerator.EXPECT().GenID(gomock.Any()).Return(int64(999), nil) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, record *entity.EvaluatorRecord) error { + require.NotSame(t, input, record.EvaluatorInputData) + record.EvaluatorInputData.InputFields["actual_output"].SetText("persisted preview") + return nil + }) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), evaluatorDO, gomock.Any(), gomock.Any(), int64(2), int64(999)).DoAndReturn( + func(_ context.Context, _ *entity.Evaluator, got *entity.EvaluatorInputData, _ *entity.EvaluatorRunConfig, _, _ int64) (map[string]string, string, error) { + require.Same(t, input, got) + assert.Equal(t, originalText, got.InputFields["actual_output"].GetText()) + return nil, "", nil + }, + ) + + record, err := (&EvaluatorServiceImpl{ + evaluatorRepo: evaluatorRepo, limiter: limiter, plainRateLimiter: plainLimiter, + idgen: idGenerator, evaluatorRecordRepo: recordRepo, evalAsyncRepo: asyncRepo, + evaluatorSourceServices: map[entity.EvaluatorType]EvaluatorSourceService{entity.EvaluatorTypeAgent: source}, + }).AsyncRunEvaluator(context.Background(), &entity.AsyncRunEvaluatorRequest{ + SpaceID: 2, EvaluatorVersionID: 101, InputData: input, + }) + require.NoError(t, err) + require.NotNil(t, record) + assert.Equal(t, originalText, input.InputFields["actual_output"].GetText()) + assert.Same(t, input, record.EvaluatorInputData) +} + +func TestEvaluatorServiceImpl_AsyncRunEvaluator_CoordinatorOrderAndFailureCompensation(t *testing.T) { + t.Parallel() + + newFixture := func(t *testing.T) (*EvaluatorServiceImpl, *repomocks.MockIEvaluatorRecordRepo, *repomocks.MockIEvalAsyncRepo, *mocks.MockEvaluatorSourceService) { + t.Helper() + ctrl := gomock.NewController(t) + evaluatorRepo := repomocks.NewMockIEvaluatorRepo(ctrl) + limiter := repomocks.NewMockRateLimiter(ctrl) + plainLimiter := repomocks.NewMockIPlainRateLimiter(ctrl) + idGenerator := idgenmocks.NewMockIIDGenerator(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + source := mocks.NewMockEvaluatorSourceService(ctrl) + evaluatorDO := &entity.Evaluator{ + ID: 100, + SpaceID: 2, + EvaluatorType: entity.EvaluatorTypeAgent, + AgentEvaluatorVersion: &entity.AgentEvaluatorVersion{ + ID: 101, + }, + } + evaluatorRepo.EXPECT().BatchGetEvaluatorByVersionID(gomock.Any(), nil, []int64{int64(101)}, false, false).Return([]*entity.Evaluator{evaluatorDO}, nil) + limiter.EXPECT().AllowInvoke(gomock.Any(), int64(2)).Return(true) + plainLimiter.EXPECT().AllowInvokeWithKeyLimit(gomock.Any(), "async_run_evaluator:100", gomock.Any()).Return(true) + idGenerator.EXPECT().GenID(gomock.Any()).Return(int64(999), nil) + return &EvaluatorServiceImpl{ + evaluatorRepo: evaluatorRepo, + limiter: limiter, + plainRateLimiter: plainLimiter, + idgen: idGenerator, + evaluatorRecordRepo: recordRepo, + evalAsyncRepo: asyncRepo, + evaluatorSourceServices: map[entity.EvaluatorType]EvaluatorSourceService{ + entity.EvaluatorTypeAgent: source, + }, + }, recordRepo, asyncRepo, source + } + + req := func() *entity.AsyncRunEvaluatorRequest { + return &entity.AsyncRunEvaluatorRequest{ + SpaceID: 2, + EvaluatorVersionID: 101, + InputData: &entity.EvaluatorInputData{}, + AsyncCtx: &entity.EvalAsyncCtx{ + Event: &entity.ExptItemEvalEvent{ExptID: 10}, + ResumeReady: false, + }, + } + } + + t.Run("record then context then provider", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + order := make([]string, 0, 3) + gomock.InOrder( + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, record *entity.EvaluatorRecord) error { + order = append(order, "record") + assert.Equal(t, entity.EvaluatorRunStatusAsyncInvoking, record.Status) + return nil + }), + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).DoAndReturn(func(_ context.Context, _ string, actx *entity.EvalAsyncCtx) error { + order = append(order, "context") + assert.Equal(t, int64(999), actx.RecordID) + assert.Equal(t, int64(101), actx.EvaluatorVersionID) + assert.False(t, actx.ResumeReady) + return nil + }), + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).DoAndReturn( + func(context.Context, *entity.Evaluator, *entity.EvaluatorInputData, *entity.EvaluatorRunConfig, int64, int64) (map[string]string, string, error) { + order = append(order, "provider") + return nil, "", nil + }, + ), + ) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.NoError(t, err) + require.NotNil(t, record) + assert.Equal(t, []string{"record", "context", "provider"}, order) + }) + + t.Run("context failure marks same record fail and skips provider", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(errors.New("redis down")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(true, nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.Error(t, err) + require.NotNil(t, record) + assert.Equal(t, int64(999), record.ID) + assert.Equal(t, entity.EvaluatorRunStatusFail, record.Status) + }) + + t.Run("dispatch failure marks same record fail", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).Return(nil, "", errors.New("dispatch failed")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(true, nil) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.Error(t, err) + require.NotNil(t, record) + assert.Equal(t, entity.EvaluatorRunStatusFail, record.Status) + }) + + t.Run("missing async repo marks same record fail", func(t *testing.T) { + s, recordRepo, _, source := newFixture(t) + s.evalAsyncRepo = nil + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(true, nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.Error(t, err) + require.NotNil(t, record) + assert.Equal(t, entity.EvaluatorRunStatusFail, record.Status) + }) + + t.Run("dispatch ack failure preserves concurrent terminal callback", func(t *testing.T) { + for _, terminalStatus := range []entity.EvaluatorRunStatus{ + entity.EvaluatorRunStatusSuccess, + entity.EvaluatorRunStatusFail, + } { + t.Run(fmt.Sprintf("status_%d", terminalStatus), func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).Return(nil, "", errors.New("dispatch ack lost")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(false, nil) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(999), false, gomock.Any()).Return(&entity.EvaluatorRecord{ + ID: 999, + SpaceID: 2, + Status: terminalStatus, + EvaluatorOutputData: &entity.EvaluatorOutputData{}, + }, nil) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.NoError(t, err) + require.NotNil(t, record) + assert.Equal(t, terminalStatus, record.Status) + }) + } + }) + + t.Run("dispatch metadata persistence failure does not fail accepted work", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).Return(map[string]string{"session_id": "s1"}, "trace-1", nil) + recordRepo.EXPECT().UpdateEvaluatorRecordAsyncDispatch(gomock.Any(), int64(999), int64(2), "trace-1", gomock.Any()).Return(errors.New("mysql unavailable")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.NoError(t, err) + require.NotNil(t, record) + assert.Equal(t, entity.EvaluatorRunStatusAsyncInvoking, record.Status) + assert.Equal(t, "trace-1", record.TraceID) + assert.Equal(t, "s1", record.EvaluatorOutputData.Ext["session_id"]) + }) + + t.Run("dispatch failure returns CAS error", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).Return(nil, "", errors.New("dispatch failed")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(false, errors.New("cas failed")) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.Error(t, err) + require.NotNil(t, record) + assert.Contains(t, err.Error(), "mark evaluator async kickoff failed") + }) + + t.Run("dispatch failure returns reload error after lost CAS", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).Return(nil, "", errors.New("dispatch failed")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(false, nil) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(999), false, gomock.Any()).Return(nil, errors.New("reload failed")) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.Error(t, err) + require.NotNil(t, record) + assert.Contains(t, err.Error(), "reload evaluator record") + }) + + t.Run("dispatch failure remains error when lost CAS has no terminal record", func(t *testing.T) { + s, recordRepo, asyncRepo, source := newFixture(t) + recordRepo.EXPECT().CreateEvaluatorRecord(gomock.Any(), gomock.Any()).Return(nil) + asyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), "evaluator:999", gomock.Any()).Return(nil) + source.EXPECT().AsyncRun(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), int64(2), int64(999)).Return(nil, "", errors.New("dispatch failed")) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(999), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusFail, gomock.Any()).Return(false, nil) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(999), false, gomock.Any()).Return(nil, nil) + + record, err := s.AsyncRunEvaluator(context.Background(), req()) + require.EqualError(t, err, "dispatch failed") + require.NotNil(t, record) + assert.Equal(t, entity.EvaluatorRunStatusAsyncInvoking, record.Status) + }) +} + +func TestEvaluatorServiceImpl_failAsyncEvaluatorRecord_NilRecord(t *testing.T) { + t.Parallel() + s := &EvaluatorServiceImpl{} + want := errors.New("run failed") + record, err := s.failAsyncEvaluatorRecord(context.Background(), nil, want) + assert.Nil(t, record) + assert.ErrorIs(t, err, want) +} + +func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult_RejectsNonTerminalStatus(t *testing.T) { + t.Parallel() + + s := &EvaluatorServiceImpl{} + _, err := s.ReportEvaluatorInvokeResult(context.Background(), nil) + require.Error(t, err) + _, err = s.ReportEvaluatorInvokeResult(context.Background(), &entity.ReportEvaluatorRecordParam{ + RecordID: 1, + SpaceID: 2, + Status: entity.EvaluatorRunStatusUnknown, + }) + require.Error(t, err) +} + +func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult_UsesTerminalCAS(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + s := &EvaluatorServiceImpl{evaluatorRecordRepo: recordRepo} + param := &entity.ReportEvaluatorRecordParam{ + SpaceID: 2, + RecordID: 100, + Status: entity.EvaluatorRunStatusSuccess, + OutputData: &entity.EvaluatorOutputData{Ext: map[string]string{"new": "1"}}, + } + + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(&entity.EvaluatorRecord{ + ID: 100, + SpaceID: 2, + Status: entity.EvaluatorRunStatusAsyncInvoking, + EvaluatorOutputData: &entity.EvaluatorOutputData{ + Ext: map[string]string{"old": "1"}, + }, + }, nil) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(100), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ int64, _, _ entity.EvaluatorRunStatus, out *entity.EvaluatorOutputData) (bool, error) { + assert.Equal(t, "1", out.Ext["old"]) + assert.Equal(t, "1", out.Ext["new"]) + return false, nil + }, + ) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(&entity.EvaluatorRecord{ID: 100, SpaceID: 2, Status: entity.EvaluatorRunStatusFail}, nil) + + outcome, err := s.ReportEvaluatorInvokeResult(context.Background(), param) + require.NoError(t, err) + assert.Equal(t, entity.ReportEvaluatorResultConflict, outcome) +} + +func TestEvaluatorServiceImpl_ReportEvaluatorInvokeResult_ClampsFallbackDurationToZero(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + s := &EvaluatorServiceImpl{evaluatorRecordRepo: recordRepo} + futureCreatedAt := time.Now().Add(time.Second).UnixMilli() + + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(&entity.EvaluatorRecord{ + ID: 100, + SpaceID: 2, + Status: entity.EvaluatorRunStatusAsyncInvoking, + BaseInfo: &entity.BaseInfo{ + CreatedAt: gptr.Of(futureCreatedAt), + }, + }, nil) + recordRepo.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(100), int64(2), entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ int64, _, _ entity.EvaluatorRunStatus, out *entity.EvaluatorOutputData) (bool, error) { + require.NotNil(t, out) + assert.Zero(t, out.TimeConsumingMS) + return true, nil + }, + ) + + outcome, err := s.ReportEvaluatorInvokeResult(context.Background(), &entity.ReportEvaluatorRecordParam{ + SpaceID: 2, + RecordID: 100, + Status: entity.EvaluatorRunStatusSuccess, + OutputData: &entity.EvaluatorOutputData{}, + }) + require.NoError(t, err) + assert.Equal(t, entity.ReportEvaluatorResultApplied, outcome) +} + +func TestEvaluatorServiceImpl_ArmEvaluatorResume(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + recordStatus entity.EvaluatorRunStatus + withEvent bool + wantPublished bool + }{ + {name: "pending record only arms", recordStatus: entity.EvaluatorRunStatusAsyncInvoking, withEvent: true}, + {name: "terminal record republishes recovery", recordStatus: entity.EvaluatorRunStatusSuccess, withEvent: true, wantPublished: true}, + {name: "direct invocation has no experiment recovery", recordStatus: entity.EvaluatorRunStatusSuccess, withEvent: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + publisher := eventmocks.NewMockExptEventPublisher(ctrl) + var event *entity.ExptItemEvalEvent + if tt.withEvent { + event = &entity.ExptItemEvalEvent{ExptID: 1, ExptRunID: 2, EvalSetItemID: 3} + } + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(&entity.EvalAsyncCtx{Event: event, ResumeReady: true}, nil) + if tt.withEvent { + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).DoAndReturn(func(ctx context.Context, _ int64, _ bool, _ ...entity.GetEvaluatorRecordOptionFn) (*entity.EvaluatorRecord, error) { + assert.True(t, contexts.CtxWriteDB(ctx)) + return &entity.EvaluatorRecord{ID: 100, Status: tt.recordStatus}, nil + }) + } + if tt.wantPublished { + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, ev *entity.ExptItemEvalEvent, _ *time.Duration, modify func(*entity.ExptItemEvalEvent)) error { + modify(ev) + assert.True(t, ev.AsyncEvaluatorReportTrigger) + return nil + }, + ) + } + s := &EvaluatorServiceImpl{evalAsyncRepo: asyncRepo, evaluatorRecordRepo: recordRepo, exptEventPublisher: publisher} + require.NoError(t, s.ArmEvaluatorResume(context.Background(), 100)) + }) + } +} + +func TestEvaluatorServiceImpl_ArmEvaluatorResume_RetriesPublish(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + publisher := eventmocks.NewMockExptEventPublisher(ctrl) + event := &entity.ExptItemEvalEvent{ExptID: 1} + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(&entity.EvalAsyncCtx{Event: event, ResumeReady: true}, nil) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(&entity.EvaluatorRecord{ID: 100, Status: entity.EvaluatorRunStatusSuccess}, nil) + gomock.InOrder( + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Any(), gomock.Any()).Return(errors.New("mq unavailable")), + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Any(), gomock.Any()).Return(nil), + ) + s := &EvaluatorServiceImpl{evalAsyncRepo: asyncRepo, evaluatorRecordRepo: recordRepo, exptEventPublisher: publisher} + require.NoError(t, s.ArmEvaluatorResume(context.Background(), 100)) +} + +func TestEvaluatorServiceImpl_ArmEvaluatorResume_RetriesResumeReadyMark(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + event := &entity.ExptItemEvalEvent{ExptID: 1} + gomock.InOrder( + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(nil, errors.New("redis unavailable")), + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(&entity.EvalAsyncCtx{Event: event, ResumeReady: true}, nil), + ) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(&entity.EvaluatorRecord{ID: 100, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil) + + s := &EvaluatorServiceImpl{evalAsyncRepo: asyncRepo, evaluatorRecordRepo: recordRepo} + require.NoError(t, s.ArmEvaluatorResume(context.Background(), 100)) +} + +func TestEvaluatorServiceImpl_ArmEvaluatorResume_Errors(t *testing.T) { + t.Parallel() + + t.Run("missing async repo", func(t *testing.T) { + t.Parallel() + s := &EvaluatorServiceImpl{} + require.Error(t, s.ArmEvaluatorResume(context.Background(), 100)) + }) + + t.Run("mark resume ready error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(nil, errors.New("redis failed")).Times(3) + s := &EvaluatorServiceImpl{evalAsyncRepo: asyncRepo} + require.Error(t, s.ArmEvaluatorResume(context.Background(), 100)) + }) + + t.Run("record reload error", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(&entity.EvalAsyncCtx{Event: &entity.ExptItemEvalEvent{ExptID: 1}}, nil) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(nil, errors.New("mysql failed")) + s := &EvaluatorServiceImpl{evalAsyncRepo: asyncRepo, evaluatorRecordRepo: recordRepo} + require.Error(t, s.ArmEvaluatorResume(context.Background(), 100)) + }) + + t.Run("missing record", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + asyncRepo := repomocks.NewMockIEvalAsyncRepo(ctrl) + recordRepo := repomocks.NewMockIEvaluatorRecordRepo(ctrl) + asyncRepo.EXPECT().MarkEvalAsyncResumeReady(gomock.Any(), "evaluator:100").Return(&entity.EvalAsyncCtx{Event: &entity.ExptItemEvalEvent{ExptID: 1}}, nil) + recordRepo.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(100), false).Return(nil, nil) + s := &EvaluatorServiceImpl{evalAsyncRepo: asyncRepo, evaluatorRecordRepo: recordRepo} + err := s.ArmEvaluatorResume(context.Background(), 100) + statusErr, ok := errorx.FromStatusError(err) + require.True(t, ok) + assert.Equal(t, int32(errno.EvaluatorRecordNotFoundCode), statusErr.Code()) + }) +} + +func TestEvaluatorServiceImpl_publishEvaluatorResumeEvent(t *testing.T) { + t.Parallel() + + t.Run("nil dependencies are no-op", func(t *testing.T) { + t.Parallel() + s := &EvaluatorServiceImpl{} + require.NoError(t, s.publishEvaluatorResumeEvent(context.Background(), nil)) + require.NoError(t, s.publishEvaluatorResumeEvent(context.Background(), &entity.ExptItemEvalEvent{})) + }) + + t.Run("returns last error after all retries", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + publisher := eventmocks.NewMockExptEventPublisher(ctrl) + event := &entity.ExptItemEvalEvent{ExptID: 1} + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Any(), gomock.Any()).Return(errors.New("mq unavailable")).Times(3) + s := &EvaluatorServiceImpl{exptEventPublisher: publisher} + err := s.publishEvaluatorResumeEvent(context.Background(), event) + require.EqualError(t, err, "mq unavailable") + }) + + t.Run("context cancellation stops retry delay", func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + publisher := eventmocks.NewMockExptEventPublisher(ctrl) + event := &entity.ExptItemEvalEvent{ExptID: 1} + ctx, cancel := context.WithCancel(context.Background()) + publisher.EXPECT().PublishExptRecordEvalEvent(gomock.Any(), event, gomock.Any(), gomock.Any()).DoAndReturn( + func(context.Context, *entity.ExptItemEvalEvent, *time.Duration, func(*entity.ExptItemEvalEvent)) error { + cancel() + return errors.New("mq unavailable") + }, + ).Times(1) + s := &EvaluatorServiceImpl{exptEventPublisher: publisher} + err := s.publishEvaluatorResumeEvent(ctx, event) + require.ErrorIs(t, err, context.Canceled) + }) +} diff --git a/backend/modules/evaluation/domain/service/expt_run_item_impl.go b/backend/modules/evaluation/domain/service/expt_run_item_impl.go index 2fdf724b71..e852b5207d 100644 --- a/backend/modules/evaluation/domain/service/expt_run_item_impl.go +++ b/backend/modules/evaluation/domain/service/expt_run_item_impl.go @@ -8,6 +8,7 @@ import ( "fmt" "strconv" "strings" + "sync" "time" "github.com/bytedance/gg/gptr" @@ -230,6 +231,27 @@ func (e *ExptItemEvalCtxExecutor) storeTurnRunResult(ctx context.Context, etec * if err := e.TurnResultRepo.SaveTurnRunLogs(persistCtx, []*entity.ExptTurnResultRunLog{clone}); err != nil { return err } + resumeCtx, resumeCancel := context.WithTimeout(context.WithoutCancel(ctx), exptRunLogPersistTimeout) + defer resumeCancel() + var resumeWG sync.WaitGroup + for _, record := range result.EvaluatorResults { + if record == nil || record.ID <= 0 || record.Status != entity.EvaluatorRunStatusAsyncInvoking { + continue + } + recordID := record.ID + resumeWG.Add(1) + go func() { + defer resumeWG.Done() + if err := e.evaluatorService.ArmEvaluatorResume(resumeCtx, recordID); err != nil { + // The turn references are already durable and the provider has accepted the work. + // Failing the item here would be unretriable (AsyncAbort sets CtxForceNoRetry) and + // could overwrite a valid terminal callback. Keep the item processing while the + // provider retries the callback; the existing zombie policy remains the final fallback. + logs.CtxError(ctx, "[ExptTurnEval] arm evaluator async resume failed after refs persisted, keep item processing, record_id: %d, err: %v", recordID, err) + } + }() + } + resumeWG.Wait() logs.CtxInfo(ctx, "[ExptTurnEval] expt turn eval finished, expt_id: %v, expt_run_id: %v, item_id: %v, turn_id: %v, run_log: %v, err: %v", etec.Expt.ID, etec.Event.ExptRunID, etec.EvalSetItem.ItemID, turn.ID, json.Jsonify(clone), result.EvalErr) diff --git a/backend/modules/evaluation/domain/service/expt_run_item_impl_test.go b/backend/modules/evaluation/domain/service/expt_run_item_impl_test.go index 073e31d344..4f48ba2b97 100644 --- a/backend/modules/evaluation/domain/service/expt_run_item_impl_test.go +++ b/backend/modules/evaluation/domain/service/expt_run_item_impl_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "testing" + "time" "github.com/bytedance/gg/gptr" "github.com/stretchr/testify/assert" @@ -1197,3 +1198,271 @@ func Test_buildItemCompleteEvent_LinkAB_Equivalence(t *testing.T) { require.Equal(t, fromLinkA, fromLinkB, "链路A与链路B的 item-complete 组装结果必须完全一致") } + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_ArmsAsyncEvaluatorAfterSave(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), int64(100)).Return(nil) + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1}, + }}, + }, + } + result := &entity.ExptTurnRunResult{EvaluatorResults: []*entity.EvaluatorRecord{{ + ID: 100, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking, + }}} + require.NoError(t, executor.storeTurnRunResult(context.Background(), etec, result)) +} + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_ArmingHasIndependentTimeoutBudget(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + + var persistDeadline time.Time + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, _ []*entity.ExptTurnResultRunLog) error { + var ok bool + persistDeadline, ok = ctx.Deadline() + require.True(t, ok) + time.Sleep(20 * time.Millisecond) + return nil + }, + ) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), int64(100)).DoAndReturn( + func(ctx context.Context, _ int64) error { + resumeDeadline, ok := ctx.Deadline() + require.True(t, ok) + assert.True(t, resumeDeadline.After(persistDeadline), "arming must not consume the turn-log persistence timeout budget") + return nil + }, + ) + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1}, + }}, + }, + } + result := &entity.ExptTurnRunResult{EvaluatorResults: []*entity.EvaluatorRecord{{ + ID: 100, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking, + }}} + require.NoError(t, executor.storeTurnRunResult(context.Background(), etec, result)) +} + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_ArmsPendingEvaluatorsConcurrently(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).Return(nil) + + secondStarted := make(chan struct{}) + firstObservedSecond := make(chan bool, 1) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), int64(100)).DoAndReturn( + func(context.Context, int64) error { + select { + case <-secondStarted: + firstObservedSecond <- true + case <-time.After(time.Second): + firstObservedSecond <- false + } + return nil + }, + ) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), int64(200)).DoAndReturn( + func(context.Context, int64) error { + close(secondStarted) + return nil + }, + ) + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1}, + }}, + }, + } + result := &entity.ExptTurnRunResult{EvaluatorResults: []*entity.EvaluatorRecord{ + {ID: 100, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking}, + {ID: 200, EvaluatorVersionID: 201, Status: entity.EvaluatorRunStatusAsyncInvoking}, + }} + + require.NoError(t, executor.storeTurnRunResult(context.Background(), etec, result)) + require.True(t, <-firstObservedSecond, "one slow arming operation must not delay the other pending evaluators") +} + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_DoesNotArmWhenSaveFails(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).Return(errors.New("save failed")) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), gomock.Any()).Times(0) + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1}, + }}, + }, + } + result := &entity.ExptTurnRunResult{EvaluatorResults: []*entity.EvaluatorRecord{{ + ID: 100, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking, + }}} + require.Error(t, executor.storeTurnRunResult(context.Background(), etec, result)) +} + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_ArmErrorKeepsAsyncProcessing(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).Return(nil) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), int64(100)).Return(errors.New("arm failed")) + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1}, + }}, + }, + } + result := &entity.ExptTurnRunResult{AsyncAbort: true, EvaluatorResults: []*entity.EvaluatorRecord{{ + ID: 100, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking, + }}} + require.NoError(t, executor.storeTurnRunResult(context.Background(), etec, result)) +} + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_ArmsEveryPendingEvaluator(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, logs []*entity.ExptTurnResultRunLog) error { + require.Len(t, logs, 1) + require.NotNil(t, logs[0].EvaluatorResultIds) + require.Len(t, logs[0].EvaluatorResultIds.Registered, 5) + seen := make(map[int64]int) + for _, ref := range logs[0].EvaluatorResultIds.Registered { + require.NotNil(t, ref) + seen[ref.VersionID]++ + } + for _, versionID := range []int64{101, 102, 201, 202, 203} { + assert.Equal(t, 1, seen[versionID]) + } + return nil + }, + ) + for _, recordID := range []int64{1201, 1202, 1203} { + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), recordID).Return(nil) + } + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1}, + }}, + }, + } + result := &entity.ExptTurnRunResult{ + AsyncAbort: true, + EvaluatorResults: []*entity.EvaluatorRecord{ + {ID: 1101, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusSuccess}, + {ID: 1102, EvaluatorVersionID: 102, Status: entity.EvaluatorRunStatusSuccess}, + {ID: 1201, EvaluatorVersionID: 201, Status: entity.EvaluatorRunStatusAsyncInvoking}, + {ID: 1202, EvaluatorVersionID: 202, Status: entity.EvaluatorRunStatusAsyncInvoking}, + {ID: 1203, EvaluatorVersionID: 203, Status: entity.EvaluatorRunStatusAsyncInvoking}, + }, + } + require.NoError(t, executor.storeTurnRunResult(context.Background(), etec, result)) +} + +func TestExptItemEvalCtxExecutor_storeTurnRunResult_AllEvaluatorsTerminalCompletesWithoutArming(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + turnRepo := repomocks.NewMockIExptTurnResultRepo(ctrl) + evaluatorSvc := servicemocks.NewMockEvaluatorService(ctrl) + + turnRepo.EXPECT().SaveTurnRunLogs(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, logs []*entity.ExptTurnResultRunLog) error { + require.Len(t, logs, 1) + assert.Equal(t, entity.TurnRunState_Success, logs[0].Status) + require.NotNil(t, logs[0].EvaluatorResultIds) + require.Len(t, logs[0].EvaluatorResultIds.Registered, 5) + return nil + }, + ) + evaluatorSvc.EXPECT().ArmEvaluatorResume(gomock.Any(), gomock.Any()).Times(0) + + executor := &ExptItemEvalCtxExecutor{TurnResultRepo: turnRepo, evaluatorService: evaluatorSvc} + etec := &entity.ExptTurnEvalCtx{ + Turn: &entity.Turn{ID: 1}, + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, SpaceID: 2}, + Event: &entity.ExptItemEvalEvent{ExptRunID: 3}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 4}, + ExistItemEvalResult: &entity.ExptItemEvalResult{TurnResultRunLogs: map[int64]*entity.ExptTurnResultRunLog{ + 1: {ID: 5, TurnID: 1, Status: entity.TurnRunState_Processing}, + }}, + }, + } + result := &entity.ExptTurnRunResult{ + EvaluatorResults: []*entity.EvaluatorRecord{ + {ID: 1101, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusSuccess}, + {ID: 1102, EvaluatorVersionID: 102, Status: entity.EvaluatorRunStatusSuccess}, + {ID: 1201, EvaluatorVersionID: 201, Status: entity.EvaluatorRunStatusSuccess}, + {ID: 1202, EvaluatorVersionID: 202, Status: entity.EvaluatorRunStatusSuccess}, + {ID: 1203, EvaluatorVersionID: 203, Status: entity.EvaluatorRunStatusSuccess}, + }, + } + require.NoError(t, executor.storeTurnRunResult(context.Background(), etec, result)) +} diff --git a/backend/modules/evaluation/domain/service/expt_run_item_turn_impl.go b/backend/modules/evaluation/domain/service/expt_run_item_turn_impl.go index bd8e45a841..4c706e174c 100644 --- a/backend/modules/evaluation/domain/service/expt_run_item_turn_impl.go +++ b/backend/modules/evaluation/domain/service/expt_run_item_turn_impl.go @@ -113,7 +113,7 @@ func (e *DefaultExptTurnEvaluationImpl) CallTarget(ctx context.Context, etec *en tr := etec.ExptTurnRunResult.GetTargetResult() - if etec.Event.AsyncReportTrigger { + if etec.Event.AsyncReportTrigger || etec.Event.AsyncEvaluatorReportTrigger { etec.Event.WithCtxTargetCalled(ctx) return tr, nil } @@ -137,7 +137,7 @@ func (e *DefaultExptTurnEvaluationImpl) CallTarget(ctx context.Context, etec *en } func (e *DefaultExptTurnEvaluationImpl) validateEvalTargetCtx(etec *entity.ExptTurnEvalCtx) error { - if etec.Event.AsyncReportTrigger && etec.ExptTurnRunResult.GetTargetResult() == nil { + if (etec.Event.AsyncReportTrigger || etec.Event.AsyncEvaluatorReportTrigger) && etec.ExptTurnRunResult.GetTargetResult() == nil { return errorx.NewByCode(errno.CommonInternalErrorCode, errorx.WithExtraMsg("target result must not be nil in async reported event")) } return nil @@ -914,7 +914,6 @@ func (e *DefaultExptTurnEvaluationImpl) asyncCallEvaluatorWithAlias( var err error defer func() { e.metric.EmitTurnExecEvaluatorResult(etec.Event.SpaceID, err != nil) }() - ts := time.Now() evaluatorRecord, err := e.evaluatorService.AsyncRunEvaluator(ctx, &entity.AsyncRunEvaluatorRequest{ SpaceID: etec.Event.SpaceID, EvaluatorVersionID: ev.GetEvaluatorVersionID(), @@ -927,22 +926,19 @@ func (e *DefaultExptTurnEvaluationImpl) asyncCallEvaluatorWithAlias( EvaluatorRunConf: runConf, Alias: alias, SourceType: entity.EvaluatorRecordSourceTypeBuiltin, + AsyncCtx: &entity.EvalAsyncCtx{ + Event: etec.Event, + Session: etec.Event.Session, + EvaluatorVersionID: ev.GetEvaluatorVersionID(), + ResumeReady: false, + }, }) - if err != nil { - return err + if evaluatorRecord != nil { + collector.store(evaluatorRecord) } - - asyncCtxKey := fmt.Sprintf("evaluator:%d", evaluatorRecord.ID) - if err = e.evalAsyncRepo.SetEvalAsyncCtx(ctx, asyncCtxKey, &entity.EvalAsyncCtx{ - Event: etec.Event, - RecordID: evaluatorRecord.ID, - AsyncUnixMS: ts.UnixMilli(), - Session: etec.Event.Session, - EvaluatorVersionID: ev.GetEvaluatorVersionID(), - }); err != nil { + if err != nil { return err } - collector.store(evaluatorRecord) return nil } @@ -957,8 +953,6 @@ func (e *DefaultExptTurnEvaluationImpl) asyncCallEvaluator( var err error defer func() { e.metric.EmitTurnExecEvaluatorResult(etec.Event.SpaceID, err != nil) }() - ts := time.Now() - asyncReq := &entity.AsyncRunEvaluatorRequest{ SpaceID: etec.Event.SpaceID, EvaluatorVersionID: ev.GetEvaluatorVersionID(), @@ -969,40 +963,20 @@ func (e *DefaultExptTurnEvaluationImpl) asyncCallEvaluator( TurnID: etec.Turn.ID, Ext: etec.Ext, EvaluatorRunConf: ec.RunConf, + AsyncCtx: &entity.EvalAsyncCtx{ + Event: etec.Event, + Session: etec.Event.Session, + EvaluatorVersionID: ev.GetEvaluatorVersionID(), + ResumeReady: false, + }, } evaluatorRecord, err := e.evaluatorService.AsyncRunEvaluator(ctx, asyncReq) - if err != nil { - if e.evaluatorService != nil { - failReq := &entity.RunEvaluatorRequest{ - SpaceID: asyncReq.SpaceID, - EvaluatorVersionID: asyncReq.EvaluatorVersionID, - InputData: asyncReq.InputData, - ExperimentID: asyncReq.ExperimentID, - ExperimentRunID: asyncReq.ExperimentRunID, - ItemID: asyncReq.ItemID, - TurnID: asyncReq.TurnID, - Ext: asyncReq.Ext, - EvaluatorRunConf: asyncReq.EvaluatorRunConf, - } - if failedRecord, createErr := e.evaluatorService.CreateEvaluatorRunFailRecord(ctx, failReq, err); createErr == nil && failedRecord != nil { - collector.store(failedRecord) - } - } - return err + if evaluatorRecord != nil { + collector.store(evaluatorRecord) } - - asyncCtxKey := fmt.Sprintf("evaluator:%d", evaluatorRecord.ID) - if err = e.evalAsyncRepo.SetEvalAsyncCtx(ctx, asyncCtxKey, &entity.EvalAsyncCtx{ - Event: etec.Event, - RecordID: evaluatorRecord.ID, - AsyncUnixMS: ts.UnixMilli(), - Session: etec.Event.Session, - EvaluatorVersionID: ev.GetEvaluatorVersionID(), - }); err != nil { + if err != nil { return err } - - collector.store(evaluatorRecord) return nil } diff --git a/backend/modules/evaluation/domain/service/expt_run_item_turn_impl_test.go b/backend/modules/evaluation/domain/service/expt_run_item_turn_impl_test.go index 4ade374a31..be23c1c641 100644 --- a/backend/modules/evaluation/domain/service/expt_run_item_turn_impl_test.go +++ b/backend/modules/evaluation/domain/service/expt_run_item_turn_impl_test.go @@ -5,6 +5,7 @@ package service import ( "context" "errors" + "fmt" "runtime" "strconv" "sync" @@ -391,23 +392,13 @@ func TestDefaultExptTurnEvaluationImpl_asyncCallEvaluator_Agent(t *testing.T) { assert.Equal(t, int64(4), req.ItemID) assert.Equal(t, int64(5), req.TurnID) assert.Equal(t, etec.Ext, req.Ext) + require.NotNil(t, req.AsyncCtx) + assert.Equal(t, etec.Event, req.AsyncCtx.Event) + assert.False(t, req.AsyncCtx.ResumeReady) return mockEvaluatorRecord, nil }, ) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, key string, val *entity.EvalAsyncCtx) error { - assert.Equal(t, "evaluator:202", key) - assert.Equal(t, int64(202), val.RecordID) - assert.Equal(t, int64(101), val.EvaluatorVersionID) - assert.Equal(t, etec.Event, val.Event) - // Check timestamp - assert.True(t, val.AsyncUnixMS <= time.Now().UnixMilli()) - assert.True(t, val.AsyncUnixMS > time.Now().Add(-time.Minute).UnixMilli()) - return nil - }, - ) - err := service.asyncCallEvaluator(context.Background(), ev, ec, etec, inputData, &collector) assert.NoError(t, err) @@ -472,12 +463,9 @@ func TestDefaultExptTurnEvaluationImpl_asyncCallEvaluator_Agent_Errors(t *testin mockSetup: func() { mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), true) runErr := errors.New("async run error") - mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(nil, runErr) - mockEvaluatorService.EXPECT().CreateEvaluatorRunFailRecord(gomock.Any(), gomock.Any(), runErr).Return(&entity.EvaluatorRecord{ - ID: 303, - EvaluatorVersionID: 101, - Status: entity.EvaluatorRunStatusFail, - }, nil) + mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ + ID: 303, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusFail, + }, runErr) }, wantErr: true, }, @@ -487,7 +475,6 @@ func TestDefaultExptTurnEvaluationImpl_asyncCallEvaluator_Agent_Errors(t *testin mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), true) runErr := errors.New("async run error") mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(nil, runErr) - mockEvaluatorService.EXPECT().CreateEvaluatorRunFailRecord(gomock.Any(), gomock.Any(), runErr).Return(nil, errors.New("create failed record error")) }, wantErr: true, }, @@ -496,10 +483,8 @@ func TestDefaultExptTurnEvaluationImpl_asyncCallEvaluator_Agent_Errors(t *testin mockSetup: func() { mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), true) mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ - ID: 202, - EvaluatorVersionID: 101, - }, nil) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("set ctx error")) + ID: 202, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusFail, + }, errors.New("set ctx error")) }, wantErr: true, }, @@ -908,6 +893,119 @@ func TestDefaultExptTurnEvaluationImpl_CallTarget_AsyncReport(t *testing.T) { } } +func TestDefaultExptTurnEvaluationImpl_CallTarget_AsyncEvaluatorReportReusesTarget(t *testing.T) { + t.Parallel() + + for _, runMode := range []entity.ExptRunMode{ + entity.EvaluationModeSubmit, + entity.EvaluationModeRetryAll, + entity.EvaluationModeRetryItems, + } { + runMode := runMode + t.Run(fmt.Sprintf("run_mode_%d", runMode), func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + benefitSvc := benefitmocks.NewMockIBenefitService(ctrl) + targetSvc := svcmocks.NewMockIEvalTargetService(ctrl) + benefitSvc.EXPECT().CheckAndDeductEvalBenefit(gomock.Any(), gomock.Any()).Times(0) + targetSvc.EXPECT().ExecuteTarget(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + targetSvc.EXPECT().AsyncExecuteTarget(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + + service := &DefaultExptTurnEvaluationImpl{benefitService: benefitSvc, evalTargetService: targetSvc} + status := entity.EvalTargetRunStatusSuccess + target := &entity.EvalTargetRecord{ID: 101, Status: &status} + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, TargetVersionID: 1}, + Event: &entity.ExptItemEvalEvent{ + ExptRunMode: runMode, + RetryTimes: 0, + AsyncEvaluatorReportTrigger: true, + Session: &entity.Session{UserID: "u"}, + }, + }, + ExptTurnRunResult: &entity.ExptTurnRunResult{TargetResult: target}, + } + + got, err := service.CallTarget(context.Background(), etec) + + require.NoError(t, err) + require.Same(t, target, got) + }) + } +} + +func TestDefaultExptTurnEvaluationImpl_CallTarget_AsyncEvaluatorReportRequiresTarget(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + benefitSvc := benefitmocks.NewMockIBenefitService(ctrl) + targetSvc := svcmocks.NewMockIEvalTargetService(ctrl) + benefitSvc.EXPECT().CheckAndDeductEvalBenefit(gomock.Any(), gomock.Any()).Times(0) + targetSvc.EXPECT().ExecuteTarget(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + targetSvc.EXPECT().AsyncExecuteTarget(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + + service := &DefaultExptTurnEvaluationImpl{benefitService: benefitSvc, evalTargetService: targetSvc} + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ID: 1, TargetVersionID: 1}, + Event: &entity.ExptItemEvalEvent{AsyncEvaluatorReportTrigger: true, Session: &entity.Session{UserID: "u"}}, + }, + ExptTurnRunResult: &entity.ExptTurnRunResult{}, + } + + got, err := service.CallTarget(context.Background(), etec) + + require.Error(t, err) + require.Nil(t, got) +} + +func TestDefaultExptTurnEvaluationImpl_ResultSetAsyncEvaluatorReportSkipsTargetValidation(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + metric := metricsmocks.NewMockExptMetric(ctrl) + metric.EXPECT().EmitTurnExecEval(int64(2), gomock.Any()) + metric.EXPECT().EmitTurnExecResult(int64(2), gomock.Any(), true, gomock.Any(), gomock.Any(), gomock.Any()) + + existingRecord := &entity.EvaluatorRecord{ + ID: 1001, + EvaluatorVersionID: 2001, + Status: entity.EvaluatorRunStatusSuccess, + } + service := &DefaultExptTurnEvaluationImpl{metric: metric} + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Expt: &entity.Experiment{ + TargetVersionID: 0, + ExptType: entity.ExptType_Offline, + EvalConf: &entity.EvaluationConfiguration{ConnectorConf: entity.Connector{ + EvaluatorsConf: &entity.EvaluatorsConf{}, + }}, + }, + Event: &entity.ExptItemEvalEvent{ + SpaceID: 2, + AsyncEvaluatorReportTrigger: true, + Session: &entity.Session{UserID: "u"}, + }, + }, + ExptTurnRunResult: &entity.ExptTurnRunResult{ + TargetResult: nil, + EvaluatorResults: []*entity.EvaluatorRecord{existingRecord}, + }, + } + + result := service.Eval(context.Background(), etec) + + require.NoError(t, result.EvalErr) + require.NotNil(t, result.TargetResult) + require.NotNil(t, result.TargetResult.EvalTargetOutputData) + assert.Empty(t, result.TargetResult.EvalTargetOutputData.OutputFields) + require.Len(t, result.EvaluatorResults, 1) + assert.Same(t, existingRecord, result.EvaluatorResults[0]) +} + func TestDefaultExptTurnEvaluationImpl_CallTarget_ExistedRecord_Status(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -1299,7 +1397,6 @@ func TestDefaultExptTurnEvaluationImpl_CallEvaluators(t *testing.T) { mockEvaluatorService.EXPECT().ShouldInterceptEvaluator(gomock.Any(), gomock.Any()).Return(nil, false, nil) mockEvaluatorService.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil) mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), false) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) }, etec: &entity.ExptTurnEvalCtx{ ExptItemEvalCtx: &entity.ExptItemEvalCtx{ @@ -4128,7 +4225,6 @@ func TestDefaultExptTurnEvaluationImpl_CallEvaluators_WithRefresh(t *testing.T) &entity.EvaluatorRecord{ID: 201, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil, ) mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), false) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) mockEvaluatorRecordService.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(201), false).Return( &entity.EvaluatorRecord{ID: 201, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusSuccess}, nil, ) @@ -4167,7 +4263,6 @@ func TestDefaultExptTurnEvaluationImpl_CallEvaluators_WithRefresh(t *testing.T) &entity.EvaluatorRecord{ID: 201, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil, ) mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), false) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) mockEvaluatorRecordService.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(201), false).Return( &entity.EvaluatorRecord{ID: 201, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil, ) @@ -4206,7 +4301,6 @@ func TestDefaultExptTurnEvaluationImpl_CallEvaluators_WithRefresh(t *testing.T) &entity.EvaluatorRecord{ID: 201, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil, ) mockMetric.EXPECT().EmitTurnExecEvaluatorResult(gomock.Any(), false) - mockEvalAsyncRepo.EXPECT().SetEvalAsyncCtx(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) mockEvaluatorRecordService.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(201), false).Return(nil, errors.New("db error")) return &DefaultExptTurnEvaluationImpl{ @@ -5007,3 +5101,376 @@ func TestPickHelpers(t *testing.T) { assert.Equal(t, "sub-key", pickDatasetKey(etec)) }) } + +func TestDefaultExptTurnEvaluationImpl_asyncCallEvaluatorWithAlias_CustomRPC(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + evaluatorSvc := svcmocks.NewMockEvaluatorService(ctrl) + metric := metricsmocks.NewMockExptMetric(ctrl) + service := &DefaultExptTurnEvaluationImpl{evaluatorService: evaluatorSvc, metric: metric} + providerCode := "trae_work_long_running" + evaluator := &entity.Evaluator{ + EvaluatorType: entity.EvaluatorTypeCustomRPC, + CustomRPCEvaluatorVersion: &entity.CustomRPCEvaluatorVersion{ + ID: 101, + EvaluatorID: 100, + ProviderEvaluatorCode: &providerCode, + AccessProtocol: entity.EvaluatorAccessProtocolRPC, + IsAsync: true, + }, + } + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Event: &entity.ExptItemEvalEvent{SpaceID: 2, ExptID: 3, ExptRunID: 4, Session: &entity.Session{UserID: "u"}}, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 5}, + }, + Turn: &entity.Turn{ID: 6}, + } + input := &entity.EvaluatorInputData{} + runConf := &entity.EvaluatorRunConfig{Env: gptr.Of("ppe_trae")} + metric.EXPECT().EmitTurnExecEvaluatorResult(int64(2), false) + evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, req *entity.AsyncRunEvaluatorRequest) (*entity.EvaluatorRecord, error) { + assert.Equal(t, int64(101), req.EvaluatorVersionID) + assert.Equal(t, "judge_a", req.Alias) + assert.Equal(t, entity.EvaluatorRecordSourceTypeBuiltin, req.SourceType) + assert.Same(t, runConf, req.EvaluatorRunConf) + require.NotNil(t, req.AsyncCtx) + assert.Same(t, etec.Event, req.AsyncCtx.Event) + assert.False(t, req.AsyncCtx.ResumeReady) + return &entity.EvaluatorRecord{ID: 7, EvaluatorVersionID: 101, Alias: "judge_a", Status: entity.EvaluatorRunStatusAsyncInvoking}, nil + }, + ) + + collector := &evalRecordCollector{} + require.NoError(t, service.asyncCallEvaluatorWithAlias(context.Background(), evaluator, runConf, "judge_a", etec, input, collector)) + require.Len(t, collector.records, 1) + assert.Equal(t, "judge_a", collector.records[0].Alias) +} + +// TestDefaultExptTurnEvaluationImpl_AsyncTargetCallbackRunsMixedEvaluators is the +// regression guard for the full target/evaluator state machine. In particular, +// an AsyncReportTrigger means "the target result is ready"; it must not be +// mistaken for AsyncEvaluatorReportTrigger and skip evaluator execution. +func TestDefaultExptTurnEvaluationImpl_AsyncTargetCallbackRunsMixedEvaluators(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + metric := metricsmocks.NewMockExptMetric(ctrl) + evaluatorSvc := svcmocks.NewMockEvaluatorService(ctrl) + benefitSvc := benefitmocks.NewMockIBenefitService(ctrl) + recordSvc := svcmocks.NewMockEvaluatorRecordService(ctrl) + + service := &DefaultExptTurnEvaluationImpl{ + metric: metric, + evaluatorService: evaluatorSvc, + benefitService: benefitSvc, + evaluatorRecordService: recordSvc, + } + + const ( + spaceID = int64(2) + targetID = int64(900) + ) + targetStatus := entity.EvalTargetRunStatusSuccess + targetRecord := &entity.EvalTargetRecord{ + ID: targetID, + Status: &targetStatus, + EvalTargetOutputData: &entity.EvalTargetOutputData{OutputFields: map[string]*entity.Content{ + "actual_output": {Text: gptr.Of("target done")}, + }}, + } + + newSyncEvaluator := func(versionID int64) *entity.Evaluator { + return &entity.Evaluator{ + ID: versionID, EvaluatorType: entity.EvaluatorTypePrompt, + PromptEvaluatorVersion: &entity.PromptEvaluatorVersion{ID: versionID}, + } + } + newAsyncCustomRPCEvaluator := func(versionID int64) *entity.Evaluator { + code := fmt.Sprintf("async-%d", versionID) + return &entity.Evaluator{ + ID: versionID, EvaluatorType: entity.EvaluatorTypeCustomRPC, + CustomRPCEvaluatorVersion: &entity.CustomRPCEvaluatorVersion{ + ID: versionID, EvaluatorID: versionID, ProviderEvaluatorCode: &code, + AccessProtocol: entity.EvaluatorAccessProtocolRPC, IsAsync: true, + }, + } + } + newAgentEvaluator := func(versionID int64) *entity.Evaluator { + return &entity.Evaluator{ + ID: versionID, EvaluatorType: entity.EvaluatorTypeAgent, + AgentEvaluatorVersion: &entity.AgentEvaluatorVersion{ID: versionID, EvaluatorID: versionID}, + } + } + newConf := func(versionID int64) *entity.EvaluatorConf { + return &entity.EvaluatorConf{ + EvaluatorVersionID: versionID, + IngressConf: &entity.EvaluatorIngressConf{ + EvalSetAdapter: &entity.FieldAdapter{FieldConfs: []*entity.FieldConf{}}, + TargetAdapter: &entity.FieldAdapter{FieldConfs: []*entity.FieldConf{}}, + }, + RunConf: &entity.EvaluatorRunConfig{Env: gptr.Of("ppe_trae_work_async_evaluator")}, + } + } + + syncVersions := map[int64]struct{}{101: {}, 102: {}} + agentVersions := map[int64]struct{}{201: {}} + asyncCustomRPCVersions := map[int64]struct{}{202: {}, 203: {}} + asyncVersions := map[int64]struct{}{201: {}, 202: {}, 203: {}} + allEvaluators := []*entity.Evaluator{ + newSyncEvaluator(101), newSyncEvaluator(102), + newAgentEvaluator(201), newAsyncCustomRPCEvaluator(202), newAsyncCustomRPCEvaluator(203), + } + allConfs := []*entity.EvaluatorConf{ + newConf(101), newConf(102), newConf(201), newConf(202), newConf(203), + } + + event := &entity.ExptItemEvalEvent{ + SpaceID: spaceID, ExptID: 3, ExptRunID: 4, EvalSetItemID: 5, + Session: &entity.Session{UserID: "u"}, AsyncReportTrigger: true, + } + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{ + Event: event, + EvalSetItem: &entity.EvaluationSetItem{ItemID: 5}, + Expt: &entity.Experiment{ + ID: 3, SpaceID: spaceID, TargetVersionID: 99, + Target: &entity.EvalTarget{ + ID: 88, EvalTargetType: entity.EvalTargetTypeCustomRPCServer, + EvalTargetVersion: &entity.EvalTargetVersion{ + ID: 99, EvalTargetType: entity.EvalTargetTypeCustomRPCServer, + CustomRPCServer: &entity.CustomRPCServer{IsAsync: gptr.Of(true)}, + }, + }, + Evaluators: allEvaluators, + EvalConf: &entity.EvaluationConfiguration{ConnectorConf: entity.Connector{ + TargetConf: &entity.TargetConf{TargetVersionID: 99}, + EvaluatorsConf: &entity.EvaluatorsConf{EvaluatorConcurNum: gptr.Of(5), EvaluatorConf: allConfs}, + }}, + }, + }, + Turn: &entity.Turn{ID: 6, FieldDataList: []*entity.FieldData{}}, + ExptTurnRunResult: &entity.ExptTurnRunResult{TargetResult: targetRecord}, + } + + metric.EXPECT().EmitTurnExecEval(spaceID, gomock.Any()) + metric.EXPECT().EmitTurnExecResult(spaceID, gomock.Any(), true, gomock.Any(), gomock.Any(), gomock.Any()) + benefitSvc.EXPECT().CheckAndDeductEvalBenefit(gomock.Any(), gomock.Any()).Return(&benefit.CheckAndDeductEvalBenefitResult{}, nil) + evaluatorSvc.EXPECT().ShouldInterceptEvaluator(gomock.Any(), gomock.Any()).Return(nil, false, nil).Times(5) + metric.EXPECT().EmitTurnExecEvaluatorResult(spaceID, false).Times(5) + + var mu sync.Mutex + runCounts := make(map[int64]int) + evaluatorSvc.EXPECT().RunEvaluator(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, req *entity.RunEvaluatorRequest) (*entity.EvaluatorRecord, error) { + mu.Lock() + runCounts[req.EvaluatorVersionID]++ + mu.Unlock() + _, ok := syncVersions[req.EvaluatorVersionID] + require.True(t, ok, "unexpected sync evaluator %d", req.EvaluatorVersionID) + require.Equal(t, "ppe_trae_work_async_evaluator", gptr.Indirect(req.EvaluatorRunConf.Env)) + return &entity.EvaluatorRecord{ID: 1000 + req.EvaluatorVersionID, EvaluatorVersionID: req.EvaluatorVersionID, Status: entity.EvaluatorRunStatusSuccess}, nil + }, + ).Times(2) + evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, req *entity.AsyncRunEvaluatorRequest) (*entity.EvaluatorRecord, error) { + mu.Lock() + runCounts[req.EvaluatorVersionID]++ + mu.Unlock() + _, ok := asyncVersions[req.EvaluatorVersionID] + require.True(t, ok, "unexpected async evaluator %d", req.EvaluatorVersionID) + _, isAgent := agentVersions[req.EvaluatorVersionID] + _, isCustomRPC := asyncCustomRPCVersions[req.EvaluatorVersionID] + require.True(t, isAgent || isCustomRPC, "async evaluator %d must be Agent or CustomRPC", req.EvaluatorVersionID) + require.Equal(t, "ppe_trae_work_async_evaluator", gptr.Indirect(req.EvaluatorRunConf.Env)) + require.NotNil(t, req.AsyncCtx) + require.Same(t, event, req.AsyncCtx.Event) + require.False(t, req.AsyncCtx.ResumeReady) + return &entity.EvaluatorRecord{ID: 1000 + req.EvaluatorVersionID, EvaluatorVersionID: req.EvaluatorVersionID, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil + }, + ).Times(3) + recordSvc.EXPECT().GetEvaluatorRecord(gomock.Any(), gomock.Any(), false).DoAndReturn( + func(_ context.Context, recordID int64, _ bool) (*entity.EvaluatorRecord, error) { + versionID := recordID - 1000 + return &entity.EvaluatorRecord{ID: recordID, EvaluatorVersionID: versionID, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil + }, + ).Times(3) + + result := service.Eval(context.Background(), etec) + require.NoError(t, result.EvalErr) + require.Same(t, targetRecord, result.TargetResult) + require.True(t, result.AsyncAbort) + require.Len(t, result.EvaluatorResults, 5) + for versionID := range syncVersions { + record := result.GetEvaluatorRecord(versionID) + require.NotNil(t, record) + assert.Equal(t, entity.EvaluatorRunStatusSuccess, record.Status) + } + for versionID := range asyncVersions { + record := result.GetEvaluatorRecord(versionID) + require.NotNil(t, record) + assert.Equal(t, entity.EvaluatorRunStatusAsyncInvoking, record.Status) + } + for versionID, count := range runCounts { + assert.Equal(t, 1, count, "evaluator %d must run exactly once", versionID) + } + assert.Len(t, runCounts, 5) +} + +// TestDefaultExptTurnEvaluationImpl_AsyncEvaluatorCallbacksPreserveAllRecords +// models staggered callbacks after the initial mixed run. Every callback event +// must reuse the complete persisted record set and must never re-run target or +// evaluators. The turn only converges after the final async record is terminal. +func TestDefaultExptTurnEvaluationImpl_AsyncEvaluatorCallbacksPreserveAllRecords(t *testing.T) { + t.Parallel() + + targetStatus := entity.EvalTargetRunStatusSuccess + target := &entity.EvalTargetRecord{ID: 900, Status: &targetStatus, EvalTargetOutputData: &entity.EvalTargetOutputData{OutputFields: map[string]*entity.Content{}}} + newExpt := func() *entity.Experiment { + return &entity.Experiment{ + TargetVersionID: 99, + Target: &entity.EvalTarget{EvalTargetVersion: &entity.EvalTargetVersion{ID: 99, CustomRPCServer: &entity.CustomRPCServer{IsAsync: gptr.Of(true)}}}, + Evaluators: []*entity.Evaluator{{ID: 101}, {ID: 102}, {ID: 201}, {ID: 202}, {ID: 203}}, + EvalConf: &entity.EvaluationConfiguration{ConnectorConf: entity.Connector{EvaluatorsConf: &entity.EvaluatorsConf{}}}, + } + } + newRecords := func(statuses map[int64]entity.EvaluatorRunStatus) []*entity.EvaluatorRecord { + versions := []int64{101, 102, 201, 202, 203} + records := make([]*entity.EvaluatorRecord, 0, len(versions)) + for _, versionID := range versions { + records = append(records, &entity.EvaluatorRecord{ID: 1000 + versionID, EvaluatorVersionID: versionID, Status: statuses[versionID]}) + } + return records + } + + phases := []struct { + name string + statuses map[int64]entity.EvaluatorRunStatus + wantAbort bool + }{ + { + name: "first async callback keeps the other two pending", + statuses: map[int64]entity.EvaluatorRunStatus{ + 101: entity.EvaluatorRunStatusSuccess, 102: entity.EvaluatorRunStatusSuccess, + 201: entity.EvaluatorRunStatusSuccess, 202: entity.EvaluatorRunStatusAsyncInvoking, 203: entity.EvaluatorRunStatusAsyncInvoking, + }, + wantAbort: true, + }, + { + name: "second async callback keeps the last one pending", + statuses: map[int64]entity.EvaluatorRunStatus{ + 101: entity.EvaluatorRunStatusSuccess, 102: entity.EvaluatorRunStatusSuccess, + 201: entity.EvaluatorRunStatusSuccess, 202: entity.EvaluatorRunStatusSuccess, 203: entity.EvaluatorRunStatusAsyncInvoking, + }, + wantAbort: true, + }, + { + name: "last async callback completes the turn", + statuses: map[int64]entity.EvaluatorRunStatus{ + 101: entity.EvaluatorRunStatusSuccess, 102: entity.EvaluatorRunStatusSuccess, + 201: entity.EvaluatorRunStatusSuccess, 202: entity.EvaluatorRunStatusSuccess, 203: entity.EvaluatorRunStatusSuccess, + }, + wantAbort: false, + }, + } + + for _, phase := range phases { + phase := phase + t.Run(phase.name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + metric := metricsmocks.NewMockExptMetric(ctrl) + service := &DefaultExptTurnEvaluationImpl{metric: metric} + event := &entity.ExptItemEvalEvent{SpaceID: 2, AsyncEvaluatorReportTrigger: true, Session: &entity.Session{UserID: "u"}} + records := newRecords(phase.statuses) + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{Event: event, Expt: newExpt()}, + ExptTurnRunResult: &entity.ExptTurnRunResult{TargetResult: target, EvaluatorResults: records}, + } + metric.EXPECT().EmitTurnExecEval(int64(2), gomock.Any()) + metric.EXPECT().EmitTurnExecResult(int64(2), gomock.Any(), true, gomock.Any(), gomock.Any(), gomock.Any()) + + result := service.Eval(context.Background(), etec) + require.NoError(t, result.EvalErr) + require.Same(t, target, result.TargetResult) + assert.Equal(t, phase.wantAbort, result.AsyncAbort) + require.Len(t, result.EvaluatorResults, 5) + seen := make(map[int64]int) + for _, record := range result.EvaluatorResults { + require.NotNil(t, record) + seen[record.EvaluatorVersionID]++ + assert.Equal(t, phase.statuses[record.EvaluatorVersionID], record.Status) + } + for _, versionID := range []int64{101, 102, 201, 202, 203} { + assert.Equal(t, 1, seen[versionID], "record for evaluator %d must be preserved exactly once", versionID) + } + }) + } +} + +// TestDefaultExptTurnEvaluationImpl_MixedEvaluatorCompletionOrder covers both +// orderings around refreshAsyncEvaluatorRecords: the async callback can arrive +// before a slow synchronous evaluator returns, or remain pending after the +// synchronous evaluator has completed. +func TestDefaultExptTurnEvaluationImpl_MixedEvaluatorCompletionOrder(t *testing.T) { + t.Parallel() + + newCase := func(t *testing.T, refreshedStatus entity.EvaluatorRunStatus, wantAbort bool) { + ctrl := gomock.NewController(t) + metric := metricsmocks.NewMockExptMetric(ctrl) + evaluatorSvc := svcmocks.NewMockEvaluatorService(ctrl) + benefitSvc := benefitmocks.NewMockIBenefitService(ctrl) + recordSvc := svcmocks.NewMockEvaluatorRecordService(ctrl) + service := &DefaultExptTurnEvaluationImpl{metric: metric, evaluatorService: evaluatorSvc, benefitService: benefitSvc, evaluatorRecordService: recordSvc} + + code := "async-fast" + expt := &entity.Experiment{ + SpaceID: 2, + Evaluators: []*entity.Evaluator{ + {ID: 101, EvaluatorType: entity.EvaluatorTypePrompt, PromptEvaluatorVersion: &entity.PromptEvaluatorVersion{ID: 101}}, + {ID: 201, EvaluatorType: entity.EvaluatorTypeCustomRPC, CustomRPCEvaluatorVersion: &entity.CustomRPCEvaluatorVersion{ID: 201, EvaluatorID: 201, ProviderEvaluatorCode: &code, IsAsync: true}}, + }, + EvalConf: &entity.EvaluationConfiguration{ConnectorConf: entity.Connector{EvaluatorsConf: &entity.EvaluatorsConf{ + EvaluatorConcurNum: gptr.Of(2), EvaluatorConf: []*entity.EvaluatorConf{ + {EvaluatorVersionID: 101, IngressConf: &entity.EvaluatorIngressConf{EvalSetAdapter: &entity.FieldAdapter{}, TargetAdapter: &entity.FieldAdapter{}}}, + {EvaluatorVersionID: 201, IngressConf: &entity.EvaluatorIngressConf{EvalSetAdapter: &entity.FieldAdapter{}, TargetAdapter: &entity.FieldAdapter{}}}, + }, + }}}, + } + etec := &entity.ExptTurnEvalCtx{ + ExptItemEvalCtx: &entity.ExptItemEvalCtx{Expt: expt, Event: &entity.ExptItemEvalEvent{SpaceID: 2, ExptID: 3, ExptRunID: 4, Session: &entity.Session{UserID: "u"}}, EvalSetItem: &entity.EvaluationSetItem{ItemID: 5}}, + ExptTurnRunResult: &entity.ExptTurnRunResult{}, Turn: &entity.Turn{ID: 6}, + } + target := &entity.EvalTargetRecord{EvalTargetOutputData: &entity.EvalTargetOutputData{OutputFields: map[string]*entity.Content{}}} + + benefitSvc.EXPECT().CheckAndDeductEvalBenefit(gomock.Any(), gomock.Any()).Return(&benefit.CheckAndDeductEvalBenefitResult{}, nil) + evaluatorSvc.EXPECT().ShouldInterceptEvaluator(gomock.Any(), gomock.Any()).Return(nil, false, nil).Times(2) + metric.EXPECT().EmitTurnExecEvaluatorResult(int64(2), false).Times(2) + evaluatorSvc.EXPECT().RunEvaluator(gomock.Any(), gomock.Any()).DoAndReturn(func(context.Context, *entity.RunEvaluatorRequest) (*entity.EvaluatorRecord, error) { + // This makes the sync branch observably slower than an immediate provider callback. + time.Sleep(20 * time.Millisecond) + return &entity.EvaluatorRecord{ID: 1101, EvaluatorVersionID: 101, Status: entity.EvaluatorRunStatusSuccess}, nil + }) + evaluatorSvc.EXPECT().AsyncRunEvaluator(gomock.Any(), gomock.Any()).Return(&entity.EvaluatorRecord{ID: 1201, EvaluatorVersionID: 201, Status: entity.EvaluatorRunStatusAsyncInvoking}, nil) + recordSvc.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(1201), false).Return(&entity.EvaluatorRecord{ID: 1201, EvaluatorVersionID: 201, Status: refreshedStatus}, nil) + + records, err := service.CallEvaluators(context.Background(), etec, target) + require.NoError(t, err) + require.Len(t, records, 2) + result := (&entity.ExptTurnRunResult{}).SetEvaluatorResults(records) + assert.Equal(t, wantAbort, result.AbortWithEvaluatorResults(context.Background(), etec.Event)) + assert.Equal(t, entity.EvaluatorRunStatusSuccess, result.GetEvaluatorRecord(101).Status) + assert.Equal(t, refreshedStatus, result.GetEvaluatorRecord(201).Status) + } + + t.Run("sync slow async callback fast", func(t *testing.T) { + t.Parallel() + newCase(t, entity.EvaluatorRunStatusSuccess, false) + }) + t.Run("sync fast relative to async callback", func(t *testing.T) { + t.Parallel() + newCase(t, entity.EvaluatorRunStatusAsyncInvoking, true) + }) +} diff --git a/backend/modules/evaluation/domain/service/mocks/evaluator_service_mock.go b/backend/modules/evaluation/domain/service/mocks/evaluator_service_mock.go index 34917f8059..f9db9902f8 100644 --- a/backend/modules/evaluation/domain/service/mocks/evaluator_service_mock.go +++ b/backend/modules/evaluation/domain/service/mocks/evaluator_service_mock.go @@ -35,6 +35,20 @@ func (m *MockEvaluatorService) EXPECT() *MockEvaluatorServiceMockRecorder { return m.recorder } +// ArmEvaluatorResume mocks base method. +func (m *MockEvaluatorService) ArmEvaluatorResume(ctx context.Context, recordID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ArmEvaluatorResume", ctx, recordID) + ret0, _ := ret[0].(error) + return ret0 +} + +// ArmEvaluatorResume indicates an expected call of ArmEvaluatorResume. +func (mr *MockEvaluatorServiceMockRecorder) ArmEvaluatorResume(ctx, recordID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ArmEvaluatorResume", reflect.TypeOf((*MockEvaluatorService)(nil).ArmEvaluatorResume), ctx, recordID) +} + // AsyncDebugEvaluator mocks base method. func (m *MockEvaluatorService) AsyncDebugEvaluator(arg0 context.Context, arg1 *entity.AsyncDebugEvaluatorRequest) (*entity.AsyncDebugEvaluatorResponse, error) { m.ctrl.T.Helper() @@ -323,11 +337,12 @@ func (mr *MockEvaluatorServiceMockRecorder) ListEvaluatorVersion(arg0, arg1 inte } // ReportEvaluatorInvokeResult mocks base method. -func (m *MockEvaluatorService) ReportEvaluatorInvokeResult(arg0 context.Context, arg1 *entity.ReportEvaluatorRecordParam) error { +func (m *MockEvaluatorService) ReportEvaluatorInvokeResult(arg0 context.Context, arg1 *entity.ReportEvaluatorRecordParam) (entity.ReportEvaluatorResultOutcome, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReportEvaluatorInvokeResult", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(entity.ReportEvaluatorResultOutcome) + ret1, _ := ret[1].(error) + return ret0, ret1 } // ReportEvaluatorInvokeResult indicates an expected call of ReportEvaluatorInvokeResult. diff --git a/backend/modules/evaluation/domain/service/sandbox_agent_notifier_wire_test.go b/backend/modules/evaluation/domain/service/sandbox_agent_notifier_wire_test.go new file mode 100644 index 0000000000..ef1955dd94 --- /dev/null +++ b/backend/modules/evaluation/domain/service/sandbox_agent_notifier_wire_test.go @@ -0,0 +1,20 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 + +package service + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProvideSandboxAgentNotifiers(t *testing.T) { + notifier := &sandboxAgentNotifier{} + + got := ProvideSandboxAgentNotifiers(notifier) + + require.Len(t, got, 1) + assert.Same(t, notifier, got[0]) +} diff --git a/backend/modules/evaluation/domain/service/wire.go b/backend/modules/evaluation/domain/service/wire.go index 87c0af1197..0af8941cd0 100644 --- a/backend/modules/evaluation/domain/service/wire.go +++ b/backend/modules/evaluation/domain/service/wire.go @@ -42,6 +42,7 @@ var ExperimentDomainServiceSet = wire.NewSet( // 沙箱 agent 实验飞书通知 (每 1h 进度 + 单行失败)。open-source NotifyRPCAdapter 是 no-op 桩, // 商业版 fork 通过自己的 wire set 覆盖为真实的 Lark send。 NewSandboxAgentNotifier, + ProvideSandboxAgentNotifiers, // Infrastructure Sets taskrpc.TaskRPCSet, pipeline.PipelineRPCSet, @@ -57,6 +58,10 @@ var ExperimentDomainServiceSet = wire.NewSet( ProvideNilItemCompletePublisher, ) +func ProvideSandboxAgentNotifiers(notifier ISandboxAgentNotifier) []ISandboxAgentNotifier { + return []ISandboxAgentNotifier{notifier} +} + func ProvideNilItemCompletePublisher() component.IItemCompletePublisher { return nil } diff --git a/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl.go b/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl.go index 43b68dc48e..443e95ed74 100644 --- a/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl.go +++ b/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl.go @@ -6,6 +6,8 @@ package evaluator import ( "context" + "gorm.io/gorm" + "github.com/coze-dev/coze-loop/backend/infra/db" "github.com/coze-dev/coze-loop/backend/infra/idgen" "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/entity" @@ -158,16 +160,92 @@ func (r *EvaluatorRecordRepoImpl) BatchGetEvaluatorRecordForAggr(ctx context.Con return aggrRecords, nil } -func (r *EvaluatorRecordRepoImpl) UpdateEvaluatorRecordResult(ctx context.Context, recordID int64, status entity.EvaluatorRunStatus, outputData *entity.EvaluatorOutputData) error { - var score float64 +func evaluatorRecordResultValues(outputData *entity.EvaluatorOutputData) (score float64, outputDataStr string) { if outputData != nil && outputData.EvaluatorResult != nil && outputData.EvaluatorResult.Score != nil { score = *outputData.EvaluatorResult.Score } - - var outputDataStr string if outputData != nil { outputDataStr = json.Jsonify(outputData) } + return score, outputDataStr +} - return r.evaluatorRecordDao.UpdateEvaluatorRecordResult(ctx, recordID, int8(status), score, outputDataStr) +func mergeEvaluatorOutputExt(dst, src *entity.EvaluatorOutputData) *entity.EvaluatorOutputData { + if dst == nil { + dst = &entity.EvaluatorOutputData{} + } + if src == nil || len(src.Ext) == 0 { + return dst + } + if dst.Ext == nil { + dst.Ext = make(map[string]string, len(src.Ext)) + } + for k, v := range src.Ext { + if _, exists := dst.Ext[k]; !exists { + dst.Ext[k] = v + } + } + return dst +} + +func (r *EvaluatorRecordRepoImpl) UpdateEvaluatorRecordResult(ctx context.Context, recordID int64, status entity.EvaluatorRunStatus, outputData *entity.EvaluatorOutputData) error { + // This legacy entry point is used by async-zombie termination. Resolve space on the primary and + // funnel the terminal write through the same AsyncInvoking CAS so a concurrent callback cannot be overwritten. + po, err := r.evaluatorRecordDao.GetEvaluatorRecord(ctx, recordID, false, db.WithMaster()) + if err != nil || po == nil { + return err + } + _, err = r.CompareAndSwapEvaluatorRecordResult(ctx, recordID, po.SpaceID, entity.EvaluatorRunStatusAsyncInvoking, status, outputData) + return err +} + +func (r *EvaluatorRecordRepoImpl) CompareAndSwapEvaluatorRecordResult(ctx context.Context, recordID, spaceID int64, fromStatus, toStatus entity.EvaluatorRunStatus, outputData *entity.EvaluatorOutputData) (bool, error) { + if r.dbProvider == nil { + score, outputDataStr := evaluatorRecordResultValues(outputData) + rows, err := r.evaluatorRecordDao.CompareAndSwapEvaluatorRecordResult(ctx, recordID, spaceID, int8(fromStatus), int8(toStatus), score, outputDataStr) + return rows > 0, err + } + var updated bool + err := r.dbProvider.Transaction(ctx, func(tx *gorm.DB) error { + opt := db.WithTransaction(tx) + po, err := r.evaluatorRecordDao.GetEvaluatorRecord(ctx, recordID, false, opt, db.WithSelectForUpdate()) + if err != nil || po == nil || po.SpaceID != spaceID || entity.EvaluatorRunStatus(po.Status) != fromStatus { + return err + } + current, err := convertor.ConvertEvaluatorRecordPO2DO(po) + if err != nil { + return err + } + if current != nil { + outputData = mergeEvaluatorOutputExt(outputData, current.EvaluatorOutputData) + } + score, outputDataStr := evaluatorRecordResultValues(outputData) + rows, err := r.evaluatorRecordDao.CompareAndSwapEvaluatorRecordResult(ctx, recordID, spaceID, int8(fromStatus), int8(toStatus), score, outputDataStr, opt) + updated = rows > 0 + return err + }) + return updated, err +} + +func (r *EvaluatorRecordRepoImpl) UpdateEvaluatorRecordAsyncDispatch(ctx context.Context, recordID, spaceID int64, traceID string, outputData *entity.EvaluatorOutputData) error { + if r.dbProvider == nil { + _, outputDataStr := evaluatorRecordResultValues(outputData) + return r.evaluatorRecordDao.UpdateEvaluatorRecordAsyncDispatch(ctx, recordID, spaceID, traceID, outputDataStr) + } + return r.dbProvider.Transaction(ctx, func(tx *gorm.DB) error { + opt := db.WithTransaction(tx) + po, err := r.evaluatorRecordDao.GetEvaluatorRecord(ctx, recordID, false, opt, db.WithSelectForUpdate()) + if err != nil || po == nil || po.SpaceID != spaceID { + return err + } + current, err := convertor.ConvertEvaluatorRecordPO2DO(po) + if err != nil { + return err + } + if current != nil { + outputData = mergeEvaluatorOutputExt(current.EvaluatorOutputData, outputData) + } + _, outputDataStr := evaluatorRecordResultValues(outputData) + return r.evaluatorRecordDao.UpdateEvaluatorRecordAsyncDispatch(ctx, recordID, spaceID, traceID, outputDataStr, opt) + }) } diff --git a/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl_test.go b/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl_test.go index c3db2b8294..59a225e245 100644 --- a/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl_test.go +++ b/backend/modules/evaluation/infra/repo/evaluator/evaluator_record_impl_test.go @@ -13,8 +13,11 @@ import ( "github.com/bytedance/gg/gptr" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "gorm.io/gorm" + "github.com/coze-dev/coze-loop/backend/infra/db" dbmocks "github.com/coze-dev/coze-loop/backend/infra/db/mocks" fsMocks "github.com/coze-dev/coze-loop/backend/infra/fileserver/mocks" idgenmocks "github.com/coze-dev/coze-loop/backend/infra/idgen/mocks" @@ -583,110 +586,24 @@ func TestEvaluatorRecordRepoImpl_GetEvaluatorRecord(t *testing.T) { } func TestEvaluatorRecordRepoImpl_UpdateEvaluatorRecordResult(t *testing.T) { + t.Parallel() ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockEvaluatorRecordDAO := evaluatormocks.NewMockEvaluatorRecordDAO(ctrl) - - tests := []struct { - name string - recordID int64 - status entity.EvaluatorRunStatus - outputData *entity.EvaluatorOutputData - wantScore float64 - wantOutputDataStr string - daoErr error - }{ - { - name: "outputData为nil", - recordID: 1, - status: entity.EvaluatorRunStatusSuccess, - outputData: nil, - wantScore: 0, - wantOutputDataStr: "", - }, - { - name: "EvaluatorResult为nil", - recordID: 2, - status: entity.EvaluatorRunStatusFail, - outputData: &entity.EvaluatorOutputData{ - EvaluatorResult: nil, - }, - wantScore: 0, - wantOutputDataStr: pkgjson.Jsonify(&entity.EvaluatorOutputData{EvaluatorResult: nil}), - }, - { - name: "Score为nil但Correction有score", - recordID: 3, - status: entity.EvaluatorRunStatusFail, - outputData: &entity.EvaluatorOutputData{ - EvaluatorResult: &entity.EvaluatorResult{ - Score: nil, - Correction: &entity.Correction{ - Score: gptr.Of(float64(2.5)), - }, - }, - }, - wantScore: 0, - wantOutputDataStr: pkgjson.Jsonify(&entity.EvaluatorOutputData{ - EvaluatorResult: &entity.EvaluatorResult{ - Score: nil, - Correction: &entity.Correction{ - Score: gptr.Of(float64(2.5)), - }, - }, - }), - }, - { - name: "Score有值", - recordID: 4, - status: entity.EvaluatorRunStatusSuccess, - outputData: &entity.EvaluatorOutputData{ - EvaluatorResult: &entity.EvaluatorResult{ - Score: gptr.Of(float64(1.25)), - }, - }, - wantScore: 1.25, - wantOutputDataStr: pkgjson.Jsonify(&entity.EvaluatorOutputData{ - EvaluatorResult: &entity.EvaluatorResult{ - Score: gptr.Of(float64(1.25)), - }, - }), - }, - { - name: "DAO返回错误", - recordID: 5, - status: entity.EvaluatorRunStatusSuccess, - outputData: &entity.EvaluatorOutputData{ - EvaluatorResult: &entity.EvaluatorResult{ - Score: gptr.Of(float64(3)), - }, - }, - wantScore: 3, - wantOutputDataStr: pkgjson.Jsonify(&entity.EvaluatorOutputData{ - EvaluatorResult: &entity.EvaluatorResult{ - Score: gptr.Of(float64(3)), - }, - }), - daoErr: assert.AnError, - }, - } + dao := evaluatormocks.NewMockEvaluatorRecordDAO(ctrl) + repo := &EvaluatorRecordRepoImpl{evaluatorRecordDao: dao} + output := &entity.EvaluatorOutputData{EvaluatorRunError: &entity.EvaluatorRunError{Message: "zombie"}} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - repo := &EvaluatorRecordRepoImpl{ - evaluatorRecordDao: mockEvaluatorRecordDAO, - } - - mockEvaluatorRecordDAO.EXPECT(). - UpdateEvaluatorRecordResult(gomock.Any(), tt.recordID, int8(tt.status), tt.wantScore, tt.wantOutputDataStr). - Return(tt.daoErr). - Times(1) + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any()).Return(&model.EvaluatorRecord{ID: 10, SpaceID: 20}, nil) + dao.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(10), int64(20), int8(entity.EvaluatorRunStatusAsyncInvoking), int8(entity.EvaluatorRunStatusFail), float64(0), pkgjson.Jsonify(output)).Return(int64(1), nil) + require.NoError(t, repo.UpdateEvaluatorRecordResult(context.Background(), 10, entity.EvaluatorRunStatusFail, output)) +} - err := repo.UpdateEvaluatorRecordResult(context.Background(), tt.recordID, tt.status, tt.outputData) - assert.Equal(t, tt.daoErr, err) - }) - } +func TestEvaluatorRecordRepoImpl_UpdateEvaluatorRecordResult_MissingRecordIsNoop(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + dao := evaluatormocks.NewMockEvaluatorRecordDAO(ctrl) + repo := &EvaluatorRecordRepoImpl{evaluatorRecordDao: dao} + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any()).Return(nil, nil) + require.NoError(t, repo.UpdateEvaluatorRecordResult(context.Background(), 10, entity.EvaluatorRunStatusFail, nil)) } type nopReader struct{ buf *bytes.Reader } @@ -1138,3 +1055,136 @@ func TestEvaluatorRecordRepoImpl_BatchGetEvaluatorRecordForAggr(t *testing.T) { assert.Nil(t, got) }) } + +func TestEvaluatorRecordRepoImpl_CompareAndSwapEvaluatorRecordResult(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + dao := evaluatormocks.NewMockEvaluatorRecordDAO(ctrl) + repo := &EvaluatorRecordRepoImpl{evaluatorRecordDao: dao} + output := &entity.EvaluatorOutputData{EvaluatorResult: &entity.EvaluatorResult{Score: gptr.Of(0.75)}} + dao.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(10), int64(20), int8(entity.EvaluatorRunStatusAsyncInvoking), int8(entity.EvaluatorRunStatusSuccess), 0.75, pkgjson.Jsonify(output)).Return(int64(1), nil) + + updated, err := repo.CompareAndSwapEvaluatorRecordResult(context.Background(), 10, 20, entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, output) + require.NoError(t, err) + assert.True(t, updated) +} + +func TestMergeEvaluatorOutputExt(t *testing.T) { + t.Parallel() + + got := mergeEvaluatorOutputExt(nil, &entity.EvaluatorOutputData{Ext: map[string]string{"source": "1"}}) + require.NotNil(t, got) + assert.Equal(t, "1", got.Ext["source"]) + assert.Same(t, got, mergeEvaluatorOutputExt(got, nil)) +} + +func TestEvaluatorRecordRepoImpl_AsyncTransactionErrors(t *testing.T) { + t.Parallel() + + newRepo := func(t *testing.T) (*EvaluatorRecordRepoImpl, *evaluatormocks.MockEvaluatorRecordDAO) { + t.Helper() + ctrl := gomock.NewController(t) + dao := evaluatormocks.NewMockEvaluatorRecordDAO(ctrl) + provider := dbmocks.NewMockProvider(ctrl) + provider.EXPECT().Transaction(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, fc func(*gorm.DB) error, _ ...db.Option) error { + return fc(nil) + }).AnyTimes() + return &EvaluatorRecordRepoImpl{evaluatorRecordDao: dao, dbProvider: provider}, dao + } + + t.Run("callback get record error", func(t *testing.T) { + repo, dao := newRepo(t) + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any(), gomock.Any()).Return(nil, errors.New("get failed")) + updated, err := repo.CompareAndSwapEvaluatorRecordResult(context.Background(), 10, 20, entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, nil) + assert.False(t, updated) + require.EqualError(t, err, "get failed") + }) + + t.Run("callback invalid stored output", func(t *testing.T) { + repo, dao := newRepo(t) + bad := []byte("{invalid") + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any(), gomock.Any()).Return(&model.EvaluatorRecord{ + ID: 10, SpaceID: 20, Status: int32(entity.EvaluatorRunStatusAsyncInvoking), OutputData: &bad, + }, nil) + updated, err := repo.CompareAndSwapEvaluatorRecordResult(context.Background(), 10, 20, entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, nil) + assert.False(t, updated) + assert.Error(t, err) + }) + + t.Run("dispatch get record error", func(t *testing.T) { + repo, dao := newRepo(t) + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any(), gomock.Any()).Return(nil, errors.New("get failed")) + require.EqualError(t, repo.UpdateEvaluatorRecordAsyncDispatch(context.Background(), 10, 20, "trace", nil), "get failed") + }) + + t.Run("dispatch invalid stored output", func(t *testing.T) { + repo, dao := newRepo(t) + bad := []byte("{invalid") + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any(), gomock.Any()).Return(&model.EvaluatorRecord{ + ID: 10, SpaceID: 20, Status: int32(entity.EvaluatorRunStatusSuccess), OutputData: &bad, + }, nil) + assert.Error(t, repo.UpdateEvaluatorRecordAsyncDispatch(context.Background(), 10, 20, "trace", nil)) + }) +} + +func TestEvaluatorRecordRepoImpl_AsyncCallbackAndDispatchMetadataMerge(t *testing.T) { + t.Parallel() + + newRepo := func(t *testing.T) (*EvaluatorRecordRepoImpl, *evaluatormocks.MockEvaluatorRecordDAO) { + t.Helper() + ctrl := gomock.NewController(t) + dao := evaluatormocks.NewMockEvaluatorRecordDAO(ctrl) + provider := dbmocks.NewMockProvider(ctrl) + provider.EXPECT().Transaction(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, fc func(*gorm.DB) error, _ ...db.Option) error { + return fc(nil) + }).AnyTimes() + return &EvaluatorRecordRepoImpl{evaluatorRecordDao: dao, dbProvider: provider}, dao + } + + t.Run("callback after dispatch keeps kickoff ext", func(t *testing.T) { + repo, dao := newRepo(t) + storedOutput := &entity.EvaluatorOutputData{Ext: map[string]string{"session_id": "s1", "lb_key": "k1"}} + storedBytes := []byte(pkgjson.Jsonify(storedOutput)) + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any(), gomock.Any()).Return(&model.EvaluatorRecord{ + ID: 10, SpaceID: 20, Status: int32(entity.EvaluatorRunStatusAsyncInvoking), OutputData: &storedBytes, + }, nil) + dao.EXPECT().CompareAndSwapEvaluatorRecordResult(gomock.Any(), int64(10), int64(20), int8(entity.EvaluatorRunStatusAsyncInvoking), int8(entity.EvaluatorRunStatusSuccess), 0.8, gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ int64, _, _ int8, _ float64, output string, _ ...db.Option) (int64, error) { + var got entity.EvaluatorOutputData + require.NoError(t, pkgjson.Unmarshal([]byte(output), &got)) + assert.Equal(t, "done", got.EvaluatorResult.Reasoning) + assert.Equal(t, "s1", got.Ext["session_id"]) + assert.Equal(t, "k1", got.Ext["lb_key"]) + return 1, nil + }) + + updated, err := repo.CompareAndSwapEvaluatorRecordResult(context.Background(), 10, 20, entity.EvaluatorRunStatusAsyncInvoking, entity.EvaluatorRunStatusSuccess, &entity.EvaluatorOutputData{ + EvaluatorResult: &entity.EvaluatorResult{Score: gptr.Of(0.8), Reasoning: "done"}, + }) + require.NoError(t, err) + assert.True(t, updated) + }) + + t.Run("dispatch after callback keeps terminal output and adds kickoff ext", func(t *testing.T) { + repo, dao := newRepo(t) + storedOutput := &entity.EvaluatorOutputData{EvaluatorResult: &entity.EvaluatorResult{Score: gptr.Of(0.8), Reasoning: "done"}} + storedBytes := []byte(pkgjson.Jsonify(storedOutput)) + dao.EXPECT().GetEvaluatorRecord(gomock.Any(), int64(10), false, gomock.Any(), gomock.Any()).Return(&model.EvaluatorRecord{ + ID: 10, SpaceID: 20, Status: int32(entity.EvaluatorRunStatusSuccess), OutputData: &storedBytes, + }, nil) + dao.EXPECT().UpdateEvaluatorRecordAsyncDispatch(gomock.Any(), int64(10), int64(20), "trace-1", gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ int64, _ string, output string, _ ...db.Option) error { + var got entity.EvaluatorOutputData + require.NoError(t, pkgjson.Unmarshal([]byte(output), &got)) + assert.Equal(t, float64(0.8), gptr.Indirect(got.EvaluatorResult.Score)) + assert.Equal(t, "done", got.EvaluatorResult.Reasoning) + assert.Equal(t, "s1", got.Ext["session_id"]) + return nil + }) + + require.NoError(t, repo.UpdateEvaluatorRecordAsyncDispatch(context.Background(), 10, 20, "trace-1", &entity.EvaluatorOutputData{ + Ext: map[string]string{"session_id": "s1"}, + })) + }) +} diff --git a/backend/modules/evaluation/infra/repo/evaluator/mysql/convertor/evaluator_test.go b/backend/modules/evaluation/infra/repo/evaluator/mysql/convertor/evaluator_test.go index 4063cc5fc7..99e6d49d0d 100644 --- a/backend/modules/evaluation/infra/repo/evaluator/mysql/convertor/evaluator_test.go +++ b/backend/modules/evaluation/infra/repo/evaluator/mysql/convertor/evaluator_test.go @@ -442,3 +442,37 @@ func TestConvertEvaluatorVersionPO2DO(t *testing.T) { }) } } + +func TestConvertEvaluatorVersion_CustomRPCAsyncMetainfoRoundTrip(t *testing.T) { + method := evaluatordo.EvaluatorHTTPMethodPost + path := "/async_invoke_evaluator" + serviceName := "trae.work.evaluator" + original := &evaluatordo.Evaluator{ + EvaluatorType: evaluatordo.EvaluatorTypeCustomRPC, + CustomRPCEvaluatorVersion: &evaluatordo.CustomRPCEvaluatorVersion{ + ID: 101, + SpaceID: 202, + EvaluatorID: 303, + EvaluatorType: evaluatordo.EvaluatorTypeCustomRPC, + Version: "1.0.0", + AccessProtocol: evaluatordo.EvaluatorAccessProtocolFaasHTTP, + ServiceName: &serviceName, + AsyncInvokeHTTPInfo: &evaluatordo.EvaluatorHTTPInfo{ + Method: &method, + Path: &path, + }, + IsAsync: true, + }, + } + + po, err := ConvertEvaluatorVersionDO2PO(original) + require.NoError(t, err) + got, err := ConvertEvaluatorVersionPO2DO(po) + require.NoError(t, err) + require.NotNil(t, got) + require.NotNil(t, got.CustomRPCEvaluatorVersion) + assert.True(t, got.CustomRPCEvaluatorVersion.IsAsync) + require.NotNil(t, got.CustomRPCEvaluatorVersion.AsyncInvokeHTTPInfo) + assert.Equal(t, method, gptr.Indirect(got.CustomRPCEvaluatorVersion.AsyncInvokeHTTPInfo.Method)) + assert.Equal(t, path, gptr.Indirect(got.CustomRPCEvaluatorVersion.AsyncInvokeHTTPInfo.Path)) +} diff --git a/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record.go b/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record.go index 653f878dd9..09a382995f 100644 --- a/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record.go +++ b/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record.go @@ -24,6 +24,8 @@ type EvaluatorRecordDAO interface { CreateEvaluatorRecord(ctx context.Context, evaluatorRecord *model.EvaluatorRecord, opts ...db.Option) error UpdateEvaluatorRecord(ctx context.Context, evaluatorRecord *model.EvaluatorRecord, opts ...db.Option) error UpdateEvaluatorRecordResult(ctx context.Context, recordID int64, status int8, score float64, outputData string, opts ...db.Option) error + CompareAndSwapEvaluatorRecordResult(ctx context.Context, recordID, spaceID int64, fromStatus, toStatus int8, score float64, outputData string, opts ...db.Option) (int64, error) + UpdateEvaluatorRecordAsyncDispatch(ctx context.Context, recordID, spaceID int64, traceID, outputData string, opts ...db.Option) error GetEvaluatorRecord(ctx context.Context, evaluatorRecordID int64, includeDeleted bool, opts ...db.Option) (*model.EvaluatorRecord, error) BatchGetEvaluatorRecord(ctx context.Context, evaluatorRecordIDs []int64, includeDeleted bool, opts ...db.Option) ([]*model.EvaluatorRecord, error) // BatchGetEvaluatorRecordForAggr 聚合专用窄查询: 只 SELECT id, score, status, 不取 input_data/output_data/ext @@ -85,6 +87,30 @@ func (dao *EvaluatorRecordDAOImpl) UpdateEvaluatorRecordResult(ctx context.Conte }).Error } +func (dao *EvaluatorRecordDAOImpl) CompareAndSwapEvaluatorRecordResult(ctx context.Context, recordID, spaceID int64, fromStatus, toStatus int8, score float64, outputData string, opts ...db.Option) (int64, error) { + dbsession := dao.provider.NewSession(ctx, opts...) + result := dbsession.WithContext(ctx). + Model(&model.EvaluatorRecord{}). + Where("id = ? AND space_id = ? AND status = ? AND deleted_at IS NULL", recordID, spaceID, fromStatus). + Updates(map[string]interface{}{ + "status": toStatus, + "score": score, + "output_data": outputData, + }) + return result.RowsAffected, result.Error +} + +func (dao *EvaluatorRecordDAOImpl) UpdateEvaluatorRecordAsyncDispatch(ctx context.Context, recordID, spaceID int64, traceID, outputData string, opts ...db.Option) error { + dbsession := dao.provider.NewSession(ctx, opts...) + return dbsession.WithContext(ctx). + Model(&model.EvaluatorRecord{}). + Where("id = ? AND space_id = ? AND deleted_at IS NULL", recordID, spaceID). + Updates(map[string]interface{}{ + "trace_id": traceID, + "output_data": outputData, + }).Error +} + func (dao *EvaluatorRecordDAOImpl) GetEvaluatorRecord(ctx context.Context, evaluatorRecordID int64, includeDeleted bool, opts ...db.Option) (*model.EvaluatorRecord, error) { po := &model.EvaluatorRecord{} @@ -92,6 +118,9 @@ func (dao *EvaluatorRecordDAOImpl) GetEvaluatorRecord(ctx context.Context, evalu dbsession := dao.provider.NewSession(ctx, opts...) query := dbsession.WithContext(ctx).Where("id = ?", evaluatorRecordID) + if contexts.CtxWriteDB(ctx) { + query = query.Clauses(dbresolver.Write) + } if includeDeleted { query = query.Unscoped() // 解除软删除过滤 } diff --git a/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record_aggr_test.go b/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record_aggr_test.go index de08240137..7ea0758206 100644 --- a/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record_aggr_test.go +++ b/backend/modules/evaluation/infra/repo/evaluator/mysql/evaluator_record_aggr_test.go @@ -10,6 +10,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "gorm.io/driver/mysql" "gorm.io/gorm" @@ -81,3 +82,22 @@ func TestBatchGetEvaluatorRecordForAggr_DAOError(t *testing.T) { assert.Error(t, err) assert.Nil(t, got) } + +func TestCompareAndSwapEvaluatorRecordResult_SQL(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + dao, mock, cleanup := newAggrTestDAO(t, ctrl) + defer cleanup() + + mock.ExpectBegin() + mock.ExpectExec(`UPDATE `+"`evaluator_record`"+` SET .+ WHERE \(id = .+ AND space_id = .+ AND status = .+ AND deleted_at IS NULL\) AND `+"`evaluator_record`.`deleted_at`"+` IS NULL`). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), int64(10), int64(20), int8(entity.EvaluatorRunStatusAsyncInvoking)). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + + rows, err := dao.CompareAndSwapEvaluatorRecordResult(context.Background(), 10, 20, int8(entity.EvaluatorRunStatusAsyncInvoking), int8(entity.EvaluatorRunStatusSuccess), 0.8, `{}`) + require.NoError(t, err) + assert.Equal(t, int64(1), rows) + assert.NoError(t, mock.ExpectationsWereMet()) +} diff --git a/backend/modules/evaluation/infra/repo/evaluator/mysql/mocks/evaluator_record_mock.go b/backend/modules/evaluation/infra/repo/evaluator/mysql/mocks/evaluator_record_mock.go index ccf2020d44..3a6a1be5de 100644 --- a/backend/modules/evaluation/infra/repo/evaluator/mysql/mocks/evaluator_record_mock.go +++ b/backend/modules/evaluation/infra/repo/evaluator/mysql/mocks/evaluator_record_mock.go @@ -81,6 +81,26 @@ func (mr *MockEvaluatorRecordDAOMockRecorder) BatchGetEvaluatorRecordForAggr(arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchGetEvaluatorRecordForAggr", reflect.TypeOf((*MockEvaluatorRecordDAO)(nil).BatchGetEvaluatorRecordForAggr), varargs...) } +// CompareAndSwapEvaluatorRecordResult mocks base method. +func (m *MockEvaluatorRecordDAO) CompareAndSwapEvaluatorRecordResult(ctx context.Context, recordID, spaceID int64, fromStatus, toStatus int8, score float64, outputData string, opts ...db.Option) (int64, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, recordID, spaceID, fromStatus, toStatus, score, outputData} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CompareAndSwapEvaluatorRecordResult", varargs...) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CompareAndSwapEvaluatorRecordResult indicates an expected call of CompareAndSwapEvaluatorRecordResult. +func (mr *MockEvaluatorRecordDAOMockRecorder) CompareAndSwapEvaluatorRecordResult(ctx, recordID, spaceID, fromStatus, toStatus, score, outputData any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, recordID, spaceID, fromStatus, toStatus, score, outputData}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareAndSwapEvaluatorRecordResult", reflect.TypeOf((*MockEvaluatorRecordDAO)(nil).CompareAndSwapEvaluatorRecordResult), varargs...) +} + // CreateEvaluatorRecord mocks base method. func (m *MockEvaluatorRecordDAO) CreateEvaluatorRecord(arg0 context.Context, arg1 *model.EvaluatorRecord, arg2 ...db.Option) error { m.ctrl.T.Helper() @@ -139,6 +159,25 @@ func (mr *MockEvaluatorRecordDAOMockRecorder) UpdateEvaluatorRecord(arg0, arg1 a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEvaluatorRecord", reflect.TypeOf((*MockEvaluatorRecordDAO)(nil).UpdateEvaluatorRecord), varargs...) } +// UpdateEvaluatorRecordAsyncDispatch mocks base method. +func (m *MockEvaluatorRecordDAO) UpdateEvaluatorRecordAsyncDispatch(ctx context.Context, recordID, spaceID int64, traceID, outputData string, opts ...db.Option) error { + m.ctrl.T.Helper() + varargs := []any{ctx, recordID, spaceID, traceID, outputData} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateEvaluatorRecordAsyncDispatch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateEvaluatorRecordAsyncDispatch indicates an expected call of UpdateEvaluatorRecordAsyncDispatch. +func (mr *MockEvaluatorRecordDAOMockRecorder) UpdateEvaluatorRecordAsyncDispatch(ctx, recordID, spaceID, traceID, outputData any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, recordID, spaceID, traceID, outputData}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEvaluatorRecordAsyncDispatch", reflect.TypeOf((*MockEvaluatorRecordDAO)(nil).UpdateEvaluatorRecordAsyncDispatch), varargs...) +} + // UpdateEvaluatorRecordResult mocks base method. func (m *MockEvaluatorRecordDAO) UpdateEvaluatorRecordResult(arg0 context.Context, arg1 int64, arg2 int8, arg3 float64, arg4 string, arg5 ...db.Option) error { m.ctrl.T.Helper() diff --git a/backend/modules/evaluation/infra/repo/experiment/redis/convert/item_turn_eval_async_test.go b/backend/modules/evaluation/infra/repo/experiment/redis/convert/item_turn_eval_async_test.go index 8994716189..9c63000096 100644 --- a/backend/modules/evaluation/infra/repo/experiment/redis/convert/item_turn_eval_async_test.go +++ b/backend/modules/evaluation/infra/repo/experiment/redis/convert/item_turn_eval_async_test.go @@ -26,3 +26,14 @@ func TestExptItemTurnEvalAsyncCtx_CallbackURLRoundTrip(t *testing.T) { assert.Equal(t, "https://example.com/hook", out.CallbackURL) assert.Equal(t, int64(123), out.RecordID) } + +func TestExptItemTurnEvalAsyncCtx_ResumeReadyRoundTrip(t *testing.T) { + c := NewExptItemTurnEvalAsyncCtx() + in := &entity.EvalAsyncCtx{RecordID: 123, ResumeReady: true} + b, err := c.FromDO(in) + assert.NoError(t, err) + + out, err := c.ToDO(b) + assert.NoError(t, err) + assert.True(t, out.ResumeReady) +} diff --git a/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async.go b/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async.go index 985f88836d..e49d7ffebc 100644 --- a/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async.go +++ b/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async.go @@ -18,6 +18,8 @@ import ( type IEvalAsyncDAO interface { SetEvalAsyncCtx(ctx context.Context, invokeID string, actx *entity.EvalAsyncCtx) error GetEvalAsyncCtx(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) + GetEvalAsyncCtxStrong(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) + MarkEvalAsyncResumeReady(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) } func NewEvalAsyncDAO(cmdable redis.Cmdable) IEvalAsyncDAO { @@ -47,9 +49,81 @@ func (e *evalAsyncDAOImpl) SetEvalAsyncCtx(ctx context.Context, invokeID string, } func (e *evalAsyncDAOImpl) GetEvalAsyncCtx(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) { + return e.getEvalAsyncCtx(ctx, invokeID) +} + +func (e *evalAsyncDAOImpl) GetEvalAsyncCtxStrong(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) { + var lastErr error + delays := []time.Duration{0, 50 * time.Millisecond, 100 * time.Millisecond, 200 * time.Millisecond} + for _, delay := range delays { + if delay > 0 { + timer := time.NewTimer(delay) + select { + case <-ctx.Done(): + timer.Stop() + return nil, ctx.Err() + case <-timer.C: + } + } + actx, err := e.getEvalAsyncCtx(ctx, invokeID) + if err == nil || !redis.IsNilError(err) { + return actx, err + } + lastErr = err + } + if redis.IsNilError(lastErr) { + return nil, errorx.New("eval async context not found after bounded retry, invoke_id: %s", invokeID) + } + return nil, lastErr +} + +const markResumeReadyScript = ` +local current = redis.call('GET', KEYS[1]) +if not current then return -1 end +if string.find(current, '"resume_ready":true', 1, true) then return 0 end +local updated, count = string.gsub(current, '"resume_ready":false', '"resume_ready":true', 1) +if count == 0 then + updated, count = string.gsub(current, '^{', '{"resume_ready":true,', 1) +end +if count == 0 then return -2 end +redis.call('SET', KEYS[1], updated, 'EX', ARGV[1]) +return 1 +` + +func (e *evalAsyncDAOImpl) MarkEvalAsyncResumeReady(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) { + actx, err := e.GetEvalAsyncCtxStrong(ctx, invokeID) + if err != nil { + return nil, err + } + if actx == nil || actx.ResumeReady { + return actx, nil + } + key := e.makeExptItemTurnEvalAsyncCtxKey(invokeID) + // Keep the raw JSON intact instead of decoding with Redis cjson (Lua numbers cannot safely represent i64 IDs). + // resume_ready is a root-level field emitted by Go's JSON encoder, so an anchored string replacement/insertion is safe. + + updated, evalErr := e.cmdable.Eval(ctx, markResumeReadyScript, []string{key}, int64((12*time.Hour)/time.Second)).Int64() + if evalErr != nil { + return nil, errorx.Wrapf(evalErr, "redis mark resume ready fail, key: %v", key) + } + if updated < 0 { + return nil, errorx.New("mark eval async resume ready failed, invoke_id: %s, code: %d", invokeID, updated) + } + actx, err = e.GetEvalAsyncCtxStrong(ctx, invokeID) + if err != nil { + return nil, err + } + return actx, nil +} + +func (e *evalAsyncDAOImpl) getEvalAsyncCtx(ctx context.Context, invokeID string) (*entity.EvalAsyncCtx, error) { key := e.makeExptItemTurnEvalAsyncCtxKey(invokeID) got, err := e.cmdable.Get(ctx, key).Result() if err != nil { + // Preserve redis.Nil so the strong-read path can apply bounded retry. + if redis.IsNilError(err) { + return nil, err + } return nil, errorx.Wrapf(err, "redis get fail, key: %v", key) } return convert.NewExptItemTurnEvalAsyncCtx().ToDO(conv.UnsafeStringToBytes(got)) diff --git a/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async_test.go b/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async_test.go new file mode 100644 index 0000000000..8c8389c8cc --- /dev/null +++ b/backend/modules/evaluation/infra/repo/experiment/redis/dao/item_turn_eval_async_test.go @@ -0,0 +1,91 @@ +// Copyright (c) 2025 coze-dev Authors +// SPDX-License-Identifier: Apache-2.0 + +package dao + +import ( + "context" + "testing" + "time" + + redisv9 "github.com/redis/go-redis/v9" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + infraredis "github.com/coze-dev/coze-loop/backend/infra/redis" + "github.com/coze-dev/coze-loop/backend/modules/evaluation/domain/entity" +) + +func TestEvalAsyncDAO_GetStrongAndMarkResumeReady(t *testing.T) { + t.Parallel() + cmdable := infraredis.NewTestRedis(t) + dao := NewEvalAsyncDAO(cmdable) + ctx := context.Background() + + go func() { + time.Sleep(20 * time.Millisecond) + require.NoError(t, dao.SetEvalAsyncCtx(ctx, "evaluator:1", &entity.EvalAsyncCtx{RecordID: 1})) + }() + + actx, err := dao.GetEvalAsyncCtxStrong(ctx, "evaluator:1") + require.NoError(t, err) + require.NotNil(t, actx) + assert.False(t, actx.ResumeReady) + + actx, err = dao.MarkEvalAsyncResumeReady(ctx, "evaluator:1") + require.NoError(t, err) + assert.True(t, actx.ResumeReady) + + stored, err := dao.GetEvalAsyncCtx(ctx, "evaluator:1") + require.NoError(t, err) + assert.True(t, stored.ResumeReady) +} + +func TestEvalAsyncDAO_GetStrongStopsAfterBoundedRetry(t *testing.T) { + t.Parallel() + dao := NewEvalAsyncDAO(infraredis.NewTestRedis(t)) + start := time.Now() + actx, err := dao.GetEvalAsyncCtxStrong(context.Background(), "missing") + assert.Nil(t, actx) + assert.Error(t, err) + assert.NotErrorIs(t, err, redisv9.Nil) + assert.Contains(t, err.Error(), "eval async context not found") + assert.GreaterOrEqual(t, time.Since(start), 350*time.Millisecond) + assert.Less(t, time.Since(start), time.Second) +} + +func TestEvalAsyncDAO_MarkResumeReadyPreservesLargeIDsAndExistingPayload(t *testing.T) { + t.Parallel() + cmdable := infraredis.NewTestRedis(t) + dao := NewEvalAsyncDAO(cmdable) + ctx := context.Background() + const largeID int64 = 9007199254740993 + original := &entity.EvalAsyncCtx{ + RecordID: largeID, + EvaluatorVersionID: largeID - 1, + Event: &entity.ExptItemEvalEvent{ + ExptID: largeID - 2, + ExptRunID: largeID - 3, + EvalSetItemID: largeID - 4, + }, + } + require.NoError(t, dao.SetEvalAsyncCtx(ctx, "evaluator:large", original)) + + got, err := dao.MarkEvalAsyncResumeReady(ctx, "evaluator:large") + require.NoError(t, err) + require.NotNil(t, got) + assert.True(t, got.ResumeReady) + assert.Equal(t, largeID, got.RecordID) + assert.Equal(t, largeID-1, got.EvaluatorVersionID) + require.NotNil(t, got.Event) + assert.Equal(t, largeID-2, got.Event.ExptID) + assert.Equal(t, largeID-3, got.Event.ExptRunID) + assert.Equal(t, largeID-4, got.Event.EvalSetItemID) +} + +func TestMarkResumeReadyScriptAvoidsVersionSensitiveTTLCommands(t *testing.T) { + t.Parallel() + assert.NotContains(t, markResumeReadyScript, "KEEPTTL") + assert.NotContains(t, markResumeReadyScript, "PTTL") + assert.Contains(t, markResumeReadyScript, "'EX', ARGV[1]") +} diff --git a/idl/thrift/coze/loop/evaluation/coze.loop.evaluation.spi.thrift b/idl/thrift/coze/loop/evaluation/coze.loop.evaluation.spi.thrift index 5bf8877c8e..712c928051 100644 --- a/idl/thrift/coze/loop/evaluation/coze.loop.evaluation.spi.thrift +++ b/idl/thrift/coze/loop/evaluation/coze.loop.evaluation.spi.thrift @@ -189,6 +189,20 @@ struct InvokeEvaluatorResponse { 255: base.BaseResp BaseResp } +// asynchronously invoke custom evaluator request +struct AsyncInvokeEvaluatorRequest { + 1: optional i64 workspace_id (api.js_conv="true") + 2: optional i64 invoke_id (api.js_conv="true") // execution id, report result with the same id + 3: optional InvokeCustomEvaluator evaluator + 4: optional InvokeEvaluatorInputData input_data + + 255: optional base.Base Base +} + +struct AsyncInvokeEvaluatorResponse { + 255: base.BaseResp BaseResp +} + service EvaluationSPIService { SearchEvalTargetResponse SearchEvalTarget(1: SearchEvalTargetRequest req) // 搜索评测对象 InvokeEvalTargetResponse InvokeEvalTarget(1: InvokeEvalTargetRequest req) // 执行 @@ -196,4 +210,5 @@ service EvaluationSPIService { // invoke custom evaluator InvokeEvaluatorResponse InvokeEvaluator(1: InvokeEvaluatorRequest req) + AsyncInvokeEvaluatorResponse AsyncInvokeEvaluator(1: AsyncInvokeEvaluatorRequest req) } diff --git a/idl/thrift/coze/loop/evaluation/domain/evaluator.thrift b/idl/thrift/coze/loop/evaluation/domain/evaluator.thrift index 3d05afac07..5bd355e19f 100644 --- a/idl/thrift/coze/loop/evaluation/domain/evaluator.thrift +++ b/idl/thrift/coze/loop/evaluation/domain/evaluator.thrift @@ -123,10 +123,12 @@ struct CustomRPCEvaluator { 3: optional string service_name 4: optional string cluster 5: optional EvaluatorHTTPInfo invoke_http_info // 执行http信息 + 6: optional EvaluatorHTTPInfo async_invoke_http_info // 异步执行http信息 10: optional i64 timeout // ms 11: optional common.RateLimit rate_limit // 自定义评估器的限流配置 12: optional map ext // extra fields + 13: optional bool is_async // 是否异步执行;true 时调用 AsyncInvokeEvaluator } struct EvaluatorVersion { @@ -323,4 +325,4 @@ struct EvaluatorProgressMessage { 2: optional string type // 如 tool_use, tool_result 3: optional string message // 如 Check current user identity and working directory 4: optional i64 created_at_ms -} \ No newline at end of file +} diff --git a/idl/thrift/coze/loop/evaluation/domain_openapi/evaluator.thrift b/idl/thrift/coze/loop/evaluation/domain_openapi/evaluator.thrift index c1b877589a..6c16dc57a9 100644 --- a/idl/thrift/coze/loop/evaluation/domain_openapi/evaluator.thrift +++ b/idl/thrift/coze/loop/evaluation/domain_openapi/evaluator.thrift @@ -56,10 +56,12 @@ struct CustomRPCEvaluator { 3: optional string service_name 4: optional string cluster 5: optional EvaluatorHTTPInfo invoke_http_info + 6: optional EvaluatorHTTPInfo async_invoke_http_info 10: optional i64 timeout // ms 11: optional common.RateLimit rate_limit 12: optional map ext + 13: optional bool is_async } // Agent评估器Prompt配置输出规则