From 4cb0d50a71f97a98c9821b737fc7f02d9ba38be9 Mon Sep 17 00:00:00 2001 From: afirstenberg Date: Thu, 26 Dec 2024 20:02:53 -0500 Subject: [PATCH] formatting --- .../src/chat_models.ts | 15 +- .../src/tests/chat_models.test.ts | 5 +- .../src/tests/data/chat-6-mock.json | 36 +- libs/langchain-google-common/src/types.ts | 14 +- .../src/utils/gemini.ts | 21 +- .../src/tests/chat_models.int.test.ts | 12 +- .../src/tests/chat_models.int.test.ts | 995 +++++++++--------- 7 files changed, 557 insertions(+), 541 deletions(-) diff --git a/libs/langchain-google-common/src/chat_models.ts b/libs/langchain-google-common/src/chat_models.ts index a9cfbcb951ad..15bf21fd3c94 100644 --- a/libs/langchain-google-common/src/chat_models.ts +++ b/libs/langchain-google-common/src/chat_models.ts @@ -98,7 +98,10 @@ export class ChatConnection extends AbstractGoogleLLMConnection< return true; } - computeGoogleSearchToolAdjustmentFromModel(): Exclude { + computeGoogleSearchToolAdjustmentFromModel(): Exclude< + GoogleSearchToolSetting, + boolean + > { if (this.modelName.startsWith("gemini-1.0")) { return "googleSearchRetrieval"; } else if (this.modelName.startsWith("gemini-1.5")) { @@ -108,7 +111,9 @@ export class ChatConnection extends AbstractGoogleLLMConnection< } } - computeGoogleSearchToolAdjustment(apiConfig: GeminiAPIConfig): Exclude { + computeGoogleSearchToolAdjustment( + apiConfig: GeminiAPIConfig + ): Exclude { const adj = apiConfig.googleSearchToolAdjustment; if (adj === undefined || adj === true) { return this.computeGoogleSearchToolAdjustmentFromModel(); @@ -118,8 +123,10 @@ export class ChatConnection extends AbstractGoogleLLMConnection< } buildGeminiAPI(): GoogleAIAPI { - const apiConfig: GeminiAPIConfig = this.apiConfig as GeminiAPIConfig ?? {}; - const googleSearchToolAdjustment = this.computeGoogleSearchToolAdjustment(apiConfig); + const apiConfig: GeminiAPIConfig = + (this.apiConfig as GeminiAPIConfig) ?? {}; + const googleSearchToolAdjustment = + this.computeGoogleSearchToolAdjustment(apiConfig); const geminiConfig: GeminiAPIConfig = { useSystemInstruction: this.useSystemInstruction, googleSearchToolAdjustment, diff --git a/libs/langchain-google-common/src/tests/chat_models.test.ts b/libs/langchain-google-common/src/tests/chat_models.test.ts index 59998cf24768..5726d9fd445e 100644 --- a/libs/langchain-google-common/src/tests/chat_models.test.ts +++ b/libs/langchain-google-common/src/tests/chat_models.test.ts @@ -1135,7 +1135,9 @@ describe("Mock ChatGoogle - Gemini", () => { expect(result).toHaveProperty("response_metadata"); expect(result.response_metadata).toHaveProperty("groundingMetadata"); expect(result.response_metadata).toHaveProperty("groundingSupport"); - expect(Array.isArray(result.response_metadata.groundingSupport)).toEqual(true); + expect(Array.isArray(result.response_metadata.groundingSupport)).toEqual( + true + ); expect(result.response_metadata.groundingSupport).toHaveLength(4); }); @@ -1248,7 +1250,6 @@ describe("Mock ChatGoogle - Gemini", () => { expect(record.opts.data.tools[0]).toHaveProperty("googleSearch"); }); - }); describe("Mock ChatGoogle - Anthropic", () => { diff --git a/libs/langchain-google-common/src/tests/data/chat-6-mock.json b/libs/langchain-google-common/src/tests/data/chat-6-mock.json index 65568fb1810a..796fdcf9bcee 100644 --- a/libs/langchain-google-common/src/tests/data/chat-6-mock.json +++ b/libs/langchain-google-common/src/tests/data/chat-6-mock.json @@ -40,12 +40,8 @@ "endIndex": 100, "text": "The Los Angeles Dodgers won the 2024 World Series, defeating the New York Yankees 4-1 in the series." }, - "groundingChunkIndices": [ - 0 - ], - "confidenceScores": [ - 0.95898277 - ] + "groundingChunkIndices": [0], + "confidenceScores": [0.95898277] }, { "segment": { @@ -53,12 +49,8 @@ "endIndex": 377, "text": "It was also their first World Series win in a full season since 1988." }, - "groundingChunkIndices": [ - 1 - ], - "confidenceScores": [ - 0.96841997 - ] + "groundingChunkIndices": [1], + "confidenceScores": [0.96841997] }, { "segment": { @@ -66,12 +58,8 @@ "endIndex": 508, "text": "Mookie Betts earned his third World Series ring (2018, 2020, and 2024), becoming the only active player with three championships." }, - "groundingChunkIndices": [ - 2 - ], - "confidenceScores": [ - 0.99043523 - ] + "groundingChunkIndices": [2], + "confidenceScores": [0.99043523] }, { "segment": { @@ -79,17 +67,11 @@ "endIndex": 611, "text": "Shohei Ohtani, in his first year with the Dodgers, also experienced his first post-season appearance." }, - "groundingChunkIndices": [ - 0 - ], - "confidenceScores": [ - 0.95767003 - ] + "groundingChunkIndices": [0], + "confidenceScores": [0.95767003] } ], - "webSearchQueries": [ - "2024 MLB World Series winner" - ] + "webSearchQueries": ["2024 MLB World Series winner"] }, "avgLogprobs": -0.040494912748883484 } diff --git a/libs/langchain-google-common/src/types.ts b/libs/langchain-google-common/src/types.ts index 2706d190cc78..3b702cda6f87 100644 --- a/libs/langchain-google-common/src/types.ts +++ b/libs/langchain-google-common/src/types.ts @@ -313,9 +313,9 @@ export interface GeminiCitation { } export interface GoogleTypeDate { - year: number; // 1-9999 or 0 to specify a date without a year + year: number; // 1-9999 or 0 to specify a date without a year month: number; // 1-12 or 0 to specify a year without a month and day - day: number; // Must be from 1 to 31 and valid for the year and month, or 0 to specify a year by itself or a year and month where the day isn't significant + day: number; // Must be from 1 to 31 and valid for the year and month, or 0 to specify a year by itself or a year and month where the day isn't significant } export interface GeminiGroundingMetadata { @@ -328,7 +328,7 @@ export interface GeminiGroundingMetadata { export interface GeminiSearchEntryPoint { renderedContent?: string; - sdkBlob?: string; // Base64 encoded JSON representing array of tuple. + sdkBlob?: string; // Base64 encoded JSON representing array of tuple. } export interface GeminiGroundingChunk { @@ -378,8 +378,8 @@ export interface GeminiContent { */ export interface GeminiTool { functionDeclarations?: GeminiFunctionDeclaration[]; - googleSearchRetrieval?: GoogleSearchRetrieval; // Gemini-1.5 - googleSearch?: GoogleSearch; // Gemini-2.0 + googleSearchRetrieval?: GoogleSearchRetrieval; // Gemini-1.5 + googleSearch?: GoogleSearch; // Gemini-2.0 retrieval?: VertexAIRetrieval; } @@ -395,13 +395,13 @@ export type GoogleSearchToolSetting = export const GeminiSearchToolAttributes = [ "googleSearchRetrieval", "googleSearch", -] +]; export const GeminiToolAttributes = [ "functionDeclaration", "retrieval", ...GeminiSearchToolAttributes, -] +]; export interface GoogleSearchRetrieval { dynamicRetrievalConfig?: { diff --git a/libs/langchain-google-common/src/utils/gemini.ts b/libs/langchain-google-common/src/utils/gemini.ts index e6e122e85d3f..7f532983d6a4 100644 --- a/libs/langchain-google-common/src/utils/gemini.ts +++ b/libs/langchain-google-common/src/utils/gemini.ts @@ -36,7 +36,8 @@ import type { GoogleAISafetyHandler, GeminiPartFunctionCall, GoogleAIAPI, - GeminiAPIConfig, GeminiGroundingSupport, + GeminiAPIConfig, + GeminiGroundingSupport, } from "../types.js"; import { GoogleAISafetyError } from "./safety.js"; import { MediaBlob } from "../experimental/utils/media_core.js"; @@ -757,7 +758,7 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI { ): GeminiGroundingSupport[][] { const ret: GeminiGroundingSupport[][] = []; - if (!groundingSupports || groundingSupports.length === 0){ + if (!groundingSupports || groundingSupports.length === 0) { return []; } @@ -769,7 +770,6 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI { } else { ret[partIndex] = [groundingSupport]; } - }); return ret; @@ -786,10 +786,13 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI { // Citation and grounding information connected to each part / ChatGeneration // to make sure they are available in downstream filters. - const candidate = (response?.data as GenerateContentResponseData)?.candidates?.[0]; + const candidate = (response?.data as GenerateContentResponseData) + ?.candidates?.[0]; const groundingMetadata = candidate?.groundingMetadata; const citationMetadata = candidate?.citationMetadata; - const groundingParts = groundingSupportByPart(groundingMetadata?.groundingSupports); + const groundingParts = groundingSupportByPart( + groundingMetadata?.groundingSupports + ); const ret = parts.map((part, index) => { const gen = partToChatGeneration(part); @@ -1080,7 +1083,7 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI { function searchToolName(tool: GeminiTool): string | undefined { for (const name of GeminiSearchToolAttributes) { if (name in tool) { - return name + return name; } } return undefined; @@ -1092,7 +1095,7 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI { if (orig && adj && adj !== orig) { return { [adj as string]: {}, - } + }; } else { return tool; } @@ -1108,13 +1111,13 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI { // Gemini Tools may be normalized to different tool names const langChainTools: StructuredToolParams[] = []; const otherTools: GeminiTool[] = []; - tools.forEach(tool => { + tools.forEach((tool) => { if (isLangChainTool(tool)) { langChainTools.push(tool); } else { otherTools.push(cleanGeminiTool(tool as GeminiTool)); } - }) + }); const result: GeminiTool[] = [...otherTools]; diff --git a/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts b/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts index 49faa348a82f..6d1606614bd1 100644 --- a/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts +++ b/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts @@ -67,7 +67,7 @@ const testGeminiModelNames = [ ["gemini-1.5-flash-002"], ["gemini-2.0-flash-exp"], // ["gemini-2.0-flash-thinking-exp-1219"], -] +]; /* * Some models may have usage quotas still. @@ -76,7 +76,7 @@ const testGeminiModelNames = [ const testGeminiModelDelay: Record = { "gemini-2.0-flash-exp": 5000, "gemini-2.0-flash-thinking-exp-1219": 5000, -} +}; describe.each(testGeminiModelNames)("GAuth Gemini Chat (%s)", (modelName) => { let recorder: GoogleRequestRecorder; @@ -88,9 +88,9 @@ describe.each(testGeminiModelNames)("GAuth Gemini Chat (%s)", (modelName) => { const delay = testGeminiModelDelay[modelName] ?? 0; if (delay) { - console.log(`Delaying for ${delay}ms`) + console.log(`Delaying for ${delay}ms`); // eslint-disable-next-line no-promise-executor-return - await new Promise(resolve => setTimeout(resolve,delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); } }); @@ -573,8 +573,7 @@ describe.each(testGeminiModelNames)("GAuth Gemini Chat (%s)", (modelName) => { test("Supports GoogleSearchTool", async () => { const searchTool: GeminiTool = { - googleSearch: { - }, + googleSearch: {}, }; const model = new ChatVertexAI({ modelName, @@ -611,7 +610,6 @@ describe.each(testGeminiModelNames)("GAuth Gemini Chat (%s)", (modelName) => { } expect(finalMsg.content as string).toContain("Dodgers"); }); - }); describe("GAuth Anthropic Chat", () => { diff --git a/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts b/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts index da3d3147c25f..5379533c7181 100644 --- a/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts +++ b/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts @@ -6,7 +6,8 @@ import { AIMessage, AIMessageChunk, BaseMessage, - BaseMessageChunk, BaseMessageLike, + BaseMessageChunk, + BaseMessageLike, HumanMessage, HumanMessageChunk, MessageContentComplex, @@ -22,15 +23,22 @@ import { ReadThroughBlobStore, SimpleWebBlobStore, } from "@langchain/google-common/experimental/utils/media_core"; -import {GeminiTool, GooglePlatformType, GoogleRequestRecorder} from "@langchain/google-common"; -import {BaseCallbackHandler} from "@langchain/core/callbacks/base"; -import {InMemoryStore} from "@langchain/core/stores"; -import {BlobStoreGoogleCloudStorage} from "@langchain/google-gauth"; -import {GoogleCloudStorageUri} from "@langchain/google-common/experimental/media"; -import {concat} from "@langchain/core/utils/stream"; +import { + GeminiTool, + GooglePlatformType, + GoogleRequestRecorder, +} from "@langchain/google-common"; +import { BaseCallbackHandler } from "@langchain/core/callbacks/base"; +import { InMemoryStore } from "@langchain/core/stores"; +import { BlobStoreGoogleCloudStorage } from "@langchain/google-gauth"; +import { GoogleCloudStorageUri } from "@langchain/google-common/experimental/media"; +import { concat } from "@langchain/core/utils/stream"; import fs from "fs/promises"; -import {ChatPromptTemplate, MessagesPlaceholder} from "@langchain/core/prompts"; -import {ChatGoogle, ChatGoogleInput} from "../chat_models.js"; +import { + ChatPromptTemplate, + MessagesPlaceholder, +} from "@langchain/core/prompts"; +import { ChatGoogle, ChatGoogleInput } from "../chat_models.js"; import { BlobStoreAIStudioFile } from "../media.js"; class WeatherTool extends StructuredTool { @@ -281,17 +289,29 @@ const calculatorTool = tool((_) => "no-op", { * and on which platforms? */ const testGeminiModelNames = [ - {modelName: "gemini-1.5-pro-002", platformType: "gai", apiVersion: "v1beta"}, - {modelName: "gemini-1.5-pro-002", platformType: "gcp", apiVersion: "v1"}, - {modelName: "gemini-1.5-flash-002", platformType: "gai", apiVersion: "v1beta"}, - {modelName: "gemini-1.5-flash-002", platformType: "gcp", apiVersion: "v1"}, - {modelName: "gemini-2.0-flash-exp", platformType: "gai", apiVersion: "v1beta"}, - {modelName: "gemini-2.0-flash-exp", platformType: "gcp", apiVersion: "v1"}, + { + modelName: "gemini-1.5-pro-002", + platformType: "gai", + apiVersion: "v1beta", + }, + { modelName: "gemini-1.5-pro-002", platformType: "gcp", apiVersion: "v1" }, + { + modelName: "gemini-1.5-flash-002", + platformType: "gai", + apiVersion: "v1beta", + }, + { modelName: "gemini-1.5-flash-002", platformType: "gcp", apiVersion: "v1" }, + { + modelName: "gemini-2.0-flash-exp", + platformType: "gai", + apiVersion: "v1beta", + }, + { modelName: "gemini-2.0-flash-exp", platformType: "gcp", apiVersion: "v1" }, // Flash Thinking doesn't have functions or other features // {modelName: "gemini-2.0-flash-thinking-exp", platformType: "gai"}, // {modelName: "gemini-2.0-flash-thinking-exp", platformType: "gcp"}, -] +]; /* * Some models may have usage quotas still. @@ -300,537 +320,542 @@ const testGeminiModelNames = [ const testGeminiModelDelay: Record = { "gemini-2.0-flash-exp": 10000, "gemini-2.0-flash-thinking-exp-1219": 10000, -} - -describe.each(testGeminiModelNames)("Webauth ($platformType) Gemini Chat ($modelName)", ({modelName, platformType, apiVersion}) => { - let recorder: GoogleRequestRecorder; - let callbacks: BaseCallbackHandler[]; - - function newChatGoogle(fields?: ChatGoogleInput): ChatGoogle { - // const logger = new GoogleRequestLogger(); - recorder = new GoogleRequestRecorder(); - callbacks = [recorder]; - - return new ChatGoogle({ - modelName, - platformType: platformType as GooglePlatformType, - apiVersion, - callbacks, - ...fields ?? {}, - }) - } - - beforeEach(async () => { - const delay = testGeminiModelDelay[modelName] ?? 0; - if (delay) { - console.log(`Delaying for ${delay}ms`) - // eslint-disable-next-line no-promise-executor-return - await new Promise(resolve => setTimeout(resolve,delay)); +}; + +describe.each(testGeminiModelNames)( + "Webauth ($platformType) Gemini Chat ($modelName)", + ({ modelName, platformType, apiVersion }) => { + let recorder: GoogleRequestRecorder; + let callbacks: BaseCallbackHandler[]; + + function newChatGoogle(fields?: ChatGoogleInput): ChatGoogle { + // const logger = new GoogleRequestLogger(); + recorder = new GoogleRequestRecorder(); + callbacks = [recorder]; + + return new ChatGoogle({ + modelName, + platformType: platformType as GooglePlatformType, + apiVersion, + callbacks, + ...(fields ?? {}), + }); } - }); - test("invoke", async () => { - const model = newChatGoogle(); - const res = await model.invoke("What is 1 + 1?"); - expect(res).toBeDefined(); - expect(res._getType()).toEqual("ai"); + beforeEach(async () => { + const delay = testGeminiModelDelay[modelName] ?? 0; + if (delay) { + console.log(`Delaying for ${delay}ms`); + // eslint-disable-next-line no-promise-executor-return + await new Promise((resolve) => setTimeout(resolve, delay)); + } + }); - const aiMessage = res as AIMessageChunk; - expect(aiMessage.content).toBeDefined(); + test("invoke", async () => { + const model = newChatGoogle(); + const res = await model.invoke("What is 1 + 1?"); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); - expect(typeof aiMessage.content).toBe("string"); - const text = aiMessage.content as string; - expect(text).toMatch(/(1 + 1 (equals|is|=) )?2.? ?/); + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); - expect(res).toHaveProperty("response_metadata"); - expect(res.response_metadata).not.toHaveProperty("groundingMetadata"); - expect(res.response_metadata).not.toHaveProperty("groundingSupport"); + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(text).toMatch(/(1 + 1 (equals|is|=) )?2.? ?/); - console.log(recorder); - }); + expect(res).toHaveProperty("response_metadata"); + expect(res.response_metadata).not.toHaveProperty("groundingMetadata"); + expect(res.response_metadata).not.toHaveProperty("groundingSupport"); - test(`generate`, async () => { - const model = newChatGoogle(); - const messages: BaseMessage[] = [ - new SystemMessage( - "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." - ), - new HumanMessage("Flip it"), - new AIMessage("T"), - new HumanMessage("Flip the coin again"), - ]; - const res = await model.predictMessages(messages); - expect(res).toBeDefined(); - expect(res._getType()).toEqual("ai"); + console.log(recorder); + }); - const aiMessage = res as AIMessageChunk; - expect(aiMessage.content).toBeDefined(); + test(`generate`, async () => { + const model = newChatGoogle(); + const messages: BaseMessage[] = [ + new SystemMessage( + "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." + ), + new HumanMessage("Flip it"), + new AIMessage("T"), + new HumanMessage("Flip the coin again"), + ]; + const res = await model.predictMessages(messages); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); - expect(typeof aiMessage.content).toBe("string"); - const text = aiMessage.content as string; - expect(["H", "T"]).toContainEqual(text.trim()); - }); + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); - test("stream", async () => { - const model = newChatGoogle(); - const input: BaseLanguageModelInput = new ChatPromptValue([ - new SystemMessage( - "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." - ), - new HumanMessage("Flip it"), - new AIMessage("T"), - new HumanMessage("Flip the coin again"), - ]); - const res = await model.stream(input); - const resArray: BaseMessageChunk[] = []; - for await (const chunk of res) { - resArray.push(chunk); - } - expect(resArray).toBeDefined(); - expect(resArray.length).toBeGreaterThanOrEqual(1); + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(["H", "T"]).toContainEqual(text.trim()); + }); - const lastChunk = resArray[resArray.length - 1]; - expect(lastChunk).toBeDefined(); - expect(lastChunk._getType()).toEqual("ai"); - }); + test("stream", async () => { + const model = newChatGoogle(); + const input: BaseLanguageModelInput = new ChatPromptValue([ + new SystemMessage( + "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." + ), + new HumanMessage("Flip it"), + new AIMessage("T"), + new HumanMessage("Flip the coin again"), + ]); + const res = await model.stream(input); + const resArray: BaseMessageChunk[] = []; + for await (const chunk of res) { + resArray.push(chunk); + } + expect(resArray).toBeDefined(); + expect(resArray.length).toBeGreaterThanOrEqual(1); - test("function", async () => { - const tools: GeminiTool[] = [ - { - functionDeclarations: [ - { - name: "test", - description: - "Run a test with a specific name and get if it passed or failed", - parameters: { - type: "object", - properties: { - testName: { - type: "string", - description: "The name of the test that should be run.", + const lastChunk = resArray[resArray.length - 1]; + expect(lastChunk).toBeDefined(); + expect(lastChunk._getType()).toEqual("ai"); + }); + + test("function", async () => { + const tools: GeminiTool[] = [ + { + functionDeclarations: [ + { + name: "test", + description: + "Run a test with a specific name and get if it passed or failed", + parameters: { + type: "object", + properties: { + testName: { + type: "string", + description: "The name of the test that should be run.", + }, }, + required: ["testName"], }, - required: ["testName"], }, - }, - ], - }, - ]; - const model = newChatGoogle().bind({ - tools, + ], + }, + ]; + const model = newChatGoogle().bind({ + tools, + }); + const result = await model.invoke("Run a test on the cobalt project"); + expect(result).toHaveProperty("content"); + expect(result.content).toBe(""); + const args = result?.lc_kwargs?.additional_kwargs; + expect(args).toBeDefined(); + expect(args).toHaveProperty("tool_calls"); + expect(Array.isArray(args.tool_calls)).toBeTruthy(); + expect(args.tool_calls).toHaveLength(1); + const call = args.tool_calls[0]; + expect(call).toHaveProperty("type"); + expect(call.type).toBe("function"); + expect(call).toHaveProperty("function"); + const func = call.function; + expect(func).toBeDefined(); + expect(func).toHaveProperty("name"); + expect(func.name).toBe("test"); + expect(func).toHaveProperty("arguments"); + expect(typeof func.arguments).toBe("string"); + expect(func.arguments.replaceAll("\n", "")).toBe('{"testName":"cobalt"}'); }); - const result = await model.invoke("Run a test on the cobalt project"); - expect(result).toHaveProperty("content"); - expect(result.content).toBe(""); - const args = result?.lc_kwargs?.additional_kwargs; - expect(args).toBeDefined(); - expect(args).toHaveProperty("tool_calls"); - expect(Array.isArray(args.tool_calls)).toBeTruthy(); - expect(args.tool_calls).toHaveLength(1); - const call = args.tool_calls[0]; - expect(call).toHaveProperty("type"); - expect(call.type).toBe("function"); - expect(call).toHaveProperty("function"); - const func = call.function; - expect(func).toBeDefined(); - expect(func).toHaveProperty("name"); - expect(func.name).toBe("test"); - expect(func).toHaveProperty("arguments"); - expect(typeof func.arguments).toBe("string"); - expect(func.arguments.replaceAll("\n", "")).toBe('{"testName":"cobalt"}'); - }); - test("function reply", async () => { - const tools: GeminiTool[] = [ - { - functionDeclarations: [ - { - name: "test", - description: - "Run a test with a specific name and get if it passed or failed", - parameters: { - type: "object", - properties: { - testName: { - type: "string", - description: "The name of the test that should be run.", + test("function reply", async () => { + const tools: GeminiTool[] = [ + { + functionDeclarations: [ + { + name: "test", + description: + "Run a test with a specific name and get if it passed or failed", + parameters: { + type: "object", + properties: { + testName: { + type: "string", + description: "The name of the test that should be run.", + }, }, + required: ["testName"], }, - required: ["testName"], }, - }, - ], - }, - ]; - const model = newChatGoogle().bind({ - tools, - }); - const toolResult = { - testPassed: true, - }; - const messages: BaseMessageLike[] = [ - new HumanMessage("Run a test on the cobalt project."), - new AIMessage("", { - tool_calls: [ - { - id: "test", - type: "function", - function: { - name: "test", - arguments: '{"testName":"cobalt"}', + ], + }, + ]; + const model = newChatGoogle().bind({ + tools, + }); + const toolResult = { + testPassed: true, + }; + const messages: BaseMessageLike[] = [ + new HumanMessage("Run a test on the cobalt project."), + new AIMessage("", { + tool_calls: [ + { + id: "test", + type: "function", + function: { + name: "test", + arguments: '{"testName":"cobalt"}', + }, }, - }, - ], - }), - new ToolMessage(JSON.stringify(toolResult), "test"), - ]; - const res = await model.stream(messages); - const resArray: BaseMessageChunk[] = []; - for await (const chunk of res) { - resArray.push(chunk); - } - // console.log(JSON.stringify(resArray, null, 2)); - }); + ], + }), + new ToolMessage(JSON.stringify(toolResult), "test"), + ]; + const res = await model.stream(messages); + const resArray: BaseMessageChunk[] = []; + for await (const chunk of res) { + resArray.push(chunk); + } + // console.log(JSON.stringify(resArray, null, 2)); + }); - test("withStructuredOutput", async () => { - const tool = { - name: "get_weather", - description: - "Get the weather of a specific location and return the temperature in Celsius.", - parameters: { - type: "object", - properties: { - location: { - type: "string", - description: "The name of city to get the weather for.", + test("withStructuredOutput", async () => { + const tool = { + name: "get_weather", + description: + "Get the weather of a specific location and return the temperature in Celsius.", + parameters: { + type: "object", + properties: { + location: { + type: "string", + description: "The name of city to get the weather for.", + }, }, + required: ["location"], }, - required: ["location"], - }, - }; - const model = newChatGoogle().withStructuredOutput(tool); - const result = await model.invoke("What is the weather in Paris?"); - expect(result).toHaveProperty("location"); - }); - - test("media - fileData", async () => { - class MemStore extends InMemoryStore { - get length() { - return Object.keys(this.store).length; - } - } - const aliasMemory = new MemStore(); - const aliasStore = new BackedBlobStore({ - backingStore: aliasMemory, - defaultFetchOptions: { - actionIfBlobMissing: undefined, - }, - }); - const backingStore = new BlobStoreGoogleCloudStorage({ - uriPrefix: new GoogleCloudStorageUri("gs://test-langchainjs/mediatest/"), - defaultStoreOptions: { - actionIfInvalid: "prefixPath", - }, - }); - const blobStore = new ReadThroughBlobStore({ - baseStore: aliasStore, - backingStore, - }); - const resolver = new SimpleWebBlobStore(); - const mediaManager = new MediaManager({ - store: blobStore, - resolvers: [resolver], - }); - const model = newChatGoogle({ - apiConfig: { - mediaManager, - }, + }; + const model = newChatGoogle().withStructuredOutput(tool); + const result = await model.invoke("What is the weather in Paris?"); + expect(result).toHaveProperty("location"); }); - const message: MessageContentComplex[] = [ - { - type: "text", - text: "What is in this image?", - }, - { - type: "media", - fileUri: "https://js.langchain.com/v0.2/img/brand/wordmark.png", - }, - ]; + test("media - fileData", async () => { + class MemStore extends InMemoryStore { + get length() { + return Object.keys(this.store).length; + } + } + const aliasMemory = new MemStore(); + const aliasStore = new BackedBlobStore({ + backingStore: aliasMemory, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + const backingStore = new BlobStoreGoogleCloudStorage({ + uriPrefix: new GoogleCloudStorageUri( + "gs://test-langchainjs/mediatest/" + ), + defaultStoreOptions: { + actionIfInvalid: "prefixPath", + }, + }); + const blobStore = new ReadThroughBlobStore({ + baseStore: aliasStore, + backingStore, + }); + const resolver = new SimpleWebBlobStore(); + const mediaManager = new MediaManager({ + store: blobStore, + resolvers: [resolver], + }); + const model = newChatGoogle({ + apiConfig: { + mediaManager, + }, + }); - const messages: BaseMessage[] = [ - new HumanMessageChunk({ content: message }), - ]; + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "https://js.langchain.com/v0.2/img/brand/wordmark.png", + }, + ]; - try { - const res = await model.invoke(messages); + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; - console.log(res); + try { + const res = await model.invoke(messages); - expect(res).toBeDefined(); - expect(res._getType()).toEqual("ai"); + console.log(res); - const aiMessage = res as AIMessageChunk; - expect(aiMessage.content).toBeDefined(); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); - expect(typeof aiMessage.content).toBe("string"); - const text = aiMessage.content as string; - expect(text).toMatch(/LangChain/); - } catch (e) { - console.error(e); - throw e; - } - }); + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); - test("Stream token count usage_metadata", async () => { - const model = newChatGoogle({ - temperature: 0, - maxOutputTokens: 10, - }); - let res: AIMessageChunk | null = null; - for await (const chunk of await model.stream( - "Why is the sky blue? Be concise." - )) { - if (!res) { - res = chunk; - } else { - res = res.concat(chunk); + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(text).toMatch(/LangChain/); + } catch (e) { + console.error(e); + throw e; } - } - // console.log(res); - expect(res?.usage_metadata).toBeDefined(); - if (!res?.usage_metadata) { - return; - } - expect(res.usage_metadata.input_tokens).toBeGreaterThan(1); - expect(res.usage_metadata.output_tokens).toBeGreaterThan(1); - expect(res.usage_metadata.total_tokens).toBe( - res.usage_metadata.input_tokens + res.usage_metadata.output_tokens - ); - }); - - test("streamUsage excludes token usage", async () => { - const model = newChatGoogle({ - temperature: 0, - streamUsage: false, }); - let res: AIMessageChunk | null = null; - for await (const chunk of await model.stream( - "Why is the sky blue? Be concise." - )) { - if (!res) { - res = chunk; - } else { - res = res.concat(chunk); - } - } - // console.log(res); - expect(res?.usage_metadata).not.toBeDefined(); - }); - test("Invoke token count usage_metadata", async () => { - const model = newChatGoogle({ - temperature: 0, - maxOutputTokens: 10, + test("Stream token count usage_metadata", async () => { + const model = newChatGoogle({ + temperature: 0, + maxOutputTokens: 10, + }); + let res: AIMessageChunk | null = null; + for await (const chunk of await model.stream( + "Why is the sky blue? Be concise." + )) { + if (!res) { + res = chunk; + } else { + res = res.concat(chunk); + } + } + // console.log(res); + expect(res?.usage_metadata).toBeDefined(); + if (!res?.usage_metadata) { + return; + } + expect(res.usage_metadata.input_tokens).toBeGreaterThan(1); + expect(res.usage_metadata.output_tokens).toBeGreaterThan(1); + expect(res.usage_metadata.total_tokens).toBe( + res.usage_metadata.input_tokens + res.usage_metadata.output_tokens + ); }); - const res = await model.invoke("Why is the sky blue? Be concise."); - // console.log(res); - expect(res?.usage_metadata).toBeDefined(); - if (!res?.usage_metadata) { - return; - } - expect(res.usage_metadata.input_tokens).toBeGreaterThan(1); - expect(res.usage_metadata.output_tokens).toBeGreaterThan(1); - expect(res.usage_metadata.total_tokens).toBe( - res.usage_metadata.input_tokens + res.usage_metadata.output_tokens - ); - }); - test("Streaming true constructor param will stream", async () => { - const modelWithStreaming = newChatGoogle({ - maxOutputTokens: 50, - streaming: true, + test("streamUsage excludes token usage", async () => { + const model = newChatGoogle({ + temperature: 0, + streamUsage: false, + }); + let res: AIMessageChunk | null = null; + for await (const chunk of await model.stream( + "Why is the sky blue? Be concise." + )) { + if (!res) { + res = chunk; + } else { + res = res.concat(chunk); + } + } + // console.log(res); + expect(res?.usage_metadata).not.toBeDefined(); }); - let totalTokenCount = 0; - let tokensString = ""; - const result = await modelWithStreaming.invoke("What is 1 + 1?", { - callbacks: [ - ...callbacks, - { - handleLLMNewToken: (tok) => { - totalTokenCount += 1; - tokensString += tok; - }, - }, - ], + test("Invoke token count usage_metadata", async () => { + const model = newChatGoogle({ + temperature: 0, + maxOutputTokens: 10, + }); + const res = await model.invoke("Why is the sky blue? Be concise."); + // console.log(res); + expect(res?.usage_metadata).toBeDefined(); + if (!res?.usage_metadata) { + return; + } + expect(res.usage_metadata.input_tokens).toBeGreaterThan(1); + expect(res.usage_metadata.output_tokens).toBeGreaterThan(1); + expect(res.usage_metadata.total_tokens).toBe( + res.usage_metadata.input_tokens + res.usage_metadata.output_tokens + ); }); - expect(result).toBeDefined(); - expect(result.content).toBe(tokensString); + test("Streaming true constructor param will stream", async () => { + const modelWithStreaming = newChatGoogle({ + maxOutputTokens: 50, + streaming: true, + }); + + let totalTokenCount = 0; + let tokensString = ""; + const result = await modelWithStreaming.invoke("What is 1 + 1?", { + callbacks: [ + ...callbacks, + { + handleLLMNewToken: (tok) => { + totalTokenCount += 1; + tokensString += tok; + }, + }, + ], + }); - expect(totalTokenCount).toBeGreaterThan(1); - }); + expect(result).toBeDefined(); + expect(result.content).toBe(tokensString); - test("Can force a model to invoke a tool", async () => { - const model = newChatGoogle(); - const modelWithTools = model.bind({ - tools: [calculatorTool, weatherTool], - tool_choice: "calculator", + expect(totalTokenCount).toBeGreaterThan(1); }); - const result = await modelWithTools.invoke( - "Whats the weather like in paris today? What's 1836 plus 7262?" - ); - - expect(result.tool_calls).toHaveLength(1); - expect(result.tool_calls?.[0]).toBeDefined(); - if (!result.tool_calls?.[0]) return; - expect(result.tool_calls?.[0].name).toBe("calculator"); - expect(result.tool_calls?.[0].args).toHaveProperty("expression"); - }); + test("Can force a model to invoke a tool", async () => { + const model = newChatGoogle(); + const modelWithTools = model.bind({ + tools: [calculatorTool, weatherTool], + tool_choice: "calculator", + }); + + const result = await modelWithTools.invoke( + "Whats the weather like in paris today? What's 1836 plus 7262?" + ); + + expect(result.tool_calls).toHaveLength(1); + expect(result.tool_calls?.[0]).toBeDefined(); + if (!result.tool_calls?.[0]) return; + expect(result.tool_calls?.[0].name).toBe("calculator"); + expect(result.tool_calls?.[0].args).toHaveProperty("expression"); + }); - test(`stream tools`, async () => { - const model = newChatGoogle(); + test(`stream tools`, async () => { + const model = newChatGoogle(); - const weatherTool = tool( - (_) => "The weather in San Francisco today is 18 degrees and sunny.", - { - name: "current_weather_tool", - description: "Get the current weather for a given location.", - schema: z.object({ - location: z.string().describe("The location to get the weather for."), - }), + const weatherTool = tool( + (_) => "The weather in San Francisco today is 18 degrees and sunny.", + { + name: "current_weather_tool", + description: "Get the current weather for a given location.", + schema: z.object({ + location: z + .string() + .describe("The location to get the weather for."), + }), + } + ); + + const modelWithTools = model.bindTools([weatherTool]); + const stream = await modelWithTools.stream( + "Whats the weather like today in San Francisco?" + ); + let finalChunk: AIMessageChunk | undefined; + for await (const chunk of stream) { + finalChunk = !finalChunk ? chunk : concat(finalChunk, chunk); } - ); - const modelWithTools = model.bindTools([weatherTool]); - const stream = await modelWithTools.stream( - "Whats the weather like today in San Francisco?" - ); - let finalChunk: AIMessageChunk | undefined; - for await (const chunk of stream) { - finalChunk = !finalChunk ? chunk : concat(finalChunk, chunk); - } + expect(finalChunk).toBeDefined(); + if (!finalChunk) return; - expect(finalChunk).toBeDefined(); - if (!finalChunk) return; + const toolCalls = finalChunk.tool_calls; + expect(toolCalls).toBeDefined(); + if (!toolCalls) { + throw new Error("tool_calls not in response"); + } + expect(toolCalls.length).toBe(1); + expect(toolCalls[0].name).toBe("current_weather_tool"); + expect(toolCalls[0].args).toHaveProperty("location"); + }); - const toolCalls = finalChunk.tool_calls; - expect(toolCalls).toBeDefined(); - if (!toolCalls) { - throw new Error("tool_calls not in response"); + async function fileToBase64(filePath: string): Promise { + const fileData = await fs.readFile(filePath); + const base64String = Buffer.from(fileData).toString("base64"); + return base64String; } - expect(toolCalls.length).toBe(1); - expect(toolCalls[0].name).toBe("current_weather_tool"); - expect(toolCalls[0].args).toHaveProperty("location"); - }); - async function fileToBase64(filePath: string): Promise { - const fileData = await fs.readFile(filePath); - const base64String = Buffer.from(fileData).toString("base64"); - return base64String; - } + test("Gemini can understand audio", async () => { + // Update this with the correct path to an audio file on your machine. + const audioPath = + "../langchain-google-genai/src/tests/data/gettysburg10.wav"; + const audioMimeType = "audio/wav"; - test("Gemini can understand audio", async () => { - // Update this with the correct path to an audio file on your machine. - const audioPath = - "../langchain-google-genai/src/tests/data/gettysburg10.wav"; - const audioMimeType = "audio/wav"; + const model = newChatGoogle({ + temperature: 0, + maxRetries: 0, + }); - const model = newChatGoogle({ - temperature: 0, - maxRetries: 0, - }); + const audioBase64 = await fileToBase64(audioPath); - const audioBase64 = await fileToBase64(audioPath); + const prompt = ChatPromptTemplate.fromMessages([ + new MessagesPlaceholder("audio"), + ]); - const prompt = ChatPromptTemplate.fromMessages([ - new MessagesPlaceholder("audio"), - ]); + const chain = prompt.pipe(model); + const response = await chain.invoke({ + audio: new HumanMessage({ + content: [ + { + type: "media", + mimeType: audioMimeType, + data: audioBase64, + }, + { + type: "text", + text: "Summarize the content in this audio. ALso, what is the speaker's tone?", + }, + ], + }), + }); - const chain = prompt.pipe(model); - const response = await chain.invoke({ - audio: new HumanMessage({ - content: [ - { - type: "media", - mimeType: audioMimeType, - data: audioBase64, - }, - { - type: "text", - text: "Summarize the content in this audio. ALso, what is the speaker's tone?", - }, - ], - }), + expect(typeof response.content).toBe("string"); + expect((response.content as string).length).toBeGreaterThan(15); }); - expect(typeof response.content).toBe("string"); - expect((response.content as string).length).toBeGreaterThan(15); - }); - - test("Supports GoogleSearchRetrievalTool", async () => { - const searchRetrievalTool = { - googleSearchRetrieval: { - dynamicRetrievalConfig: { - mode: "MODE_DYNAMIC", - dynamicThreshold: 0.7, // default is 0.7 + test("Supports GoogleSearchRetrievalTool", async () => { + const searchRetrievalTool = { + googleSearchRetrieval: { + dynamicRetrievalConfig: { + mode: "MODE_DYNAMIC", + dynamicThreshold: 0.7, // default is 0.7 + }, }, - }, - }; - const model = newChatGoogle({ - temperature: 0, - maxRetries: 0, - }).bindTools([searchRetrievalTool]); - - const result = await model.invoke("Who won the 2024 MLB World Series?"); - expect(result.content as string).toContain("Dodgers"); - expect(result).toHaveProperty("response_metadata"); - expect(result.response_metadata).toHaveProperty("groundingMetadata"); - expect(result.response_metadata).toHaveProperty("groundingSupport"); - }); + }; + const model = newChatGoogle({ + temperature: 0, + maxRetries: 0, + }).bindTools([searchRetrievalTool]); + + const result = await model.invoke("Who won the 2024 MLB World Series?"); + expect(result.content as string).toContain("Dodgers"); + expect(result).toHaveProperty("response_metadata"); + expect(result.response_metadata).toHaveProperty("groundingMetadata"); + expect(result.response_metadata).toHaveProperty("groundingSupport"); + }); - test("Supports GoogleSearchTool", async () => { - const searchTool: GeminiTool = { - googleSearch: { - }, - }; - const model = newChatGoogle({ - temperature: 0, - maxRetries: 0, - }).bindTools([searchTool]); - - const result = await model.invoke("Who won the 2024 MLB World Series?"); - expect(result.content as string).toContain("Dodgers"); - expect(result).toHaveProperty("response_metadata"); - expect(result.response_metadata).toHaveProperty("groundingMetadata"); - expect(result.response_metadata).toHaveProperty("groundingSupport"); - }); + test("Supports GoogleSearchTool", async () => { + const searchTool: GeminiTool = { + googleSearch: {}, + }; + const model = newChatGoogle({ + temperature: 0, + maxRetries: 0, + }).bindTools([searchTool]); + + const result = await model.invoke("Who won the 2024 MLB World Series?"); + expect(result.content as string).toContain("Dodgers"); + expect(result).toHaveProperty("response_metadata"); + expect(result.response_metadata).toHaveProperty("groundingMetadata"); + expect(result.response_metadata).toHaveProperty("groundingSupport"); + }); - test("Can stream GoogleSearchRetrievalTool", async () => { - const searchRetrievalTool = { - googleSearchRetrieval: { - dynamicRetrievalConfig: { - mode: "MODE_DYNAMIC", - dynamicThreshold: 0.7, // default is 0.7 + test("Can stream GoogleSearchRetrievalTool", async () => { + const searchRetrievalTool = { + googleSearchRetrieval: { + dynamicRetrievalConfig: { + mode: "MODE_DYNAMIC", + dynamicThreshold: 0.7, // default is 0.7 + }, }, - }, - }; - const model = newChatGoogle({ - temperature: 0, - maxRetries: 0, - }).bindTools([searchRetrievalTool]); - - const stream = await model.stream("Who won the 2024 MLB World Series?"); - let finalMsg: AIMessageChunk | undefined; - for await (const msg of stream) { - finalMsg = finalMsg ? concat(finalMsg, msg) : msg; - } - if (!finalMsg) { - throw new Error("finalMsg is undefined"); - } - expect(finalMsg.content as string).toContain("Dodgers"); - }); - -}); + }; + const model = newChatGoogle({ + temperature: 0, + maxRetries: 0, + }).bindTools([searchRetrievalTool]); + + const stream = await model.stream("Who won the 2024 MLB World Series?"); + let finalMsg: AIMessageChunk | undefined; + for await (const msg of stream) { + finalMsg = finalMsg ? concat(finalMsg, msg) : msg; + } + if (!finalMsg) { + throw new Error("finalMsg is undefined"); + } + expect(finalMsg.content as string).toContain("Dodgers"); + }); + } +);