feat(gateway): 补齐桌面端高级媒体直连接口
ci / verify (pull_request) Successful in 15m34s

新增图片矢量化、视频超分、每日用量、计价与任务隔离能力,并通过环境变量解析平台凭据。

已通过 Go 全量门禁、迁移检查、镜像构建以及 Vectorizer 五格式和 Topaz 3 秒视频真实 DEV 验收。
This commit is contained in:
2026-07-22 14:02:53 +08:00
parent cbebfd7baa
commit 3056cf8fca
37 changed files with 3336 additions and 29 deletions
@@ -0,0 +1,338 @@
INSERT INTO model_catalog_providers (
provider_key, provider_code, display_name, provider_type, default_base_url,
default_auth_type, source, capability_schema, status, metadata
)
VALUES
('vectorizer', 'vectorizer', 'Vectorizer.AI', 'vectorizer', 'https://api.vectorizer.ai/api/v1',
'Basic', 'gateway', '{"modelTypes":["image_vectorize"]}'::jsonb, 'active', '{"source":"gateway.native"}'::jsonb),
('topaz', 'topaz', 'Topaz Labs', 'topaz', 'https://api.topazlabs.com',
'APIKey', 'gateway', '{"modelTypes":["video_enhance"]}'::jsonb, 'active', '{"source":"gateway.native"}'::jsonb)
ON CONFLICT (provider_key) DO UPDATE
SET provider_code = EXCLUDED.provider_code,
display_name = EXCLUDED.display_name,
provider_type = EXCLUDED.provider_type,
default_base_url = EXCLUDED.default_base_url,
default_auth_type = EXCLUDED.default_auth_type,
capability_schema = EXCLUDED.capability_schema,
updated_at = now();
INSERT INTO model_pricing_rule_sets (
rule_set_key, name, description, category, currency, status, metadata
)
VALUES (
'desktop-advanced-media-v1', '桌面端高级媒体定价',
'图片矢量化按次计费,视频超分按时长、分辨率、帧率、慢动作和模型权重计费。',
'media', 'resource', 'active', '{"source":"gateway.native","version":1}'::jsonb
)
ON CONFLICT (rule_set_key) DO UPDATE
SET name = EXCLUDED.name,
description = EXCLUDED.description,
category = EXCLUDED.category,
currency = EXCLUDED.currency,
status = EXCLUDED.status,
metadata = EXCLUDED.metadata,
updated_at = now();
INSERT INTO model_pricing_rules (
rule_set_id, rule_key, display_name, scope_type, resource_type, unit,
base_price, currency, dynamic_weight, calculator_type, dimension_schema,
formula_config, priority, status, metadata
)
VALUES
(
(SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'desktop-advanced-media-v1'),
'image_vectorize', '图片转矢量', 'model', 'image_vectorize', 'conversion',
200, 'resource', '{}'::jsonb, 'unit_weight',
'{"dimensions":["count"],"defaults":{"count":1}}'::jsonb,
'{"formula":"count * base_price"}'::jsonb, 10, 'active',
'{"operationType":"image_vectorize"}'::jsonb
),
(
(SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'desktop-advanced-media-v1'),
'video_enhance', '视频增强', 'model', 'video_enhance', '5s',
100, 'resource',
'{
"resolutionTransitions": {
"480p->480p":1,"480p->720p":1.4,"480p->1080p":1.8,"480p->1440p":2.1,"480p->2160p":2.6,
"720p->720p":1.4,"720p->1080p":1.8,"720p->1440p":2.1,"720p->2160p":2.6,
"1080p->1080p":1.8,"1080p->1440p":2.1,"1080p->2160p":2.6,
"1440p->1440p":2.1,"1440p->2160p":2.6,"2160p->2160p":2.6
},
"frameRateTransitions": {
"24->24":1,"24->30":1.25,"24->60":2.5,"24->120":5,"24->240":10,
"30->30":1.25,"30->60":2.5,"30->120":5,"30->240":10,
"60->60":2.5,"60->120":5,"60->240":10,"120->120":5,"120->240":10,"240->240":10
},
"modelWeights": {
"easy-proteus-standard-4":1,"easy-starlight-fast-2":1,
"easy-starlight-hq-1":1,"easy-starlight-mini-1":1
},
"markup":1
}'::jsonb,
'transition_matrix',
'{"dimensions":["count","duration_seconds","source_resolution","target_resolution","source_frame_rate","target_frame_rate","slow_motion_rate","model"],"defaults":{"count":1,"duration_seconds":5,"source_resolution":"720p","target_resolution":"1080p","source_frame_rate":24,"target_frame_rate":24,"slow_motion_rate":1}}'::jsonb,
'{"formula":"ceil(count * max(1, round(duration_seconds) / 5) * base_price * resolution_transition_weight * frame_rate_transition_weight * slow_motion_rate * model_weight * markup)"}'::jsonb,
20, 'active', '{"operationType":"video_upscale"}'::jsonb
),
(
(SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'desktop-advanced-media-v1'),
'music_generation', '音乐生成', 'model', 'music', 'song',
20, 'resource', '{}'::jsonb, 'unit_weight',
'{"dimensions":["count"],"defaults":{"count":1}}'::jsonb,
'{"formula":"count * base_price"}'::jsonb,
30, 'active', '{"operationType":"audio_generate"}'::jsonb
),
(
(SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'default-multimodal-v1'),
'music_generation', '音乐生成', 'model', 'music', 'song',
20, 'resource', '{}'::jsonb, 'unit_weight',
'{"dimensions":["count"],"defaults":{"count":1}}'::jsonb,
'{"formula":"count * base_price"}'::jsonb,
60, 'active', '{"operationType":"audio_generate","source":"gateway.catalog.compat"}'::jsonb
)
ON CONFLICT (rule_set_id, rule_key) WHERE rule_set_id IS NOT NULL DO UPDATE
SET display_name = EXCLUDED.display_name,
resource_type = EXCLUDED.resource_type,
unit = EXCLUDED.unit,
base_price = EXCLUDED.base_price,
currency = EXCLUDED.currency,
dynamic_weight = EXCLUDED.dynamic_weight,
calculator_type = EXCLUDED.calculator_type,
dimension_schema = EXCLUDED.dimension_schema,
formula_config = EXCLUDED.formula_config,
priority = EXCLUDED.priority,
status = EXCLUDED.status,
metadata = EXCLUDED.metadata,
updated_at = now();
WITH model_defs(provider_key, canonical_key, provider_model_name, display_name, model_type, capabilities, billing_config, rate_limits, metadata) AS (
VALUES
(
'vectorizer', 'vectorizer:easy-image-vectorizer-1', 'easy-image-vectorizer-1', '图片转矢量 v1',
'["image_vectorize"]'::jsonb,
'{"image_vectorize":{"support_url_input":true,"support_base64_input":false,"input_format_allowed":["png","jpg","jpeg","webp","bmp","gif"],"output_format_allowed":["svg","eps","pdf","dxf","png"],"max_colors_options":[0,2,4,8,16,32],"cleanup_levels":["low","standard","strong"],"async_task":true},"originalTypes":["image_vectorize"]}'::jsonb,
'{"image_vectorize":{"basePrice":200,"baseWeight":1},"currency":"resource"}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":2,"leaseTtlSeconds":180}]}'::jsonb,
'{"source":"gateway.native","sourceSpecType":"vectorizer","alias":"easy-image-vectorizer-1","description":"将位图转换为 SVG、EPS、PDF、DXF 或 PNG。","selectable":true}'::jsonb
),
(
'topaz', 'topaz:easy-proteus-standard-4', 'prob-4', '标准视频超分 v4', '["video_enhance"]'::jsonb,
'{"video_enhance":{"input_video_required":true,"input_resolutions":["480p","720p","1080p","1440p","2160p"],"output_resolutions":["720p","1080p","2k","4k"],"operations":["upscale"],"preserve_audio":true,"async_task":true,"max_file_size_mb":2048},"originalTypes":["video_enhance"]}'::jsonb,
'{"video_enhance":{"basePrice":100,"baseWeight":1},"currency":"resource"}'::jsonb,
'{"rules":[{"metric":"concurrent","limit":2,"leaseTtlSeconds":7200}]}'::jsonb,
'{"source":"gateway.native","sourceSpecType":"topaz","alias":"easy-proteus-standard-4","selectable":true}'::jsonb
),
(
'topaz', 'topaz:easy-starlight-fast-2', 'slf-2', '快速视频超分 v2', '["video_enhance"]'::jsonb,
'{"video_enhance":{"input_video_required":true,"input_resolutions":["480p","720p","1080p","1440p","2160p"],"output_resolutions":["720p","1080p","2k","4k"],"operations":["upscale"],"preserve_audio":true,"async_task":true,"max_file_size_mb":2048},"originalTypes":["video_enhance"]}'::jsonb,
'{"video_enhance":{"basePrice":100,"baseWeight":1},"currency":"resource"}'::jsonb,
'{"rules":[{"metric":"concurrent","limit":2,"leaseTtlSeconds":7200}]}'::jsonb,
'{"source":"gateway.native","sourceSpecType":"topaz","alias":"easy-starlight-fast-2","selectable":true}'::jsonb
),
(
'topaz', 'topaz:easy-starlight-hq-1', 'slhq-1', '高质量视频超分 v1', '["video_enhance"]'::jsonb,
'{"video_enhance":{"input_video_required":true,"input_resolutions":["480p","720p","1080p","1440p","2160p"],"output_resolutions":["720p","1080p","2k","4k"],"operations":["upscale"],"preserve_audio":true,"async_task":true,"max_file_size_mb":2048},"originalTypes":["video_enhance"]}'::jsonb,
'{"video_enhance":{"basePrice":100,"baseWeight":1},"currency":"resource"}'::jsonb,
'{"rules":[{"metric":"concurrent","limit":1,"leaseTtlSeconds":7200}]}'::jsonb,
'{"source":"gateway.native","sourceSpecType":"topaz","alias":"easy-starlight-hq-1","selectable":true}'::jsonb
),
(
'topaz', 'topaz:easy-starlight-mini-1', 'slm-1', '轻量视频修复增强 v1', '["video_enhance"]'::jsonb,
'{"video_enhance":{"input_video_required":true,"input_resolutions":["480p","720p","1080p","1440p","2160p"],"output_resolutions":["720p","1080p","2k","4k"],"operations":["upscale","restore","denoise"],"preserve_audio":true,"async_task":true,"max_file_size_mb":2048},"originalTypes":["video_enhance"]}'::jsonb,
'{"video_enhance":{"basePrice":100,"baseWeight":1},"currency":"resource"}'::jsonb,
'{"rules":[{"metric":"concurrent","limit":1,"leaseTtlSeconds":7200}]}'::jsonb,
'{"source":"gateway.native","sourceSpecType":"topaz","alias":"easy-starlight-mini-1","selectable":true}'::jsonb
)
)
INSERT INTO base_model_catalog (
provider_id, provider_key, canonical_model_key, provider_model_name, model_type, display_name,
capabilities, base_billing_config, default_rate_limit_policy, pricing_rule_set_id,
metadata, catalog_type, status
)
SELECT provider.id, defs.provider_key, defs.canonical_key, defs.provider_model_name, defs.model_type, defs.display_name,
defs.capabilities, defs.billing_config, defs.rate_limits,
(SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'desktop-advanced-media-v1'),
defs.metadata, 'system', 'active'
FROM model_defs defs
JOIN model_catalog_providers provider ON provider.provider_key = defs.provider_key
ON CONFLICT (canonical_model_key) DO UPDATE
SET provider_id = EXCLUDED.provider_id,
provider_key = EXCLUDED.provider_key,
provider_model_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.provider_model_name ELSE base_model_catalog.provider_model_name END,
model_type = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.model_type ELSE base_model_catalog.model_type END,
display_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.display_name ELSE base_model_catalog.display_name END,
capabilities = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.capabilities ELSE base_model_catalog.capabilities END,
base_billing_config = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.base_billing_config ELSE base_model_catalog.base_billing_config END,
default_rate_limit_policy = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.default_rate_limit_policy ELSE base_model_catalog.default_rate_limit_policy END,
pricing_rule_set_id = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.pricing_rule_set_id ELSE base_model_catalog.pricing_rule_set_id END,
metadata = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.metadata ELSE base_model_catalog.metadata END,
status = CASE WHEN base_model_catalog.customized_at IS NULL THEN 'active' ELSE base_model_catalog.status END,
updated_at = now();
INSERT INTO integration_platforms (
provider, platform_key, name, base_url, auth_type, credentials, config,
default_pricing_mode, default_discount_factor, retry_policy, rate_limit_policy,
priority, status, disabled_reason
)
VALUES
(
'vectorizer', 'vectorizer-native', 'Vectorizer.AI Native', 'https://api.vectorizer.ai/api/v1', 'basic', '{}'::jsonb,
'{"sourceSpecType":"vectorizer","credentialEnv":{"accessKey":"VECTORIZER_API_ID","secretKey":"VECTORIZER_API_SECRET"},"mode":"production","retentionDays":"7"}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":2,"leaseTtlSeconds":180}]}'::jsonb,
120, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'topaz', 'topaz-native', 'Topaz Labs Native', 'https://api.topazlabs.com', 'api_key', '{}'::jsonb,
'{"sourceSpecType":"topaz","credentialEnv":{"apiKey":"TOPAZ_API_KEY"},"maxInputBytes":2147483648,"pollIntervalMs":15000,"pollTimeoutMs":3600000}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"concurrent","limit":2,"leaseTtlSeconds":7200}]}'::jsonb,
130, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'minimax', 'minimax-native-dev', 'MiniMax Native DEV', 'https://api.minimaxi.com/v1', 'bearer', '{}'::jsonb,
'{"sourceSpecType":"minimax","credentialEnv":{"apiKey":"MINIMAX_API_KEY"}}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":5,"leaseTtlSeconds":300}]}'::jsonb,
110, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'minimax-openai', 'minimax-openai-native-dev', 'MiniMax OpenAI Native DEV', 'https://api.minimaxi.com/v1', 'bearer', '{}'::jsonb,
'{"sourceSpecType":"openai","credentialEnv":{"apiKey":"MINIMAX_API_KEY"}}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":5,"leaseTtlSeconds":300}]}'::jsonb,
105, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'volces', 'volces-native-dev', 'Volcengine Native DEV', 'https://ark.cn-beijing.volces.com/api/v3', 'bearer', '{}'::jsonb,
'{"sourceSpecType":"volces","credentialEnv":{"apiKey":"VOLCES_API_KEY"}}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":3,"leaseTtlSeconds":600}]}'::jsonb,
100, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'aliyun-bailian-openai', 'aliyun-bailian-openai-native-dev', 'Aliyun Bailian OpenAI Native DEV', 'https://dashscope.aliyuncs.com/compatible-mode/v1', 'bearer', '{}'::jsonb,
'{"sourceSpecType":"openai","credentialEnv":{"apiKey":"ALIYUN_BAILIAN_API_KEY"}}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":5,"leaseTtlSeconds":300}]}'::jsonb,
100, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'aliyun-bailian-openai', 'aliyun-bailian-rerank-native-dev', 'Aliyun Bailian Rerank Native DEV', 'https://dashscope.aliyuncs.com/compatible-api/v1', 'bearer', '{}'::jsonb,
'{"sourceSpecType":"openai","credentialEnv":{"apiKey":"ALIYUN_BAILIAN_API_KEY"}}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":60,"windowSeconds":60},{"metric":"concurrent","limit":5,"leaseTtlSeconds":300}]}'::jsonb,
100, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
),
(
'suno', 'suno-native-dev', 'Suno Native DEV', 'https://api.cqtai.com/api/cqt', 'api_key', '{}'::jsonb,
'{"sourceSpecType":"suno","credentialEnv":{"apiKey":"SUNO_API_KEY"}}'::jsonb,
'inherit_discount', 1, '{"enabled":true,"maxAttempts":2,"retryOn":["rate_limit","timeout","server_error","network"]}'::jsonb,
'{"rules":[{"metric":"rpm","limit":30,"windowSeconds":60},{"metric":"concurrent","limit":2,"leaseTtlSeconds":1200}]}'::jsonb,
100, 'disabled', '需要通过环境变量注入 DEV/部署凭据后显式启用'
)
ON CONFLICT (platform_key) DO UPDATE
SET name = EXCLUDED.name,
base_url = EXCLUDED.base_url,
auth_type = EXCLUDED.auth_type,
credentials = '{}'::jsonb,
config = EXCLUDED.config,
default_pricing_mode = EXCLUDED.default_pricing_mode,
default_discount_factor = EXCLUDED.default_discount_factor,
retry_policy = EXCLUDED.retry_policy,
rate_limit_policy = EXCLUDED.rate_limit_policy,
priority = EXCLUDED.priority,
updated_at = now();
WITH platform_model_defs(platform_key, canonical_key, model_name, provider_model_name, model_alias) AS (
VALUES
('vectorizer-native', 'vectorizer:easy-image-vectorizer-1', 'easy-image-vectorizer-1', 'easy-image-vectorizer-1', 'easy-image-vectorizer-1'),
('topaz-native', 'topaz:easy-proteus-standard-4', 'easy-proteus-standard-4', 'prob-4', 'easy-proteus-standard-4'),
('topaz-native', 'topaz:easy-starlight-fast-2', 'easy-starlight-fast-2', 'slf-2', 'easy-starlight-fast-2'),
('topaz-native', 'topaz:easy-starlight-hq-1', 'easy-starlight-hq-1', 'slhq-1', 'easy-starlight-hq-1'),
('topaz-native', 'topaz:easy-starlight-mini-1', 'easy-starlight-mini-1', 'slm-1', 'easy-starlight-mini-1')
)
INSERT INTO platform_models (
platform_id, base_model_id, model_name, provider_model_name, model_alias, model_type,
display_name, capabilities, pricing_mode, pricing_rule_set_id, billing_config,
retry_policy, rate_limit_policy, enabled
)
SELECT platform.id, base.id, defs.model_name, defs.provider_model_name, defs.model_alias, base.model_type,
base.display_name, base.capabilities, 'inherit_discount', base.pricing_rule_set_id, base.base_billing_config,
'{"enabled":true,"maxAttempts":2}'::jsonb, base.default_rate_limit_policy, true
FROM platform_model_defs defs
JOIN integration_platforms platform ON platform.platform_key = defs.platform_key
JOIN base_model_catalog base ON base.canonical_model_key = defs.canonical_key
ON CONFLICT (platform_id, model_name) DO UPDATE
SET base_model_id = EXCLUDED.base_model_id,
provider_model_name = EXCLUDED.provider_model_name,
model_alias = EXCLUDED.model_alias,
model_type = EXCLUDED.model_type,
display_name = EXCLUDED.display_name,
capabilities = EXCLUDED.capabilities,
pricing_mode = EXCLUDED.pricing_mode,
pricing_rule_set_id = EXCLUDED.pricing_rule_set_id,
billing_config = EXCLUDED.billing_config,
retry_policy = EXCLUDED.retry_policy,
rate_limit_policy = EXCLUDED.rate_limit_policy,
enabled = EXCLUDED.enabled,
updated_at = now();
INSERT INTO platform_models (
platform_id, base_model_id, model_name, provider_model_name, model_alias, model_type,
display_name, capabilities, pricing_mode, pricing_rule_set_id, billing_config,
retry_policy, rate_limit_policy, enabled
)
SELECT platform.id, base.id, base.provider_model_name, base.provider_model_name,
COALESCE(NULLIF(base.metadata->>'alias', ''), base.provider_model_name), base.model_type,
base.display_name, base.capabilities, 'inherit_discount',
CASE WHEN platform.platform_key = 'suno-native-dev'
THEN (SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'desktop-advanced-media-v1')
ELSE base.pricing_rule_set_id
END,
base.base_billing_config, '{"enabled":true,"maxAttempts":2}'::jsonb,
base.default_rate_limit_policy, true
FROM integration_platforms platform
JOIN base_model_catalog base ON
(platform.platform_key = 'minimax-native-dev'
AND base.provider_key = 'minimax'
AND base.model_type ?| ARRAY['text_to_speech', 'voice_clone', 'video_generate'])
OR
(platform.platform_key = 'minimax-openai-native-dev'
AND base.provider_key = 'minimax-openai'
AND base.model_type ?| ARRAY['text_generate'])
OR
(platform.platform_key = 'volces-native-dev'
AND base.provider_key = 'volces'
AND base.model_type ?| ARRAY['image_generate', 'image_edit'])
OR
(platform.platform_key = 'aliyun-bailian-openai-native-dev'
AND base.provider_key = 'aliyun-bailian-openai'
AND base.model_type ?| ARRAY['text_embedding'])
OR
(platform.platform_key = 'aliyun-bailian-rerank-native-dev'
AND base.provider_key = 'aliyun-bailian-openai'
AND base.model_type ?| ARRAY['text_rerank'])
OR
(platform.platform_key = 'suno-native-dev'
AND base.provider_key = 'suno'
AND base.model_type ?| ARRAY['audio_generate'])
WHERE platform.platform_key IN (
'minimax-native-dev', 'minimax-openai-native-dev', 'volces-native-dev',
'aliyun-bailian-openai-native-dev', 'aliyun-bailian-rerank-native-dev', 'suno-native-dev'
)
AND base.status = 'active'
ON CONFLICT (platform_id, model_name) DO UPDATE
SET base_model_id = EXCLUDED.base_model_id,
provider_model_name = EXCLUDED.provider_model_name,
model_alias = EXCLUDED.model_alias,
model_type = EXCLUDED.model_type,
display_name = EXCLUDED.display_name,
capabilities = EXCLUDED.capabilities,
pricing_mode = EXCLUDED.pricing_mode,
pricing_rule_set_id = EXCLUDED.pricing_rule_set_id,
billing_config = EXCLUDED.billing_config,
retry_policy = EXCLUDED.retry_policy,
rate_limit_policy = EXCLUDED.rate_limit_policy,
enabled = EXCLUDED.enabled,
updated_at = now();