fix(image_gen): disable default-on upscaling everywhere — opt-in only

The Aug 8 default-on upscaling policy (66ea4e686) chained the Clarity
Upscaler after every sub-2MP generation. Clarity is an SD1.5 creative
tile-diffusion enhancer (creativity 0.35, "masterpiece" prompt prefix) —
it redraws content, which degraded output on 100% of generations for
models like GPT Image 2 and Ideogram whose value is precise text
rendering, CJK, and photorealistic detail.

Policy now: no model upscales by default, on FAL or Krea. The `upscale`
tool param remains as a per-call opt-in (`upscale: true`); explicit
requests still chain Clarity (FAL) / Krea Enhance as before.

- FAL catalog: all 17 default-on entries flipped to upscale=False
- Krea plugin: medium + medium-turbo per-model defaults flipped off
- Tool schema: upscale param described as opt-in with a fidelity warning
- Tests updated: catalog invariant now pins all-off; default-on cases
  now assert no upscaler call
- Docs (en + zh) updated to the opt-in policy
This commit is contained in:
Teknium
2026-08-16 11:00:00 -07:00
parent ca84f13b97
commit f06c41522e
6 changed files with 73 additions and 90 deletions

View File

@@ -57,9 +57,9 @@ _MODELS: Dict[str, Dict[str, Any]] = {
"strengths": "Illustration, anime, painting, expressive styles. Faster + cheaper.",
"price": "$0.030 (text) / $0.035 (style refs) / $0.040 (moodboards)",
"path": "medium",
# 1.5K native — default the Enhance pass on (mirrors the FAL
# catalog policy: sub-2MP models upscale by default).
"upscale": True,
# Upscaling is opt-in everywhere (Aug 2026 policy: default-on
# enhance passes degraded output quality).
"upscale": False,
},
"krea-2-large": {
"display": "Krea 2 Large",
@@ -76,8 +76,8 @@ _MODELS: Dict[str, Dict[str, Any]] = {
"strengths": "Fastest Krea 2 — medium quality at lower latency / cost.",
"price": "$0.015 (text) / $0.0175 (style refs)",
"path": "medium-turbo",
# 1.5K native — default the Enhance pass on.
"upscale": True,
# Opt-in only (Aug 2026 policy).
"upscale": False,
},
}

View File

@@ -657,24 +657,18 @@ class TestUpscalePass:
assert result["image"].endswith("native.png")
assert mock_post.call_count == 2 # enhance attempted, fell back
def test_medium_upscales_by_default(self):
"""krea-2-medium is 1.5K native — the Enhance pass defaults ON."""
enhance_job = {
"job_id": "00000000-0000-0000-0000-00000000e0e0",
"status": "completed",
"created_at": "2026-05-27T00:00:00Z",
"completed_at": "2026-05-27T00:01:00Z",
"result": {"urls": ["https://krea.cdn/enhanced.png"]},
}
result, mock_post, _ = self._run_generate(upscale=None, enhance_job=enhance_job)
def test_medium_skips_upscale_by_default(self):
"""Upscaling is opt-in only (Aug 2026 policy) — even for
krea-2-medium's 1.5K native output, no automatic Enhance pass."""
result, mock_post, _ = self._run_generate(upscale=None, enhance_job=None)
assert result["success"] is True
assert result["upscaled"] is True
assert result["image"].endswith("enhanced.png")
assert mock_post.call_count == 2
assert result["upscaled"] is False
assert result["image"].endswith("native.png")
assert mock_post.call_count == 1 # only the generation submit
def test_large_skips_upscale_by_default(self):
"""krea-2-large is 2K native — no automatic Enhance pass."""
"""krea-2-large: no automatic Enhance pass either."""
result, mock_post, _ = self._run_generate(
upscale=None, enhance_job=None, model="krea-2-large",
)
@@ -685,7 +679,7 @@ class TestUpscalePass:
assert mock_post.call_count == 1 # only the generation submit
def test_explicit_false_disables_default(self):
"""Explicit upscale=False wins over medium's default-on."""
"""Explicit upscale=False matches the off default."""
result, mock_post, _ = self._run_generate(upscale=False, enhance_job=None)
assert result["success"] is True

