feat: add time limit for StartToGoal

This commit is contained in:
lilong.129
2025-07-04 14:23:09 +08:00
parent e5a8ee107b
commit 147020cbe5
17 changed files with 513 additions and 26 deletions

View File

@@ -23,7 +23,7 @@ func TestGetImageFromBuffer(t *testing.T) {
require.Nil(t, err)
cvResult, err := service.ReadFromBuffer(buf)
assert.Nil(t, err)
fmt.Println(fmt.Sprintf("cvResult: %v", cvResult))
fmt.Printf("cvResult: %v\n", cvResult)
}
func TestGetImageFromPath(t *testing.T) {
@@ -32,5 +32,5 @@ func TestGetImageFromPath(t *testing.T) {
require.Nil(t, err)
cvResult, err := service.ReadFromPath(imagePath)
assert.Nil(t, err)
fmt.Println(fmt.Sprintf("cvResult: %v", cvResult))
fmt.Printf("cvResult: %v\n", cvResult)
}

View File

@@ -21,18 +21,24 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
if options.MaxRetryTimes > 0 {
logger = logger.Int("max_retry_times", options.MaxRetryTimes)
}
if options.Timeout > 0 {
logger = logger.Int("timeout_seconds", options.Timeout)
}
logger.Msg("StartToGoal")
// Create timeout context for entire StartToGoal process if Timeout is specified
if options.Timeout > 0 {
// Handle TimeLimit and Timeout with unified context mechanism
var isTimeLimitMode bool
if options.TimeLimit > 0 {
// TimeLimit takes precedence over Timeout
logger = logger.Int("time_limit_seconds", options.TimeLimit)
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(options.TimeLimit)*time.Second)
defer cancel()
isTimeLimitMode = true
} else if options.Timeout > 0 {
// Use Timeout only if TimeLimit is not set
logger = logger.Int("timeout_seconds", options.Timeout)
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(options.Timeout)*time.Second)
defer cancel()
log.Info().Int("timeout_seconds", options.Timeout).Msg("StartToGoal timeout configured for entire process")
}
logger.Msg("StartToGoal")
var allPlannings []*PlanningExecutionResult
var attempt int
@@ -40,16 +46,26 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
attempt++
log.Info().Int("attempt", attempt).Msg("planning attempt")
// Check for context cancellation (interrupt signal or timeout)
// Check for context cancellation (timeout, time limit, or interrupt)
select {
case <-ctx.Done():
cause := context.Cause(ctx)
// Handle TimeLimit timeout - return success
if isTimeLimitMode && errors.Is(cause, context.DeadlineExceeded) {
log.Info().
Int("attempt", attempt).
Int("completed_plannings", len(allPlannings)).
Int("time_limit_seconds", options.TimeLimit).
Msg("StartToGoal time limit reached, stopping gracefully")
return allPlannings, nil
}
// Handle other cancellations (Timeout, interrupt, external cancellation) - return error
log.Warn().
Int("attempt", attempt).
Int("completed_plannings", len(allPlannings)).
Err(cause).
Msg("StartToGoal cancelled")
// Return the specific error type based on the cancellation cause
return allPlannings, errors.Wrap(cause, "StartToGoal cancelled")
default:
}
@@ -99,10 +115,25 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
// Invoke tool calls
for _, toolCall := range planningResult.ToolCalls {
// Check for context cancellation before each action
// Check for context cancellation (timeout, time limit, or interrupt) before each action
select {
case <-ctx.Done():
cause := context.Cause(ctx)
// Handle TimeLimit timeout - return success
if isTimeLimitMode && errors.Is(cause, context.DeadlineExceeded) {
log.Info().
Int("attempt", attempt).
Int("completed_plannings", len(allPlannings)).
Int("completed_tool_calls", len(planningResult.SubActions)).
Int("total_tool_calls", len(planningResult.ToolCalls)).
Int("time_limit_seconds", options.TimeLimit).
Msg("StartToGoal time limit reached during tool call execution, stopping gracefully")
planningResult.Elapsed = time.Since(planningStartTime).Milliseconds()
allPlannings = append(allPlannings, planningResult)
return allPlannings, nil
}
// Handle other cancellations (Timeout, external cancellation) - return error
log.Warn().
Int("attempt", attempt).
Int("completed_plannings", len(allPlannings)).
@@ -112,7 +143,6 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
Msg("invokeToolCalls cancelled")
planningResult.Elapsed = time.Since(planningStartTime).Milliseconds()
allPlannings = append(allPlannings, planningResult)
// Return the specific error type based on the cancellation cause
return allPlannings, errors.Wrap(cause, "invokeToolCalls cancelled")
default:
}

View File

@@ -201,6 +201,7 @@ type ActionOptions struct {
Steps int `json:"steps,omitempty" yaml:"steps,omitempty" desc:"Number of steps for action"`
Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty" desc:"Direction for swipe operations or custom coordinates"`
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty" desc:"Timeout in seconds for action execution"`
TimeLimit int `json:"time_limit,omitempty" yaml:"time_limit,omitempty" desc:"Time limit in seconds for action execution, stops gracefully when reached"`
Frequency int `json:"frequency,omitempty" yaml:"frequency,omitempty" desc:"Action frequency"`
ScreenOptions
@@ -281,6 +282,9 @@ func (o *ActionOptions) Options() []ActionOption {
if o.Timeout != 0 {
options = append(options, WithTimeout(o.Timeout))
}
if o.TimeLimit != 0 {
options = append(options, WithTimeLimit(o.TimeLimit))
}
if o.Frequency != 0 {
options = append(options, WithFrequency(o.Frequency))
}
@@ -562,6 +566,12 @@ func WithTimeout(seconds int) ActionOption {
}
}
func WithTimeLimit(seconds int) ActionOption {
return func(o *ActionOptions) {
o.TimeLimit = seconds
}
}
func WithIgnoreNotFoundError(ignoreError bool) ActionOption {
return func(o *ActionOptions) {
o.IgnoreNotFoundError = ignoreError