\nAZURE_OPENAI_API_VERSION="2024-02-01"`,
+ dependencies: "@langchain/openai",
+ },
+ aws: {
+ value: "aws",
+ label: "AWS",
+ default: false,
+ text: `import { BedrockEmbeddings } from "@langchain/aws";\n\nconst ${embeddingsVarName} = new BedrockEmbeddings(${awsParams});`,
+ envs: `BEDROCK_AWS_REGION=your-region`,
+ dependencies: "@langchain/aws",
+ },
+ vertex: {
+ value: "vertex",
+ label: "VertexAI",
+ default: false,
+ text: `import { VertexAIEmbeddings } from "@langchain/google-vertexai";\n\nconst ${embeddingsVarName} = new VertexAIEmbeddings(${vertexParams});`,
+ envs: `GOOGLE_APPLICATION_CREDENTIALS=credentials.json`,
+ dependencies: "@langchain/google-vertexai",
+ },
+ mistral: {
+ value: "mistral",
+ label: "MistralAI",
+ default: false,
+ text: `import { MistralAIEmbeddings } from "@langchain/mistralai";\n\nconst ${embeddingsVarName} = new MistralAIEmbeddings(${mistralParams});`,
+ envs: `MISTRAL_API_KEY=your-api-key`,
+ dependencies: "@langchain/mistralai",
+ },
+ cohere: {
+ value: "cohereParams",
+ label: "Cohere",
+ default: false,
+ text: `import { CohereEmbeddings } from "@langchain/cohere";\n\nconst ${embeddingsVarName} = new CohereEmbeddings(${cohereParams});`,
+ envs: `COHERE_API_KEY=your-api-key`,
+ dependencies: "@langchain/cohere",
+ },
+ };
+
+ const displayedTabs = providers.map((provider) => tabs[provider]);
+
+ return (
+
+
Pick your embedding model:
+
+ {displayedTabs.map((tab) => (
+
+ Install dependencies
+ {tab.dependencies}
+ {tab.envs}
+ {tab.text}
+
+ ))}
+
+
+ );
+}
diff --git a/docs/core_docs/src/theme/VectorStoreTabs.js b/docs/core_docs/src/theme/VectorStoreTabs.js
new file mode 100644
index 000000000000..a8ff549e70db
--- /dev/null
+++ b/docs/core_docs/src/theme/VectorStoreTabs.js
@@ -0,0 +1,105 @@
+import React from "react";
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+import CodeBlock from "@theme-original/CodeBlock";
+import Npm2Yarn from "@theme/Npm2Yarn";
+
+export default function VectorStoreTabs(props) {
+ const { customVarName } = props;
+
+ const vectorStoreVarName = customVarName ?? "vectorStore";
+
+ const tabItems = [
+ {
+ value: "Memory",
+ label: "Memory",
+ text: `import { MemoryVectorStore } from "langchain/vectorstores/memory";\n\nconst ${vectorStoreVarName} = new MemoryVectorStore(embeddings);`,
+ dependencies: "langchain",
+ default: true,
+ },
+ {
+ value: "Chroma",
+ label: "Chroma",
+ text: `import { Chroma } from "@langchain/community/vectorstores/chroma";\n\nconst ${vectorStoreVarName} = new Chroma(embeddings, {\n collectionName: "a-test-collection",\n});`,
+ dependencies: "@langchain/community",
+ default: true,
+ },
+ {
+ value: "FAISS",
+ label: "FAISS",
+ text: `import { FaissStore } from "@langchain/community/vectorstores/faiss";\n\nconst ${vectorStoreVarName} = new FaissStore(embeddings, {});`,
+ dependencies: "@langchain/community",
+ default: false,
+ },
+ {
+ value: "MongoDB",
+ label: "MongoDB",
+ text: `import { MongoDBAtlasVectorSearch } from "@langchain/mongodb"
+import { MongoClient } from "mongodb";
+
+const client = new MongoClient(process.env.MONGODB_ATLAS_URI || "");
+const collection = client
+ .db(process.env.MONGODB_ATLAS_DB_NAME)
+ .collection(process.env.MONGODB_ATLAS_COLLECTION_NAME);
+
+const ${vectorStoreVarName} = new MongoDBAtlasVectorSearch(embeddings, {
+ collection: collection,
+ indexName: "vector_index",
+ textKey: "text",
+ embeddingKey: "embedding",
+});`,
+ dependencies: "@langchain/mongodb",
+ default: false,
+ },
+ {
+ value: "PGVector",
+ label: "PGVector",
+ text: `import PGVectorStore from "@langchain/community/vectorstores/pgvector";
+
+const ${vectorStoreVarName} = await PGVectorStore.initialize(embeddings, {})`,
+ dependencies: "@langchain/community",
+ default: false,
+ },
+ {
+ value: "Pinecone",
+ label: "Pinecone",
+ text: `import { PineconeStore } from "@langchain/pinecone";
+import { Pinecone as PineconeClient } from "@pinecone-database/pinecone";
+
+const pinecone = new PineconeClient();
+const ${vectorStoreVarName} = new PineconeStore(embeddings, {
+ pineconeIndex,
+ maxConcurrency: 5,
+});`,
+ dependencies: "@langchain/pinecone",
+ default: false,
+ },
+ {
+ value: "Qdrant",
+ label: "Qdrant",
+ text: `import { QdrantVectorStore } from "@langchain/qdrant";
+
+const ${vectorStoreVarName} = await QdrantVectorStore.fromExistingCollection(embeddings, {
+ url: process.env.QDRANT_URL,
+ collectionName: "langchainjs-testing",
+});`,
+ dependencies: "@langchain/qdrant",
+ default: false,
+ },
+ ];
+
+ return (
+
+
Pick your vector store:
+
+ {tabItems.map((tab) => (
+
+ Install dependencies
+ {tab.dependencies}
+ {tab.text}
+
+ ))}
+
+
+ );
+}
diff --git a/examples/src/memory/file.ts b/examples/src/memory/file.ts
index 5728ec3af26d..e8791f5fbcf3 100644
--- a/examples/src/memory/file.ts
+++ b/examples/src/memory/file.ts
@@ -66,6 +66,6 @@ const sessions = await chatHistory.getAllSessions();
console.log(sessions);
/*
[
- { sessionId: 'langchain-test-session', context: { title: "Introducing Jim" } }
+ { id: 'langchain-test-session', context: { title: "Introducing Jim" } }
]
*/
diff --git a/examples/src/tools/google_trends.ts b/examples/src/tools/google_trends.ts
new file mode 100644
index 000000000000..25cf5f174ba3
--- /dev/null
+++ b/examples/src/tools/google_trends.ts
@@ -0,0 +1,9 @@
+import { SERPGoogleTrendsTool } from "@langchain/community/tools/google_trends";
+
+export async function run() {
+ const tool = new SERPGoogleTrendsTool();
+
+ const res = await tool.invoke("Monster");
+
+ console.log(res);
+}
diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore
index 5064c1f14c79..4fde6ded00ff 100644
--- a/libs/langchain-community/.gitignore
+++ b/libs/langchain-community/.gitignore
@@ -70,10 +70,18 @@ tools/google_places.cjs
tools/google_places.js
tools/google_places.d.ts
tools/google_places.d.cts
+tools/google_trends.cjs
+tools/google_trends.js
+tools/google_trends.d.ts
+tools/google_trends.d.cts
tools/google_routes.cjs
tools/google_routes.js
tools/google_routes.d.ts
tools/google_routes.d.cts
+tools/google_scholar.cjs
+tools/google_scholar.js
+tools/google_scholar.d.ts
+tools/google_scholar.d.cts
tools/ifttt.cjs
tools/ifttt.js
tools/ifttt.d.ts
@@ -798,6 +806,10 @@ stores/message/mongodb.cjs
stores/message/mongodb.js
stores/message/mongodb.d.ts
stores/message/mongodb.d.cts
+stores/message/neo4j.cjs
+stores/message/neo4j.js
+stores/message/neo4j.d.ts
+stores/message/neo4j.d.cts
stores/message/planetscale.cjs
stores/message/planetscale.js
stores/message/planetscale.d.ts
diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js
index a6b1b299f83c..40dbe23bdc9c 100644
--- a/libs/langchain-community/langchain.config.js
+++ b/libs/langchain-community/langchain.config.js
@@ -51,7 +51,9 @@ export const config = {
"tools/google_calendar": "tools/google_calendar/index",
"tools/google_custom_search": "tools/google_custom_search",
"tools/google_places": "tools/google_places",
+ "tools/google_trends": "tools/google_trends",
"tools/google_routes": "tools/google_routes",
+ "tools/google_scholar": "tools/google_scholar",
"tools/ifttt": "tools/ifttt",
"tools/searchapi": "tools/searchapi",
"tools/searxng_search": "tools/searxng_search",
@@ -250,6 +252,7 @@ export const config = {
"stores/message/ioredis": "stores/message/ioredis",
"stores/message/momento": "stores/message/momento",
"stores/message/mongodb": "stores/message/mongodb",
+ "stores/message/neo4j": "stores/message/neo4j",
"stores/message/planetscale": "stores/message/planetscale",
"stores/message/postgres": "stores/message/postgres",
"stores/message/redis": "stores/message/redis",
@@ -474,6 +477,7 @@ export const config = {
"stores/message/ipfs_datastore",
"stores/message/momento",
"stores/message/mongodb",
+ "stores/message/neo4j",
"stores/message/planetscale",
"stores/message/postgres",
"stores/message/redis",
diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json
index 6cdbe97e2664..b759b36153af 100644
--- a/libs/langchain-community/package.json
+++ b/libs/langchain-community/package.json
@@ -1,6 +1,6 @@
{
"name": "@langchain/community",
- "version": "0.3.18",
+ "version": "0.3.19",
"description": "Third-party integrations for LangChain.js",
"type": "module",
"engines": {
@@ -874,6 +874,15 @@
"import": "./tools/google_places.js",
"require": "./tools/google_places.cjs"
},
+ "./tools/google_trends": {
+ "types": {
+ "import": "./tools/google_trends.d.ts",
+ "require": "./tools/google_trends.d.cts",
+ "default": "./tools/google_trends.d.ts"
+ },
+ "import": "./tools/google_trends.js",
+ "require": "./tools/google_trends.cjs"
+ },
"./tools/google_routes": {
"types": {
"import": "./tools/google_routes.d.ts",
@@ -883,6 +892,15 @@
"import": "./tools/google_routes.js",
"require": "./tools/google_routes.cjs"
},
+ "./tools/google_scholar": {
+ "types": {
+ "import": "./tools/google_scholar.d.ts",
+ "require": "./tools/google_scholar.d.cts",
+ "default": "./tools/google_scholar.d.ts"
+ },
+ "import": "./tools/google_scholar.js",
+ "require": "./tools/google_scholar.cjs"
+ },
"./tools/ifttt": {
"types": {
"import": "./tools/ifttt.d.ts",
@@ -2512,6 +2530,15 @@
"import": "./stores/message/mongodb.js",
"require": "./stores/message/mongodb.cjs"
},
+ "./stores/message/neo4j": {
+ "types": {
+ "import": "./stores/message/neo4j.d.ts",
+ "require": "./stores/message/neo4j.d.cts",
+ "default": "./stores/message/neo4j.d.ts"
+ },
+ "import": "./stores/message/neo4j.js",
+ "require": "./stores/message/neo4j.cjs"
+ },
"./stores/message/planetscale": {
"types": {
"import": "./stores/message/planetscale.d.ts",
@@ -3191,10 +3218,18 @@
"tools/google_places.js",
"tools/google_places.d.ts",
"tools/google_places.d.cts",
+ "tools/google_trends.cjs",
+ "tools/google_trends.js",
+ "tools/google_trends.d.ts",
+ "tools/google_trends.d.cts",
"tools/google_routes.cjs",
"tools/google_routes.js",
"tools/google_routes.d.ts",
"tools/google_routes.d.cts",
+ "tools/google_scholar.cjs",
+ "tools/google_scholar.js",
+ "tools/google_scholar.d.ts",
+ "tools/google_scholar.d.cts",
"tools/ifttt.cjs",
"tools/ifttt.js",
"tools/ifttt.d.ts",
@@ -3919,6 +3954,10 @@
"stores/message/mongodb.js",
"stores/message/mongodb.d.ts",
"stores/message/mongodb.d.cts",
+ "stores/message/neo4j.cjs",
+ "stores/message/neo4j.js",
+ "stores/message/neo4j.d.ts",
+ "stores/message/neo4j.d.cts",
"stores/message/planetscale.cjs",
"stores/message/planetscale.js",
"stores/message/planetscale.d.ts",
diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts
index fdef37989705..eb5de253b404 100644
--- a/libs/langchain-community/src/load/import_constants.ts
+++ b/libs/langchain-community/src/load/import_constants.ts
@@ -131,6 +131,7 @@ export const optionalImportEntrypoints: string[] = [
"langchain_community/stores/message/ioredis",
"langchain_community/stores/message/momento",
"langchain_community/stores/message/mongodb",
+ "langchain_community/stores/message/neo4j",
"langchain_community/stores/message/planetscale",
"langchain_community/stores/message/postgres",
"langchain_community/stores/message/redis",
diff --git a/libs/langchain-community/src/load/import_map.ts b/libs/langchain-community/src/load/import_map.ts
index cfc0af93456c..2ec7b20bc542 100644
--- a/libs/langchain-community/src/load/import_map.ts
+++ b/libs/langchain-community/src/load/import_map.ts
@@ -11,7 +11,9 @@ export * as tools__dynamic from "../tools/dynamic.js";
export * as tools__dataforseo_api_search from "../tools/dataforseo_api_search.js";
export * as tools__google_custom_search from "../tools/google_custom_search.js";
export * as tools__google_places from "../tools/google_places.js";
+export * as tools__google_trends from "../tools/google_trends.js";
export * as tools__google_routes from "../tools/google_routes.js";
+export * as tools__google_scholar from "../tools/google_scholar.js";
export * as tools__ifttt from "../tools/ifttt.js";
export * as tools__searchapi from "../tools/searchapi.js";
export * as tools__searxng_search from "../tools/searxng_search.js";
diff --git a/libs/langchain-community/src/load/import_type.ts b/libs/langchain-community/src/load/import_type.ts
index 097584aef493..61efffe903c7 100644
--- a/libs/langchain-community/src/load/import_type.ts
+++ b/libs/langchain-community/src/load/import_type.ts
@@ -53,6 +53,7 @@ export interface SecretMap {
REMOTE_RETRIEVER_AUTH_BEARER?: string;
REPLICATE_API_TOKEN?: string;
SEARXNG_API_BASE?: string;
+ SERPAPI_API_KEY?: string;
TENCENT_SECRET_ID?: string;
TENCENT_SECRET_KEY?: string;
TOGETHER_AI_API_KEY?: string;
diff --git a/libs/langchain-community/src/stores/message/file_system.ts b/libs/langchain-community/src/stores/message/file_system.ts
index f81af5f7a4ef..0e49b5ee95dd 100644
--- a/libs/langchain-community/src/stores/message/file_system.ts
+++ b/libs/langchain-community/src/stores/message/file_system.ts
@@ -189,8 +189,8 @@ export class FileSystemChatMessageHistory extends BaseListChatMessageHistory {
async getAllSessions(): Promise {
await this.init();
const userSessions = store[this.userId]
- ? Object.values(store[this.userId]).map((session) => ({
- id: session.id,
+ ? Object.entries(store[this.userId]).map(([id, session]) => ({
+ id,
context: session.context,
}))
: [];
diff --git a/libs/langchain-community/src/stores/message/neo4j.ts b/libs/langchain-community/src/stores/message/neo4j.ts
new file mode 100644
index 000000000000..a5f132900470
--- /dev/null
+++ b/libs/langchain-community/src/stores/message/neo4j.ts
@@ -0,0 +1,160 @@
+import neo4j, { Driver, Record, auth } from "neo4j-driver";
+import { v4 as uuidv4 } from "uuid";
+import { BaseListChatMessageHistory } from "@langchain/core/chat_history";
+import {
+ BaseMessage,
+ mapStoredMessagesToChatMessages,
+} from "@langchain/core/messages";
+
+export type Neo4jChatMessageHistoryConfigInput = {
+ sessionId?: string | number;
+ sessionNodeLabel?: string;
+ messageNodeLabel?: string;
+ url: string;
+ username: string;
+ password: string;
+ windowSize?: number;
+};
+
+const defaultConfig = {
+ sessionNodeLabel: "ChatSession",
+ messageNodeLabel: "ChatMessage",
+ windowSize: 3,
+};
+
+export class Neo4jChatMessageHistory extends BaseListChatMessageHistory {
+ lc_namespace: string[] = ["langchain", "stores", "message", "neo4j"];
+
+ sessionId: string | number;
+
+ sessionNodeLabel: string;
+
+ messageNodeLabel: string;
+
+ windowSize: number;
+
+ private driver: Driver;
+
+ constructor({
+ sessionId = uuidv4(),
+ sessionNodeLabel = defaultConfig.sessionNodeLabel,
+ messageNodeLabel = defaultConfig.messageNodeLabel,
+ url,
+ username,
+ password,
+ windowSize = defaultConfig.windowSize,
+ }: Neo4jChatMessageHistoryConfigInput) {
+ super();
+
+ this.sessionId = sessionId;
+ this.sessionNodeLabel = sessionNodeLabel;
+ this.messageNodeLabel = messageNodeLabel;
+ this.windowSize = windowSize;
+
+ if (url && username && password) {
+ try {
+ this.driver = neo4j.driver(url, auth.basic(username, password));
+ } catch (e: any) {
+ throw new Error(
+ `Could not create a Neo4j driver instance. Please check the connection details.\nCause: ${e.message}`
+ );
+ }
+ } else {
+ throw new Error("Neo4j connection details not provided.");
+ }
+ }
+
+ static async initialize(
+ props: Neo4jChatMessageHistoryConfigInput
+ ): Promise {
+ const instance = new Neo4jChatMessageHistory(props);
+
+ try {
+ await instance.verifyConnectivity();
+ } catch (e: any) {
+ throw new Error(
+ `Could not verify connection to the Neo4j database.\nCause: ${e.message}`
+ );
+ }
+
+ return instance;
+ }
+
+ async verifyConnectivity() {
+ const connectivity = await this.driver.getServerInfo();
+ return connectivity;
+ }
+
+ async getMessages(): Promise {
+ const getMessagesCypherQuery = `
+ MERGE (chatSession:${this.sessionNodeLabel} {id: $sessionId})
+ WITH chatSession
+ MATCH (chatSession)-[:LAST_MESSAGE]->(lastMessage)
+ MATCH p=(lastMessage)<-[:NEXT*0..${this.windowSize * 2 - 1}]-()
+ WITH p, length(p) AS length
+ ORDER BY length DESC LIMIT 1
+ UNWIND reverse(nodes(p)) AS node
+ RETURN {data:{content: node.content}, type:node.type} AS result
+ `;
+
+ try {
+ const { records } = await this.driver.executeQuery(
+ getMessagesCypherQuery,
+ {
+ sessionId: this.sessionId,
+ }
+ );
+ const results = records.map((record: Record) => record.get("result"));
+
+ return mapStoredMessagesToChatMessages(results);
+ } catch (e: any) {
+ throw new Error(`Ohno! Couldn't get messages.\nCause: ${e.message}`);
+ }
+ }
+
+ async addMessage(message: BaseMessage): Promise {
+ const addMessageCypherQuery = `
+ MERGE (chatSession:${this.sessionNodeLabel} {id: $sessionId})
+ WITH chatSession
+ OPTIONAL MATCH (chatSession)-[lastMessageRel:LAST_MESSAGE]->(lastMessage)
+ CREATE (chatSession)-[:LAST_MESSAGE]->(newLastMessage:${this.messageNodeLabel})
+ SET newLastMessage += {type:$type, content:$content}
+ WITH newLastMessage, lastMessageRel, lastMessage
+ WHERE lastMessage IS NOT NULL
+ CREATE (lastMessage)-[:NEXT]->(newLastMessage)
+ DELETE lastMessageRel
+ `;
+
+ try {
+ await this.driver.executeQuery(addMessageCypherQuery, {
+ sessionId: this.sessionId,
+ type: message.getType(),
+ content: message.content,
+ });
+ } catch (e: any) {
+ throw new Error(`Ohno! Couldn't add message.\nCause: ${e.message}`);
+ }
+ }
+
+ async clear() {
+ const clearMessagesCypherQuery = `
+ MATCH p=(chatSession:${this.sessionNodeLabel} {id: $sessionId})-[:LAST_MESSAGE]->(lastMessage)<-[:NEXT*0..]-()
+ UNWIND nodes(p) as node
+ DETACH DELETE node
+ `;
+
+ try {
+ await this.driver.executeQuery(clearMessagesCypherQuery, {
+ sessionId: this.sessionId,
+ });
+ } catch (e: any) {
+ throw new Error(
+ `Ohno! Couldn't clear chat history.\nCause: ${e.message}`
+ );
+ }
+ }
+
+ async close() {
+ await this.driver.close();
+ }
+}
diff --git a/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts b/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts
index 1610bcbee0e1..0229caa30fe1 100644
--- a/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts
+++ b/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts
@@ -143,5 +143,7 @@ test("FileSystemChatMessageHistory set context and get all sessions", async () =
expect(sessions.length).toBe(2);
expect(sessions[0].context).toEqual(context1);
+ expect(sessions[0].id).toBeDefined();
expect(sessions[1].context).toEqual(context2);
+ expect(sessions[1].id).toBeDefined();
});
diff --git a/libs/langchain-community/src/stores/tests/neo4j.int.test.ts b/libs/langchain-community/src/stores/tests/neo4j.int.test.ts
new file mode 100644
index 000000000000..2f6c17d01ed6
--- /dev/null
+++ b/libs/langchain-community/src/stores/tests/neo4j.int.test.ts
@@ -0,0 +1,138 @@
+import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
+import { HumanMessage, AIMessage } from "@langchain/core/messages";
+import neo4j from "neo4j-driver";
+import { Neo4jChatMessageHistory } from "../message/neo4j.js";
+
+const goodConfig = {
+ url: "bolt://host.docker.internal:7687",
+ username: "neo4j",
+ password: "langchain",
+};
+
+describe("The Neo4jChatMessageHistory class", () => {
+ describe("Test suite", () => {
+ it("Runs at all", () => {
+ expect(true).toEqual(true);
+ });
+ });
+
+ describe("Class instantiation", () => {
+ it("Requires a url, username and password, throwing an error if not provided", async () => {
+ const badConfig = {};
+ await expect(
+ // @ts-expect-error Bad config
+ Neo4jChatMessageHistory.initialize(badConfig)
+ ).rejects.toThrow(neo4j.Neo4jError);
+ });
+
+ it("Creates a class instance from - at minimum - a url, username and password", async () => {
+ const instance = await Neo4jChatMessageHistory.initialize(goodConfig);
+ expect(instance).toBeInstanceOf(Neo4jChatMessageHistory);
+ await instance.close();
+ });
+
+ it("Class instances have expected, configurable fields, and sensible defaults", async () => {
+ const instance = await Neo4jChatMessageHistory.initialize(goodConfig);
+
+ expect(instance.sessionId).toBeDefined();
+ expect(instance.sessionNodeLabel).toEqual("ChatSession");
+ expect(instance.windowSize).toEqual(3);
+ expect(instance.messageNodeLabel).toEqual("ChatMessage");
+
+ const secondInstance = await Neo4jChatMessageHistory.initialize({
+ ...goodConfig,
+ sessionId: "Shibboleet",
+ sessionNodeLabel: "Conversation",
+ messageNodeLabel: "Communication",
+ windowSize: 4,
+ });
+
+ expect(secondInstance.sessionId).toBeDefined();
+ expect(secondInstance.sessionId).toEqual("Shibboleet");
+ expect(instance.sessionId).not.toEqual(secondInstance.sessionId);
+ expect(secondInstance.sessionNodeLabel).toEqual("Conversation");
+ expect(secondInstance.messageNodeLabel).toEqual("Communication");
+ expect(secondInstance.windowSize).toEqual(4);
+
+ await instance.close();
+ await secondInstance.close();
+ });
+ });
+
+ describe("Core functionality", () => {
+ let instance: undefined | Neo4jChatMessageHistory;
+
+ beforeEach(async () => {
+ instance = await Neo4jChatMessageHistory.initialize(goodConfig);
+ });
+
+ afterEach(async () => {
+ await instance?.clear();
+ await instance?.close();
+ });
+
+ it("Connects verifiably to the underlying Neo4j database", async () => {
+ const connected = await instance?.verifyConnectivity();
+ expect(connected).toBeDefined();
+ });
+
+ it("getMessages()", async () => {
+ let results = await instance?.getMessages();
+ expect(results).toEqual([]);
+ const messages = [
+ new HumanMessage(
+ "My first name is a random set of numbers and letters"
+ ),
+ new AIMessage("And other alphanumerics that changes hourly forever"),
+ new HumanMessage(
+ "My last name, a thousand vowels fading down a sinkhole to a susurrus"
+ ),
+ new AIMessage("It couldn't just be John Doe or Bingo"),
+ new HumanMessage(
+ "My address, a made-up language written out in living glyphs"
+ ),
+ new AIMessage("Lifted from demonic literature and religious text"),
+ new HumanMessage("Telephone: uncovered by purveyors of the ouija"),
+ new AIMessage("When checked against the CBGB women's room graffiti"),
+ new HumanMessage("My social: a sudoku"),
+ new AIMessage("My age is obscure"),
+ ];
+ await instance?.addMessages(messages);
+ results = (await instance?.getMessages()) || [];
+ const windowSize = instance?.windowSize || 0;
+ expect(results.length).toEqual(windowSize * 2);
+ expect(results).toEqual(messages.slice(windowSize * -2));
+ });
+
+ it("addMessage()", async () => {
+ const messages = [
+ new HumanMessage("99 Bottles of beer on the wall, 99 bottles of beer!"),
+ new AIMessage(
+ "Take one down, pass it around, 98 bottles of beer on the wall."
+ ),
+ new HumanMessage("How many bottles of beer are currently on the wall?"),
+ new AIMessage("There are currently 98 bottles of beer on the wall."),
+ ];
+ for (const message of messages) {
+ await instance?.addMessage(message);
+ }
+ const results = await instance?.getMessages();
+ expect(results).toEqual(messages);
+ });
+
+ it("clear()", async () => {
+ const messages = [
+ new AIMessage("I'm not your enemy."),
+ new HumanMessage("That sounds like something that my enemy would say."),
+ new AIMessage("You're being difficult."),
+ new HumanMessage("I'm being guarded."),
+ ];
+ await instance?.addMessages(messages);
+ let results = await instance?.getMessages();
+ expect(results).toEqual(messages);
+ await instance?.clear();
+ results = await instance?.getMessages();
+ expect(results).toEqual([]);
+ });
+ });
+});
diff --git a/libs/langchain-community/src/tools/google_scholar.ts b/libs/langchain-community/src/tools/google_scholar.ts
new file mode 100644
index 000000000000..7d874910126b
--- /dev/null
+++ b/libs/langchain-community/src/tools/google_scholar.ts
@@ -0,0 +1,122 @@
+import { Tool } from "@langchain/core/tools";
+import { getEnvironmentVariable } from "@langchain/core/utils/env";
+
+/**
+ * Interface for parameters required by the SERPGoogleScholarAPITool class.
+ */
+export interface GoogleScholarAPIParams {
+ /**
+ * Optional API key for accessing the SerpApi service.
+ */
+ apiKey?: string;
+}
+
+/**
+ * Tool for querying Google Scholar using the SerpApi service.
+ */
+export class SERPGoogleScholarAPITool extends Tool {
+ /**
+ * Specifies the name of the tool, used internally by LangChain.
+ */
+ static lc_name() {
+ return "SERPGoogleScholarAPITool";
+ }
+
+ /**
+ * Returns a mapping of secret environment variable names to their usage in the tool.
+ * @returns {object} Mapping of secret names to their environment variable counterparts.
+ */
+ get lc_secrets(): { [key: string]: string } | undefined {
+ return {
+ apiKey: "SERPAPI_API_KEY",
+ };
+ }
+
+ // Name of the tool, used for logging or identification within LangChain.
+ name = "serp_google_scholar";
+
+ // The API key used for making requests to SerpApi.
+ protected apiKey: string;
+
+ /**
+ * Description of the tool for usage documentation.
+ */
+ description = `A wrapper around Google Scholar API via SerpApi. Useful for querying academic
+ articles and papers by keywords or authors. Input should be a search query string.`;
+
+ /**
+ * Constructs a new instance of SERPGoogleScholarAPITool.
+ * @param fields - Optional parameters including an API key.
+ */
+ constructor(fields?: GoogleScholarAPIParams) {
+ super(...arguments);
+
+ // Retrieve API key from fields or environment variables.
+ const apiKey = fields?.apiKey ?? getEnvironmentVariable("SERPAPI_API_KEY");
+
+ // Throw an error if no API key is found.
+ if (!apiKey) {
+ throw new Error(
+ `SerpApi key not set. You can set it as "SERPAPI_API_KEY" in your environment variables.`
+ );
+ }
+ this.apiKey = apiKey;
+ }
+
+ /**
+ * Makes a request to SerpApi for Google Scholar results.
+ * @param input - Search query string.
+ * @returns A JSON string containing the search results.
+ * @throws Error if the API request fails or returns an error.
+ */
+ async _call(input: string): Promise {
+ // Construct the URL for the API request.
+ const url = `https://serpapi.com/search.json?q=${encodeURIComponent(
+ input
+ )}&engine=google_scholar&api_key=${this.apiKey}`;
+
+ // Make an HTTP GET request to the SerpApi service.
+ const response = await fetch(url);
+
+ // Handle non-OK responses by extracting the error message.
+ if (!response.ok) {
+ let message;
+ try {
+ const json = await response.json(); // Attempt to parse the error response.
+ message = json.error; // Extract the error message from the response.
+ } catch (error) {
+ // Handle cases where the response isn't valid JSON.
+ message =
+ "Unable to parse error message: SerpApi did not return a JSON response.";
+ }
+ // Throw an error with detailed information about the failure.
+ throw new Error(
+ `Got ${response.status}: ${response.statusText} error from SerpApi: ${message}`
+ );
+ }
+
+ // Parse the JSON response from SerpApi.
+ const json = await response.json();
+
+ // Transform the raw response into a structured format.
+ const results =
+ json.organic_results?.map((item: any) => ({
+ title: item.title, // Title of the article or paper.
+ link: item.link, // Direct link to the article or paper.
+ snippet: item.snippet, // Brief snippet or description.
+ publication_info:
+ item.publication_info?.summary
+ ?.split(" - ") // Split the summary at hyphens.
+ .slice(1) // Remove the authors from the start of the string.
+ .join(" - ") ?? "", // Rejoin remaining parts as publication info.
+ authors:
+ item.publication_info?.authors
+ ?.map((author: any) => author.name) // Extract the list of author names.
+ .join(", ") ?? "", // Join author names with a comma.
+ total_citations: item.inline_links?.cited_by?.total ?? "", // Total number of citations.
+ })) ?? `No results found for ${input} on Google Scholar.`;
+
+ // Return the results as a formatted JSON string.
+ return JSON.stringify(results, null, 2);
+ }
+}
diff --git a/libs/langchain-community/src/tools/google_trends.ts b/libs/langchain-community/src/tools/google_trends.ts
new file mode 100644
index 000000000000..18f063e791e8
--- /dev/null
+++ b/libs/langchain-community/src/tools/google_trends.ts
@@ -0,0 +1,161 @@
+import { getEnvironmentVariable } from "@langchain/core/utils/env";
+import { Tool } from "@langchain/core/tools";
+
+/**
+ * Interfaces for the response from the SerpApi Google Trends API.
+ */
+interface TimelineValue {
+ query: string;
+ value: string;
+ extracted_value: number;
+}
+
+interface TimelineData {
+ date: string;
+ timestamp: string;
+ values: TimelineValue[];
+}
+
+/**
+ * Interface for parameters required by SERPGoogleTrendsTool class.
+ */
+export interface SERPGoogleTrendsToolParams {
+ apiKey?: string;
+}
+
+/**
+ * Tool that queries the Google Trends API. Uses default interest over time.
+ */
+export class SERPGoogleTrendsTool extends Tool {
+ static lc_name() {
+ return "SERPGoogleTrendsTool";
+ }
+
+ get lc_secrets(): { [key: string]: string } | undefined {
+ return {
+ apiKey: "SERPAPI_API_KEY",
+ };
+ }
+
+ name = "google_trends";
+
+ protected apiKey: string;
+
+ description = `A wrapper around Google Trends API. Useful for analyzing and retrieving trending search data based on keywords,
+ categories, or regions. Input should be a search query or specific parameters for trends analysis.`;
+
+ constructor(fields?: SERPGoogleTrendsToolParams) {
+ super(...arguments);
+ const apiKey = fields?.apiKey ?? getEnvironmentVariable("SERPAPI_API_KEY");
+ if (apiKey === undefined) {
+ throw new Error(
+ `Google Trends API key not set. You can set it as "SERPAPI_API_KEY" in your environment variables.`
+ );
+ }
+ this.apiKey = apiKey;
+ }
+
+ async _call(query: string): Promise {
+ /**
+ * Related queries only accepts one at a time, and multiple
+ * queries at once on interest over time (default) is effectively the same as
+ * each query one by one.
+ *
+ * SerpApi caches queries, so the first time will be slower
+ * and subsequent identical queries will be very fast.
+ */
+ if (query.split(",").length > 1) {
+ throw new Error("Please do one query at a time");
+ }
+ const serpapiApiKey = this.apiKey;
+ const params = new URLSearchParams({
+ engine: "google_trends",
+ api_key: serpapiApiKey,
+ q: query,
+ });
+
+ const res = await fetch(
+ `https://serpapi.com/search.json?${params.toString()}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ if (!res.ok) {
+ throw new Error(`Error fetching data from SerpAPI: ${res.statusText}`);
+ }
+
+ const clientDict = await res.json();
+ const totalResults = clientDict.interest_over_time?.timeline_data ?? [];
+
+ if (totalResults.length === 0) {
+ return "No good Trend Result was found";
+ }
+
+ const startDate = totalResults[0].date.split(" ");
+ const endDate = totalResults[totalResults.length - 1].date.split(" ");
+ const values = totalResults.map(
+ (result: TimelineData) => result.values[0].extracted_value
+ );
+ const minValue = Math.min(...values);
+ const maxValue = Math.max(...values);
+ const avgValue =
+ values.reduce((a: number, b: number) => a + b, 0) / values.length;
+ const percentageChange =
+ ((values[values.length - 1] - values[0]) / (values[0] || 1)) * 100;
+
+ const relatedParams = new URLSearchParams({
+ engine: "google_trends",
+ api_key: serpapiApiKey,
+ data_type: "RELATED_QUERIES",
+ q: query,
+ });
+
+ const relatedRes = await fetch(
+ `https://serpapi.com/search.json?${relatedParams.toString()}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ let rising = [];
+ let top = [];
+ if (!relatedRes.ok) {
+ // Sometimes related queries from SerpAPI will fail, but interest over time will be fine
+ console.error(
+ `Error fetching related queries from SerpAPI: ${relatedRes.statusText}`
+ );
+ } else {
+ const relatedDict = await relatedRes.json();
+ rising =
+ relatedDict.related_queries?.rising?.map(
+ (result: { query: string }) => result.query
+ ) ?? [];
+ top =
+ relatedDict.related_queries?.top?.map(
+ (result: { query: string }) => result.query
+ ) ?? [];
+ }
+
+ const doc = [
+ `Query: ${query}`,
+ `Date From: ${startDate[0]} ${startDate[1]}, ${startDate[2]}`,
+ `Date To: ${endDate[0]} ${endDate[1]} ${endDate[2]}`,
+ `Min Value: ${minValue}`,
+ `Max Value: ${maxValue}`,
+ `Average Value: ${avgValue}`,
+ `Percent Change: ${percentageChange.toFixed(2)}%`,
+ `Trend values: ${values.join(", ")}`,
+ `Rising Related Queries: ${rising.join(", ")}`,
+ `Top Related Queries: ${top.join(", ")}`,
+ ];
+
+ return doc.join("\n\n");
+ }
+}
diff --git a/libs/langchain-community/src/tools/tests/google_scholar.int.test.ts b/libs/langchain-community/src/tools/tests/google_scholar.int.test.ts
new file mode 100644
index 000000000000..fe243ae42aa9
--- /dev/null
+++ b/libs/langchain-community/src/tools/tests/google_scholar.int.test.ts
@@ -0,0 +1,38 @@
+import { test, expect, describe } from "@jest/globals";
+import { getEnvironmentVariable } from "@langchain/core/utils/env";
+import { SERPGoogleScholarAPITool } from "../google_scholar.js";
+
+describe("SERPGoogleScholarAPITool", () => {
+ test("should be setup with correct parameters", async () => {
+ const instance = new SERPGoogleScholarAPITool({
+ apiKey: getEnvironmentVariable("SERPAPI_API_KEY"),
+ });
+ expect(instance.name).toBe("serp_google_scholar");
+ });
+
+ test("SERPGoogleScholarAPITool returns a string for valid query", async () => {
+ const tool = new SERPGoogleScholarAPITool({
+ apiKey: getEnvironmentVariable("SERPAPI_API_KEY"),
+ });
+ const result = await tool.invoke("Artificial Intelligence");
+ expect(typeof result).toBe("string");
+ });
+
+ test("SERPGoogleScholarAPITool returns non-empty string for valid query", async () => {
+ const tool = new SERPGoogleScholarAPITool({
+ apiKey: getEnvironmentVariable("SERPAPI_API_KEY"),
+ });
+ const result = await tool.invoke("Artificial Intelligence");
+ expect(result.length).toBeGreaterThan(0);
+ });
+
+ test("SERPGoogleScholarAPITool returns 'No results found' for bad query", async () => {
+ const tool = new SERPGoogleScholarAPITool({
+ apiKey: getEnvironmentVariable("SERPAPI_API_KEY"),
+ });
+ const result = await tool.invoke("dsalkfjsdlfjasdflasdl");
+ expect(result).toBe(
+ '"No results found for dsalkfjsdlfjasdflasdl on Google Scholar."'
+ );
+ });
+});
diff --git a/libs/langchain-community/src/tools/tests/google_trends.int.test.ts b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts
new file mode 100644
index 000000000000..49be189bd7ad
--- /dev/null
+++ b/libs/langchain-community/src/tools/tests/google_trends.int.test.ts
@@ -0,0 +1,34 @@
+import { expect, describe } from "@jest/globals";
+import { SERPGoogleTrendsTool } from "../google_trends.js";
+
+describe("SERPGoogleTrendsTool", () => {
+ test("should be setup with correct parameters", async () => {
+ const instance = new SERPGoogleTrendsTool();
+ expect(instance.name).toBe("google_trends");
+ });
+
+ test("SERPGoogleTrendsTool returns expected result for valid query", async () => {
+ const tool = new SERPGoogleTrendsTool();
+
+ const result = await tool._call("Coffee");
+
+ expect(result).toContain("Query: Coffee");
+ expect(result).toContain("Date From:");
+ expect(result).toContain("Date To:");
+ expect(result).toContain("Min Value:");
+ expect(result).toContain("Max Value:");
+ expect(result).toContain("Average Value:");
+ expect(result).toContain("Percent Change:");
+ expect(result).toContain("Trend values:");
+ expect(result).toContain("Rising Related Queries:");
+ expect(result).toContain("Top Related Queries:");
+ });
+
+ test("SERPGoogleTrendsTool returns 'No good Trend Result was found' for a non-existent query", async () => {
+ const tool = new SERPGoogleTrendsTool();
+
+ const result = await tool._call("earghajgpajrpgjaprgag");
+
+ expect(result).toBe("No good Trend Result was found");
+ });
+});
diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json
index fe3fa5a001ec..3408ef93e4ef 100644
--- a/libs/langchain-google-common/package.json
+++ b/libs/langchain-google-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@langchain/google-common",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "Core types and classes for Google services.",
"type": "module",
"engines": {
diff --git a/libs/langchain-google-common/src/types.ts b/libs/langchain-google-common/src/types.ts
index bb49cf2edd4f..b88b3e01d090 100644
--- a/libs/langchain-google-common/src/types.ts
+++ b/libs/langchain-google-common/src/types.ts
@@ -309,6 +309,22 @@ export interface GeminiContent {
export interface GeminiTool {
functionDeclarations?: GeminiFunctionDeclaration[];
+ googleSearchRetrieval?: GoogleSearchRetrieval;
+ retrieval?: VertexAIRetrieval;
+}
+
+export interface GoogleSearchRetrieval {
+ dynamicRetrievalConfig?: {
+ mode?: string;
+ dynamicThreshold?: number;
+ };
+}
+
+export interface VertexAIRetrieval {
+ vertexAiSearch: {
+ datastore: string;
+ };
+ disableAttribution?: boolean;
}
export interface GeminiFunctionDeclaration {
diff --git a/libs/langchain-google-common/src/utils/common.ts b/libs/langchain-google-common/src/utils/common.ts
index bf8ddb228382..b40ce25fe3fc 100644
--- a/libs/langchain-google-common/src/utils/common.ts
+++ b/libs/langchain-google-common/src/utils/common.ts
@@ -62,32 +62,43 @@ function processToolChoice(
}
export function convertToGeminiTools(tools: GoogleAIToolType[]): GeminiTool[] {
- const geminiTools: GeminiTool[] = [
- {
- functionDeclarations: [],
- },
- ];
+ const geminiTools: GeminiTool[] = [];
+ let functionDeclarationsIndex = -1;
tools.forEach((tool) => {
- if (
- "functionDeclarations" in tool &&
- Array.isArray(tool.functionDeclarations)
- ) {
- const funcs: GeminiFunctionDeclaration[] = tool.functionDeclarations;
- geminiTools[0].functionDeclarations?.push(...funcs);
- } else if (isLangChainTool(tool)) {
- const jsonSchema = zodToGeminiParameters(tool.schema);
- geminiTools[0].functionDeclarations?.push({
- name: tool.name,
- description: tool.description ?? `A function available to call.`,
- parameters: jsonSchema as GeminiFunctionSchema,
- });
- } else if (isOpenAITool(tool)) {
- geminiTools[0].functionDeclarations?.push({
- name: tool.function.name,
- description:
- tool.function.description ?? `A function available to call.`,
- parameters: jsonSchemaToGeminiParameters(tool.function.parameters),
- });
+ if ("googleSearchRetrieval" in tool || "retrieval" in tool) {
+ geminiTools.push(tool);
+ } else {
+ if (functionDeclarationsIndex === -1) {
+ geminiTools.push({
+ functionDeclarations: [],
+ });
+ functionDeclarationsIndex = geminiTools.length - 1;
+ }
+ if (
+ "functionDeclarations" in tool &&
+ Array.isArray(tool.functionDeclarations)
+ ) {
+ const funcs: GeminiFunctionDeclaration[] = tool.functionDeclarations;
+ geminiTools[functionDeclarationsIndex].functionDeclarations!.push(
+ ...funcs
+ );
+ } else if (isLangChainTool(tool)) {
+ const jsonSchema = zodToGeminiParameters(tool.schema);
+ geminiTools[functionDeclarationsIndex].functionDeclarations!.push({
+ name: tool.name,
+ description: tool.description ?? `A function available to call.`,
+ parameters: jsonSchema as GeminiFunctionSchema,
+ });
+ } else if (isOpenAITool(tool)) {
+ geminiTools[functionDeclarationsIndex].functionDeclarations!.push({
+ name: tool.function.name,
+ description:
+ tool.function.description ?? `A function available to call.`,
+ parameters: jsonSchemaToGeminiParameters(tool.function.parameters),
+ });
+ } else {
+ throw new Error(`Received invalid tool: ${JSON.stringify(tool)}`);
+ }
}
});
return geminiTools;
diff --git a/libs/langchain-google-common/src/utils/gemini.ts b/libs/langchain-google-common/src/utils/gemini.ts
index e6d0f6e96001..48bd41fb5c2f 100644
--- a/libs/langchain-google-common/src/utils/gemini.ts
+++ b/libs/langchain-google-common/src/utils/gemini.ts
@@ -1015,34 +1015,29 @@ export function getGeminiAPI(config?: GeminiAPIConfig): GoogleAIAPI {
};
}
- function structuredToolsToGeminiTools(
- tools: StructuredToolParams[]
- ): GeminiTool[] {
- return [
- {
- functionDeclarations: tools.map(structuredToolToFunctionDeclaration),
- },
- ];
- }
-
function formatTools(parameters: GoogleAIModelRequestParams): GeminiTool[] {
const tools: GoogleAIToolType[] | undefined = parameters?.tools;
if (!tools || tools.length === 0) {
return [];
}
- if (tools.every(isLangChainTool)) {
- return structuredToolsToGeminiTools(tools);
- } else {
- if (
- tools.length === 1 &&
- (!("functionDeclarations" in tools[0]) ||
- !tools[0].functionDeclarations?.length)
- ) {
- return [];
- }
- return tools as GeminiTool[];
+ // Group all LangChain tools into a single functionDeclarations array
+ const langChainTools = tools.filter(isLangChainTool);
+ const otherTools = tools.filter(
+ (tool) => !isLangChainTool(tool)
+ ) as GeminiTool[];
+
+ const result: GeminiTool[] = [...otherTools];
+
+ if (langChainTools.length > 0) {
+ result.push({
+ functionDeclarations: langChainTools.map(
+ structuredToolToFunctionDeclaration
+ ),
+ });
}
+
+ return result;
}
function formatToolConfig(
diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json
index f40f0a0be029..6a971b220ef6 100644
--- a/libs/langchain-google-gauth/package.json
+++ b/libs/langchain-google-gauth/package.json
@@ -1,6 +1,6 @@
{
"name": "@langchain/google-gauth",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "Google auth based authentication support for Google services",
"type": "module",
"engines": {
@@ -35,7 +35,7 @@
"author": "LangChain",
"license": "MIT",
"dependencies": {
- "@langchain/google-common": "~0.1.3",
+ "@langchain/google-common": "~0.1.4",
"google-auth-library": "^8.9.0"
},
"peerDependencies": {
diff --git a/libs/langchain-google-gauth/src/auth.ts b/libs/langchain-google-gauth/src/auth.ts
index bb8053b9c521..fff6cc73cb2a 100644
--- a/libs/langchain-google-gauth/src/auth.ts
+++ b/libs/langchain-google-gauth/src/auth.ts
@@ -85,32 +85,20 @@ export class GAuthClient implements GoogleAbstractedClient {
}
async request(opts: GoogleAbstractedClientOps): Promise {
- try {
- const ret = await this.gauth.request(opts);
- const [contentType] = ret?.headers?.["content-type"]?.split(/;/) ?? [""];
- if (opts.responseType !== "stream") {
- return ret;
- } else if (contentType === "text/event-stream") {
- return {
- ...ret,
- data: new NodeSseJsonStream(ret.data),
- };
- } else {
- return {
- ...ret,
- data: new NodeJsonStream(ret.data),
- };
- }
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (xx: any) {
- console.error("call to gauth.request", JSON.stringify(xx, null, 2));
- console.error(
- "call to gauth.request opts=",
- JSON.stringify(opts, null, 2)
- );
- console.error("call to gauth.request message:", xx?.message);
- throw xx;
+ const ret = await this.gauth.request(opts);
+ const [contentType] = ret?.headers?.["content-type"]?.split(/;/) ?? [""];
+ if (opts.responseType !== "stream") {
+ return ret;
+ } else if (contentType === "text/event-stream") {
+ return {
+ ...ret,
+ data: new NodeSseJsonStream(ret.data),
+ };
+ } else {
+ return {
+ ...ret,
+ data: new NodeJsonStream(ret.data),
+ };
}
}
}
diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json
index 737438881e94..8761e8b75975 100644
--- a/libs/langchain-google-vertexai-web/package.json
+++ b/libs/langchain-google-vertexai-web/package.json
@@ -1,6 +1,6 @@
{
"name": "@langchain/google-vertexai-web",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "LangChain.js support for Google Vertex AI Web",
"type": "module",
"engines": {
@@ -32,7 +32,7 @@
"author": "LangChain",
"license": "MIT",
"dependencies": {
- "@langchain/google-webauth": "~0.1.3"
+ "@langchain/google-webauth": "~0.1.4"
},
"peerDependencies": {
"@langchain/core": ">=0.2.21 <0.4.0"
diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json
index 58c6b3a1503f..0c238e99bc12 100644
--- a/libs/langchain-google-vertexai/package.json
+++ b/libs/langchain-google-vertexai/package.json
@@ -1,6 +1,6 @@
{
"name": "@langchain/google-vertexai",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "LangChain.js support for Google Vertex AI",
"type": "module",
"engines": {
@@ -32,7 +32,7 @@
"author": "LangChain",
"license": "MIT",
"dependencies": {
- "@langchain/google-gauth": "~0.1.3"
+ "@langchain/google-gauth": "~0.1.4"
},
"peerDependencies": {
"@langchain/core": ">=0.2.21 <0.4.0"
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 a3b8bbe4b2d8..ddcdf579a394 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
@@ -617,3 +617,50 @@ describe("GAuth Anthropic Chat", () => {
expect(toolCalls?.[0].args).toHaveProperty("location");
});
});
+
+describe("GoogleSearchRetrievalTool", () => {
+ test("Supports GoogleSearchRetrievalTool", async () => {
+ const searchRetrievalTool = {
+ googleSearchRetrieval: {
+ dynamicRetrievalConfig: {
+ mode: "MODE_DYNAMIC",
+ dynamicThreshold: 0.7, // default is 0.7
+ },
+ },
+ };
+ const model = new ChatVertexAI({
+ model: "gemini-1.5-pro",
+ 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");
+ });
+
+ test("Can stream GoogleSearchRetrievalTool", async () => {
+ const searchRetrievalTool = {
+ googleSearchRetrieval: {
+ dynamicRetrievalConfig: {
+ mode: "MODE_DYNAMIC",
+ dynamicThreshold: 0.7, // default is 0.7
+ },
+ },
+ };
+ const model = new ChatVertexAI({
+ model: "gemini-1.5-pro",
+ 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");
+ });
+});
diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json
index 953b925c8a49..f3bee58fb805 100644
--- a/libs/langchain-google-webauth/package.json
+++ b/libs/langchain-google-webauth/package.json
@@ -1,6 +1,6 @@
{
"name": "@langchain/google-webauth",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "Web-based authentication support for Google services",
"type": "module",
"engines": {
@@ -32,7 +32,7 @@
"author": "LangChain",
"license": "MIT",
"dependencies": {
- "@langchain/google-common": "~0.1.3",
+ "@langchain/google-common": "~0.1.4",
"web-auth-library": "^1.0.3"
},
"peerDependencies": {
diff --git a/test-int-deps-docker-compose.yml b/test-int-deps-docker-compose.yml
index e14c4d65779a..2c875f6f5221 100644
--- a/test-int-deps-docker-compose.yml
+++ b/test-int-deps-docker-compose.yml
@@ -36,4 +36,17 @@ services:
qdrant:
image: qdrant/qdrant:v1.9.1
ports:
- - 6333:6333
\ No newline at end of file
+ - 6333:6333
+ neo4j:
+ image: neo4j:latest
+ volumes:
+ - $HOME/neo4j/logs:/var/lib/neo4j/logs
+ - $HOME/neo4j/config:/var/lib/neo4j/config
+ - $HOME/neo4j/data:/var/lib/neo4j/data
+ - $HOME/neo4j/plugins:/var/lib/neo4j/plugins
+ environment:
+ - NEO4J_dbms_security_auth__enabled=false
+ ports:
+ - "7474:7474"
+ - "7687:7687"
+ restart: always
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 466e5b0d4763..8ca08ed7c45d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12398,7 +12398,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@langchain/google-common@^0.1.0, @langchain/google-common@workspace:*, @langchain/google-common@workspace:libs/langchain-google-common, @langchain/google-common@~0.1.3":
+"@langchain/google-common@^0.1.0, @langchain/google-common@workspace:*, @langchain/google-common@workspace:libs/langchain-google-common, @langchain/google-common@~0.1.4":
version: 0.0.0-use.local
resolution: "@langchain/google-common@workspace:libs/langchain-google-common"
dependencies:
@@ -12433,13 +12433,13 @@ __metadata:
languageName: unknown
linkType: soft
-"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.1.3":
+"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.1.4":
version: 0.0.0-use.local
resolution: "@langchain/google-gauth@workspace:libs/langchain-google-gauth"
dependencies:
"@jest/globals": ^29.5.0
"@langchain/core": "workspace:*"
- "@langchain/google-common": ~0.1.3
+ "@langchain/google-common": ~0.1.4
"@langchain/scripts": ">=0.1.0 <0.2.0"
"@swc/core": ^1.3.90
"@swc/jest": ^0.2.29
@@ -12512,7 +12512,7 @@ __metadata:
"@jest/globals": ^29.5.0
"@langchain/core": "workspace:*"
"@langchain/google-common": ^0.1.0
- "@langchain/google-webauth": ~0.1.3
+ "@langchain/google-webauth": ~0.1.4
"@langchain/scripts": ">=0.1.0 <0.2.0"
"@langchain/standard-tests": 0.0.0
"@swc/core": ^1.3.90
@@ -12548,7 +12548,7 @@ __metadata:
"@jest/globals": ^29.5.0
"@langchain/core": "workspace:*"
"@langchain/google-common": ^0.1.0
- "@langchain/google-gauth": ~0.1.3
+ "@langchain/google-gauth": ~0.1.4
"@langchain/scripts": ">=0.1.0 <0.2.0"
"@langchain/standard-tests": 0.0.0
"@swc/core": ^1.3.90
@@ -12577,13 +12577,13 @@ __metadata:
languageName: unknown
linkType: soft
-"@langchain/google-webauth@workspace:libs/langchain-google-webauth, @langchain/google-webauth@~0.1.3":
+"@langchain/google-webauth@workspace:libs/langchain-google-webauth, @langchain/google-webauth@~0.1.4":
version: 0.0.0-use.local
resolution: "@langchain/google-webauth@workspace:libs/langchain-google-webauth"
dependencies:
"@jest/globals": ^29.5.0
"@langchain/core": "workspace:*"
- "@langchain/google-common": ~0.1.3
+ "@langchain/google-common": ~0.1.4
"@langchain/scripts": ">=0.1.0 <0.2.0"
"@swc/core": ^1.3.90
"@swc/jest": ^0.2.29