easyai-ai-gateway/apps/api/internal/clients/simulation.go

120 lines
3.7 KiB
Go

package clients
import (
"context"
"fmt"
"strings"
)
type SimulationClient struct{}
func (c SimulationClient) Run(ctx context.Context, request Request) (Response, error) {
_ = ctx
profile := simulationProfile(request)
if profile == "retryable_failure" {
return Response{}, &ClientError{Code: "server_error", Message: "simulated retryable failure", 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{
Result: simulatedResult(request),
Usage: simulatedUsage(request),
Progress: simulatedProgress(request),
}, nil
}
func simulationProfile(request Request) string {
if value := stringValue(request.Candidate.Credentials, "simulationFailure"); value != "" {
return value
}
if value := stringValue(request.Candidate.PlatformConfig, "simulationFailure"); value != "" {
return value
}
if value := stringValue(request.Body, "simulationProfile"); value != "" {
return value
}
if value := stringValue(request.Body, "testProfile"); value != "" {
return value
}
return "success"
}
func simulatedResult(request Request) map[string]any {
switch request.Kind {
case "chat.completions":
return map[string]any{
"id": "chatcmpl-simulated",
"object": "chat.completion",
"created": nowUnix(),
"model": request.Model,
"choices": []any{map[string]any{
"index": 0,
"finish_reason": "stop",
"message": map[string]any{
"role": "assistant",
"content": fmt.Sprintf("simulation response from %s", request.Candidate.Provider),
},
}},
"usage": map[string]any{"prompt_tokens": 12, "completion_tokens": 8, "total_tokens": 20},
}
case "responses":
return map[string]any{
"id": "resp-simulated",
"object": "response",
"created_at": nowUnix(),
"model": request.Model,
"output_text": fmt.Sprintf("simulation response from %s", request.Candidate.Provider),
"usage": map[string]any{"input_tokens": 12, "output_tokens": 8, "total_tokens": 20},
}
case "images.edits":
return 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"),
}},
}
default:
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"),
}},
}
}
}
func simulatedUsage(request Request) Usage {
if request.ModelType == "chat" || request.Kind == "responses" {
return Usage{InputTokens: 12, OutputTokens: 8, TotalTokens: 20}
}
return Usage{}
}
func simulatedProgress(request Request) []Progress {
provider := request.Candidate.Provider
if provider == "" {
provider = "simulation"
}
return []Progress{
{Phase: "normalizing", Progress: 0.2, Message: "request normalized", Payload: map[string]any{"provider": provider}},
{Phase: "submitting", Progress: 0.55, Message: "simulation client submitted", Payload: map[string]any{"clientId": request.Candidate.ClientID}},
{Phase: "fetching_result", Progress: 0.85, Message: "simulation result ready", Payload: map[string]any{"kind": request.Kind}},
}
}
func firstNonEmptyPrompt(body map[string]any, fallback string) string {
for _, key := range []string{"prompt", "input"} {
if value := strings.TrimSpace(stringValue(body, key)); value != "" {
return value
}
}
return fallback
}