genkit
genkit copied to clipboard
[JS] toolRequest is null: Tool functions aren't called if there are optional parameters and not all parameters are populated
Describe the bug GenKit doesn't return toolRequest if a tool function is requested with optional parameters.
Here is the tool function definition
export const creatSaveNameTool = (database: IDatabase, agentDocId: string) => {
return action(
{
name: "saveName", // give this a unique name however you'd like
description:
"Call after the user has provided their first name or their last name or their first and last names e.g. in response to the question like: can you tell me your first and last name",
inputSchema: z.object({
firstName: z.string().optional(),
lastName: z.string().optional(),
}),
outputSchema: z.object({
savedSuccessfully: z.boolean(),
}),
},
async (input: { firstName?: string; lastName?: string }) => {
const userId = agentDocId.split("_")[0];
const updateData: any = {};
if (input.firstName) {
updateData.firstName = input.firstName.trim();
}
if (input.lastName) {
updateData.lastName = input.lastName.trim();
}
await database.set("table", userId, updateData, true);
return { savedSuccessfully: true };
}
);
};
To Reproduce
- Define a prompt that requires a function call like When the user provides their first and/or last name, call the saveName tool (if you support calling tools) using a format similar to the following (note: make it compatible with GenKit): { "toolCall": { "name": "saveName", "arguments": { "firstName": "user's first name", "lastName": "user's last name" } } }
- Prompt "Hello my name is Bob"
- The content returns {"message":{"role":"model","content":[{"text":"{\n "toolCall": {\n "name": "saveName",\n "arguments": {\n "firstName": "Bob",\n "lastName": null\n }\n }\n}\n```"}]},"index":0,"usage":{},"finishReason":"stop","finishMessage":"","custom":{"safetyRatings":[{"category":"HARM_CATEGORY_SEXUALLY_EXPLICIT","probability":"NEGLIGIBLE"},{"category":"HARM_CATEGORY_HATE_SPEECH","probability":"NEGLIGIBLE"},{"category":"HARM_CATEGORY_HARASSMENT","probability":"NEGLIGIBLE"},{"category":"HARM_CATEGORY_DANGEROUS_CONTENT","probability":"NEGLIGIBLE"}]}}
Expected behavior
The framework should populate the toolRequest object
To fix the issue I have had to build a work around from the promptResult returned from generate():
getLastToolRequestEntry(promptResult: GenerateResponse) {
const historyEntries = promptResult.toHistory();
const lastEntry =
historyEntries.length > 0
? historyEntries[historyEntries.length - 1]
: null;
// If toolRequest is already present, return it
if (lastEntry?.content[0]?.toolRequest) {
return lastEntry.content[0].toolRequest;
}
// Otherwise, search for "toolCall" in the content
// this is a bug in gemini and other models whereabout if you
// have an optional parameter the GenKit framework doesn't actually call the function
const content = lastEntry?.content[0]?.text || "";
if (content.includes("toolCall")) {
try {
// Extract the JSON part containing "toolCall"
const toolCallStart = content.indexOf("{");
const toolCallEnd = content.lastIndexOf("}") + 1;
const toolCallJson = content.substring(toolCallStart, toolCallEnd);
// Attempt to parse the extracted JSON
const parsedToolCall = JSON.parse(toolCallJson);
// Construct the toolRequest object
const toolRequest = {
name: parsedToolCall.toolCall.name,
ref: "forcedParse",
input: parsedToolCall.toolCall.arguments,
};
// Return the constructed toolRequest
return toolRequest;
} catch (error) {
console.error("Failed to parse toolCall JSON:", error);
return null;
}
}
// Return null if no toolRequest found
return null;
}
NOTE it works if you provide a first and last name
Screenshots If applicable, add screenshots to help explain your problem.
Runtime (please complete the following information):
- Windows
** Node version
- run
node --versionat paste here v18.17.0