JuliusBrussee/caveman · error · TypeError
Native FunctionTool returned an unexpected output
Error message
Native FunctionTool returned an unexpected output
What it means
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.
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=...)
Example fix
// before executor.completed(call, tool.fn(**call.tool_kwargs)) // after result = tool.call(call.tool_kwargs) executor.completed(call, result)
Defensive patterns
Strategy: type-guard
Validate before calling
from llama_index.core.tools import ToolOutput
if not isinstance(result, ToolOutput):
result = ToolOutput(content=str(result), tool_name=call.tool_name, raw_input=call.tool_kwargs, raw_output=result) Type guard
def is_tool_output(result) -> bool:
return isinstance(result, ToolOutput) Try / catch
try:
executor.completed(call, result)
except TypeError as e:
log.error("expected ToolOutput: %s", e) Prevention
- Always route tool invocations through the framework's call helpers
- Never pass raw function results to completed()
- Convert custom tool returns to ToolOutput explicitly
When it happens
Trigger: Calling completed() with the raw function return value, a string, or a dict instead of the ToolOutput the tool call produced.
Common situations: Custom tool wrappers that bypass LlamaIndex's call_and_return, or manually invoking tool.fn and passing its result to completed().
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Expected a native ToolSelection
- Expected an existing native LlamaIndex LLM
- Agno scope resolver must return a Caveman Scope
- AutoGen requires a stable Caveman Scope for each agent or…
- Caveman delegates through public chat_with_tools methods
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/26ffc6f075cc100f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/llama_index.py:190
and self.metadata.get_parameters_dict() == RECOVERY_SCHEMA)
def invocation(self):
return _Invocation(self, self.scope, self.tools, self.successful, True)
def selected(self, call: ToolSelection):
if self.binding is not None and not self.registered(self.tools, self.invocation()):
raise ValueError("Native executor registration changed")
if not isinstance(call, ToolSelection) or not isinstance(call.tool_id, str) or not call.tool_id or not isinstance(call.tool_kwargs, dict):
raise TypeError("Expected a native ToolSelection")
matches = [tool for tool in self.tools if tool.metadata.name == call.tool_name]
if len(matches) != 1:
raise ValueError("Native tool is not registered")
self.successful.pop(call.tool_id, None)
return matches[0]
def completed(self, call, result):
if not isinstance(result, ToolOutput):
raise TypeError("Native FunctionTool returned an unexpected output")
if not result.is_error and call.tool_name != "caveman_retrieve" and len(self.successful) < 4096:
self.successful[call.tool_id] = call.tool_name
return result
@dataclass(frozen=True)
class CavemanLLMTools:
"""Native model and tools for an application-owned loop; no scheduler."""
model: LLM
tools: tuple[FunctionTool, ...]
_registration: Any
def execute(self, call: ToolSelection) -> ToolOutput:
tool = self._registration.selected(call)
return self._registration.completed(call, tool.call(**call.tool_kwargs))
async def aexecute(self, call: ToolSelection) -> ToolOutput:
tool = self._registration.selected(call)View on GitHub (pinned to 3ee70a1026)