calesthio/OpenMontage · error · ValueError
face_choose items must be strings or objects
Error message
face_choose items must be strings or objects
What it means
Raised by KlingLipSyncTool._normalize_face_choose when an entry in the face_choose list is neither a string nor a dict. The tool accepts face_choose as a single dict, a list of face-id strings, or a list of objects carrying face_id/id plus per-face timing fields; any other element type (int, None, tuple, nested list) is rejected before the Kling API is called.
Source
Thrown at tools/avatar/kling_lip_sync.py:410
if inputs.get("face_choose"):
raw = inputs["face_choose"]
if isinstance(raw, dict):
raw = [raw]
if not isinstance(raw, list):
raise ValueError("face_choose must be a list of face choice objects")
normalized: list[dict[str, Any]] = []
for item in raw:
if isinstance(item, str):
normalized.append({"face_id": item})
elif isinstance(item, dict):
if not (item.get("face_id") or item.get("id")):
raise ValueError("face_choose items must include face_id")
record = dict(item)
if "face_id" not in record and record.get("id"):
record["face_id"] = record.pop("id")
normalized.append(record)
else:
raise ValueError("face_choose items must be strings or objects")
return normalized
if inputs.get("face_id"):
return [{"face_id": str(inputs["face_id"])}]
return []
def _face_selection(
self,
faces: list[dict[str, Any]],
inputs: dict[str, Any],
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
explicit = self._normalize_face_choose(inputs)
if explicit:
return explicit, {
"selection_method": "user_selected",
"selection_reason": "face_choose or face_id was provided",
"selected_face": explicit,
}
if len(faces) == 1:View on GitHub (pinned to 95e1c3d0ab)
Solutions
- Use face-id strings: face_choose=["face_1", "face_2"]
- Or use objects with face_id or id keys: face_choose=[{"face_id": "face_1", "sound_insert_time": 0}]
- If you only have numeric face indexes, map them to the face_id values returned by identify_face first
- A single dict is also accepted and auto-wrapped: face_choose={"face_id": "face_1"}
Example fix
# before
inputs = {"face_choose": [0, 1]}
# after
inputs = {"face_choose": ["face_1", "face_2"]} Defensive patterns
Strategy: validation
Validate before calling
def valid_face_choose(fc):
if fc is None:
return True
if isinstance(fc, dict):
fc = [fc]
if not isinstance(fc, list):
return False
return all(isinstance(i, str) or (isinstance(i, dict) and (i.get("face_id") or i.get("id"))) for i in fc)
if not valid_face_choose(inputs.get("face_choose")):
raise ValueError("face_choose must contain face-id strings or objects with face_id") Type guard
def is_face_choose_item(v) -> bool:
return isinstance(v, str) or (isinstance(v, dict) and bool(v.get("face_id") or v.get("id"))) Try / catch
try:
tool.run(inputs)
except ValueError as e:
if "face_choose" in str(e):
fix_face_choose_and_retry(inputs) # normalize items to {"face_id": str}
raise Prevention
- Always build face_choose entries as {"face_id": <id-from-identify_face>}
- Never put numeric indexes or nulls into face_choose
- Validate the list shape before calling the tool
When it happens
Trigger: Calling kling_lip_sync with inputs like face_choose=[123, "abc"], face_choose=[None], or face_choose=[["face1"]] (a nested list). The loop in _normalize_face_choose hits the else branch for the first non-str/non-dict element and raises ValueError immediately.
Common situations: Passing face indexes instead of face ids (face_choose=[0, 1]), copying a JSON payload where face ids were numbers, or a template/agent emitting null entries for undetected faces.
Related errors
- Kling identify_face requires video_url or video_id; local vi
- Kling identify_face requires video_id or video_url
- Kling avatar requires image_url or image_path
- mode must be one of: {', '.join(AVATAR_MODES)}
- Kling avatar requires audio_id, sound_file, sound_file_url,
AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15).
Data as JSON: /api/errors/1d80107f062fab96.
Report an issue: GitHub.