diff --git a/libs/langchain-google-genai/src/tests/chat_models.int.test.ts b/libs/langchain-google-genai/src/tests/chat_models.int.test.ts index 66820bb2f859..f2f8c919e6ab 100644 --- a/libs/langchain-google-genai/src/tests/chat_models.int.test.ts +++ b/libs/langchain-google-genai/src/tests/chat_models.int.test.ts @@ -16,9 +16,7 @@ import { import { StructuredTool } from "@langchain/core/tools"; import { z } from "zod"; import { FunctionDeclarationSchemaType } from "@google/generative-ai"; -import { zodToJsonSchema } from "zod-to-json-schema"; import { ChatGoogleGenerativeAI } from "../chat_models.js"; -import { removeAdditionalProperties } from "../utils/zod_to_genai_parameters.js"; const dummyToolResponse = `[{"title":"Weather in New York City","url":"https://www.weatherapi.com/","content":"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.71, 'lon': -74.01, 'tz_id': 'America/New_York', 'localtime_epoch': 1718659486, 'localtime': '2024-06-17 17:24'}, 'current': {'last_updated_epoch': 1718658900, 'last_updated': '2024-06-17 17:15', 'temp_c': 27.8, 'temp_f': 82.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 159, 'wind_dir': 'SSE', 'pressure_mb': 1021.0, 'pressure_in': 30.15, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 58, 'cloud': 25, 'feelslike_c': 29.0, 'feelslike_f': 84.2, 'windchill_c': 26.9, 'windchill_f': 80.5, 'heatindex_c': 27.9, 'heatindex_f': 82.2, 'dewpoint_c': 17.1, 'dewpoint_f': 62.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 7.0, 'gust_mph': 18.3, 'gust_kph': 29.4}}","score":0.98192,"raw_content":null},{"title":"New York, NY Monthly Weather | AccuWeather","url":"https://www.accuweather.com/en/us/new-york/10021/june-weather/349727","content":"Get the monthly weather forecast for New York, NY, including daily high/low, historical averages, to help you plan ahead.","score":0.97504,"raw_content":null}]`; @@ -505,49 +503,3 @@ test("Invoke token count usage_metadata", async () => { res.usage_metadata.input_tokens + res.usage_metadata.output_tokens ); }); - -test("removeAdditionalProperties can remove all instances of additionalProperties", async () => { - function extractKeys(obj: Record, keys: string[] = []) { - for (const key in obj) { - keys.push(key); - if (typeof obj[key] === "object" && obj[key] !== null) { - extractKeys(obj[key], keys); - } - } - return keys; - } - - const idealResponseSchema = z.object({ - idealResponse: z - .string() - .optional() - .describe("The ideal response to the question"), - }); - const questionSchema = z.object({ - question: z.string().describe("Question text"), - type: z.enum(["singleChoice", "multiChoice"]).describe("Question type"), - options: z.array(z.string()).describe("List of possible answers"), - correctAnswer: z - .string() - .optional() - .describe("correct answer from the possible answers"), - idealResponses: z - .array(idealResponseSchema) - .describe("Array of ideal responses to the question"), - }); - - const schema = z.object({ - questions: z.array(questionSchema).describe("Array of question objects"), - }); - - const parsedSchemaArr = removeAdditionalProperties(zodToJsonSchema(schema)); - const arrSchemaKeys = extractKeys(parsedSchemaArr); - expect( - arrSchemaKeys.find((key) => key === "additionalProperties") - ).toBeUndefined(); - const parsedSchemaObj = removeAdditionalProperties(zodToJsonSchema(schema)); - const arrSchemaObj = extractKeys(parsedSchemaObj); - expect( - arrSchemaObj.find((key) => key === "additionalProperties") - ).toBeUndefined(); -}); diff --git a/libs/langchain-google-genai/src/tests/chat_models.test.ts b/libs/langchain-google-genai/src/tests/chat_models.test.ts index f61800b80e4e..36991a15ebca 100644 --- a/libs/langchain-google-genai/src/tests/chat_models.test.ts +++ b/libs/langchain-google-genai/src/tests/chat_models.test.ts @@ -1,6 +1,9 @@ import { test } from "@jest/globals"; import type { HarmBlockThreshold, HarmCategory } from "@google/generative-ai"; +import { z } from "zod"; +import { zodToJsonSchema } from "zod-to-json-schema"; import { ChatGoogleGenerativeAI } from "../chat_models.js"; +import { removeAdditionalProperties } from "../utils/zod_to_genai_parameters.js"; test("Google AI - `temperature` must be in range [0.0,1.0]", async () => { expect( @@ -74,3 +77,54 @@ test("Google AI - `safetySettings` category array must be unique", async () => { }) ).toThrow(); }); + +test("removeAdditionalProperties can remove all instances of additionalProperties", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function extractKeys(obj: Record, keys: string[] = []) { + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + keys.push(key); + if (typeof obj[key] === "object" && obj[key] !== null) { + extractKeys(obj[key], keys); + } + } + } + return keys; + } + + const idealResponseSchema = z.object({ + idealResponse: z + .string() + .optional() + .describe("The ideal response to the question"), + }); + const questionSchema = z.object({ + question: z.string().describe("Question text"), + type: z.enum(["singleChoice", "multiChoice"]).describe("Question type"), + options: z.array(z.string()).describe("List of possible answers"), + correctAnswer: z + .string() + .optional() + .describe("correct answer from the possible answers"), + idealResponses: z + .array(idealResponseSchema) + .describe("Array of ideal responses to the question"), + }); + + const schema = z.object({ + questions: z.array(questionSchema).describe("Array of question objects"), + }); + + const parsedSchemaArr = removeAdditionalProperties(zodToJsonSchema(schema)); + const arrSchemaKeys = extractKeys(parsedSchemaArr); + expect( + arrSchemaKeys.find((key) => key === "additionalProperties") + ).toBeUndefined(); + const parsedSchemaObj = removeAdditionalProperties( + zodToJsonSchema(questionSchema) + ); + const arrSchemaObj = extractKeys(parsedSchemaObj); + expect( + arrSchemaObj.find((key) => key === "additionalProperties") + ).toBeUndefined(); +}); diff --git a/libs/langchain-google-genai/src/utils/zod_to_genai_parameters.ts b/libs/langchain-google-genai/src/utils/zod_to_genai_parameters.ts index 31ca36fa9114..a570327a6467 100644 --- a/libs/langchain-google-genai/src/utils/zod_to_genai_parameters.ts +++ b/libs/langchain-google-genai/src/utils/zod_to_genai_parameters.ts @@ -24,25 +24,30 @@ export interface GenerativeAIJsonSchemaDirty extends GenerativeAIJsonSchema { } export function removeAdditionalProperties( + // eslint-disable-next-line @typescript-eslint/no-explicit-any obj: Record ): GenerativeAIJsonSchema { if (typeof obj === "object" && obj !== null) { + const newObj = { ...obj }; + if ( - "additionalProperties" in obj && - typeof obj.additionalProperties === "boolean" + "additionalProperties" in newObj && + typeof newObj.additionalProperties === "boolean" ) { - delete obj.additionalProperties; + delete newObj.additionalProperties; } - for (const key in obj) { - if (key in obj) { - if (Array.isArray(obj[key])) { - obj[key] = obj[key].map(removeAdditionalProperties); - } else if (typeof obj[key] === "object" && obj[key] !== null) { - obj[key] = removeAdditionalProperties(obj[key]); + for (const key in newObj) { + if (key in newObj) { + if (Array.isArray(newObj[key])) { + newObj[key] = newObj[key].map(removeAdditionalProperties); + } else if (typeof newObj[key] === "object" && newObj[key] !== null) { + newObj[key] = removeAdditionalProperties(newObj[key]); } } } + + return newObj as GenerativeAIJsonSchema; } return obj as GenerativeAIJsonSchema;