{"record":{"id":"db308ad34b57aff1","repo":"run-llama/llama_index","slug":"spec-functions-must-be-of-type-list-union-str-tu","errorCode":null,"errorMessage":"spec_functions must be of type: List[Union[str, Tuple[str, str]]]","messagePattern":"spec_functions must be of type: List\\[Union\\[str, Tuple\\[str, str\\]\\]\\]","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"llama-index-core/llama_index/core/tools/tool_spec/base.py","lineNumber":95,"sourceCode":"            if isinstance(func_spec, str):\n                func = getattr(self, func_spec)\n                if inspect.iscoroutinefunction(func):\n                    func_async = func\n                else:\n                    func_sync = func\n                metadata = func_to_metadata_mapping.get(func_spec, None)\n                if metadata is None:\n                    metadata = self.get_metadata_from_fn_name(func_spec)\n            elif isinstance(func_spec, tuple) and len(func_spec) == 2:\n                func_sync = getattr(self, func_spec[0])\n                func_async = getattr(self, func_spec[1])\n                metadata = func_to_metadata_mapping.get(func_spec[0], None)\n                if metadata is None:\n                    metadata = func_to_metadata_mapping.get(func_spec[1], None)\n                    if metadata is None:\n                        metadata = self.get_metadata_from_fn_name(func_spec[0])\n            else:\n                raise ValueError(\n                    \"spec_functions must be of type: List[Union[str, Tuple[str, str]]]\"\n                )\n\n            tool = FunctionTool.from_defaults(\n                fn=func_sync,\n                async_fn=func_async,\n                tool_metadata=metadata,\n            )\n            tool_list.append(tool)\n        return tool_list\n\n    async def to_tool_list_async(\n        self,\n        spec_functions: Optional[List[SPEC_FUNCTION_TYPE]] = None,\n        func_to_metadata_mapping: Optional[Dict[str, ToolMetadata]] = None,\n    ) -> List[FunctionTool]:\n        \"\"\"Asynchronously convert a tool spec to a list of tools.\"\"\"\n        return await asyncio.to_thread(","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/run-llama/llama_index/blob/afd0fef371831f9bda13e5af7167cf4e981278ab/llama-index-core/llama_index/core/tools/tool_spec/base.py#L77-L113","documentation":"BaseToolSpec.to_tool_list() validates each entry of spec_functions. Entries must be a plain string (function name) or a tuple of exactly two strings (sync function name, async function name). Anything else - ints, 3-tuples, single-element tuples, lists - raises this ValueError before any tool is built.","triggerScenarios":"Calling to_tool_list() on a tool spec subclass while passing spec_functions=[...] (or overriding/spec_functions containing) entries like ('fn',), ['fn'], ('sync','async','extra'), or a non-string value.","commonSituations":"Hand-writing a custom ToolSpec and mistyping spec_functions; passing a list of function objects instead of names; copying an example that used 3-tuples from an older API.","solutions":["Normalize every entry to either 'fn_name' or ('sync_fn_name', 'async_fn_name').","If you have function objects, use their __name__: spec_functions=[fn.__name__ for fn in fns].","Check for trailing commas that turn a string into a 1-tuple: ('query',) is invalid."],"exampleFix":"# before\nspec_functions = [\"search\", (\"sync_read\",), \"summarize\"]\ntools = spec.to_tool_list(spec_functions=spec_functions)\n\n# after\nspec_functions = [\"search\", (\"sync_read\", \"async_read\"), \"summarize\"]\ntools = spec.to_tool_list(spec_functions=spec_functions)","handlingStrategy":"validation","validationCode":"def validate_spec_functions(spec_functions):\n    for entry in spec_functions:\n        if isinstance(entry, str):\n            continue\n        if isinstance(entry, tuple) and len(entry) == 2 and all(isinstance(x, str) for x in entry):\n            continue\n        raise TypeError(f'Bad spec_functions entry: {entry!r}; use str or (sync, async) tuple')\n    return spec_functions","typeGuard":"from typing import Union, Tuple, List\n\ndef is_valid_spec_functions(value) -> bool:\n    return isinstance(value, list) and all(\n        isinstance(e, str) or (isinstance(e, tuple) and len(e) == 2 and all(isinstance(p, str) for p in e))\n        for e in value\n    )","tryCatchPattern":"try:\n    tools = spec.to_tool_list(spec_functions=spec_functions)\nexcept ValueError as e:\n    if 'spec_functions' in str(e):\n        raise ValueError(f'Fix spec_functions shape: {spec_functions}') from e\n    raise","preventionTips":["Run a shape assertion on spec_functions before calling to_tool_list.","Generate entries from function objects via __name__ instead of hand-writing tuples.","Beware trailing commas: ('fn',) is a 1-tuple and will fail."],"tags":["tool-spec","validation","python","value-error"],"backgroundTag":null,"analyzedSha":"afd0fef371831f9bda13e5af7167cf4e981278ab","analyzedAt":"2026-08-15T05:42:58.429Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}