{"record":{"id":"26ffc6f075cc100f","repo":"JuliusBrussee/caveman","slug":"native-functiontool-returned-an-unexpected-output","errorCode":null,"errorMessage":"Native FunctionTool returned an unexpected output","messagePattern":"Native FunctionTool returned an unexpected output","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/llama_index.py","lineNumber":190,"sourceCode":"            and self.metadata.get_parameters_dict() == RECOVERY_SCHEMA)\n\n    def invocation(self):\n        return _Invocation(self, self.scope, self.tools, self.successful, True)\n\n    def selected(self, call: ToolSelection):\n        if self.binding is not None and not self.registered(self.tools, self.invocation()):\n            raise ValueError(\"Native executor registration changed\")\n        if not isinstance(call, ToolSelection) or not isinstance(call.tool_id, str) or not call.tool_id or not isinstance(call.tool_kwargs, dict):\n            raise TypeError(\"Expected a native ToolSelection\")\n        matches = [tool for tool in self.tools if tool.metadata.name == call.tool_name]\n        if len(matches) != 1:\n            raise ValueError(\"Native tool is not registered\")\n        self.successful.pop(call.tool_id, None)\n        return matches[0]\n\n    def completed(self, call, result):\n        if not isinstance(result, ToolOutput):\n            raise TypeError(\"Native FunctionTool returned an unexpected output\")\n        if not result.is_error and call.tool_name != \"caveman_retrieve\" and len(self.successful) < 4096:\n            self.successful[call.tool_id] = call.tool_name\n        return result\n\n\n@dataclass(frozen=True)\nclass CavemanLLMTools:\n    \"\"\"Native model and tools for an application-owned loop; no scheduler.\"\"\"\n    model: LLM\n    tools: tuple[FunctionTool, ...]\n    _registration: Any\n\n    def execute(self, call: ToolSelection) -> ToolOutput:\n        tool = self._registration.selected(call)\n        return self._registration.completed(call, tool.call(**call.tool_kwargs))\n\n    async def aexecute(self, call: ToolSelection) -> ToolOutput:\n        tool = self._registration.selected(call)","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/llama_index.py#L172-L208","documentation":"Error from the completed() step of the LlamaIndex recovery flow: the native FunctionTool returned an output whose shape the middleware does not recognize. Recovery depends on reading the tool's output structure, so an unexpected payload cannot be interpreted and is surfaced as an error.","triggerScenarios":"Calling completed() with the raw function return value, a string, or a dict instead of the ToolOutput the tool call produced.","commonSituations":"Custom tool wrappers that bypass LlamaIndex's call_and_return, or manually invoking tool.fn and passing its result to completed().","solutions":["Pass the ToolOutput returned by the framework's tool invocation, not the raw function result","Wrap manual calls with tool.call(...)/call_and_return so a ToolOutput is produced","If you have a raw result, construct ToolOutput(content=..., tool_name=..., raw_input=..., raw_output=...)"],"exampleFix":"// before\nexecutor.completed(call, tool.fn(**call.tool_kwargs))\n// after\nresult = tool.call(call.tool_kwargs)\nexecutor.completed(call, result)","handlingStrategy":"type-guard","validationCode":"from llama_index.core.tools import ToolOutput\nif not isinstance(result, ToolOutput):\n    result = ToolOutput(content=str(result), tool_name=call.tool_name, raw_input=call.tool_kwargs, raw_output=result)","typeGuard":"def is_tool_output(result) -> bool:\n    return isinstance(result, ToolOutput)","tryCatchPattern":"try:\n    executor.completed(call, result)\nexcept TypeError as e:\n    log.error(\"expected ToolOutput: %s\", e)","preventionTips":["Always route tool invocations through the framework's call helpers","Never pass raw function results to completed()","Convert custom tool returns to ToolOutput explicitly"],"tags":["python","type-error","llamaindex"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}