Skip to content

[Feature]: Persist Tool Call References in Chat #6

Description

@aayushch

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

  1. 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.

  2. 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.

  3. 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:

tool_calls?: string[];

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_cardsSearch cards, get_eventGet 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

  1. Start dev server (scripts/dev.sh)
  2. Open chat, ask a question that triggers tool calls (e.g. "What cards do I have today?")
  3. Confirm: pulsing indicator shows during tool execution, then tool names persist as pills on the completed message
  4. Reload the conversation — confirm tool call pills still appear (loaded from DB)
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions