genkit icon indicating copy to clipboard operation
genkit copied to clipboard

[JS] toolRequest is null: Tool functions aren't called if there are optional parameters and not all parameters are populated

Open AshleyTuring opened this issue 1 year ago • 0 comments

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

  1. 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" } } }
  2. Prompt "Hello my name is Bob"
  3. 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 --version at paste here v18.17.0

AshleyTuring avatar Aug 15 '24 10:08 AshleyTuring