Skip to content

Conversation

@coder543
Copy link

@coder543 coder543 commented Dec 15, 2025

This PR allows webui to give models access to two tools: a calculator and a code interpreter. The calculator is a simple expression calculator, used to enhance math abilities. The code interpreter runs arbitrary JavaScript in a (relatively isolated) Web Worker, and returns the output to the model, which can be used for more advanced analysis.

This PR also lays the groundwork for a modular tool system, such that one could easily imagine adding a Canvas tool or a Web Search tool.

AI Disclosure: I spent about 8 hours yesterday developing this with significant assistance from AI. I'm perfectly capable of writing this kind of frontend code if I had the time, but this was just a fun project. I'm sharing this PR because the result generally worked well, but I have not had time to ensure that all of the code meets my quality standards. I chose to share it anyways because I feel like tool calling is an essential feature that has been missing so far, and this implementation results in an elegant, effective user experience. I may have more time to carefully review the code changes in the near future, in which case I will update this description and the PR as needed, but I figured there was no harm in making this available in case other people were interested in having tool calling in their llama-server webui.

When an assistant message emits tool calls, the web UI...

  • Executes any enabled tools locally in the browser
  • Persists the results as role: tool messages linked via tool_call_id (including execution duration)
  • Automatically continues generation with a follow-up completion request that includes the tool outputs

Included tools

  • Calculator: evaluates a constrained math expression syntax (operators + selected Math.* functions/constants).
  • Code Interpreter (JavaScript): runs arbitrary JS in a Web Worker with a configurable timeout, capturing console
    output + the final evaluated value, with improved error reporting (line/column/snippet).

UX changes

  • Collapses assistant→tool→assistant chains into a single assistant “reasoning” thread and renders tool calls inline
    (arguments + result + timing) to avoid extra message bubbles.
    • This is probably where most of the complexity in this PR is, but it is essential to getting a good UX here. The simplest possible implementation involved creating a message bubble as the model started reasoning, then creating a separate message bubble for a tool call, then another message bubble as the model continued reasoning, and so on. It was essentially unusable. Having the UI layer collapse all of these related messages into one continuous message mirrors the experience that users expect.

Configuration & extensibility

  • Introduces a small tool registry so tools self-register with their schema + settings; the Settings UI auto-populates
    a Tools section (toggles + per-tool fields like timeout), and defaults are derived from tool registrations.

Tests

  • Adds unit + browser/e2e coverage for interpreter behavior, inline tool rendering, timeout settings UI, streaming
    reactivity/regressions, etc. These tests were created when bugs were encountered. I would be perfectly fine with throwing most of them away, but I figured there was no harm in including them.

Videos

Calculator tool

Screen.Recording.2025-12-15.at.8.10.04.AM.mov

Code Interpreter tool

Screen.Recording.2025-12-15.at.8.11.08.AM.mov

Code interpreter and calculator, including the model recovering from a syntax error in its first code interpreter attempt

Screen.Recording.2025-12-15.at.8.14.48.AM.mov

Demonstrating how tool calling works for an Instruct model

Screen.Recording.2025-12-15.at.8.12.32.AM.mov

Demonstrating how the regenerate button will correctly treat the entire response as one message, instead of regenerating just the last segment after the last tool call.

Screen.Recording.2025-12-15.at.8.39.48.AM.mov

Deleting an entire response

Screen.Recording.2025-12-15.at.8.53.48.AM.mov

Screenshots

New Settings Screen for Tools

image

Known Bugs

  1. The delete button dialog pops up a count of messages that will be deleted, but the user would only expect that they are deleting "one" message.
  2. Sometimes the server returns that there was an error in the input stream after a tool call, and I haven't been able to reliably reproduce that.

@allozaur
Copy link
Collaborator

Hey, thanks a lot for your contribution! Will take a closer look at this when also reviewing and testing #17487. These are in a way intertwining changes and we need to be thoughtful when merging changes to the master branch.

@ServeurpersoCom
Copy link
Collaborator

This approach is interesting, but for a cleaner and more extensible codebase, it would be better to implement proper MCP protocol support first. This means providing small example MCP servers in Python (calculator, web browsing, etc.), and then nothing would prevent us from adding local JavaScript execution as an additional MCP tool, using the same unified context management architecture.
#17487 implements a full MCP client with protocol-compliant transport layers (WebSocket + Streamable HTTP), proper JSON-RPC 2.0 messaging, and an agentic orchestrator that can work with any MCP server.

@coder543
Copy link
Author

coder543 commented Dec 16, 2025

MCP is a much higher bar to clear, and I don’t see it as a replacement for this. Client-side tools immediately benefit everyone, where MCP is a much more advanced, much more niche technology. It certainly gets lots of hype because it can do cool things, but having tools that exist purely in the chat app allows everyone to instantly to give their models access to a code interpreter and calculator with no additional configuration, no additional services, etc.

One could even see MCP as a subset of this client side tool registry: there could be an MCP tool which lets models interact with configured MCP servers to discover which tools they offer and then run those tools through the MCP tool plugin.

Presenting tools over MCP that are just built into the client feels like only having a hammer and seeing every problem as a nail. Tool calls are the first level abstraction here, and MCPs are an abstraction on top of that. There’s no need to use MCP to access the tool calls that are part of the application itself; MCP is meant for connecting with external systems.

@ServeurpersoCom
Copy link
Collaborator

ServeurpersoCom commented Dec 16, 2025