Add files via upload

This commit is contained in:
公明
2026-07-20 17:38:38 +08:00
committed by GitHub
parent 9bafc2ab98
commit c50de7770b
3 changed files with 127 additions and 1 deletions
+9 -1
View File
@@ -588,11 +588,14 @@ func runEinoADKAgentLoop(ctx context.Context, args *einoADKRunLoopArgs, baseMsgs
zap.Duration("backoff", backoff))
}
if progress != nil {
progress("eino_run_retry", fmt.Sprintf("遇到临时错误(限流或网络波动),%d 秒后第 %d/%d 次重试…", int(backoff.Seconds()), attemptNo, maxAttempts), map[string]interface{}{
errorKind, errorSummary := einoTransientRunErrorUserDetail(runErr)
progress("eino_run_retry", fmt.Sprintf("遇到临时错误,%d 秒后第 %d/%d 次重试。原因:%s", int(backoff.Seconds()), attemptNo, maxAttempts, errorSummary), map[string]interface{}{
"conversationId": conversationID,
"source": "eino",
"orchestration": orchMode,
"error": runErr.Error(),
"errorKind": errorKind,
"errorSummary": errorSummary,
"attempt": attemptNo,
"maxAttempts": maxAttempts,
"backoffSec": int(backoff.Seconds()),
@@ -601,7 +604,12 @@ func runEinoADKAgentLoop(ctx context.Context, args *einoADKRunLoopArgs, baseMsgs
"conversationId": conversationID,
"source": "eino",
"orchestration": orchMode,
"error": runErr.Error(),
"errorKind": errorKind,
"errorSummary": errorSummary,
"attempt": attemptNo,
"maxAttempts": maxAttempts,
"backoffSec": int(backoff.Seconds()),
"contextSource": string(ctxSource),
})
}
@@ -94,6 +94,84 @@ func isRetryableHTTPStatus(status int) bool {
}
}
func einoTransientRunErrorUserDetail(err error) (kind, summary string) {
if err == nil {
return "", ""
}
msg := strings.TrimSpace(err.Error())
lower := strings.ToLower(msg)
if status := httpStatusFromErrorText(lower); status > 0 {
switch {
case status == 429:
kind = "rate_limit"
case status == 408 || status == 409 || status == 425:
kind = "retryable_http"
case status >= 500 && status <= 599:
kind = "upstream_server"
default:
kind = "http_error"
}
} else {
var apiErr *einoopenai.APIError
if errors.As(err, &apiErr) && apiErr.HTTPStatusCode > 0 {
switch {
case apiErr.HTTPStatusCode == 429:
kind = "rate_limit"
case apiErr.HTTPStatusCode == 408 || apiErr.HTTPStatusCode == 409 || apiErr.HTTPStatusCode == 425:
kind = "retryable_http"
case apiErr.HTTPStatusCode >= 500 && apiErr.HTTPStatusCode <= 599:
kind = "upstream_server"
default:
kind = "http_error"
}
}
}
if kind == "" {
switch {
case strings.Contains(lower, "too many requests") ||
strings.Contains(lower, "rate limit") ||
strings.Contains(lower, "rate_limit") ||
strings.Contains(lower, "ratelimit"):
kind = "rate_limit"
case strings.Contains(lower, "overloaded") ||
strings.Contains(lower, "capacity") ||
strings.Contains(lower, "temporarily unavailable") ||
strings.Contains(lower, "service unavailable"):
kind = "upstream_busy"
case strings.Contains(lower, "connection reset") ||
strings.Contains(lower, "connection refused") ||
strings.Contains(lower, "connection closed") ||
strings.Contains(lower, "i/o timeout") ||
strings.Contains(lower, "no such host") ||
strings.Contains(lower, "network is unreachable") ||
strings.Contains(lower, "broken pipe") ||
strings.Contains(lower, "read tcp") ||
strings.Contains(lower, "write tcp") ||
strings.Contains(lower, "dial tcp") ||
strings.Contains(lower, "tls handshake timeout") ||
strings.Contains(lower, "goaway") ||
strings.Contains(lower, "unexpected eof"):
kind = "network"
case strings.Contains(lower, "stream error") ||
strings.Contains(lower, "unexpected end of json"):
kind = "stream"
default:
kind = "transient"
}
}
return kind, einoTrimRetryErrorSummary(msg)
}
func einoTrimRetryErrorSummary(msg string) string {
msg = strings.Join(strings.Fields(strings.TrimSpace(msg)), " ")
const maxRunes = 500
runes := []rune(msg)
if len(runes) <= maxRunes {
return msg
}
return string(runes[:maxRunes]) + "..."
}
func httpStatusFromErrorText(msg string) int {
match := httpStatusInErrorPattern.FindStringSubmatch(msg)
if len(match) != 2 {
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"strings"
"testing"
"time"
@@ -62,6 +63,45 @@ func TestEinoTransientRetryBackoff(t *testing.T) {
}
}
func TestEinoTransientRunErrorUserDetail(t *testing.T) {
t.Parallel()
cases := []struct {
name string
err error
wantKind string
}{
{"rate limit", errors.New("HTTP 429 Too Many Requests"), "rate_limit"},
{"upstream", errors.New("upstream returned 503"), "upstream_server"},
{"network", errors.New("read tcp: connection reset by peer"), "network"},
{"stream", errors.New("unexpected end of JSON"), "stream"},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
kind, summary := einoTransientRunErrorUserDetail(tc.err)
if kind != tc.wantKind {
t.Fatalf("kind=%q, want %q", kind, tc.wantKind)
}
if summary == "" {
t.Fatal("summary should not be empty")
}
})
}
}
func TestEinoTrimRetryErrorSummary(t *testing.T) {
t.Parallel()
raw := strings.Repeat("报错 ", 260)
got := einoTrimRetryErrorSummary(raw)
if len([]rune(got)) > 503 {
t.Fatalf("summary too long: %d runes", len([]rune(got)))
}
if !strings.HasSuffix(got, "...") {
t.Fatal("trimmed summary should end with ellipsis")
}
}
func TestEinoMessagesForRunRestart(t *testing.T) {
t.Parallel()
base := []adk.Message{schema.UserMessage("hi")}