fix(video): runway/higgsfield model defaults diverge from schema
runway (estimate_cost/estimate_runtime/execute) defaulted to gen4_turbo and higgsfield (execute) defaulted to kling_3.0, while both schemas advertise seedance_2.0 as model.default. Omitting model under-quoted cost (runway 6x: $0.25 vs $1.50) and silently generated a different model than advertised, violating the Decision-Communication / cost-accuracy contract. Root cause was a default duplicated across schema + 3 methods that drifted. Collapse it to a single _DEFAULT_MODEL constant referenced everywhere. Add tests/tools/test_provider_model_defaults.py to lock each tool's estimate default to its schema default and guard the execute path.
This commit is contained in:
@@ -24,6 +24,11 @@ from tools.base_tool import (
|
||||
ToolTier,
|
||||
)
|
||||
|
||||
# Single source of truth for the default model. Referenced by both the input
|
||||
# schema and every code path that reads `model`, so estimate_cost / estimate_runtime
|
||||
# / execute can never silently diverge from the advertised default again.
|
||||
_DEFAULT_MODEL = "seedance_2.0"
|
||||
|
||||
|
||||
class HiggsFieldVideo(BaseTool):
|
||||
name = "higgsfield_video"
|
||||
@@ -89,7 +94,7 @@ class HiggsFieldVideo(BaseTool):
|
||||
"wan_2.5",
|
||||
"soul_cinema",
|
||||
],
|
||||
"default": "seedance_2.0",
|
||||
"default": _DEFAULT_MODEL,
|
||||
"description": "Underlying model. Defaults to Seedance 2.0 (preferred premium) — see .agents/skills/seedance-2-0/",
|
||||
},
|
||||
"duration": {
|
||||
@@ -134,7 +139,7 @@ class HiggsFieldVideo(BaseTool):
|
||||
return ToolStatus.UNAVAILABLE
|
||||
|
||||
def estimate_cost(self, inputs: dict[str, Any]) -> float:
|
||||
model = inputs.get("model", "seedance_2.0")
|
||||
model = inputs.get("model", _DEFAULT_MODEL)
|
||||
duration = int(inputs.get("duration", "5"))
|
||||
# Approximate per-clip costs based on Higgsfield credit pricing.
|
||||
# Seedance 2.0 on Higgsfield runs ~50-80 credits per 5s clip ≈ $0.50-$1.20.
|
||||
@@ -151,7 +156,7 @@ class HiggsFieldVideo(BaseTool):
|
||||
return base * (duration / 5)
|
||||
|
||||
def estimate_runtime(self, inputs: dict[str, Any]) -> float:
|
||||
model = inputs.get("model", "seedance_2.0")
|
||||
model = inputs.get("model", _DEFAULT_MODEL)
|
||||
if model in ("veo_3.1", "sora_2", "seedance_2.0"):
|
||||
return 120.0
|
||||
if model == "seedance_2.0_fast":
|
||||
@@ -171,7 +176,7 @@ class HiggsFieldVideo(BaseTool):
|
||||
api_key, api_secret = creds
|
||||
start = time.time()
|
||||
operation = inputs.get("operation", "text_to_video")
|
||||
model = inputs.get("model", "kling_3.0")
|
||||
model = inputs.get("model", _DEFAULT_MODEL)
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"prompt": inputs["prompt"],
|
||||
|
||||
@@ -47,6 +47,11 @@ _RUNTIME_SECONDS = {
|
||||
"seedance_2.0_fast": 60.0,
|
||||
}
|
||||
|
||||
# Single source of truth for the default model. Referenced by both the input
|
||||
# schema and every code path that reads `model`, so estimate_cost / estimate_runtime
|
||||
# / execute can never silently diverge from the advertised default again.
|
||||
_DEFAULT_MODEL = "seedance_2.0"
|
||||
|
||||
|
||||
class RunwayVideo(BaseTool):
|
||||
name = "runway_video"
|
||||
@@ -101,7 +106,7 @@ class RunwayVideo(BaseTool):
|
||||
"model": {
|
||||
"type": "string",
|
||||
"enum": ["seedance_2.0", "seedance_2.0_fast", "gen4_turbo", "gen4_aleph", "gen3a_turbo"],
|
||||
"default": "seedance_2.0",
|
||||
"default": _DEFAULT_MODEL,
|
||||
"description": (
|
||||
"seedance_2.0 = preferred premium default (single-pass synced audio, multi-shot, lip-sync — "
|
||||
"Runway Unlimited/Enterprise plan, non-US only). "
|
||||
@@ -149,12 +154,12 @@ class RunwayVideo(BaseTool):
|
||||
return os.environ.get("RUNWAY_API_KEY") or os.environ.get("RUNWAYML_API_SECRET")
|
||||
|
||||
def estimate_cost(self, inputs: dict[str, Any]) -> float:
|
||||
model = inputs.get("model", "gen4_turbo")
|
||||
model = inputs.get("model", _DEFAULT_MODEL)
|
||||
duration = inputs.get("duration", 5)
|
||||
return _COST_PER_SECOND.get(model, 0.05) * duration
|
||||
|
||||
def estimate_runtime(self, inputs: dict[str, Any]) -> float:
|
||||
model = inputs.get("model", "gen4_turbo")
|
||||
model = inputs.get("model", _DEFAULT_MODEL)
|
||||
return _RUNTIME_SECONDS.get(model, 30.0)
|
||||
|
||||
def execute(self, inputs: dict[str, Any]) -> ToolResult:
|
||||
@@ -168,7 +173,7 @@ class RunwayVideo(BaseTool):
|
||||
import requests
|
||||
|
||||
start = time.time()
|
||||
model = inputs.get("model", "gen4_turbo")
|
||||
model = inputs.get("model", _DEFAULT_MODEL)
|
||||
operation = inputs.get("operation", "text_to_video")
|
||||
ratio_friendly = inputs.get("ratio", "16:9")
|
||||
ratio_pixels = _RATIO_MAP.get(ratio_friendly, "1280:720")
|
||||
|
||||
Reference in New Issue
Block a user