View File

@@ -57,23 +57,15 @@ class TestFalCatalog:
assert not missing, f"{mid} missing required keys: {missing}"
def test_upscale_defaults_track_native_resolution(self, image_tool):
"""Default-on upscaling: every model whose native output is below
~2MP upscales by default so users never silently get low-res images.
Models that already emit >=2MP natively (Seedream tiers, Krea 2
Large on FAL) skip the pass — upscaling them wastes money."""
native_hi_res = {
"bytedance/seedream/v5/pro/text-to-image", # 1536²-2048² native
"bytedance/seedream/v5/lite/text-to-image", # up to 4K native
"fal-ai/krea/v2/large/text-to-image", # 2K native
}
def test_upscale_defaults_are_all_off(self, image_tool):
"""Upscaling is opt-in only (Aug 2026 policy). The default-on
experiment chained the Clarity Upscaler — a creative SD1.5
tile-diffusion enhancer — after every sub-2MP generation, which
degraded output quality (mangled GPT Image 2 / Ideogram text
rendering, CJK, faces). No catalog entry may default upscale on."""
for mid, meta in image_tool.FAL_MODELS.items():
if mid in native_hi_res:
assert meta["upscale"] is False, \
f"{mid} is native hi-res — should not double-upscale"
else:
assert meta["upscale"] is True, \
f"{mid} should default to upscale=True (sub-2MP native)"
assert meta["upscale"] is False, \
f"{mid} must not default upscale on — opt-in per call only"
def test_edit_capable_entries_declare_a_full_edit_contract(self, image_tool):
@@ -594,8 +586,8 @@ class TestUpscaleOptIn:
model="bytedance/seedream/v5/lite/text-to-image",
upscale=True, upscaler_called=True)
def test_explicit_false_disables_default_on_model(self, image_tool, monkeypatch):
"""Klein defaults to upscale=True (sub-2MP native) — explicit False wins."""
def test_explicit_false_stays_off(self, image_tool, monkeypatch):
"""Explicit False and the catalog default agree: no upscale."""
self._run(image_tool, monkeypatch,
model="fal-ai/flux-2/klein/9b", upscale=False, upscaler_called=False)
@@ -604,9 +596,10 @@ class TestUpscaleOptIn:
model="bytedance/seedream/v5/lite/text-to-image",
upscale=None, upscaler_called=False)
def test_omitted_keeps_catalog_default_on(self, image_tool, monkeypatch):
def test_omitted_is_off_for_previously_default_on_model(self, image_tool, monkeypatch):
"""flux-2-pro was the old default-on model — now off like the rest."""
self._run(image_tool, monkeypatch,
model="fal-ai/flux-2-pro", upscale=None, upscaler_called=True)
model="fal-ai/flux-2-pro", upscale=None, upscaler_called=False)
def test_upscale_failure_falls_back_to_native(self, image_tool, monkeypatch):
monkeypatch.setenv("FAL_IMAGE_MODEL", "fal-ai/flux-2/klein/9b")

View File

