feat(piagent): fall back to concurrent single-image requests when count unsupported
Some image endpoints reject the `n` count parameter (Unknown parameter tools[0].n / param: n). Detect that error and re-issue the batch as concurrent count=1 requests, each with a suffixed idempotency key, then assemble the URLs into a single GeneratedImage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -215,9 +216,83 @@ func (c *ImageClient) Generate(ctx context.Context, prompt string, opts ImageReq
|
||||
break
|
||||
}
|
||||
}
|
||||
if count > 1 && isImageGenerationCountFallbackError(lastErr) {
|
||||
logx.Errorf(
|
||||
"gpt image generation count fallback model=%s size=%s count=%d prompt_runes=%d elapsed=%s idempotency_key=%s err=%v",
|
||||
model,
|
||||
size,
|
||||
count,
|
||||
promptRunes,
|
||||
time.Since(startedAt).Round(time.Millisecond),
|
||||
idempotencyKey,
|
||||
lastErr,
|
||||
)
|
||||
return c.generateImageCountFallback(ctx, prompt, model, size, count, images, idempotencyKey)
|
||||
}
|
||||
return GeneratedImage{}, lastErr
|
||||
}
|
||||
|
||||
func (c *ImageClient) generateImageCountFallback(ctx context.Context, prompt string, model string, size string, count int, images []string, idempotencyKey string) (GeneratedImage, error) {
|
||||
fallbackCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
urls := make([]string, count)
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var firstErr error
|
||||
setErr := func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
for index := 0; index < count; index++ {
|
||||
index := index
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
fallbackOpts := ImageRequestOptions{
|
||||
Model: model,
|
||||
Size: size,
|
||||
Count: 1,
|
||||
Images: append([]string(nil), images...),
|
||||
}
|
||||
if idempotencyKey != "" {
|
||||
fallbackOpts.IdempotencyKey = fmt.Sprintf("%s-%d", idempotencyKey, index+1)
|
||||
}
|
||||
image, err := c.Generate(fallbackCtx, prompt, fallbackOpts)
|
||||
if err != nil {
|
||||
setErr(err)
|
||||
return
|
||||
}
|
||||
url := image.URL
|
||||
if url == "" && len(image.URLs) > 0 {
|
||||
url = image.URLs[0]
|
||||
}
|
||||
if url == "" {
|
||||
setErr(fmt.Errorf("image generation count fallback returned empty image"))
|
||||
return
|
||||
}
|
||||
urls[index] = url
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
mu.Lock()
|
||||
err := firstErr
|
||||
mu.Unlock()
|
||||
if err != nil {
|
||||
return GeneratedImage{}, err
|
||||
}
|
||||
return GeneratedImage{URL: urls[0], URLs: urls}, nil
|
||||
}
|
||||
|
||||
func (c *ImageClient) imageRequestBody(ctx context.Context, model string, prompt string, size string, count int, images []string, stream bool) ([]byte, string, error) {
|
||||
if len(images) > 0 {
|
||||
if c.inputImageTransport == "url" && imageInputsCanUseURLTransport(images) {
|
||||
@@ -787,6 +862,21 @@ func isImageGenerationStreamFallbackError(err error) bool {
|
||||
strings.Contains(message, "text/event-stream")
|
||||
}
|
||||
|
||||
func isImageGenerationCountFallbackError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
message := strings.ToLower(err.Error())
|
||||
if !strings.Contains(message, "unknown parameter") {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(message, "tools[0].n") ||
|
||||
strings.Contains(message, "param:n") ||
|
||||
strings.Contains(message, "param: n") ||
|
||||
strings.Contains(message, "parameter: 'n'") ||
|
||||
strings.Contains(message, `parameter: "n"`)
|
||||
}
|
||||
|
||||
func shouldFallbackImageGenerationStream(err error, idempotencyKey string) bool {
|
||||
if isImageGenerationStreamFallbackError(err) {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user