feat(volces): 接入任务兼容查询与上游取消
This commit is contained in:
@@ -526,6 +526,20 @@ candidatesLoop:
|
||||
candidateBody := preprocessing.Body
|
||||
candidatePricing := pricingByCandidate[pricingCandidateKey(candidate)]
|
||||
response, err := s.runCandidate(ctx, task, user, candidateBody, preprocessing.Log, candidate, candidatePricing, nextAttemptNo, onDelta, responseExecution, singleSourceProtected, runnerPolicy.CacheAffinityPolicy, cacheAffinityKeys.Record)
|
||||
if err != nil && isVolcesRemoteTaskCancellation(candidate, err) {
|
||||
cancelled, changed, cancelErr := s.store.CancelSubmittedTask(ctx, task.ID, task.ExecutionToken, "任务已由火山引擎取消")
|
||||
if cancelErr != nil {
|
||||
return Result{}, cancelErr
|
||||
}
|
||||
if changed {
|
||||
// CancelSubmittedTask atomically transfers any reservation to the release Outbox.
|
||||
walletReservationFinalized = true
|
||||
if emitErr := s.emit(ctx, task.ID, "task.cancelled", "cancelled", "cancelled", 1, "任务已由火山引擎取消", map[string]any{"taskId": task.ID, "reason": "upstream_cancelled"}, isSimulation(task, candidate)); emitErr != nil {
|
||||
return Result{}, emitErr
|
||||
}
|
||||
return Result{Task: cancelled, Output: cancelled.Result}, nil
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
attemptNo = nextAttemptNo
|
||||
var billings []any
|
||||
@@ -592,6 +606,13 @@ candidatesLoop:
|
||||
ResponseDurationMS: record.ResponseDurationMS,
|
||||
})
|
||||
if finishErr != nil {
|
||||
if errors.Is(finishErr, store.ErrTaskExecutionLeaseLost) {
|
||||
latest, latestErr := s.store.GetTask(ctx, task.ID)
|
||||
if latestErr == nil && latest.Status == "cancelled" {
|
||||
walletReservationFinalized = true
|
||||
return Result{Task: latest, Output: latest.Result}, nil
|
||||
}
|
||||
}
|
||||
return Result{}, finishErr
|
||||
}
|
||||
walletReservationFinalized = true
|
||||
@@ -965,6 +986,12 @@ func (s *Service) runCandidate(ctx context.Context, task store.GatewayTask, user
|
||||
}
|
||||
return s.store.SetTaskRemoteTask(context.WithoutCancel(ctx), task.ID, task.ExecutionToken, attemptID, remoteTaskID, payload)
|
||||
},
|
||||
OnRemoteTaskPolled: func(remoteTaskID string, payload map[string]any) error {
|
||||
if strings.TrimSpace(remoteTaskID) == "" {
|
||||
return nil
|
||||
}
|
||||
return s.store.SetTaskRemoteTask(context.WithoutCancel(ctx), task.ID, task.ExecutionToken, attemptID, remoteTaskID, payload)
|
||||
},
|
||||
Stream: boolFromMap(providerBody, "stream"),
|
||||
StreamDelta: onDelta,
|
||||
UpstreamProtocol: candidate.ResponseProtocol,
|
||||
@@ -1199,12 +1226,19 @@ func (s *Service) failTask(ctx context.Context, taskID string, executionToken st
|
||||
if err != nil {
|
||||
return store.GatewayTask{}, err
|
||||
}
|
||||
if failed.Status == "cancelled" {
|
||||
return failed, nil
|
||||
}
|
||||
if eventErr := s.emit(ctx, taskID, "task.failed", "failed", "failed", 1, message, map[string]any{"code": code, "requestId": requestID, "metrics": metrics}, simulated); eventErr != nil {
|
||||
return store.GatewayTask{}, eventErr
|
||||
}
|
||||
return failed, nil
|
||||
}
|
||||
|
||||
func isVolcesRemoteTaskCancellation(candidate store.RuntimeModelCandidate, err error) bool {
|
||||
return isVolcesCancellationCandidate(candidate) && strings.EqualFold(clients.ErrorCode(err), "volces_task_cancelled")
|
||||
}
|
||||
|
||||
type failedAttemptRecord struct {
|
||||
Task store.GatewayTask
|
||||
Body map[string]any
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/clients"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
"github.com/riverqueue/river/rivertype"
|
||||
)
|
||||
@@ -104,6 +105,61 @@ func (s *Service) CancelTask(ctx context.Context, taskID string, user *auth.User
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CancelVolcesVideoTask extends local queue cancellation with the official
|
||||
// Volces DELETE call once a video task has a persisted remote task id.
|
||||
func (s *Service) CancelVolcesVideoTask(ctx context.Context, task store.GatewayTask, user *auth.User) (TaskCancelResult, error) {
|
||||
local, err := s.CancelTask(ctx, task.ID, user)
|
||||
if err != nil || local.Cancelled || strings.TrimSpace(task.RemoteTaskID) == "" {
|
||||
return local, err
|
||||
}
|
||||
if taskCancelTerminalStatus(task.Status) {
|
||||
return local, nil
|
||||
}
|
||||
var latest store.TaskAttempt
|
||||
for _, attempt := range task.Attempts {
|
||||
if attempt.PlatformModelID != "" && (latest.AttemptNo == 0 || attempt.AttemptNo >= latest.AttemptNo) {
|
||||
latest = attempt
|
||||
}
|
||||
}
|
||||
candidate, found, err := s.store.GetRuntimeModelCandidateForRemoteTask(ctx, latest.PlatformModelID, latest.PlatformID)
|
||||
if err != nil {
|
||||
return TaskCancelResult{}, err
|
||||
}
|
||||
if !found || !isVolcesCancellationCandidate(candidate) {
|
||||
return local, nil
|
||||
}
|
||||
httpClient, err := s.httpClientForCandidate(candidate, false)
|
||||
if err != nil {
|
||||
return TaskCancelResult{}, err
|
||||
}
|
||||
_, _, err = (clients.VolcesClient{HTTPClient: httpClient}).DeleteVideoTask(ctx, clients.Request{
|
||||
Kind: "videos.generations", Candidate: candidate, HTTPClient: httpClient, RemoteTaskID: task.RemoteTaskID,
|
||||
})
|
||||
if err != nil {
|
||||
return TaskCancelResult{}, err
|
||||
}
|
||||
cancelledTask, cancelled, err := s.store.CancelSubmittedTask(ctx, task.ID, task.ExecutionToken, "任务已由火山引擎取消")
|
||||
if err != nil {
|
||||
return TaskCancelResult{}, err
|
||||
}
|
||||
if !cancelled {
|
||||
latestTask, latestErr := s.store.GetTask(ctx, task.ID)
|
||||
if latestErr == nil {
|
||||
return taskCancelUnavailable(latestTask, "任务状态已变化,未覆盖本地最终状态"), nil
|
||||
}
|
||||
return local, nil
|
||||
}
|
||||
if err := s.emit(ctx, cancelledTask.ID, "task.cancelled", "cancelled", "cancelled", 1, "任务已由火山引擎取消", map[string]any{"taskId": cancelledTask.ID, "reason": "upstream_cancel"}, cancelledTask.RunMode == "simulation"); err != nil {
|
||||
return TaskCancelResult{}, err
|
||||
}
|
||||
return TaskCancelResult{TaskID: cancelledTask.ID, Cancelled: true, Cancellable: true, Submitted: true, Message: "任务已由火山引擎取消"}, nil
|
||||
}
|
||||
|
||||
func isVolcesCancellationCandidate(candidate store.RuntimeModelCandidate) bool {
|
||||
provider := strings.ToLower(strings.TrimSpace(candidate.Provider))
|
||||
return provider == "volces" || provider == "volces-openai"
|
||||
}
|
||||
|
||||
func taskCancelUnavailable(task store.GatewayTask, message string) TaskCancelResult {
|
||||
return TaskCancelResult{
|
||||
TaskID: task.ID,
|
||||
|
||||
Reference in New Issue
Block a user