feat: improve simulation media tasks

This commit is contained in:
2026-05-11 00:39:19 +08:00
parent d86651ff55
commit ada765d90e
5 changed files with 370 additions and 34 deletions
+210 -14
View File
@@ -3,24 +3,60 @@ package clients
import (
"context"
"fmt"
"math/rand"
"strings"
"time"
)
type SimulationClient struct{}
const (
defaultSimulationTextMinDuration = 800 * time.Millisecond
defaultSimulationTextMaxDuration = 2400 * time.Millisecond
defaultSimulationImageMinDuration = 10 * time.Second
defaultSimulationImageMaxDuration = 30 * time.Second
defaultSimulationVideoMinDuration = 2 * time.Minute
defaultSimulationVideoMaxDuration = 3 * time.Minute
maxSimulationDuration = 10 * time.Minute
)
func (c SimulationClient) Run(ctx context.Context, request Request) (Response, error) {
_ = ctx
profile := simulationProfile(request)
responseStartedAt := time.Now()
duration := simulationDuration(request)
if duration > 0 {
timer := time.NewTimer(duration)
select {
case <-ctx.Done():
timer.Stop()
return Response{}, ctx.Err()
case <-timer.C:
}
}
responseFinishedAt := time.Now()
if profile == "retryable_failure" {
return Response{}, &ClientError{Code: "server_error", Message: "simulated retryable failure", Retryable: true}
return Response{}, &ClientError{
Code: "server_error",
Message: "simulated retryable failure",
RequestID: "simulated-request",
ResponseStartedAt: responseStartedAt,
ResponseFinishedAt: responseFinishedAt,
ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
Retryable: true,
}
}
if profile == "fatal_failure" || profile == "non_retryable_failure" {
return Response{}, &ClientError{Code: "bad_request", Message: "simulated non-retryable failure", Retryable: false}
return Response{}, &ClientError{
Code: "bad_request",
Message: "simulated non-retryable failure",
RequestID: "simulated-request",
ResponseStartedAt: responseStartedAt,
ResponseFinishedAt: responseFinishedAt,
ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
Retryable: false,
}
}
responseStartedAt := time.Now()
result := simulatedResult(request)
responseFinishedAt := time.Now()
return Response{
Result: result,
RequestID: requestIDFromResult(result),
@@ -80,24 +116,73 @@ func simulatedResult(request Request) map[string]any {
"id": "img-edit-simulated",
"created": nowUnix(),
"model": request.Model,
"data": []any{map[string]any{
"url": "/static/simulation/image-edit.png",
"revised_prompt": firstNonEmptyPrompt(request.Body, "simulation image edit"),
}},
"data": simulatedImageData(request, "/static/simulation/image-edit.svg", "simulation image edit"),
}
default:
case "images.generations":
return map[string]any{
"id": "img-simulated",
"created": nowUnix(),
"model": request.Model,
"data": []any{map[string]any{
"url": "/static/simulation/image.png",
"revised_prompt": firstNonEmptyPrompt(request.Body, "simulation image"),
}},
"data": simulatedImageData(request, "/static/simulation/image.svg", "simulation image"),
}
case "videos.generations":
return map[string]any{
"id": "video-simulated",
"created": nowUnix(),
"model": request.Model,
"data": simulatedVideoData(request),
}
default:
modelType := strings.ToLower(request.ModelType)
kind := strings.ToLower(request.Kind)
if strings.Contains(modelType, "video") || strings.Contains(kind, "video") {
return map[string]any{
"id": "video-simulated",
"created": nowUnix(),
"model": request.Model,
"data": simulatedVideoData(request),
}
}
return map[string]any{
"id": "img-simulated",
"created": nowUnix(),
"model": request.Model,
"data": simulatedImageData(request, "/static/simulation/image.svg", "simulation image"),
}
}
}
func simulatedImageData(request Request, url string, fallbackPrompt string) []any {
count := simulatedOutputCount(request.Body)
items := make([]any, 0, count)
for index := 0; index < count; index += 1 {
items = append(items, map[string]any{
"url": url,
"assetSource": "simulation",
"index": index,
"revised_prompt": firstNonEmptyPrompt(request.Body, fallbackPrompt),
})
}
return items
}
func simulatedVideoData(request Request) []any {
count := simulatedOutputCount(request.Body)
items := make([]any, 0, count)
for index := 0; index < count; index += 1 {
items = append(items, map[string]any{
"url": "/static/simulation/video.mp4",
"video_url": "/static/simulation/video.mp4",
"poster": "/static/simulation/video-poster.svg",
"duration": simulatedVideoDurationSeconds(request),
"assetSource": "simulation",
"index": index,
"revised_prompt": firstNonEmptyPrompt(request.Body, "simulation video"),
})
}
return items
}
func simulatedUsage(request Request) Usage {
if request.ModelType == "chat" || request.Kind == "responses" {
return Usage{InputTokens: 12, OutputTokens: 8, TotalTokens: 20}
@@ -117,6 +202,117 @@ func simulatedProgress(request Request) []Progress {
}
}
func simulationDuration(request Request) time.Duration {
if fixedMS := simulationDurationMS(request, "simulationDurationMs", "testDurationMs"); fixedMS >= 0 {
return clampSimulationDuration(time.Duration(fixedMS) * time.Millisecond)
}
if fixedSeconds := simulationDurationSeconds(request, "simulationDurationSeconds", "testDurationSeconds"); fixedSeconds >= 0 {
return clampSimulationDuration(time.Duration(fixedSeconds) * time.Second)
}
minDuration, maxDuration := defaultSimulationDurationRange(request)
if minMS := simulationDurationMS(request, "simulationMinDurationMs", "simulationDurationMinMs", "testMinDurationMs", "testDurationMinMs"); minMS >= 0 {
minDuration = time.Duration(minMS) * time.Millisecond
}
if maxMS := simulationDurationMS(request, "simulationMaxDurationMs", "simulationDurationMaxMs", "testMaxDurationMs", "testDurationMaxMs"); maxMS >= 0 {
maxDuration = time.Duration(maxMS) * time.Millisecond
}
if minSeconds := simulationDurationSeconds(request, "simulationMinDurationSeconds", "simulationDurationMinSeconds", "testMinDurationSeconds", "testDurationMinSeconds"); minSeconds >= 0 {
minDuration = time.Duration(minSeconds) * time.Second
}
if maxSeconds := simulationDurationSeconds(request, "simulationMaxDurationSeconds", "simulationDurationMaxSeconds", "testMaxDurationSeconds", "testDurationMaxSeconds"); maxSeconds >= 0 {
maxDuration = time.Duration(maxSeconds) * time.Second
}
minDuration = clampSimulationDuration(minDuration)
maxDuration = clampSimulationDuration(maxDuration)
if maxDuration < minDuration {
maxDuration = minDuration
}
spread := maxDuration - minDuration
if spread <= 0 {
return minDuration
}
return minDuration + time.Duration(rand.Int63n(int64(spread)+1))
}
func defaultSimulationDurationRange(request Request) (time.Duration, time.Duration) {
if simulationVideoRequest(request) {
return defaultSimulationVideoMinDuration, defaultSimulationVideoMaxDuration
}
if simulationImageRequest(request) {
return defaultSimulationImageMinDuration, defaultSimulationImageMaxDuration
}
return defaultSimulationTextMinDuration, defaultSimulationTextMaxDuration
}
func simulationVideoRequest(request Request) bool {
kind := strings.ToLower(request.Kind)
modelType := strings.ToLower(request.ModelType)
return strings.Contains(kind, "video") || strings.Contains(modelType, "video")
}
func simulationImageRequest(request Request) bool {
kind := strings.ToLower(request.Kind)
modelType := strings.ToLower(request.ModelType)
return strings.Contains(kind, "image") || strings.Contains(modelType, "image")
}
func simulationDurationSeconds(request Request, keys ...string) int {
for _, source := range []map[string]any{request.Body, request.Candidate.PlatformConfig, request.Candidate.Credentials} {
for _, key := range keys {
value := intValue(source, key, -1)
if value >= 0 {
return value
}
}
}
return -1
}
func simulationDurationMS(request Request, keys ...string) int {
for _, source := range []map[string]any{request.Body, request.Candidate.PlatformConfig, request.Candidate.Credentials} {
for _, key := range keys {
value := intValue(source, key, -1)
if value >= 0 {
return value
}
}
}
return -1
}
func clampSimulationDuration(duration time.Duration) time.Duration {
if duration < 0 {
return 0
}
if duration > maxSimulationDuration {
return maxSimulationDuration
}
return duration
}
func simulatedOutputCount(body map[string]any) int {
count := intValue(body, "n", 0)
if count <= 0 {
count = intValue(body, "count", 0)
}
if count <= 0 {
return 1
}
if count > 20 {
return 20
}
return count
}
func simulatedVideoDurationSeconds(request Request) int {
for _, key := range []string{"duration", "duration_seconds", "durationSeconds"} {
if value := intValue(request.Body, key, 0); value > 0 {
return value
}
}
return 5
}
func firstNonEmptyPrompt(body map[string]any, fallback string) string {
for _, key := range []string{"prompt", "input"} {
if value := strings.TrimSpace(stringValue(body, key)); value != "" {