Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strong-worms-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@langchain/core": patch
---

make mustache prompt with nested object working correctly
46 changes: 27 additions & 19 deletions langchain-core/src/prompts/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1064,25 +1064,33 @@ export class ChatPromptTemplate<
await this._parseImagePrompts(promptMessage, allValues)
);
} else {
const inputValues = promptMessage.inputVariables.reduce(
(acc, inputVariable) => {
if (
!(inputVariable in allValues) &&
!(isMessagesPlaceholder(promptMessage) && promptMessage.optional)
) {
const error = addLangChainErrorFields(
new Error(
`Missing value for input variable \`${inputVariable.toString()}\``
),
"INVALID_PROMPT_INPUT"
);
throw error;
}
acc[inputVariable] = allValues[inputVariable];
return acc;
},
{} as InputValues
);
let inputValues: InputValues;

if (this.templateFormat === "mustache") {
inputValues = { ...allValues };
} else {
inputValues = promptMessage.inputVariables.reduce(
(acc, inputVariable) => {
if (
!(inputVariable in allValues) &&
!(
isMessagesPlaceholder(promptMessage) && promptMessage.optional
)
) {
const error = addLangChainErrorFields(
new Error(
`Missing value for input variable \`${inputVariable.toString()}\``
),
"INVALID_PROMPT_INPUT"
);
throw error;
}
acc[inputVariable] = allValues[inputVariable];
return acc;
},
{} as InputValues
);
}
const message = await promptMessage.formatMessages(inputValues);
resultMessages = resultMessages.concat(message);
}
Expand Down
7 changes: 5 additions & 2 deletions langchain-core/src/prompts/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ export const parseFString = (template: string): ParsedTemplateNode[] => {
* to make it compatible with other LangChain string parsing template formats.
*
* @param {mustache.TemplateSpans} template The result of parsing a mustache template with the mustache.js library.
* @param {string[]} context Array of section variable names for nested context
* @returns {ParsedTemplateNode[]}
*/
const mustacheTemplateToNodes = (
template: mustache.TemplateSpans
template: mustache.TemplateSpans,
context: string[] = []
): ParsedTemplateNode[] => {
const nodes: ParsedTemplateNode[] = [];

Expand All @@ -101,7 +103,8 @@ const mustacheTemplateToNodes = (

// If this is a section with nested content, recursively process it
if (temp[0] === "#" && temp.length > 4 && Array.isArray(temp[4])) {
const nestedNodes = mustacheTemplateToNodes(temp[4]);
const newContext = [...context, temp[1]];
const nestedNodes = mustacheTemplateToNodes(temp[4], newContext);
nodes.push(...nestedNodes);
}
} else {
Expand Down
29 changes: 29 additions & 0 deletions langchain-core/src/prompts/tests/chat.mustache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,35 @@ test("with ChatPromptTemplate", async () => {
);
});

test("nested object", async () => {
const samplePrompt =
"Here are the {{name}}:\n\n{{#locations}}\n- {{name}}\n{{/locations}}\n{{name}} end";

const sampleVariables = {
locations: [
{
name: "Tokyo",
},
{
name: "Los Angeles",
},
{
name: "Palo Alto",
},
],
name: "locations",
};

const expectedResult =
"Here are the locations:\n\n- Tokyo\n- Los Angeles\n- Palo Alto\nlocations end";
const prompt = ChatPromptTemplate.fromMessages([["system", samplePrompt]], {
templateFormat: "mustache",
});
expect(await prompt.format(sampleVariables)).toBe(
`System: ${expectedResult}`
);
});

test("Rendering a prompt with conditionals doesn't result in empty text blocks", async () => {
const manifest = {
lc: 1,
Expand Down
Loading