{"record":{"id":"91a88058b6fe1abb","repo":"BerriAI/litellm","slug":"models-must-be-a-string-or-list-of-strings","errorCode":null,"errorMessage":"'models' must be a string or list of strings","messagePattern":"'models' must be a string or list of strings","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"litellm/batch_completion/main.py","lineNumber":237,"sourceCode":"        It sends requests concurrently and collects responses from all models that respond.\n    \"\"\"\n    import concurrent.futures\n\n    # ANSI escape codes for colored output\n\n    if \"model\" in kwargs:\n        kwargs.pop(\"model\")\n    if \"models\" in kwargs:\n        models = kwargs.pop(\"models\")\n    else:\n        raise Exception(\"'models' param not in kwargs\")\n\n    if isinstance(models, str):\n        models = [models]\n    elif isinstance(models, (list, tuple)):\n        models = list(models)\n    else:\n        raise TypeError(\"'models' must be a string or list of strings\")\n\n    if len(models) == 0:\n        return []\n\n    responses: Final = []\n\n    with concurrent.futures.ThreadPoolExecutor(max_workers=len(models)) as executor:\n        futures: Final = [executor.submit(litellm.completion, *args, model=model, **kwargs) for model in models]\n\n        for future in futures:\n            try:\n                result = future.result()\n                if result is not None:\n                    responses.append(result)\n            except Exception as e:\n                print_verbose(f\"batch_completion_models_all_responses: model request failed: {e}\")\n                continue\n","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/batch_completion/main.py#L219-L255","documentation":"TypeError raised by litellm.batch_completion_models when the 'models' kwarg is neither a string, list, nor tuple — e.g. a dict, set, generator, or None. After popping 'models' from kwargs the function normalizes it (str -> [str], list/tuple -> list) and rejects anything else before building the ThreadPoolExecutor.","triggerScenarios":"Passing models as a set, dict, range, iterator, or None: litellm.batch_completion_models(models={'gpt-4o': 1}) or models=iter([...]). A single string is fine (wrapped into a list); other iterables like generators are not.","commonSituations":"Using a set for dedup and passing it straight through; passing a lazily-built generator from another function; a None leaking in from an optional config field.","solutions":["Convert to a list first: models=list(models) for sets/generators/ranges.","Guard against None: models=models or [] (note empty list returns [] quickly).","For dicts, decide whether you meant the keys: models=list(models_dict.keys())."],"exampleFix":"# before\nlitellm.batch_completion_models(models={\"gpt-4o\", \"claude-3-5-sonnet\"}, messages=[...])\n\n# after\nlitellm.batch_completion_models(models=list({\"gpt-4o\", \"claude-3-5-sonnet\"}), messages=[...])","handlingStrategy":"type-guard","validationCode":"models = kwargs.get(\"models\")\nif isinstance(models, (set, tuple, range)):\n    kwargs[\"models\"] = list(models)\nelif not isinstance(models, (str, list)):\n    raise TypeError(\"models must be str or list\")","typeGuard":"def is_valid_models_value(v) -> bool:\n    return isinstance(v, (str, list, tuple))","tryCatchPattern":null,"preventionTips":["Normalize collections to list before passing.","Reject None early in your own wrapper.","Avoid generators for models — materialize them first."],"tags":["batch-completion","type-validation","api-misuse","python"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}