Description
When useLLM is configured with toolsConfig, any error that happens on the tool-execution path is silently dropped. The error reaches neither llm.error nor the await llm.sendMessage(...) try/catch, so apps that wire an ErrorBanner to those signals show nothing while the JS console reports an unhandled rejection (or a Logger.error).
Where it happens
Two swallowing points in the controller:
-
parseToolCall catches every parse failure and returns []:
https://github.com/software-mansion/react-native-executorch/blob/main/packages/react-native-executorch/src/utils/llm.ts#L42-L45
} catch (e) {
Logger.error(e);
return [];
}
-
LLMController.sendMessage invokes executeToolCallback fire-and-forget — no .catch, not awaited:
https://github.com/software-mansion/react-native-executorch/blob/main/packages/react-native-executorch/src/controllers/LLMController.ts#L427-L441
if (this.toolsConfig) {
const toolCalls = parseToolCall(response);
for (const toolCall of toolCalls) {
this.toolsConfig
.executeToolCallback(toolCall)
.then((toolResponse: string | null) => { ... });
}
}
Reproduction
- Open the
llm demo app → Tool calling screen.
- Switch the picker to a non-tool-trained model (e.g. Llama 3.2 1B QLoRA).
- Send a tool-shaped prompt (
What events do I have today?).
- JS logs an error from
parseToolCall (or an unhandled rejection if the callback throws), but the on-screen ErrorBanner stays empty.
Expected
Tool-side errors should propagate so apps can display them. Concretely:
LLMController.sendMessage should await the tool callbacks under try/catch and forward any failure into the existing error path the hook already exposes as error.
parseToolCall parse failures are a softer case — empty result is sometimes correct (model legitimately decided not to call a tool). At minimum, distinguish "no tool call in output" from "malformed tool-call JSON" and surface the latter (e.g. via a typed warning or an error channel).
Notes
Description
When
useLLMis configured withtoolsConfig, any error that happens on the tool-execution path is silently dropped. The error reaches neitherllm.errornor theawait llm.sendMessage(...)try/catch, so apps that wire anErrorBannerto those signals show nothing while the JS console reports an unhandled rejection (or aLogger.error).Where it happens
Two swallowing points in the controller:
parseToolCallcatches every parse failure and returns[]:https://github.com/software-mansion/react-native-executorch/blob/main/packages/react-native-executorch/src/utils/llm.ts#L42-L45
LLMController.sendMessageinvokesexecuteToolCallbackfire-and-forget — no.catch, not awaited:https://github.com/software-mansion/react-native-executorch/blob/main/packages/react-native-executorch/src/controllers/LLMController.ts#L427-L441
Reproduction
llmdemo app → Tool calling screen.What events do I have today?).parseToolCall(or an unhandled rejection if the callback throws), but the on-screenErrorBannerstays empty.Expected
Tool-side errors should propagate so apps can display them. Concretely:
LLMController.sendMessageshouldawaitthe tool callbacks under try/catch and forward any failure into the existing error path the hook already exposes aserror.parseToolCallparse failures are a softer case — empty result is sometimes correct (model legitimately decided not to call a tool). At minimum, distinguish "no tool call in output" from "malformed tool-call JSON" and surface the latter (e.g. via a typed warning or anerrorchannel).Notes
main.