Chat Tools in Detail
Complete reference of every tool the chat model can call — built-ins, code sandbox, SQL, skills, sub-agents and integrations including activation, limits and security.
Kasimir's chat model is agentic: during a single answer it can call tools (functions), receive their results, and keep working on top of them — until it produces a final answer. Which tools are available in a turn depends on what the user activated in the Tools menu, which integrations are connected, and which server-side sidecars are configured. This page documents every tool exactly as implemented in the code (server/api/chat/conversations/[id]/messages.post.ts, server/utils/tools/adapters/*, app/components/chat/ChatToolsMenu.vue).
Architecture in one sentence
There are two categories: built-ins (render_chart, render_scene, web_search, fetch_page) are wired directly into the chat handler; everything else flows through the provider-adapter registry (server/utils/tools/registry.ts), which per request determines which adapters are connected/activated and feeds their tools into the LLM prompt.
How a tool call runs
For each turn the chat handler assembles a tools array from three sources: built-ins, adapter tools (via getAvailableTools), and sub-agents (ask_*). The model then runs a ReAct loop of at most MAX_ROUNDS = 8 rounds. In each round the model may call one or more tools; the handler runs them, appends results as tool messages, and calls the model again. In the final round the tools array is dropped (roundTools = round < MAX_ROUNDS - 1 ? tools : undefined) so the model is forced to answer instead of searching forever.
Phase | Behaviour |
|---|---|
Build tool list |
|
Dispatch |
|
Live feedback | Every call sends |
Result cap | Results fed live back to the model are truncated at |
Persistence | Adapter tool calls land in |
Leaked calls | If a model leaks a tool call as text (Mistral |
Robustness against broken arguments
vLLM models occasionally stream truncated tool arguments. The handler normalizes function.arguments before echoing via JSON.stringify(JSON.parse(...)); an unrecoverable payload degrades to {} (tool no-ops + model retries) instead of killing the whole turn with "Unterminated string". Tool-call IDs are also coerced to Mistral's enforced ^[a-zA-Z0-9]{9}$ shape.
Activation via the Tools menu
The Tools menu (ChatToolsMenu.vue) is a flat action list (no toggle switches): one click activates a tool for the chat state and closes the menu; active tools render as removable pills above the composer bar. The check (✓) marks the active state. Activations are sent in the request body of POST …/messages.
Menu item | Body field | Default | Semantics |
|---|---|---|---|
Web search |
| on when a provider is configured | Enables |
Company knowledge |
| empty (off) | Activating selects ALL visible ready docs; RAG, not a tool |
Data analysis |
|
| Enables |
Database (per connection) |
| empty (off) | Activates exactly the selected SQL connections |
Skills | — (always active) | self-gating | Display-only; |
Integrations |
| empty (opt-in) | An integration contributes tools ONLY when its provider ID is listed here |
Opt-in, Langdock-style
Integrations are fundamentally opt-in: a connected adapter contributes zero tools per turn unless its provider ID is in enabled_tool_providers. Exceptions that self-gate: sql (via the connection picker), code (via code_interpreter), and skills (always active). An assistant bound to the conversation (ai_apps.tool_providers) auto-enables its integrations — the user only has to establish the OAuth connection.
Built-in tools
These four are hard-coded in the chat handler. render_chart and render_scene are always registered (no provider/config needed); web_search and fetch_page only when a search provider is configured.
render_chart — inline chart
Aspect | Detail |
|---|---|
Purpose | Render numeric data as an interactive Chart.js chart in the chat bubble |
When | Always available; the model is instructed to use it after SQL queries / for tables instead of ASCII charts |
Types |
|
Input |
|
Limits | ≤ 1000 labels, ≤ 12 datasets, ≤ 1000 data points/dataset, title/label ≤ 200 chars, color ≤ 64 chars |
Security |
|
Output | SSE event |
Errors | Invalid spec ⇒ error hint to the model (incl. downsampling / |
render_scene — interactive 3D scene
Aspect | Detail |
|---|---|
Purpose | Render a simple 3D scene (room/floor plan) inline with three.js |
When | ONLY when the user explicitly wants a spatial/3D visualization; not for charts |
Input |
|
Limits | Room 0.5–100 m (height 0.5–20 m), ≤ 20 openings, ≤ 100 objects |
Security |
|
Output | SSE event |
web_search — step 1 of web research
Aspect | Detail |
|---|---|
Purpose | Returns ONLY titles + URLs + 1-line snippets (top 5) |
Activation |
|
Providers |
|
Input |
|
Limits | Top 5 results, snippet ≤ 300 chars; isolate semaphore |
Required follow-up | The system prompt enforces: never answer from snippets alone — then |
Persistence | Results become numbered |
fetch_page — step 2 of web research
Aspect | Detail |
|---|---|
Purpose | Fetches a page's readable full text (up to 30,000 chars of cleaned text) |
Input |
|
Path | Prefers the Scrapling sidecar ( |
Security |
|
Error behaviour | 403/429/451/timeout are NORMAL (cloud-IP blocks) — the prompt instructs trying ONE alternative URL and otherwise answering honestly from snippets + URLs |
Concurrency | Isolate semaphore |
"Concrete products" enforcement
When a search provider is active, the handler adds a prompt rule: for questions about specific products, manufacturers, model numbers, datasheets, standards, prices or tender (LV) lines the model MUST use web_search (then fetch_page) first and may never invent such facts from memory.
run_python — data analysis (code sandbox)
Aspect | Detail |
|---|---|
Adapter |
|
Activation |
|
Purpose | Runs a complete Python script in an isolated sandbox (pandas, numpy, matplotlib, openpyxl); no internet access |
Statefulness | Stateless — every call starts fresh, variables don't survive; always send the complete script |
File injection | The most recently claimed chat attachments ( |
Artifact return | Generated files (e.g. |
Limits | Sidecar: CPU 30 s, RAM 768 MB, FSIZE 20 MB; |
Security | Read-only rootfs, fixed subnet + iptables egress DROP (no network), fresh tmp workspace per run |
SQL tools — read-only database access
Four tools from sqlAdapter (provider sql) routed through the read-only kasimir-sqlbridge sidecar. Active once the sidecar is configured AND the user activated ≥ 1 connection in the picker (sql_connection_ids). An empty array ⇒ no SQL tools this turn.
Tool | Purpose | Key rules |
|---|---|---|
| Lists available connections (name, engine, host label, | Call FIRST; never credentials; only visible connections |
| Tables of a connection | List truncated above 20,000 chars ( |
| Columns (name, type, nullable) of a table | Use the exact table name from |
| One single read query, returns columns + rows | ONLY |
Hard invariants of the SQL adapter
The decrypted DSN never leaves a tool result. sql_query rejects anything that isn't a single SELECT/WITH statement before calling the sidecar (assertReadOnlySql); the sidecar re-checks (read-only transaction, row cap, timeouts). Access is checked per connection: own user-scope rows, any company-scope rows of the company, and — only in a project chat — project-scope rows of exactly that project. A project-bound connection is invisible in a private chat or another project. Errors are returned as a Fehler: result, not thrown (except AbortError).
use_skill — skills (progressive disclosure)
Aspect | Detail |
|---|---|
Adapter |
|
Availability | Only when active skills exist for the company (or the assistant via |
Pattern | A single |
Input |
|
Flow |
|
Cost | Purely DB-side — no external provider, no extra token spend beyond the description |
ask_* — delegation to sub-agents
When the conversation is bound to an assistant that has direct sub-agents (ai_app_subagents), one tool ask_<sanitized-id> is registered per child assistant. Input is task (the fully phrased sub-task). Execution runs via runSubagent, which enforces all guards: depth ≤ MAX_DELEGATION_DEPTH, cycle detection via a chain set (a grandchild can never loop back to a top persona), and a shared per-turn budget MAX_SUBAGENT_CALLS. When the budget is exhausted, the model is told to solve the task itself.
Integrations (provider adapters)
All of the following are opt-in via enabled_tool_providers (unless a bound assistant enables them). Each adapter checks its own connection via isAvailableFor; if absent, the sources popover shows a "Connect" CTA (connectUrl() → /integrations).
Provider | Tools (selection) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| loaded dynamically from the upstream MCP server (registered only when a server URL is set in runtime config) |
Collision rule
If two adapters register the same tool name, the one registered first wins. The order in ensureToolsReady() deliberately places first-party adapters before the MCP bridge adapter.
What chat does NOT have
No image-generation tool in chat
There is no generate_image/image-gen tool in the chat tool loop. Image generation is a workflow node (#7/#11), not a tool the chat model can call. Likewise load_table is a workflow concept, and the doc_search defined in agentTools.ts is the tool of the agentic workflow LLM node — chat instead uses RAG retrieval (built-in, not a tool call) over the composer selection for document knowledge.