From 88c75e99ac9ba1d3d846132127e54359832f75b3 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 24 Jan 2025 16:14:28 -0800 Subject: [PATCH] feat(langchain): Add hub entrypoint with automatic dynamic entrypoint of models (#7583) --- langchain/.gitignore | 4 + langchain/langchain.config.js | 4 +- langchain/package.json | 13 ++ .../create_openai_functions_agent.int.test.ts | 2 +- .../create_openai_tools_agent.int.test.ts | 2 +- .../tests/create_react_agent.int.test.ts | 2 +- .../create_structured_chat_agent.int.test.ts | 2 +- .../agents/tests/create_xml_agent.int.test.ts | 2 +- .../chat_models/tests/universal.int.test.ts | 3 +- langchain/src/chat_models/universal.ts | 1 + langchain/src/{hub.ts => hub/base.ts} | 118 +++++++----------- langchain/src/hub/index.ts | 67 ++++++++++ langchain/src/hub/node.ts | 62 +++++++++ langchain/src/hub/tests/hub.int.test.ts | 90 +++++++++++++ langchain/src/load/import_constants.ts | 1 + langchain/src/load/index.ts | 1 + langchain/src/tests/hub.int.test.ts | 41 ------ 17 files changed, 291 insertions(+), 124 deletions(-) rename langchain/src/{hub.ts => hub/base.ts} (73%) create mode 100644 langchain/src/hub/index.ts create mode 100644 langchain/src/hub/node.ts create mode 100644 langchain/src/hub/tests/hub.int.test.ts delete mode 100644 langchain/src/tests/hub.int.test.ts diff --git a/langchain/.gitignore b/langchain/.gitignore index 7bd9d4deb0d7..4dd24efab36d 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -294,6 +294,10 @@ hub.cjs hub.js hub.d.ts hub.d.cts +hub/node.cjs +hub/node.js +hub/node.d.ts +hub/node.d.cts util/document.cjs util/document.js util/document.d.ts diff --git a/langchain/langchain.config.js b/langchain/langchain.config.js index d2a317e76fb7..2804e5f0fe59 100644 --- a/langchain/langchain.config.js +++ b/langchain/langchain.config.js @@ -118,7 +118,8 @@ export const config = { "storage/in_memory": "storage/in_memory", "storage/file_system": "storage/file_system", // hub - hub: "hub", + hub: "hub/index", + "hub/node": "hub/node", // utilities "util/document": "util/document", "util/math": "util/math", @@ -217,6 +218,7 @@ export const config = { "storage/file_system", // Prevent export due to circular dependency with "load" entrypoint "hub", + "hub/node", "experimental/prompts/handlebars", ], extraImportMapEntries: [ diff --git a/langchain/package.json b/langchain/package.json index 9d89d69c3eb8..2f22050df856 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -306,6 +306,10 @@ "hub.js", "hub.d.ts", "hub.d.cts", + "hub/node.cjs", + "hub/node.js", + "hub/node.d.ts", + "hub/node.d.cts", "util/document.cjs", "util/document.js", "util/document.d.ts", @@ -1222,6 +1226,15 @@ "import": "./hub.js", "require": "./hub.cjs" }, + "./hub/node": { + "types": { + "import": "./hub/node.d.ts", + "require": "./hub/node.d.cts", + "default": "./hub/node.d.ts" + }, + "import": "./hub/node.js", + "require": "./hub/node.cjs" + }, "./util/document": { "types": { "import": "./util/document.d.ts", diff --git a/langchain/src/agents/tests/create_openai_functions_agent.int.test.ts b/langchain/src/agents/tests/create_openai_functions_agent.int.test.ts index 471419f1b8d8..5d38bf822605 100644 --- a/langchain/src/agents/tests/create_openai_functions_agent.int.test.ts +++ b/langchain/src/agents/tests/create_openai_functions_agent.int.test.ts @@ -4,7 +4,7 @@ import { test, expect } from "@jest/globals"; import { ChatOpenAI } from "@langchain/openai"; import type { ChatPromptTemplate } from "@langchain/core/prompts"; import { TavilySearchResults } from "../../util/testing/tools/tavily_search.js"; -import { pull } from "../../hub.js"; +import { pull } from "../../hub/index.js"; import { AgentExecutor, createOpenAIFunctionsAgent } from "../index.js"; const tools = [new TavilySearchResults({ maxResults: 1 })]; diff --git a/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts b/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts index 8d4c1b6305fd..f36ad405f123 100644 --- a/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts +++ b/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts @@ -8,7 +8,7 @@ import { tool } from "@langchain/core/tools"; import { z } from "zod"; import { AsyncLocalStorage } from "async_hooks"; import { TavilySearchResults } from "../../util/testing/tools/tavily_search.js"; -import { pull } from "../../hub.js"; +import { pull } from "../../hub/index.js"; import { AgentExecutor, createOpenAIToolsAgent } from "../index.js"; const tools = [new TavilySearchResults({ maxResults: 1 })]; diff --git a/langchain/src/agents/tests/create_react_agent.int.test.ts b/langchain/src/agents/tests/create_react_agent.int.test.ts index 6621c4a4f042..6cfcec47e839 100644 --- a/langchain/src/agents/tests/create_react_agent.int.test.ts +++ b/langchain/src/agents/tests/create_react_agent.int.test.ts @@ -2,7 +2,7 @@ import { test, expect } from "@jest/globals"; import { OpenAI } from "@langchain/openai"; import type { PromptTemplate } from "@langchain/core/prompts"; import { TavilySearchResults } from "../../util/testing/tools/tavily_search.js"; -import { pull } from "../../hub.js"; +import { pull } from "../../hub/index.js"; import { AgentExecutor, createReactAgent } from "../index.js"; const tools = [new TavilySearchResults({ maxResults: 1 })]; diff --git a/langchain/src/agents/tests/create_structured_chat_agent.int.test.ts b/langchain/src/agents/tests/create_structured_chat_agent.int.test.ts index d1fd1ae895e5..e25ac6f9c77d 100644 --- a/langchain/src/agents/tests/create_structured_chat_agent.int.test.ts +++ b/langchain/src/agents/tests/create_structured_chat_agent.int.test.ts @@ -2,7 +2,7 @@ import { test, expect } from "@jest/globals"; import { ChatOpenAI } from "@langchain/openai"; import type { ChatPromptTemplate } from "@langchain/core/prompts"; import { TavilySearchResults } from "../../util/testing/tools/tavily_search.js"; -import { pull } from "../../hub.js"; +import { pull } from "../../hub/index.js"; import { AgentExecutor, createStructuredChatAgent } from "../index.js"; const tools = [new TavilySearchResults({ maxResults: 1 })]; diff --git a/langchain/src/agents/tests/create_xml_agent.int.test.ts b/langchain/src/agents/tests/create_xml_agent.int.test.ts index 3f54a09b3b29..e4f6bb137588 100644 --- a/langchain/src/agents/tests/create_xml_agent.int.test.ts +++ b/langchain/src/agents/tests/create_xml_agent.int.test.ts @@ -2,7 +2,7 @@ import { test, expect } from "@jest/globals"; import type { PromptTemplate } from "@langchain/core/prompts"; import { ChatOpenAI } from "@langchain/openai"; import { TavilySearchResults } from "../../util/testing/tools/tavily_search.js"; -import { pull } from "../../hub.js"; +import { pull } from "../../hub/index.js"; import { AgentExecutor, createXmlAgent } from "../index.js"; const tools = [new TavilySearchResults({ maxResults: 1 })]; diff --git a/langchain/src/chat_models/tests/universal.int.test.ts b/langchain/src/chat_models/tests/universal.int.test.ts index 0df247708146..a6a10403858a 100644 --- a/langchain/src/chat_models/tests/universal.int.test.ts +++ b/langchain/src/chat_models/tests/universal.int.test.ts @@ -1,4 +1,5 @@ /* eslint-disable no-process-env */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { tool } from "@langchain/core/tools"; import { z } from "zod"; import { it } from "@jest/globals"; @@ -8,7 +9,7 @@ import { AIMessageChunk } from "@langchain/core/messages"; import { concat } from "@langchain/core/utils/stream"; import { awaitAllCallbacks } from "@langchain/core/callbacks/promises"; import { AgentExecutor, createReactAgent } from "../../agents/index.js"; -import { pull } from "../../hub.js"; +import { pull } from "../../hub/index.js"; import { initChatModel } from "../universal.js"; // Make copies of API keys and remove them from the environment to avoid conflicts. diff --git a/langchain/src/chat_models/universal.ts b/langchain/src/chat_models/universal.ts index b6fd4af95d22..9d184e4bb3c7 100644 --- a/langchain/src/chat_models/universal.ts +++ b/langchain/src/chat_models/universal.ts @@ -75,6 +75,7 @@ async function _initChatModelHelper( `Unable to infer model provider for { model: ${model} }, please specify modelProvider directly.` ); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { modelProvider: _unused, ...passedParams } = params; try { diff --git a/langchain/src/hub.ts b/langchain/src/hub/base.ts similarity index 73% rename from langchain/src/hub.ts rename to langchain/src/hub/base.ts index b2cef621173a..6b4c2de020c5 100644 --- a/langchain/src/hub.ts +++ b/langchain/src/hub/base.ts @@ -1,7 +1,6 @@ -import { Client } from "langsmith"; -import { Runnable } from "@langchain/core/runnables"; import type { BaseLanguageModel } from "@langchain/core/language_models/base"; -import { load } from "./load/index.js"; +import type { Runnable } from "@langchain/core/runnables"; +import { Client } from "langsmith"; /** * Push a prompt to the hub. @@ -11,7 +10,7 @@ import { load } from "./load/index.js"; * @param options * @returns The URL of the newly pushed prompt in the hub. */ -export async function push( +export async function basePush( repoFullName: string, runnable: Runnable, options?: { @@ -40,21 +39,9 @@ export async function push( return client.pushPrompt(repoFullName, payloadOptions); } -/** - * Pull a prompt from the hub. - * @param ownerRepoCommit The name of the repo containing the prompt, as well as an optional commit hash separated by a slash. - * @param options - * @returns - */ -export async function pull( +export async function basePull( ownerRepoCommit: string, - options?: { - apiKey?: string; - apiUrl?: string; - includeModel?: boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - modelClass?: new (...args: any[]) => BaseLanguageModel; - } + options?: { apiKey?: string; apiUrl?: string; includeModel?: boolean } ) { const client = new Client(options); @@ -76,13 +63,46 @@ export async function pull( lc_hub_commit_hash: promptObject.commit_hash, }; + // Some nested mustache prompts have improperly parsed variables that include a dot. + if (promptObject.manifest.kwargs.template_format === "mustache") { + const stripDotNotation = (varName: string) => varName.split(".")[0]; + + const { input_variables } = promptObject.manifest.kwargs; + if (Array.isArray(input_variables)) { + promptObject.manifest.kwargs.input_variables = + input_variables.map(stripDotNotation); + } + + const { messages } = promptObject.manifest.kwargs; + if (Array.isArray(messages)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + promptObject.manifest.kwargs.messages = messages.map((message: any) => { + const nestedVars = message?.kwargs?.prompt?.kwargs?.input_variables; + if (Array.isArray(nestedVars)) { + // eslint-disable-next-line no-param-reassign + message.kwargs.prompt.kwargs.input_variables = + nestedVars.map(stripDotNotation); + } + return message; + }); + } + } + return promptObject; +} + +export function generateModelImportMap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + modelClass?: new (...args: any[]) => BaseLanguageModel +) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const modelImportMap: Record = {}; // TODO: Fix in 0.4.0. We can't get lc_id without instantiating the class, so we // must put them inline here. In the future, make this less hacky // This should probably use dynamic imports and have a web-only entrypoint // in a future breaking release - if (options?.modelClass !== undefined) { - const modelLcName = (options.modelClass as any)?.lc_name(); + if (modelClass !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const modelLcName = (modelClass as any)?.lc_name(); let importMapKey; if (modelLcName === "ChatAnthropic") { importMapKey = "chat_models__anthropic"; @@ -105,62 +125,8 @@ export async function pull( } modelImportMap[importMapKey] = { ...modelImportMap[importMapKey], - [modelLcName]: options.modelClass, + [modelLcName]: modelClass, }; } - - // Some nested mustache prompts have improperly parsed variables that include a dot. - if (promptObject.manifest.kwargs.template_format === "mustache") { - const stripDotNotation = (varName: string) => varName.split(".")[0]; - - const { input_variables } = promptObject.manifest.kwargs; - if (Array.isArray(input_variables)) { - promptObject.manifest.kwargs.input_variables = - input_variables.map(stripDotNotation); - } - - const { messages } = promptObject.manifest.kwargs; - if (Array.isArray(messages)) { - promptObject.manifest.kwargs.messages = messages.map((message: any) => { - const nestedVars = message?.kwargs?.prompt?.kwargs?.input_variables; - if (Array.isArray(nestedVars)) { - // eslint-disable-next-line no-param-reassign - message.kwargs.prompt.kwargs.input_variables = - nestedVars.map(stripDotNotation); - } - return message; - }); - } - } - - try { - const loadedPrompt = await load( - JSON.stringify(promptObject.manifest), - undefined, - undefined, - modelImportMap - ); - return loadedPrompt; - } catch (e: any) { - if (options?.includeModel && options?.modelClass === undefined) { - throw new Error( - [ - e.message, - "", - `To load prompts with an associated non-OpenAI model, you must pass a "modelClass" parameter like this:`, - "", - "```", - `import { ChatAnthropic } from "@langchain/anthropic";`, - "", - `const prompt = await pull("my-prompt", {`, - ` includeModel: true,`, - ` modelClass: ChatAnthropic,`, - `});`, - "```", - ].join("\n") - ); - } else { - throw e; - } - } + return modelImportMap; } diff --git a/langchain/src/hub/index.ts b/langchain/src/hub/index.ts new file mode 100644 index 000000000000..71377ba0f11c --- /dev/null +++ b/langchain/src/hub/index.ts @@ -0,0 +1,67 @@ +import { Runnable } from "@langchain/core/runnables"; +import type { BaseLanguageModel } from "@langchain/core/language_models/base"; +import { load } from "../load/index.js"; +import { basePush, basePull, generateModelImportMap } from "./base.js"; + +export { basePush as push }; + +/** + * Pull a prompt from the hub. + * + * @param ownerRepoCommit The name of the repo containing the prompt, as well as an optional commit hash separated by a slash. + * @param options.apiKey LangSmith API key to use when pulling the prompt + * @param options.apiUrl LangSmith API URL to use when pulling the prompt + * @param options.includeModel Whether to also instantiate and attach a model instance to the prompt, + * if the prompt has associated model metadata. If set to true, invoking the resulting pulled prompt will + * also invoke the instantiated model. For non-OpenAI models, you must also set "modelClass" to the + * correct class of the model. + * @param options.modelClass If includeModel is true, the class of the model to instantiate. Required + * for non-OpenAI models. If you are running in Node or another environment that supports dynamic imports, + * you may instead import this function from "langchain/hub/node" and pass "includeModel: true" instead + * of specifying this parameter. + * @returns + */ +export async function pull( + ownerRepoCommit: string, + options?: { + apiKey?: string; + apiUrl?: string; + includeModel?: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + modelClass?: new (...args: any[]) => BaseLanguageModel; + } +) { + const promptObject = await basePull(ownerRepoCommit, options); + try { + const loadedPrompt = await load( + JSON.stringify(promptObject.manifest), + undefined, + undefined, + generateModelImportMap(options?.modelClass) + ); + return loadedPrompt; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (options?.includeModel) { + throw new Error( + [ + e.message, + "", + `To load prompts with an associated non-OpenAI model, you must use the "langchain/hub/node" entrypoint, or pass a "modelClass" parameter like this:`, + "", + "```", + `import { pull } from "langchain/hub";`, + `import { ChatAnthropic } from "@langchain/anthropic";`, + "", + `const prompt = await pull("my-prompt", {`, + ` includeModel: true,`, + ` modelClass: ChatAnthropic,`, + `});`, + "```", + ].join("\n") + ); + } else { + throw e; + } + } +} diff --git a/langchain/src/hub/node.ts b/langchain/src/hub/node.ts new file mode 100644 index 000000000000..ab01777f5d2a --- /dev/null +++ b/langchain/src/hub/node.ts @@ -0,0 +1,62 @@ +import { Runnable } from "@langchain/core/runnables"; +import { basePush, basePull, generateModelImportMap } from "./base.js"; +import { load } from "../load/index.js"; + +// TODO: Make this the default, add web entrypoint in next breaking release + +export { basePush as push }; + +/** + * Pull a prompt from the hub. + * @param ownerRepoCommit The name of the repo containing the prompt, as well as an optional commit hash separated by a slash. + * @param options.apiKey LangSmith API key to use when pulling the prompt + * @param options.apiUrl LangSmith API URL to use when pulling the prompt + * @param options.includeModel Whether to also instantiate and attach a model instance to the prompt, + * if the prompt has associated model metadata. If set to true, invoking the resulting pulled prompt will + * also invoke the instantiated model. You must have the appropriate LangChain integration package installed. + * @returns + */ +export async function pull( + ownerRepoCommit: string, + options?: { + apiKey?: string; + apiUrl?: string; + includeModel?: boolean; + } +) { + const promptObject = await basePull(ownerRepoCommit, options); + let modelClass; + if (options?.includeModel) { + if (Array.isArray(promptObject.manifest.kwargs?.last?.kwargs?.bound?.id)) { + const modelName = + promptObject.manifest.kwargs?.last?.kwargs?.bound?.id.at(-1); + if (modelName === "ChatAnthropic") { + modelClass = (await import("@langchain/anthropic")).ChatAnthropic; + } else if (modelName === "ChatAzureOpenAI") { + modelClass = (await import("@langchain/openai")).AzureChatOpenAI; + } else if (modelName === "ChatGoogleVertexAI") { + modelClass = (await import("@langchain/google-vertexai")).ChatVertexAI; + } else if (modelName === "ChatGoogleGenerativeAI") { + modelClass = (await import("@langchain/google-genai")) + .ChatGoogleGenerativeAI; + } else if (modelName === "ChatBedrockConverse") { + modelClass = (await import("@langchain/aws")).ChatBedrockConverse; + } else if (modelName === "ChatMistral") { + modelClass = (await import("@langchain/mistralai")).ChatMistralAI; + } else if (modelName === "ChatGroq") { + modelClass = (await import("@langchain/groq")).ChatGroq; + } else if (modelName !== undefined) { + console.warn( + `Received unknown model name from prompt hub: "${modelName}"` + ); + } + } + } + const loadedPrompt = await load( + JSON.stringify(promptObject.manifest), + undefined, + undefined, + generateModelImportMap(modelClass) + ); + return loadedPrompt; +} diff --git a/langchain/src/hub/tests/hub.int.test.ts b/langchain/src/hub/tests/hub.int.test.ts new file mode 100644 index 000000000000..130e3d3e9bb9 --- /dev/null +++ b/langchain/src/hub/tests/hub.int.test.ts @@ -0,0 +1,90 @@ +/* eslint-disable no-process-env */ + +import { ChatPromptTemplate, PromptTemplate } from "@langchain/core/prompts"; +import { + AIMessage, + HumanMessage, + SystemMessage, +} from "@langchain/core/messages"; +import { ChatPromptValue } from "@langchain/core/prompt_values"; +import { ChatAnthropic } from "@langchain/anthropic"; +import * as hub from "../index.js"; +import { pull as nodePull } from "../node.js"; + +test("Test LangChain Hub client pushing a new repo", async () => { + const prompt = PromptTemplate.fromTemplate( + `You are a parrot. The current date is ${new Date().toISOString()}\n{input}` + ); + const repoName = `${ + process.env.LANGCHAIN_HUB_USERNAME + }/langchainjs-${new Date().getTime()}`; + await hub.push(repoName, prompt, { + newRepoIsPublic: false, + }); + const pulledPrompt = await hub.pull(repoName); + expect(await prompt.invoke({ input: "testing" })).toEqual( + await pulledPrompt.invoke({ input: "testing" }) + ); + const pulledPromptNode = await nodePull(repoName); + expect(await prompt.invoke({ input: "testing" })).toEqual( + await pulledPromptNode.invoke({ input: "testing" }) + ); +}); + +test("Test LangChain Hub client pushing a new chat template repo", async () => { + const prompt = ChatPromptTemplate.fromTemplate( + `You are a parrot. The current date is ${new Date().toISOString()}\n{input}` + ); + const repoName = `${ + process.env.LANGCHAIN_HUB_USERNAME + }/langchainjs-${new Date().getTime()}`; + await hub.push(repoName, prompt, { + newRepoIsPublic: false, + }); + const pulledPrompt = await hub.pull(repoName); + expect(await prompt.invoke({ input: "testing" })).toEqual( + await pulledPrompt.invoke({ input: "testing" }) + ); + const pulledPromptNode = await nodePull(repoName); + expect(await prompt.invoke({ input: "testing" })).toEqual( + await pulledPromptNode.invoke({ input: "testing" }) + ); +}); + +test("Test LangChain Hub with a faulty mustache prompt", async () => { + const pulledPrompt = await hub.pull("jacob/lahzo-testing"); + const res = await pulledPrompt.invoke({ + agent: { name: "testing" }, + messages: [new AIMessage("foo")], + }); + expect(res).toEqual( + new ChatPromptValue([ + new SystemMessage("You are a chatbot."), + new HumanMessage("testing"), + new AIMessage("foo"), + ]) + ); +}); + +test("Test LangChain Hub while loading model", async () => { + const pulledPrompt = await hub.pull("jacob/lahzo-testing", { + includeModel: true, + modelClass: ChatAnthropic, + }); + const res = await pulledPrompt.invoke({ + agent: { name: "testing" }, + messages: [new AIMessage("foo")], + }); + expect(res).toBeInstanceOf(AIMessage); +}); + +test("Test LangChain Hub while loading model with dynamic imports", async () => { + const pulledPrompt = await nodePull("jacob/lahzo-testing", { + includeModel: true, + }); + const res = await pulledPrompt.invoke({ + agent: { name: "testing" }, + messages: [new AIMessage("foo")], + }); + expect(res).toBeInstanceOf(AIMessage); +}); diff --git a/langchain/src/load/import_constants.ts b/langchain/src/load/import_constants.ts index 40b10e81e3f0..9fc3f8291a6a 100644 --- a/langchain/src/load/import_constants.ts +++ b/langchain/src/load/import_constants.ts @@ -24,5 +24,6 @@ export const optionalImportEntrypoints: string[] = [ "langchain/stores/file/node", "langchain/storage/file_system", "langchain/hub", + "langchain/hub/node", "langchain/experimental/prompts/handlebars", ]; diff --git a/langchain/src/load/index.ts b/langchain/src/load/index.ts index 473bf402eb81..d4952c8739be 100644 --- a/langchain/src/load/index.ts +++ b/langchain/src/load/index.ts @@ -18,6 +18,7 @@ export async function load( secretsMap: Record = {}, // eslint-disable-next-line @typescript-eslint/no-explicit-any optionalImportsMap: OptionalImportMap & Record = {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any additionalImportsMap: Record = {} ): Promise { return coreLoad(text, { diff --git a/langchain/src/tests/hub.int.test.ts b/langchain/src/tests/hub.int.test.ts deleted file mode 100644 index e87f7460f7b0..000000000000 --- a/langchain/src/tests/hub.int.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable no-process-env */ - -import { ChatPromptTemplate } from "@langchain/core/prompts"; -import { - AIMessage, - HumanMessage, - SystemMessage, -} from "@langchain/core/messages"; -import { ChatPromptValue } from "@langchain/core/prompt_values"; -import * as hub from "../hub.js"; - -test("Test LangChain Hub client pushing a new repo", async () => { - const prompt = ChatPromptTemplate.fromTemplate( - `You are a parrot. The current date is ${new Date().toISOString()}\n{input}` - ); - const repoName = `${ - process.env.LANGCHAIN_HUB_USERNAME - }/langchainjs-${new Date().getTime()}`; - await hub.push(repoName, prompt, { - newRepoIsPublic: false, - }); - const pulledPrompt = await hub.pull(repoName); - expect(await prompt.invoke({ input: "testing" })).toEqual( - await pulledPrompt.invoke({ input: "testing" }) - ); -}); - -test("Test LangChain Hub with a faulty mustache prompt", async () => { - const pulledPrompt = await hub.pull("jacob/lahzo-testing"); - const res = await pulledPrompt.invoke({ - agent: { name: "testing" }, - messages: [new AIMessage("foo")], - }); - expect(res).toEqual( - new ChatPromptValue([ - new SystemMessage("You are a chatbot."), - new HumanMessage("testing"), - new AIMessage("foo"), - ]) - ); -});