mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-29 14:10:50 +02:00
181 lines
4.6 KiB
Go
181 lines
4.6 KiB
Go
package multiagent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/cloudwego/eino/adk"
|
|
)
|
|
|
|
type einoRunErrorHandler struct {
|
|
conversationID string
|
|
orchMode string
|
|
progress func(eventType, message string, data interface{})
|
|
pending *einoPendingToolCalls
|
|
nativeCancelFallback func() error
|
|
}
|
|
|
|
type einoRunErrorHandlerConfig struct {
|
|
ConversationID string
|
|
OrchMode string
|
|
Progress func(eventType, message string, data interface{})
|
|
Pending *einoPendingToolCalls
|
|
NativeCancelFallback func() error
|
|
}
|
|
|
|
func newEinoRunErrorHandler(cfg einoRunErrorHandlerConfig) *einoRunErrorHandler {
|
|
return &einoRunErrorHandler{
|
|
conversationID: cfg.ConversationID,
|
|
orchMode: cfg.OrchMode,
|
|
progress: cfg.Progress,
|
|
pending: cfg.Pending,
|
|
nativeCancelFallback: cfg.NativeCancelFallback,
|
|
}
|
|
}
|
|
|
|
func (h *einoRunErrorHandler) Handle(runErr error) error {
|
|
if h == nil || runErr == nil {
|
|
return runErr
|
|
}
|
|
var cancelErr *adk.CancelError
|
|
if errors.As(runErr, &cancelErr) {
|
|
h.flushPending(runErr)
|
|
if h.nativeCancelFallback != nil {
|
|
return h.nativeCancelFallback()
|
|
}
|
|
return context.Canceled
|
|
}
|
|
if errors.Is(runErr, context.DeadlineExceeded) {
|
|
h.flushPending(runErr)
|
|
h.emitError(runErr, "timeout")
|
|
return runErr
|
|
}
|
|
if errors.Is(runErr, context.Canceled) {
|
|
h.flushPending(runErr)
|
|
h.emitError(runErr, "")
|
|
return runErr
|
|
}
|
|
if isEinoIterationLimitError(runErr) {
|
|
h.flushPending(runErr)
|
|
if h.progress != nil {
|
|
h.progress("iteration_limit_reached", runErr.Error(), map[string]interface{}{
|
|
"conversationId": h.conversationID,
|
|
"source": "eino",
|
|
"orchestration": h.orchMode,
|
|
})
|
|
}
|
|
h.emitError(runErr, "iteration_limit")
|
|
return runErr
|
|
}
|
|
h.flushPending(runErr)
|
|
h.emitError(runErr, "")
|
|
return runErr
|
|
}
|
|
|
|
func (h *einoRunErrorHandler) flushPending(err error) {
|
|
if h != nil && h.pending != nil {
|
|
h.pending.FlushAsFailed(err)
|
|
}
|
|
}
|
|
|
|
func (h *einoRunErrorHandler) emitError(err error, kind string) {
|
|
if h == nil || h.progress == nil || err == nil {
|
|
return
|
|
}
|
|
userErr := einoUserFacingRunError(err)
|
|
data := map[string]interface{}{
|
|
"conversationId": h.conversationID,
|
|
"source": "eino",
|
|
"error": err.Error(),
|
|
}
|
|
if kind != "" {
|
|
data["errorKind"] = kind
|
|
} else if userErr.kind != "" {
|
|
data["errorKind"] = userErr.kind
|
|
}
|
|
if userErr.summary != "" {
|
|
data["errorSummary"] = userErr.summary
|
|
}
|
|
if userErr.retryExhausted {
|
|
data["retryExhausted"] = true
|
|
if userErr.totalRetries > 0 {
|
|
data["totalRetries"] = userErr.totalRetries
|
|
}
|
|
}
|
|
if userErr.rawLastError != "" {
|
|
data["lastError"] = userErr.rawLastError
|
|
}
|
|
message := err.Error()
|
|
if userErr.message != "" {
|
|
message = userErr.message
|
|
}
|
|
h.progress("error", message, data)
|
|
}
|
|
|
|
type einoRunUserError struct {
|
|
message string
|
|
kind string
|
|
summary string
|
|
rawLastError string
|
|
retryExhausted bool
|
|
totalRetries int
|
|
}
|
|
|
|
func einoUserFacingRunError(err error) einoRunUserError {
|
|
var out einoRunUserError
|
|
if err == nil {
|
|
return out
|
|
}
|
|
var retryErr *adk.RetryExhaustedError
|
|
if !errors.As(err, &retryErr) {
|
|
return out
|
|
}
|
|
out.retryExhausted = true
|
|
out.totalRetries = retryErr.TotalRetries
|
|
lastErr := retryErr.LastErr
|
|
if lastErr == nil {
|
|
out.kind = "model_retry_exhausted"
|
|
out.summary = "模型调用多次重试后仍未成功。"
|
|
out.message = out.summary
|
|
return out
|
|
}
|
|
out.rawLastError = strings.TrimSpace(lastErr.Error())
|
|
if isEinoShouldRetryOutputRejected(lastErr) {
|
|
out.kind = "empty_model_output"
|
|
out.summary = "模型输出被重试策略拒绝;常见原因是空内容或缺少有效输出。"
|
|
out.message = formatEinoRetryExhaustedMessage(out.rawLastError, retryErr.TotalRetries)
|
|
return out
|
|
}
|
|
kind, summary := einoTransientRunErrorUserDetail(lastErr)
|
|
if strings.TrimSpace(summary) == "" {
|
|
summary = einoTrimRetryErrorSummary(lastErr.Error())
|
|
}
|
|
if kind == "" {
|
|
kind = "model_retry_exhausted"
|
|
}
|
|
out.kind = kind
|
|
out.summary = summary
|
|
out.message = formatEinoRetryExhaustedMessage(summary, retryErr.TotalRetries)
|
|
return out
|
|
}
|
|
|
|
func isEinoShouldRetryOutputRejected(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
return strings.Contains(strings.ToLower(err.Error()), "model output rejected by shouldretry")
|
|
}
|
|
|
|
func formatEinoRetryExhaustedMessage(summary string, totalRetries int) string {
|
|
summary = strings.TrimSpace(summary)
|
|
if summary == "" {
|
|
summary = "模型调用多次重试后仍未成功。"
|
|
}
|
|
if totalRetries > 0 {
|
|
return fmt.Sprintf("模型调用重试已耗尽(已重试 %d 次):%s", totalRetries, summary)
|
|
}
|
|
return "模型调用重试已耗尽:" + summary
|
|
}
|