@@ -13,8 +13,10 @@ Architecture:
aspect_ratio) into the model-specific payload and filters to the
``supports`` whitelist so models never receive rejected keys.
- Upscaling via FAL's Clarity Upscaler is gated per-model via the ``upscale``
flag — on for FLUX 2 Pro (backward-compat), off for all faster/newer models
where upscaling would either hurt latency or add marginal quality.
flag — OFF by default for every model. Clarity is an SD1.5 creative
tile-diffusion enhancer (creativity 0.35 redraws content); chained by
default it mangled GPT Image 2 / Ideogram text rendering, CJK, and faces
(Aug 2026 quality regression). Upscaling is strictly per-call opt-in.
Pricing shown in UI strings is as-of the initial commit; we accept drift and
update when it's noticed.
@@ -93,6 +95,8 @@ logger = logging.getLogger(__name__)
# rejected parameters (each FAL model rejects unknown keys differently).
#
# ``upscale`` controls whether to chain Clarity Upscaler after generation.
# Policy (Aug 2026): False everywhere — the default-on experiment degraded
# output quality (Clarity redraws content). Opt-in per call only.
FAL_MODELS: Dict[str, Dict[str, Any]] = {
"fal-ai/flux-2/klein/9b": {
@@ -115,7 +119,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "num_inference_steps", "seed",
"output_format", "enable_safety_checker",
},
"upscale": True,
"upscale": False,
# Image-to-image / editing: FLUX.2 [klein] 9B edit endpoint takes
# `image_urls` (list). Natural-language edits, multi-ref.
"edit_endpoint": "fal-ai/flux-2/klein/9b/edit",
@@ -150,7 +154,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"num_images", "output_format", "enable_safety_checker",
"safety_tolerance", "sync_mode", "seed",
},
"upscale": True, # Backward-compat: current default behavior.
"upscale": False, # opt-in only (was default-on pre-Aug 2026)
# Edit endpoint accepts up to 9 reference images.
"edit_endpoint": "fal-ai/flux-2-pro/edit",
"edit_supports": {
@@ -183,7 +187,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"seed", "output_format", "enable_safety_checker",
"enable_prompt_expansion",
},
"upscale": True,
"upscale": False,
},
"fal-ai/nano-banana-pro": {
"display": "Nano Banana Pro (Gemini 3 Pro Image)",
@@ -209,7 +213,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"safety_tolerance", "seed", "sync_mode", "resolution",
"enable_web_search", "limit_generations",
},
"upscale": True,
"upscale": False,
# Nano Banana Pro edit (Gemini 3 Pro Image): natural-language edits
# with up to 2 reference images via `image_urls`.
"edit_endpoint": "fal-ai/nano-banana-pro/edit",
@@ -244,7 +248,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"resolution", "enable_web_search", "limit_generations",
"thinking_level",
},
"upscale": True,
"upscale": False,
"edit_endpoint": "fal-ai/nano-banana-2/edit",
"edit_supports": {
"prompt", "image_urls", "aspect_ratio", "num_images",
@@ -276,7 +280,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "quality", "num_images", "output_format",
"background", "sync_mode",
},
"upscale": True,
"upscale": False,
# Edit endpoint: high-fidelity edits preserving composition/lighting.
"edit_endpoint": "fal-ai/gpt-image-1.5/edit",
"edit_supports": {
@@ -315,7 +319,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
# openai_api_key (BYOK) intentionally omitted — all users go
# through the shared FAL billing path.
},
"upscale": True,
"upscale": False,
# GPT Image 2 edit endpoint lives under the OpenAI namespace on FAL
# (NOT fal-ai/). Takes `image_urls` (list) + optional mask. We don't
# send `image_size` on edit so the model auto-infers from input.
@@ -346,7 +350,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "rendering_speed", "expand_prompt",
"style", "seed",
},
"upscale": True,
"upscale": False,
# Ideogram V3 edit endpoint takes `image_urls` (list).
"edit_endpoint": "fal-ai/ideogram/v3/edit",
"edit_supports": {
@@ -374,7 +378,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "enable_safety_checker",
"colors", "background_color",
},
"upscale": True,
"upscale": False,
},
"fal-ai/qwen-image": {
"display": "Qwen Image",
@@ -398,7 +402,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "num_inference_steps", "guidance_scale",
"num_images", "output_format", "acceleration", "seed", "sync_mode",
},
"upscale": True,
"upscale": False,
# Qwen edit uses the Qwen Image 2.0 Pro editing endpoint, which takes
# `image_urls` (list) + natural-language edit instructions.
"edit_endpoint": "fal-ai/qwen-image-2/pro/edit",
@@ -429,7 +433,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "aspect_ratio", "creativity", "seed",
"image_style_references",
},
"upscale": True,
"upscale": False,
},
"fal-ai/krea/v2/large/text-to-image": {
"display": "Krea 2 Large",
@@ -531,7 +535,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "expansion_model", "num_images",
"seed", "sync_mode", "enable_safety_checker", "output_format",
},
"upscale": True,
"upscale": False,
},
"ideogram/v4/fast": {
"display": "Ideogram V4 (Fast)",
@@ -552,7 +556,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "expansion_model", "rendering_speed",
"num_images", "seed", "sync_mode",
},
"upscale": True,
"upscale": False,
},
"alibaba/qwen-image-3/text-to-image": {
"display": "Qwen Image 3",
@@ -576,7 +580,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"seed", "sync_mode", "output_format",
"enable_prompt_expansion", "enable_safety_checker",
},
"upscale": True,
"upscale": False,
# Qwen Image 3 edit: 1-3 reference images, identity-preserving edits.
"edit_endpoint": "alibaba/qwen-image-3/edit",
"edit_supports": {
@@ -605,7 +609,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "aspect_ratio", "num_images", "output_format",
"sync_mode",
},
"upscale": True,
"upscale": False,
},
"google/nano-banana-2-lite": {
"display": "Nano Banana 2 Lite",
@@ -628,7 +632,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"output_format", "safety_tolerance", "sync_mode",
"system_prompt", "limit_generations", "thinking_level",
},
"upscale": True,
"upscale": False,
# Fast multi-turn local edits with reference images via `image_urls`.
"edit_endpoint": "google/nano-banana-2-lite/edit",
"edit_supports": {
@@ -656,7 +660,7 @@ FAL_MODELS: Dict[str, Dict[str, Any]] = {
"prompt", "image_size", "enable_safety_checker",
"colors", "background_color",
},
"upscale": True,
"upscale": False,
},
}
@@ -1466,12 +1470,12 @@ IMAGE_GENERATE_SCHEMA = {
"upscale": {
"type": "boolean",
"description": (
"Optional override for the high-resolution pass. Models "
"with sub-2MP native output upscale automatically (~2x, "
"extra cost/latency); pass false for a faster/cheaper "
"draft at native resolution, or true to force the pass "
"on native hi-res models and image edits. Omit to keep "
"the per-model default."
"Optional post-generation high-resolution pass (~2x, "
"extra cost/latency). Off by default for every model — "
"pass true to opt in. The upscaler is a creative "
"enhancer and can alter fine detail (rendered text, "
"faces), so only use it when resolution matters more "
"than fidelity."
),
},
},

