From 6c6f11b6985d7a39de783506c6febdf960da3445 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 24 Jan 2025 12:45:49 -0800 Subject: [PATCH 1/2] Add shim for hub mustache templates with nested input variables --- langchain-core/src/prompts/chat.ts | 30 +++++++++++++----- .../src/prompts/tests/chat.mustache.test.ts | 31 +++++++++++++++++++ langchain/src/hub.ts | 23 ++++++++++++++ langchain/src/tests/hub.int.test.ts | 21 +++++++++++++ 4 files changed, 97 insertions(+), 8 deletions(-) diff --git a/langchain-core/src/prompts/chat.ts b/langchain-core/src/prompts/chat.ts index 406d9f6b6f17..ed864856f671 100644 --- a/langchain-core/src/prompts/chat.ts +++ b/langchain-core/src/prompts/chat.ts @@ -771,16 +771,30 @@ function _coerceMessagePromptTemplateLike< ) { const messageContent = messagePromptTemplateLike[1]; if ( - typeof messageContent !== "string" || - messageContent[0] !== "{" || - messageContent[messageContent.length - 1] !== "}" + extra?.templateFormat === "mustache" && + typeof messageContent === "string" && + messageContent.slice(0, 2) === "{{" && + messageContent.slice(-2) === "}}" ) { - throw new Error( - `Invalid placeholder template: "${messagePromptTemplateLike[1]}". Expected a variable name surrounded by curly braces.` - ); + const variableName = messageContent.slice(2, -2); + return new MessagesPlaceholder({ variableName, optional: true }); + } else if ( + typeof messageContent === "string" && + messageContent[0] === "{" && + messageContent[messageContent.length - 1] === "}" + ) { + const variableName = messageContent.slice(1, -1); + return new MessagesPlaceholder({ variableName, optional: true }); } - const variableName = messageContent.slice(1, -1); - return new MessagesPlaceholder({ variableName, optional: true }); + throw new Error( + `Invalid placeholder template for format ${ + extra?.templateFormat ?? `"f-string"` + }: "${ + messagePromptTemplateLike[1] + }". Expected a variable name surrounded by ${ + extra?.templateFormat === "mustache" ? "double" : "single" + } curly braces.` + ); } const message = coerceMessageLikeToMessage(messagePromptTemplateLike); let templateData: diff --git a/langchain-core/src/prompts/tests/chat.mustache.test.ts b/langchain-core/src/prompts/tests/chat.mustache.test.ts index f315a0824240..3d02a09acc96 100644 --- a/langchain-core/src/prompts/tests/chat.mustache.test.ts +++ b/langchain-core/src/prompts/tests/chat.mustache.test.ts @@ -149,3 +149,34 @@ test("Mustache image template with nested URL and chat prompts HumanMessagePromp expect(template.inputVariables.sort()).toEqual(["image_url", "name"]); }); + +test("Mustache image template with nested props", async () => { + const template = ChatPromptTemplate.fromMessages( + [ + ["human", "{{agent.name}}"], + ["placeholder", "{{messages}}"], + ], + { + templateFormat: "mustache", + } + ); + + const messages = await template.formatMessages({ + agent: { name: "testing" }, + messages: [ + { + role: "assistant", + content: "hi there!", + }, + ], + }); + + expect(messages).toEqual([ + new HumanMessage({ + content: "testing", + }), + new AIMessage("hi there!"), + ]); + + expect(template.inputVariables.sort()).toEqual(["agent", "messages"]); +}); diff --git a/langchain/src/hub.ts b/langchain/src/hub.ts index 7e3413c998f3..7640e0d5d31e 100644 --- a/langchain/src/hub.ts +++ b/langchain/src/hub.ts @@ -109,6 +109,29 @@ export async function pull( }; } + // 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)) { + message.kwargs.prompt.kwargs.input_variables = + nestedVars.map(stripDotNotation); + } + return message; + }); + } + } + try { const loadedPrompt = await load( JSON.stringify(promptObject.manifest), diff --git a/langchain/src/tests/hub.int.test.ts b/langchain/src/tests/hub.int.test.ts index 442520289705..e87f7460f7b0 100644 --- a/langchain/src/tests/hub.int.test.ts +++ b/langchain/src/tests/hub.int.test.ts @@ -1,6 +1,12 @@ /* 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 () => { @@ -18,3 +24,18 @@ test("Test LangChain Hub client pushing a new repo", async () => { 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"), + ]) + ); +}); From 9a9812b97bf9bb2039dbdc50acb124e2581458e2 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 24 Jan 2025 12:52:25 -0800 Subject: [PATCH 2/2] Fix lint --- langchain/src/hub.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/langchain/src/hub.ts b/langchain/src/hub.ts index 7640e0d5d31e..b2cef621173a 100644 --- a/langchain/src/hub.ts +++ b/langchain/src/hub.ts @@ -124,6 +124,7 @@ export async function pull( 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); }