Problem or Motivation
Persist Tool Call References in Chat
Context
When the chat LLM makes tool calls (e.g. search_cards, get_event, semantic_search), the UI briefly shows a "Looking up: search_cards" indicator that disappears as soon as the tool finishes. This happens because activeTools is a transient store — tools are added on "calling" and removed on "done". The user wants persistent, inline references showing which tools were called, without showing the tool response.
Current Flow
-
Backend (engine/laya/pipeline/chat.py:549-582): Streams chat_stream_tool events with status: "calling" and status: "done" for each tool. The final chat_stream_done event sends the completed ChatMessage — but does not include tool_calls_log even though it's stored in the DB.
-
Frontend (ui/src/lib/components/chat/ChatSidebar.svelte:215-224): Adds/removes tool names from activeTools store. The indicator at line 582-590 renders only while activeTools.length > 0. On chat_stream_done (line 240), activeTools is cleared entirely.
-
ChatMessage type (ui/src/lib/api/types.ts:672-680): Has no tool_calls field. The backend model (engine/laya/models/chat.py:11-18) also lacks it. But tool_calls_json is stored in the DB and is available.
Proposed Solution
Approach
Track completed tool calls per-message during streaming and include them in the final ChatMessage, then render them as a persistent inline element in ChatMessage.svelte.
1. Add tool_calls field to ChatMessage (backend + frontend types)
engine/laya/models/chat.py — Add to ChatMessage:
tool_calls: list[str] = Field(default_factory=list) # tool names used
ui/src/lib/api/types.ts — Add to ChatMessage:
2. Include tool_calls in the chat_stream_done event
engine/laya/pipeline/chat.py (~line 647-658) — Add tool_calls to the final yield:
"tool_calls": [t["name"] for t in tool_calls_log],
Also update the non-streaming path (~line 433) to include the same field.
3. Include tool_calls when loading conversation history
engine/laya/api/chat_api.py (~line 217-250) — Add tool_calls_json to the SELECT query and parse it into the ChatMessage response.
4. Track completed tools during streaming (frontend)
ui/src/lib/stores/chat.ts — Add a new store:
export const completedTools = writable<string[]>([]);
ui/src/lib/components/chat/ChatSidebar.svelte — In the chat_stream_tool handler (line 215-224):
- On
status === 'done': also append the tool name to completedTools
- On
chat_stream_done: merge completedTools into the final message's tool_calls field, then reset completedTools
- On
chat_stream_start: reset completedTools
5. Render persistent tool call references in ChatMessage.svelte
Below the thinking block and above the main response, render a compact line for messages that have tool_calls:
{#if !isUser && message.tool_calls?.length}
<div class="mb-1.5 flex flex-wrap items-center gap-1.5 text-laya-micro text-surface-500">
<svg ...><!-- small search/tool icon --></svg>
{#each message.tool_calls as tool}
<span class="rounded bg-surface-700/50 px-1.5 py-0.5">{humanize(tool)}</span>
{/each}
</div>
{/if}
A humanize() function maps tool names to readable labels (e.g. search_cards → Search cards, get_event → Get event).
6. Keep the existing live indicator
The activeTools indicator in ChatSidebar.svelte (line 582-590) stays as-is for the pulsing "in progress" state. It naturally clears when tools finish. The persistent display lives inside ChatMessage.svelte on the completed message.
Files to Modify
| File |
Change |
engine/laya/models/chat.py |
Add tool_calls: list[str] to ChatMessage |
engine/laya/pipeline/chat.py |
Include tool_calls in both streaming and non-streaming done events |
engine/laya/api/chat_api.py |
SELECT and parse tool_calls_json in message loading |
ui/src/lib/api/types.ts |
Add tool_calls?: string[] to ChatMessage |
ui/src/lib/stores/chat.ts |
Add completedTools store |
ui/src/lib/components/chat/ChatSidebar.svelte |
Track completed tools during stream, merge on done |
ui/src/lib/components/chat/ChatMessage.svelte |
Render persistent tool call pills |
Verification
- Start dev server (
scripts/dev.sh)
- Open chat, ask a question that triggers tool calls (e.g. "What cards do I have today?")
- Confirm: pulsing indicator shows during tool execution, then tool names persist as pills on the completed message
- Reload the conversation — confirm tool call pills still appear (loaded from DB)
- Check messages with no tool calls render normally (no empty pill row)
Alternatives Considered
No response
Component
UI (Svelte frontend)
How important is this to you?
Nice to have
Additional Context
No response
Problem or Motivation
Persist Tool Call References in Chat
Context
When the chat LLM makes tool calls (e.g.
search_cards,get_event,semantic_search), the UI briefly shows a "Looking up: search_cards" indicator that disappears as soon as the tool finishes. This happens becauseactiveToolsis a transient store — tools are added on"calling"and removed on"done". The user wants persistent, inline references showing which tools were called, without showing the tool response.Current Flow
Backend (
engine/laya/pipeline/chat.py:549-582): Streamschat_stream_toolevents withstatus: "calling"andstatus: "done"for each tool. The finalchat_stream_doneevent sends the completedChatMessage— but does not includetool_calls_logeven though it's stored in the DB.Frontend (
ui/src/lib/components/chat/ChatSidebar.svelte:215-224): Adds/removes tool names fromactiveToolsstore. The indicator at line 582-590 renders only whileactiveTools.length > 0. Onchat_stream_done(line 240),activeToolsis cleared entirely.ChatMessage type (
ui/src/lib/api/types.ts:672-680): Has notool_callsfield. The backend model (engine/laya/models/chat.py:11-18) also lacks it. Buttool_calls_jsonis stored in the DB and is available.Proposed Solution
Approach
Track completed tool calls per-message during streaming and include them in the final ChatMessage, then render them as a persistent inline element in ChatMessage.svelte.
1. Add
tool_callsfield to ChatMessage (backend + frontend types)engine/laya/models/chat.py— Add toChatMessage:ui/src/lib/api/types.ts— Add toChatMessage:2. Include tool_calls in the
chat_stream_doneeventengine/laya/pipeline/chat.py(~line 647-658) — Addtool_callsto the final yield:Also update the non-streaming path (~line 433) to include the same field.
3. Include tool_calls when loading conversation history
engine/laya/api/chat_api.py(~line 217-250) — Addtool_calls_jsonto the SELECT query and parse it into the ChatMessage response.4. Track completed tools during streaming (frontend)
ui/src/lib/stores/chat.ts— Add a new store:ui/src/lib/components/chat/ChatSidebar.svelte— In thechat_stream_toolhandler (line 215-224):status === 'done': also append the tool name tocompletedToolschat_stream_done: mergecompletedToolsinto the final message'stool_callsfield, then resetcompletedToolschat_stream_start: resetcompletedTools5. Render persistent tool call references in ChatMessage.svelte
Below the thinking block and above the main response, render a compact line for messages that have
tool_calls:{#if !isUser && message.tool_calls?.length} <div class="mb-1.5 flex flex-wrap items-center gap-1.5 text-laya-micro text-surface-500"> <svg ...><!-- small search/tool icon --></svg> {#each message.tool_calls as tool} <span class="rounded bg-surface-700/50 px-1.5 py-0.5">{humanize(tool)}</span> {/each} </div> {/if}A
humanize()function maps tool names to readable labels (e.g.search_cards→Search cards,get_event→Get event).6. Keep the existing live indicator
The
activeToolsindicator in ChatSidebar.svelte (line 582-590) stays as-is for the pulsing "in progress" state. It naturally clears when tools finish. The persistent display lives inside ChatMessage.svelte on the completed message.Files to Modify
engine/laya/models/chat.pytool_calls: list[str]toChatMessageengine/laya/pipeline/chat.pytool_callsin both streaming and non-streaming done eventsengine/laya/api/chat_api.pytool_calls_jsonin message loadingui/src/lib/api/types.tstool_calls?: string[]toChatMessageui/src/lib/stores/chat.tscompletedToolsstoreui/src/lib/components/chat/ChatSidebar.svelteui/src/lib/components/chat/ChatMessage.svelteVerification
scripts/dev.sh)Alternatives Considered
No response
Component
UI (Svelte frontend)
How important is this to you?
Nice to have
Additional Context
No response