fix: complete Kling idempotency inputs

Include every request field that can alter Kling video, image, avatar, or lip-sync media in the public idempotency contract. Add a shared regression matrix that detects future cache-key collisions while excluding transport-only controls.
This commit is contained in:
xucailiang
2026-07-10 21:24:11 +08:00
parent b9b9b82b64
commit 6b9ba782d8
5 changed files with 163 additions and 3 deletions

View File

@@ -401,6 +401,116 @@ def test_provider_agent_skills_reference_kling_official():
assert "kling-official" in KlingLipSync().agent_skills
@pytest.mark.parametrize(
("tool", "output_variants"),
[
(
KlingOfficialVideo(),
{
"prompt": "changed prompt",
"operation": "image_to_video",
"api_family": "omni",
"model_name": "kling-v2-6",
"model_variant": "kling-v2-5-turbo",
"duration": "10",
"aspect_ratio": "9:16",
"resolution": "1080p",
"mode": "pro",
"sound": "on",
"negative_prompt": "blur",
"cfg_scale": 0.7,
"reference_image_url": "https://example.com/first.png",
"reference_image_path": "/tmp/first.png",
"reference_tail_image_url": "https://example.com/tail.png",
"reference_tail_image_path": "/tmp/tail.png",
"reference_image_urls": ["https://example.com/ref.png"],
"reference_image_paths": ["/tmp/ref.png"],
"reference_video_url": "https://example.com/ref.mp4",
"video_urls": ["https://example.com/ref-2.mp4"],
"image_list": [{"image_url": "https://example.com/list.png"}],
"video_list": [{"video_url": "https://example.com/list.mp4"}],
"element_list": [{"element_id": 123}],
"multi_shot": True,
"shot_type": "intelligence",
"multi_prompt": [{"prompt": "second shot", "duration": "3"}],
"camera_control": {"type": "simple", "config": {"horizontal": 1}},
"watermark": True,
},
),
(
KlingOfficialImage(),
{
"prompt": "changed prompt",
"negative_prompt": "blur",
"operation": "omni",
"api_family": "omni",
"model_name": "kling-image-o1",
"image_url": "https://example.com/source.png",
"image_path": "/tmp/source.png",
"image_urls": ["https://example.com/ref.png"],
"image_paths": ["/tmp/ref.png"],
"image_list": [{"image_url": "https://example.com/list.png"}],
"image_reference": "subject",
"image_fidelity": 0.7,
"human_fidelity": 0.8,
"resolution": "2k",
"aspect_ratio": "1:1",
"n": 2,
"result_type": "series",
"series_amount": "3",
"element_list": [{"element_id": 123}],
"watermark": True,
},
),
(
KlingAvatar(),
{
"image_url": "https://example.com/avatar.png",
"image_path": "/tmp/avatar.png",
"audio_id": "audio-a",
"sound_file": "inline-audio",
"sound_file_url": "https://example.com/audio.mp3",
"sound_file_path": "/tmp/audio.mp3",
"audio_path": "/tmp/audio-alias.mp3",
"prompt": "natural presenter motion",
"mode": "pro",
},
),
(
KlingLipSync(),
{
"operation": "full_lip_sync",
"video_id": "video-a",
"video_url": "https://example.com/video.mp4",
"session_id": "session-a",
"face_id": "face-a",
"face_choose": [{"face_id": "face-a", "audio_id": "audio-a"}],
"auto_select_face": True,
"audio_id": "audio-a",
"sound_file": "inline-audio",
"sound_file_url": "https://example.com/audio.mp3",
"sound_file_path": "/tmp/audio.mp3",
"audio_path": "/tmp/audio-alias.mp3",
"sound_start_time": 100,
"sound_end_time": 4100,
"sound_insert_time": 500,
"sound_volume": 1.2,
"original_audio_volume": 0.4,
},
),
],
)
def test_kling_output_inputs_change_idempotency_keys(tool, output_variants):
baseline = tool.idempotency_key({})
collisions = [
field
for field, value in output_variants.items()
if tool.idempotency_key({field: value}) == baseline
]
assert collisions == [], f"{tool.name} idempotency key ignores: {collisions}"
def test_phase3_does_not_register_audio_or_video_effect_tools(isolated_tool_registry):
isolated_tool_registry.discover("tools")
assert isolated_tool_registry.get("kling_audio") is None

View File

@@ -124,7 +124,17 @@ class KlingAvatar(BaseTool):
backoff_seconds=2.0,
retryable_errors=["1302", "1303", "5000", "5001", "5002"],
)
idempotency_key_fields = ["image_url", "image_path", "audio_id", "sound_file", "sound_file_path", "mode"]
idempotency_key_fields = [
"image_url",
"image_path",
"audio_id",
"sound_file",
"sound_file_url",
"sound_file_path",
"audio_path",
"prompt",
"mode",
]
side_effects = ["paid remote generation via official Kling API", "writes avatar video to output_path"]
user_visible_verification = ["Watch generated avatar video for identity preservation and mouth motion"]
quality_score = 0.82

View File

@@ -139,15 +139,23 @@ class KlingLipSync(BaseTool):
retryable_errors=["1302", "1303", "5000", "5001", "5002"],
)
idempotency_key_fields = [
"operation",
"video_id",
"video_url",
"session_id",
"face_id",
"face_choose",
"auto_select_face",
"audio_id",
"sound_file",
"sound_file_url",
"sound_file_path",
"audio_path",
"sound_start_time",
"sound_end_time",
"sound_insert_time",
"sound_volume",
"original_audio_volume",
]
side_effects = [
"paid remote generation via official Kling API",

View File

@@ -122,13 +122,25 @@ class KlingOfficialImage(BaseTool):
)
idempotency_key_fields = [
"prompt",
"negative_prompt",
"operation",
"api_family",
"model_name",
"image_url",
"image_path",
"image_urls",
"image_paths",
"image_list",
"image_reference",
"image_fidelity",
"human_fidelity",
"aspect_ratio",
"resolution",
"n",
"result_type",
"series_amount",
"element_list",
"watermark",
]
side_effects = [
"paid remote generation via official Kling API",

View File

@@ -142,13 +142,33 @@ class KlingOfficialVideo(BaseTool):
)
idempotency_key_fields = [
"prompt",
"negative_prompt",
"operation",
"api_family",
"model_name",
"reference_image_url",
"reference_image_path",
"model_variant",
"duration",
"aspect_ratio",
"resolution",
"mode",
"sound",
"cfg_scale",
"camera_control",
"reference_image_url",
"reference_image_path",
"reference_tail_image_url",
"reference_tail_image_path",
"reference_image_urls",
"reference_image_paths",
"reference_video_url",
"video_urls",
"image_list",
"video_list",
"element_list",
"multi_shot",
"shot_type",
"multi_prompt",
"watermark",
]
side_effects = [
"paid remote generation via official Kling API",