{"record":{"id":"c8748adfe6e8dec1","repo":"calesthio/OpenMontage","slug":"model-id-must-be-one-of-choices","errorCode":null,"errorMessage":"model_id must be one of: {choices}","messagePattern":"model_id must be one of: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/audio/fal_elevenlabs_tts.py","lineNumber":215,"sourceCode":"        \"writes an audio file to output_path\",\n        \"submits one paid fal.ai ElevenLabs speech request\",\n    ]\n    user_visible_verification = [\n        \"Listen to the generated voice sample before approving full narration\",\n    ]\n\n    def _get_api_key(self) -> str | None:\n        return os.environ.get(\"FAL_KEY\") or os.environ.get(\"FAL_AI_API_KEY\")\n\n    def get_status(self) -> ToolStatus:\n        return ToolStatus.AVAILABLE if self._get_api_key() else ToolStatus.UNAVAILABLE\n\n    def _resolve_model(self, requested: str | None) -> tuple[str, str]:\n        model_name = requested or \"eleven-v3\"\n        model_name = self._MODEL_ALIASES.get(model_name, model_name)\n        if model_name not in self._MODELS:\n            choices = \", \".join(self._MODELS)\n            raise ValueError(f\"model_id must be one of: {choices}\")\n        return model_name, self._MODELS[model_name]\n\n    def estimate_cost(self, inputs: dict[str, Any]) -> float:\n        model_name, _ = self._resolve_model(inputs.get(\"model_id\"))\n        return round(\n            len(inputs.get(\"text\", \"\")) * self._PRICE_PER_CHARACTER[model_name],\n            4,\n        )\n\n    @staticmethod\n    def _output_extension(output_format: str) -> str:\n        return {\n            \"mp3\": \"mp3\",\n            \"pcm\": \"pcm\",\n            \"opus\": \"opus\",\n        }.get(output_format.split(\"_\", 1)[0], \"audio\")\n\n    def execute(self, inputs: dict[str, Any]) -> ToolResult:","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/audio/fal_elevenlabs_tts.py#L197-L233","documentation":"Raised by fal_elevenlabs_tts._resolve_model when the requested model_id (after alias mapping) is not a key in _MODELS. The wrapper whitelists supported ElevenLabs models on fal.ai and maps aliases first, so this only fires for genuinely unknown names.","triggerScenarios":"Passing model_id like 'eleven-turbo-v2-5' or 'tts-1' that is neither in _MODEL_ALIASES nor _MODELS; also raised from estimate_cost, so cost-checking a bad model fails the same way.","commonSituations":"Copy-pasting model names from OpenAI or ElevenLabs native docs that fal.ai does not host; new model released upstream before this wrapper's whitelist is updated; typo in a config file.","solutions":["Use one of the listed choices — typically 'eleven-v3' (the default) or 'eleven-multilingual-v2'","If an alias should exist, add the mapping to _MODEL_ALIASES in fal_elevenlabs_tts.py","Omit model_id entirely to get the default eleven-v3"],"exampleFix":"// before\ninputs = {\"text\": \"hi\", \"model_id\": \"tts-1\"}  # raises\n\n// after\ninputs = {\"text\": \"hi\", \"model_id\": \"eleven-v3\"}","handlingStrategy":"type-guard","validationCode":"valid = set(tool._MODELS) | set(tool._MODEL_ALIASES)\nif inputs.get(\"model_id\") and inputs[\"model_id\"] not in valid:\n    inputs[\"model_id\"] = \"eleven-v3\"  # or surface a choice list","typeGuard":"def is_valid_fal_tts_model(model_id: str | None, tool) -> bool:\n    name = tool._MODEL_ALIASES.get(model_id, model_id)\n    return name in tool._MODELS","tryCatchPattern":null,"preventionTips":["Pin model names to the tool's whitelist, not to upstream marketing names","Omit model_id to use the safe default","On tool updates, re-check _MODELS before shipping config changes"],"tags":["fal","tts","elevenlabs","model-id","validation"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}