Skip to content

Commit

Permalink
Fix: validate input or throw when using MessagesPlaceholder (langchai…
Browse files Browse the repository at this point in the history
…n-ai#2874)

* fix: validate types or throw when using MessagesPlaceholder class

* chore: lint files

* fix: better error message and account for null/undefined values

* chore: lint files

* fix: upate isBaseMessage method explicitly

* chore: lint files

* fix: error messahe

* chore: lint files

* fix: tests, give error name

* chore: lint files

* fix: cleaner check

* Disallow nullish values

* Fix test

---------

Co-authored-by: jacoblee93 <[email protected]>
  • Loading branch information
bracesproul and jacoblee93 authored Oct 12, 2023
1 parent afc01bb commit 2039f07
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 4 deletions.
34 changes: 32 additions & 2 deletions langchain/src/prompts/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,40 @@ export class MessagesPlaceholder<
return [this.variableName];
}

formatMessages(
validateInputOrThrow(
input: Array<unknown>,
variableName: Extract<keyof RunInput, string>
): input is BaseMessage[] {
let isInputBaseMessage = false;

if (Array.isArray(input)) {
isInputBaseMessage = input.every((message) =>
isBaseMessage(message as BaseMessage)
);
} else {
isInputBaseMessage = isBaseMessage(input as BaseMessage);
}

if (!isInputBaseMessage) {
const readableInput =
typeof input === "string" ? input : JSON.stringify(input, null, 2);

const error = new Error(
`Error: Field "${variableName}" in prompt uses a MessagesPlaceholder, which expects an array of BaseMessages as an input value. Received: ${readableInput}`
);
error.name = "InputFormatError";
throw error;
}

return true;
}

async formatMessages(
values: TypedPromptInputValues<RunInput>
): Promise<BaseMessage[]> {
return Promise.resolve(values[this.variableName] as BaseMessage[]);
this.validateInputOrThrow(values[this.variableName], this.variableName);

return values[this.variableName];
}
}

Expand Down
49 changes: 49 additions & 0 deletions langchain/src/prompts/tests/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,52 @@ test("Test BaseMessage", async () => {
new FunctionMessage({ content: "{}", name: "get_weather" }),
]);
});

test("Throws if trying to pass non BaseMessage inputs to MessagesPlaceholder", async () => {
const prompt = ChatPromptTemplate.fromMessages([
["system", "some string"],
new MessagesPlaceholder("chatHistory"),
["human", "{question}"],
]);
const value = "this is not a valid input type!";

try {
await prompt.formatMessages({
chatHistory: value,
question: "What is the meaning of life?",
});
} catch (e) {
// eslint-disable-next-line no-instanceof/no-instanceof
if (e instanceof Error) {
expect(e.name).toBe("InputFormatError");
} else {
throw e;
}
}
});

test("Does not throws if null or undefined is passed as input to MessagesPlaceholder", async () => {
const prompt = ChatPromptTemplate.fromMessages([
["system", "some string"],
new MessagesPlaceholder("chatHistory"),
new MessagesPlaceholder("chatHistory2"),
["human", "{question}"],
]);
const value1 = null;
const value2 = undefined;

try {
await prompt.formatMessages({
chatHistory: value1,
chatHistory2: value2,
question: "What is the meaning of life?",
});
} catch (e) {
// eslint-disable-next-line no-instanceof/no-instanceof
if (e instanceof Error) {
expect(e.name).toBe("InputFormatError");
} else {
throw e;
}
}
});
4 changes: 2 additions & 2 deletions langchain/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,9 @@ export type BaseMessageLike =
| string;

export function isBaseMessage(
messageLike: BaseMessageLike
messageLike?: unknown
): messageLike is BaseMessage {
return typeof (messageLike as BaseMessage)._getType === "function";
return typeof (messageLike as BaseMessage)?._getType === "function";
}

export function coerceMessageLikeToMessage(
Expand Down

0 comments on commit 2039f07

Please sign in to comment.