toolsrv: typed RateLimitedError, agent loop: don't count rate-limited calls as consecutive errors
This commit is contained in:
parent
5c27a948bd
commit
7a69f14277
|
|
@ -339,7 +339,12 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
|
||||||
emit(cfg, Event{Role: "tool", Name: tc.Name, Content: result})
|
emit(cfg, Event{Role: "tool", Name: tc.Name, Content: result})
|
||||||
return toolResult{ToolCallID: tc.ID, Name: tc.Name, Content: result, IsError: true}, true
|
return toolResult{ToolCallID: tc.ID, Name: tc.Name, Content: result, IsError: true}, true
|
||||||
}
|
}
|
||||||
result = fmt.Sprintf("error: %v", err)
|
var rlErr *toolsrv.RateLimitedError
|
||||||
|
if errors.As(err, &rlErr) {
|
||||||
|
result = fmt.Sprintf("error: shell is rate-limited — blocked for %v. Do not call shell() again until the block expires. Use other tools or wait.", rlErr.Remaining)
|
||||||
|
} else {
|
||||||
|
result = fmt.Sprintf("error: %v", err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result = out
|
result = out
|
||||||
resultBlocks = blocks
|
resultBlocks = blocks
|
||||||
|
|
@ -516,14 +521,24 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track consecutive rounds where every tool call errored.
|
// Track consecutive rounds where every tool call errored.
|
||||||
|
// Rate-limited errors don't count — they're a temporary block,
|
||||||
|
// not a real failure. Reset consecutiveErrors so the hard abort
|
||||||
|
// doesn't fire.
|
||||||
allErrors := len(results) > 0
|
allErrors := len(results) > 0
|
||||||
|
allRateLimited := allErrors
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if !r.IsError {
|
if !r.IsError {
|
||||||
allErrors = false
|
allErrors = false
|
||||||
|
allRateLimited = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if !strings.HasPrefix(r.Content, "error: shell is rate-limited") {
|
||||||
|
allRateLimited = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if allErrors {
|
if allRateLimited {
|
||||||
|
consecutiveErrors = 0
|
||||||
|
} else if allErrors {
|
||||||
consecutiveErrors++
|
consecutiveErrors++
|
||||||
} else {
|
} else {
|
||||||
consecutiveErrors = 0
|
consecutiveErrors = 0
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,18 @@ const (
|
||||||
blockDuration = 5 * time.Minute
|
blockDuration = 5 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RateLimitedError is returned by checkRateLimit when the shell is blocked
|
||||||
|
// due to too many validation failures. The agent loop uses this to detect
|
||||||
|
// the condition and inject a clear signal rather than counting it as a
|
||||||
|
// normal tool error.
|
||||||
|
type RateLimitedError struct {
|
||||||
|
Remaining time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RateLimitedError) Error() string {
|
||||||
|
return fmt.Sprintf("rate limited: too many validation failures, blocked for %v", e.Remaining)
|
||||||
|
}
|
||||||
|
|
||||||
// Server runs code in a sandboxed environment.
|
// Server runs code in a sandboxed environment.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
// cwd is the working directory for sandboxed commands. If empty,
|
// cwd is the working directory for sandboxed commands. If empty,
|
||||||
|
|
|
||||||
|
|
@ -383,7 +383,7 @@ func (e *Server) checkRateLimit() error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.Before(e.blockedUntil) {
|
if now.Before(e.blockedUntil) {
|
||||||
remaining := e.blockedUntil.Sub(now).Round(time.Second)
|
remaining := e.blockedUntil.Sub(now).Round(time.Second)
|
||||||
return fmt.Errorf("rate limited: too many validation failures, blocked for %v", remaining)
|
return &RateLimitedError{Remaining: remaining}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue