feat: support image quality control
This commit is contained in:
@@ -214,7 +214,7 @@ type ImageGenerationRequest struct {
|
||||
Prompt string `json:"prompt" example:"A watercolor robot reading a book"`
|
||||
N int `json:"n,omitempty" example:"1"`
|
||||
Size string `json:"size,omitempty" example:"1024x1024"`
|
||||
Quality string `json:"quality,omitempty" example:"standard"`
|
||||
Quality string `json:"quality,omitempty" example:"auto"`
|
||||
ResponseFormat string `json:"response_format,omitempty" example:"url"`
|
||||
RunMode string `json:"runMode,omitempty" example:"simulation"`
|
||||
}
|
||||
@@ -226,6 +226,7 @@ type ImageEditRequest struct {
|
||||
Mask string `json:"mask,omitempty" example:"https://example.com/mask.png"`
|
||||
N int `json:"n,omitempty" example:"1"`
|
||||
Size string `json:"size,omitempty" example:"1024x1024"`
|
||||
Quality string `json:"quality,omitempty" example:"auto"`
|
||||
ResponseFormat string `json:"response_format,omitempty" example:"url"`
|
||||
RunMode string `json:"runMode,omitempty" example:"simulation"`
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ func NewParamProcessorChain() ParamProcessorChain {
|
||||
durationProcessor{},
|
||||
audioProcessor{},
|
||||
imageCountProcessor{},
|
||||
imageQualityProcessor{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,3 +378,64 @@ func (imageCountProcessor) Process(params map[string]any, modelType string, cont
|
||||
params["n"] = count
|
||||
return true
|
||||
}
|
||||
|
||||
type imageQualityProcessor struct{}
|
||||
|
||||
func (imageQualityProcessor) Name() string { return "ImageQualityProcessor" }
|
||||
|
||||
var openAICompatibleImageQualities = map[string]struct{}{
|
||||
"low": {},
|
||||
"medium": {},
|
||||
"high": {},
|
||||
"auto": {},
|
||||
}
|
||||
|
||||
func (imageQualityProcessor) ShouldProcess(params map[string]any, modelType string, context *paramProcessContext) bool {
|
||||
if modelType != "image_generate" && modelType != "image_edit" {
|
||||
return false
|
||||
}
|
||||
_, ok := params["quality"]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (imageQualityProcessor) Process(params map[string]any, modelType string, context *paramProcessContext) bool {
|
||||
capability := capabilityForType(context.modelCapability, modelType)
|
||||
quality := stringFromAny(params["quality"])
|
||||
if supportsImageQualityControl(capability) && isOpenAICompatibleImageQuality(quality) {
|
||||
return true
|
||||
}
|
||||
|
||||
before := params["quality"]
|
||||
delete(params, "quality")
|
||||
context.recordChange(
|
||||
"ImageQualityProcessor",
|
||||
"remove",
|
||||
"quality",
|
||||
before,
|
||||
nil,
|
||||
"模型能力未开启生成质量控制,已移除 quality 参数。",
|
||||
capabilityPath(modelType, "support_quality_control"),
|
||||
capabilityValue(context.modelCapability, modelType, "support_quality_control"),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
func supportsImageQualityControl(capability map[string]any) bool {
|
||||
if capability == nil {
|
||||
return false
|
||||
}
|
||||
for _, key := range []string{"support_quality_control", "supportQualityControl", "quality_control", "qualityControl", "quality"} {
|
||||
if boolFromAny(capability[key]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isOpenAICompatibleImageQuality(value string) bool {
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
_, ok := openAICompatibleImageQualities[value]
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -660,3 +660,56 @@ func TestParamProcessorImageResolutionAndOutputCount(t *testing.T) {
|
||||
t.Fatalf("image count should be capped to 4, got %+v", processed["n"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParamProcessorImageQualityControl(t *testing.T) {
|
||||
body := map[string]any{
|
||||
"model": "mock-image",
|
||||
"prompt": "draw",
|
||||
"quality": "high",
|
||||
}
|
||||
|
||||
unsupported := preprocessRequestWithLog("images.generations", body, store.RuntimeModelCandidate{
|
||||
ModelType: "image_generate",
|
||||
Capabilities: map[string]any{
|
||||
"image_generate": map[string]any{
|
||||
"output_resolutions": []any{"1K"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if _, ok := unsupported.Body["quality"]; ok {
|
||||
t.Fatalf("quality should be removed when capability does not support it: %+v", unsupported.Body)
|
||||
}
|
||||
if len(unsupported.Log.Changes) == 0 || unsupported.Log.Changes[len(unsupported.Log.Changes)-1].CapabilityPath != "capabilities.image_generate.support_quality_control" {
|
||||
t.Fatalf("expected quality removal to be logged against support_quality_control, got %+v", unsupported.Log.Changes)
|
||||
}
|
||||
|
||||
supported := preprocessRequest("images.generations", body, store.RuntimeModelCandidate{
|
||||
ModelType: "image_generate",
|
||||
Capabilities: map[string]any{
|
||||
"image_generate": map[string]any{
|
||||
"support_quality_control": true,
|
||||
"output_resolutions": []any{"1K"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if supported["quality"] != "high" {
|
||||
t.Fatalf("quality should be retained when capability supports it: %+v", supported)
|
||||
}
|
||||
|
||||
incompatible := preprocessRequest("images.generations", map[string]any{
|
||||
"model": "mock-image",
|
||||
"prompt": "draw",
|
||||
"quality": "standard",
|
||||
}, store.RuntimeModelCandidate{
|
||||
ModelType: "image_generate",
|
||||
Capabilities: map[string]any{
|
||||
"image_generate": map[string]any{
|
||||
"support_quality_control": true,
|
||||
"output_resolutions": []any{"1K"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if _, ok := incompatible["quality"]; ok {
|
||||
t.Fatalf("OpenAI-compatible GPT image quality should reject standard: %+v", incompatible)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
-- GPT Image 系列支持 OpenAI-compatible quality 参数;其他图像模型默认不声明,
|
||||
-- runner 会在参数预处理时移除未支持模型上的 quality。
|
||||
|
||||
CREATE OR REPLACE FUNCTION pg_temp._tmp_enable_image_quality_control(capabilities jsonb)
|
||||
RETURNS jsonb AS $$
|
||||
DECLARE
|
||||
out jsonb := COALESCE(capabilities, '{}'::jsonb);
|
||||
BEGIN
|
||||
IF out ? 'image_generate' THEN
|
||||
out := jsonb_set(out, '{image_generate,support_quality_control}', 'true'::jsonb, true);
|
||||
END IF;
|
||||
IF out ? 'image_edit' THEN
|
||||
out := jsonb_set(out, '{image_edit,support_quality_control}', 'true'::jsonb, true);
|
||||
END IF;
|
||||
RETURN out;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
UPDATE base_model_catalog
|
||||
SET capabilities = pg_temp._tmp_enable_image_quality_control(capabilities),
|
||||
default_snapshot = CASE
|
||||
WHEN COALESCE(default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN default_snapshot
|
||||
WHEN jsonb_typeof(default_snapshot->'metadata'->'rawModel'->'capabilities') = 'object' THEN jsonb_set(
|
||||
jsonb_set(
|
||||
default_snapshot,
|
||||
'{capabilities}',
|
||||
pg_temp._tmp_enable_image_quality_control(COALESCE(default_snapshot->'capabilities', '{}'::jsonb)),
|
||||
true
|
||||
),
|
||||
'{metadata,rawModel,capabilities}',
|
||||
pg_temp._tmp_enable_image_quality_control(COALESCE(default_snapshot->'metadata'->'rawModel'->'capabilities', '{}'::jsonb)),
|
||||
true
|
||||
)
|
||||
ELSE jsonb_set(
|
||||
default_snapshot,
|
||||
'{capabilities}',
|
||||
pg_temp._tmp_enable_image_quality_control(COALESCE(default_snapshot->'capabilities', '{}'::jsonb)),
|
||||
true
|
||||
)
|
||||
END,
|
||||
metadata = CASE
|
||||
WHEN jsonb_typeof(metadata->'rawModel'->'capabilities') = 'object' THEN jsonb_set(
|
||||
metadata,
|
||||
'{rawModel,capabilities}',
|
||||
pg_temp._tmp_enable_image_quality_control(COALESCE(metadata->'rawModel'->'capabilities', '{}'::jsonb)),
|
||||
true
|
||||
)
|
||||
ELSE metadata
|
||||
END,
|
||||
updated_at = now()
|
||||
WHERE provider_model_name IN ('gpt-image-1', 'gpt-image-1.5', 'gpt-image-2')
|
||||
AND capabilities ?| ARRAY['image_generate', 'image_edit'];
|
||||
|
||||
UPDATE platform_models
|
||||
SET capabilities = pg_temp._tmp_enable_image_quality_control(capabilities),
|
||||
updated_at = now()
|
||||
WHERE COALESCE(NULLIF(provider_model_name, ''), model_name) IN ('gpt-image-1', 'gpt-image-1.5', 'gpt-image-2')
|
||||
AND capabilities ?| ARRAY['image_generate', 'image_edit'];
|
||||
|
||||
DROP FUNCTION pg_temp._tmp_enable_image_quality_control(jsonb);
|
||||
Reference in New Issue
Block a user