View File

@@ -160,31 +160,28 @@ This translation happens in `_build_fal_payload()` — agent code never has to k
## Upscaling
### Automatic (default-on for low-res models)
### Opt-in only
Every model whose native output is below ~2MP automatically runs a
high-resolution pass after generation, so you never silently get a low-res
image:
No model upscales by default. Modern image models emit their best quality
natively, and the available upscalers are *creative* enhancers (diffusion
passes) that can subtly redraw content — degrading rendered text, faces, and
fine detail. Upscaling only runs when the agent explicitly requests it.
| Backend | Models upscaled by default | Upscaler |
|---|---|---|
| **FAL.ai** | all except Seedream 5 Pro/Lite and Krea 2 Large (native ≥2MP) | Clarity Upscaler (2×, +$0.03/MP) |
| **Krea** | Krea 2 Medium + Medium Turbo (1.5K native); Large (2K) skips | Krea Enhance (2×, up to 8K ceiling) |
| Other backends | — | no upscaler; native resolution returned |
### The `upscale` parameter (per-call opt-in)
### The `upscale` parameter (per-call override)
- `upscale: true` — chain a high-resolution pass after generation:
The agent-facing `upscale` boolean overrides the default in either
direction:
| Backend | Upscaler |
|---|---|
| **FAL.ai** | Clarity Upscaler (2×, +$0.03/MP) |
| **Krea** | Krea Enhance (2×, up to 8K ceiling) |
| Other backends | no upscaler; native resolution returned |
- `upscale: false` — skip the automatic pass (faster/cheaper draft output)
- `upscale: true` — force the pass, even on native hi-res models or image
edits
- `upscale: false` / omitted — native resolution (the default)
`video_generate` also accepts `upscale: true` on the FAL backend, chaining
ByteDance's **SeedVR2** video upscaler (2×, $0.001/MP of output video) after
generation. Video stays opt-in — doubling every video's resolution by
default would double its cost and latency.
generation.
When the FAL image pass runs, it uses these settings:
@@ -203,7 +200,7 @@ If upscaling fails (network issue, rate limit), the original image is returned a
1. **Model resolution** — `_resolve_fal_model()` reads `image_gen.model` from `config.yaml`, falls back to the `FAL_IMAGE_MODEL` env var, then to `fal-ai/flux-2/klein/9b`.
2. **Payload building** — `_build_fal_payload()` translates your `aspect_ratio` into the model's native format (preset enum, aspect-ratio enum, or GPT literal), merges the model's default params, applies any caller overrides, then filters to the model's `supports` whitelist so unsupported keys are never sent.
3. **Submission** — `_submit_fal_request()` routes via direct FAL credentials or the managed Nous gateway.
4. **Upscaling** — runs when the model's catalog entry has `upscale: True` (the default for sub-2MP models) or the agent passed `upscale: true`; an explicit `upscale: false` always skips it.
4. **Upscaling** — runs only when the agent passed `upscale: true`; every model's catalog default is off.
5. **Delivery** — final image URL returned to the agent, which emits a `MEDIA:<url>` tag that platform adapters convert to native media.
## Debugging

