calesthio/OpenMontage · error · ValueError

Conflicting audio input in face_choose[0]; provide audio_id

Error message

Conflicting audio input in face_choose[0]; provide audio_id or sound_file, not both

What it means

Raised by KlingLipSyncTool._copy_audio_input when face_choose[0] contains both audio_id and sound_file. The Kling payload accepts exactly one audio source per face; supplying both an uploaded-audio reference and inline audio data is ambiguous, so the tool refuses before building the request.

Source

Thrown at tools/avatar/kling_lip_sync.py:593

    @staticmethod
    def _face_choose_result_metadata(
        face_choose: list[dict[str, Any]],
    ) -> list[dict[str, Any]]:
        metadata: list[dict[str, Any]] = []
        for item in face_choose:
            record = {key: value for key, value in item.items() if key != "sound_file"}
            if item.get("sound_file"):
                record["sound_file_provided"] = True
            metadata.append(record)
        return metadata

    @staticmethod
    def _copy_audio_input(inputs: dict[str, Any], payload: dict[str, Any]) -> dict[str, Any]:
        nested_audio_id = str(payload.get("audio_id") or "").strip()
        nested_sound_file = payload.get("sound_file")
        if nested_audio_id and nested_sound_file:
            raise ValueError(
                "Conflicting audio input in face_choose[0]; provide audio_id or sound_file, not both"
            )

        top_level_audio_id = str(inputs.get("audio_id") or "").strip()
        top_level_sound_requested = any(
            inputs.get(key)
            for key in ("sound_file", "sound_file_url", "sound_file_path", "audio_path")
        )

        if nested_audio_id:
            if (
                (top_level_audio_id and top_level_audio_id != nested_audio_id)
                or top_level_sound_requested
            ):
                raise ValueError(
                    "Conflicting audio input between top-level fields and face_choose[0]"
                )
            payload["audio_id"] = nested_audio_id

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Delete one of the two fields from face_choose[0] — keep audio_id if you pre-uploaded the audio, otherwise keep sound_file
  2. If you have a file, upload it first (or let the tool handle sound_file) and drop audio_id
  3. Validate face_choose entries for mutual exclusivity of audio_id/sound_file before calling

Example fix

# before
face = {"face_id": "f1", "audio_id": "audio_123", "sound_file": "https://cdn/vo.mp3"}

# after
face = {"face_id": "f1", "sound_file": "https://cdn/vo.mp3"}
Defensive patterns

Strategy: validation

Validate before calling

face_item = (inputs.get("face_choose") or [{}])[0]
if str(face_item.get("audio_id") or "").strip() and face_item.get("sound_file"):
    raise ValueError("face_choose[0] has both audio_id and sound_file")

Type guard

def has_single_audio_source(face_item) -> bool:
    return bool(str(face_item.get("audio_id") or "").strip()) != bool(face_item.get("sound_file"))

Prevention

When it happens

Trigger: face_choose=[{'face_id': 'f1', 'audio_id': 'audio_123', 'sound_file': 'https://.../vo.mp3'}] on the advanced path. Either non-empty audio_id (after strip) together with a truthy sound_file triggers it.

Common situations: Migrating a payload from sound_file to a pre-uploaded audio_id and leaving the old field behind; agent templates merging every audio option into one dict.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/9ac429442d37de9f. Report an issue: GitHub.