SimpleChatTC:Propogate toolcall id through tool call chain

Use HTMLElement's dataset to maintain tool call id along with
the element which maintains the toolname.

Pass it along to the tools manager and inturn the actual tool
calls and through them to the web worker handling the tool call
related code and inturn returning it back as part of the obj
which is used to return the tool call result.

Embed the tool call id, function name and function result into
the content field of chat message in terms of a xml structure

Also make use of tool role to send back the tool call result.
Do note that currently the id, name and content are all embedded
into the content field of the tool role message sent to the
ai engine on the server.

NOTE: Use the user query entry area for showing tool call result
in the above mentioned xml form, as well as for user to enter
their own queries. Based on presence of the xml format data at
beginning the logic will treat it has a tool result and if not
then as a normal user query.

The css has been updated to help show tool results/msgs in a
lightyellow background
This commit is contained in:
hanishkvc 2025-10-15 01:20:27 +05:30
parent 2bb3d747e6
commit ebc7f88b53
5 changed files with 53 additions and 17 deletions

View File

@ -21,6 +21,9 @@
.role-user {
background-color: lightgray;
}
.role-tool {
background-color: lightyellow;
}
.role-trim {
background-color: lightpink;
}

View File

@ -77,6 +77,17 @@ class ChatMessageEx {
this.trimmedContent = "";
}
/**
* Create a all in one tool call result string
* @param {string} toolCallId
* @param {string} toolName
* @param {string} toolResult
*/
static createToolCallResultAllInOne(toolCallId, toolName, toolResult) {
return `<tool_response> <id>${toolCallId}</id> <name>${toolName}</name> <content>${toolResult}</content> </tool_response>`;
}
/**
* Update based on the drip by drip data got from network in streaming mode.
* Tries to support both Chat and Completion endpoints
@ -561,15 +572,16 @@ class SimpleChat {
* Call the requested tool/function.
* Returns undefined, if the call was placed successfully
* Else some appropriate error message will be returned.
* @param {string} toolcallid
* @param {string} toolname
* @param {string} toolargs
*/
async handle_toolcall(toolname, toolargs) {
async handle_toolcall(toolcallid, toolname, toolargs) {
if (toolname === "") {
return "Tool/Function call name not specified"
}
try {
return await tools.tool_call(toolname, toolargs)
return await tools.tool_call(toolcallid, toolname, toolargs)
} catch (/** @type {any} */error) {
return `Tool/Function call raised an exception:${error.name}:${error.message}`
}
@ -633,11 +645,13 @@ class MultiChatUI {
if (ar.has_toolcall()) {
this.elDivTool.hidden = false
this.elInToolName.value = ar.ns.tool_calls[0].function.name
this.elInToolName.dataset.tool_call_id = ar.ns.tool_calls[0].id
this.elInToolArgs.value = ar.ns.tool_calls[0].function.arguments
this.elBtnTool.disabled = false
} else {
this.elDivTool.hidden = true
this.elInToolName.value = ""
this.elInToolName.dataset.tool_call_id = ""
this.elInToolArgs.value = ""
this.elBtnTool.disabled = true
}
@ -697,9 +711,9 @@ class MultiChatUI {
this.handle_tool_run(this.curChatId);
})
tools.setup((name, data)=>{
tools.setup((id, name, data)=>{
clearTimeout(this.idTimeOut)
this.elInUser.value = `<tool_response>${data}</tool_response>`
this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(id, name, data);
this.ui_reset_userinput(false)
})
@ -744,6 +758,14 @@ class MultiChatUI {
/**
* Handle user query submit request, wrt specified chat session.
* NOTE: Currently the user query entry area is used for
* * showing and allowing edits by user wrt tool call results
* in a predfined simple xml format,
* ie before they submit tool result to ai engine on server
* * as well as for user to enter their own queries.
* Based on presence of the predefined xml format data at beginning
* the logic will treat it has a tool result and if not then as a
* normal user query.
* @param {string} chatId
* @param {string} apiEP
*/
@ -768,7 +790,11 @@ class MultiChatUI {
console.debug(`WARN:SimpleChat:MCUI:${chatId}:HandleUserSubmit:Ignoring empty user input...`);
return;
}
chat.add(new ChatMessageEx(Roles.User, content))
if (content.startsWith("<tool_response>")) {
chat.add(new ChatMessageEx(Roles.Tool, content))
} else {
chat.add(new ChatMessageEx(Roles.User, content))
}
chat.show(this.elDivChat);
let theUrl = ApiEP.Url(gMe.baseURL, apiEP);
@ -808,13 +834,17 @@ class MultiChatUI {
this.elInUser.value = "toolcall in progress...";
this.elInUser.disabled = true;
let toolname = this.elInToolName.value.trim()
let toolResult = await chat.handle_toolcall(toolname, this.elInToolArgs.value)
let toolCallId = this.elInToolName.dataset.tool_call_id;
if (toolCallId === undefined) {
toolCallId = "??? ToolCallId Missing ???"
}
let toolResult = await chat.handle_toolcall(toolCallId, toolname, this.elInToolArgs.value)
if (toolResult !== undefined) {
this.elInUser.value = `<tool_response>${toolResult}</tool_response>`
this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, toolResult);
this.ui_reset_userinput(false)
} else {
this.idTimeOut = setTimeout(() => {
this.elInUser.value = `<tool_response>Tool/Function call ${toolname} taking too much time, aborting...</tool_response>`
this.elInUser.value = ChatMessageEx.createToolCallResultAllInOne(toolCallId, toolname, `Tool/Function call ${toolname} taking too much time, aborting...`);
this.ui_reset_userinput(false)
}, 10000)
}

View File

@ -32,11 +32,12 @@ let js_meta = {
/**
* Implementation of the javascript interpretor logic. Minimal skeleton for now.
* ALERT: Has access to the javascript web worker environment and can mess with it and beyond
* @param {string} toolcallid
* @param {string} toolname
* @param {any} obj
*/
function js_run(toolname, obj) {
gToolsWorker.postMessage({ name: toolname, code: obj["code"]})
function js_run(toolcallid, toolname, obj) {
gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: obj["code"]})
}
@ -62,11 +63,12 @@ let calc_meta = {
/**
* Implementation of the simple calculator logic. Minimal skeleton for now.
* ALERT: Has access to the javascript web worker environment and can mess with it and beyond
* @param {string} toolcallid
* @param {string} toolname
* @param {any} obj
*/
function calc_run(toolname, obj) {
gToolsWorker.postMessage({ name: toolname, code: `console.log(${obj["arithexpr"]})`})
function calc_run(toolcallid, toolname, obj) {
gToolsWorker.postMessage({ id: toolcallid, name: toolname, code: `console.log(${obj["arithexpr"]})`})
}

View File

@ -32,11 +32,11 @@ export function meta() {
/**
* Setup the callback that will be called when ever message
* is recieved from the Tools Web Worker.
* @param {(name: string, data: string) => void} cb
* @param {(id: string, name: string, data: string) => void} cb
*/
export function setup(cb) {
gToolsWorker.onmessage = function (ev) {
cb(ev.data.name, ev.data.data)
cb(ev.data.id, ev.data.name, ev.data.data)
}
}
@ -45,14 +45,15 @@ export function setup(cb) {
* Try call the specified tool/function call.
* Returns undefined, if the call was placed successfully
* Else some appropriate error message will be returned.
* @param {string} toolcallid
* @param {string} toolname
* @param {string} toolargs
*/
export async function tool_call(toolname, toolargs) {
export async function tool_call(toolcallid, toolname, toolargs) {
for (const fn in tc_switch) {
if (fn == toolname) {
try {
tc_switch[fn]["handler"](fn, JSON.parse(toolargs))
tc_switch[fn]["handler"](toolcallid, fn, JSON.parse(toolargs))
return undefined
} catch (/** @type {any} */error) {
return `Tool/Function call raised an exception:${error.name}:${error.message}`

View File

@ -21,5 +21,5 @@ self.onmessage = function (ev) {
console.log(`\n\nTool/Function call "${ev.data.name}" raised an exception:${error.name}:${error.message}\n\n`)
}
tconsole.console_revert()
self.postMessage({ name: ev.data.name, data: tconsole.gConsoleStr})
self.postMessage({ id: ev.data.id, name: ev.data.name, data: tconsole.gConsoleStr})
}