View File

@@ -97,12 +97,7 @@ Make me a futuristic cityscape, landscape orientation
## 自动超分(Upscale)
是否启用 FAL **Clarity Upscaler** 按模型区分:
| 模型 | 超分? | 原因 |
|---|---|---|
| `fal-ai/flux-2-pro` | ✓ | 历史兼容(选择器出现前的默认) |
| 其他 | ✗ | 亚秒级模型若再超分会失去速度优势;高分辨率模型本身已足够清晰 |
超分**默认关闭**(所有模型)。现代图像模型原生输出即为最佳质量,而可用的超分器属于*创意增强器*(扩散重绘),会细微改动内容——损伤文字渲染、人脸与细节。仅当智能体显式传入 `upscale: true` 时才会执行 FAL **Clarity Upscaler** 超分。
超分启用时的主要参数:
@@ -121,7 +116,7 @@ Make me a futuristic cityscape, landscape orientation
1. **模型解析** — `_resolve_fal_model()` 读取 `config.yaml` 的 `image_gen.model`,否则看 `FAL_IMAGE_MODEL` 环境变量,再否则默认 `fal-ai/flux-2/klein/9b`。
2. **构造请求体** — `_build_fal_payload()` 将 `aspect_ratio` 转为各模型枚举或字面量,合并默认参数与调用方覆盖,并按 `supports` 白名单过滤非法字段。
3. **提交** — `_submit_fal_request()` 根据凭据走直连 FAL 或 Nous 托管网关。
4. **超分** — 仅当模型元数据标记 `upscale: True` 时执行。
4. **超分** — 仅当调用显式传入 `upscale: true` 时执行;所有模型目录默认关闭。
5. **交付** — 最终图像 URL 返回给智能体,并发出 `MEDIA:<url>`,由各平台适配器转为原生媒体消息。
## 调试