课程目标
掌握 LangChain.js 的 Prompt 模板体系:ChatPromptTemplate 的创建与变量填充、MessagesPlaceholder 动态消息注入、FewShotPromptTemplate 动态示例、PipelinePromptTemplate 多模板组合、ImagePromptTemplate 图片提示,以及 PromptValue 中间表示层。
12.1 Prompt 的 Runnable 本质
Prompt 模板在 LangChain.js 中不只是字符串拼接工具,它是一个完整的 Runnable:
输入: Record<string, any>(变量字典)
输出: ChatPromptValue(包装了 BaseMessage[] 的中间对象)
这意味着 Prompt 可以直接参与 pipe() 组合:
const chain = prompt.pipe(model).pipe(parser);
源码位置: libs/langchain-core/src/prompts/chat.ts
继承关系:
BasePromptTemplate // 所有 Prompt 的抽象基类
├── BaseChatPromptTemplate // Chat 类型 Prompt 基类
│ └── ChatPromptTemplate // 最常用的聊天模板
│ └── FewShotChatMessagePromptTemplate
├── BaseStringPromptTemplate // 字符串输出的 Prompt 基类
│ ├── PromptTemplate // 基础字符串模板
│ └── FewShotPromptTemplate // Few-shot 字符串模板
└── PipelinePromptTemplate // 管道组合模板
12.2 ChatPromptTemplate — 核心模板
12.2.1 fromMessages 创建
最常用的方式是 ChatPromptTemplate.fromMessages(),接受消息模板数组:
import { ChatPromptTemplate } from "@langchain/core/prompts";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant that translates {input_language} to {output_language}."],
["human", "{input}"],
]);
// prompt 是一个 Runnable,调用 invoke 返回 ChatPromptValue
const result = await prompt.invoke({
input_language: "English",
output_language: "Chinese",
input: "Hello, how are you?",
});
// result.messages → [SystemMessage, HumanMessage]
源码关键: fromMessages 内部会将元组 ["human", "..."] 转换为 HumanMessagePromptTemplate 实例,通过 _coerceMessagePromptTemplateLike 函数完成。
12.2.2 变量占位符 {variable}
使用 f-string 风格的 {variable} 语法。框架在构造时会自动提取 inputVariables 并做校验:
// 构造函数中的校验逻辑(chat.ts 第 960-1001 行)
// 会检查 inputVariables 和 promptMessages 中的变量是否匹配
// 不匹配时抛出错误
12.2.3 formatMessages 核心方法
ChatPromptTemplate.formatMessages() 是核心方法,遍历所有 promptMessages,逐个格式化:
// 源码简化(chat.ts 第 1060-1105 行)
async formatMessages(values): Promise<BaseMessage[]> {
const allValues = await this.mergePartialAndUserVariables(values);
let resultMessages: BaseMessage[] = [];
for (const promptMessage of this.promptMessages) {
if (promptMessage instanceof BaseMessage) {
// 静态消息直接加入
resultMessages.push(promptMessage);
} else {
// 模板消息需要格式化
const message = await promptMessage.formatMessages(inputValues);
resultMessages = resultMessages.concat(message);
}
}
return resultMessages;
}
12.2.4 partial — 预填充部分变量
const fullPrompt = ChatPromptTemplate.fromMessages([
["system", "You speak {language}. Your name is {name}."],
["human", "{question}"],
]);
// 预填充 language,返回只需 name 和 question 的新模板
const partialPrompt = await fullPrompt.partial({ language: "Chinese" });
const result = await partialPrompt.invoke({ name: "Assistant", question: "Hi" });
12.3 MessagesPlaceholder — 动态消息注入
源码位置: libs/langchain-core/src/prompts/chat.ts 第 101-180 行
MessagesPlaceholder 用于在模板中预留一个"插槽",运行时注入任意数量的消息。最典型的用途是注入对话历史(配合第 10 课的 RunnableWithMessageHistory)。
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant."],
new MessagesPlaceholder("history"),
["human", "{input}"],
]);
const result = await prompt.invoke({
history: [
new HumanMessage("What is 1+1?"),
new AIMessage("1+1 = 2"),
],
input: "And what is 2+2?",
});
// result.messages → [SystemMessage, HumanMessage("What is 1+1?"), AIMessage("1+1 = 2"), HumanMessage("And what is 2+2?")]
12.3.1 optional 参数
// 默认情况下,如果变量缺失会抛出错误
// 设置 optional: true 后,缺失时返回空数组
new MessagesPlaceholder({ variableName: "history", optional: true });
12.3.2 元组简写
可以用 ["placeholder", "{history}"] 元组形式替代手动创建:
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant."],
["placeholder", "{history}"], // 等效于 new MessagesPlaceholder({ variableName: "history", optional: true })
["human", "{input}"],
]);
注意:用元组形式时默认 optional: true。
12.4 PromptValue — 中间表示层
源码位置: libs/langchain-core/src/prompt_values.ts
PromptValue 是 Prompt 格式化后、传入 Model 前的中间数据结构。它提供两种输出方式:
abstract class BasePromptValue extends Serializable {
abstract toString(): string; // 转为纯文本(给 LLM 用)
abstract toChatMessages(): BaseMessage[]; // 转为消息列表(给 ChatModel 用)
}
三种具体实现:
| 类 | 用途 | toString() | toChatMessages() |
|---|---|---|---|
StringPromptValue | 纯文本模板结果 | 原始字符串 | 包装为 [HumanMessage] |
ChatPromptValue | 聊天模板结果 | getBufferString(messages) | 原始消息列表 |
ImagePromptValue | 图片模板结果 | 图片 URL | 包装为多模态 HumanMessage |
这个设计让同一个 Prompt 既能对接 BaseLLM(需要字符串),又能对接 BaseChatModel(需要消息列表)。
12.5 FewShotPromptTemplate — 动态 Few-Shot 示例
源码位置: libs/langchain-core/src/prompts/few_shot.ts
12.5.1 静态示例
import { FewShotPromptTemplate } from "@langchain/core/prompts";
import { PromptTemplate } from "@langchain/core/prompts";
const examplePrompt = PromptTemplate.fromTemplate(
"Input: {input}\nOutput: {output}"
);
const fewShotPrompt = new FewShotPromptTemplate({
examples: [
{ input: "happy", output: "sad" },
{ input: "tall", output: "short" },
],
examplePrompt,
prefix: "Give the antonym of every input.",
suffix: "Input: {adjective}\nOutput:",
inputVariables: ["adjective"],
});
const result = await fewShotPrompt.format({ adjective: "big" });
// "Give the antonym of every input.\n\nInput: happy\nOutput: sad\n\nInput: tall\nOutput: short\n\nInput: big\nOutput:"
12.5.2 动态示例(配合 ExampleSelector)
const fewShotPrompt = new FewShotPromptTemplate({
exampleSelector: mySelector, // 第 13 课详解
examplePrompt,
prefix: "Give the antonym of every input.",
suffix: "Input: {adjective}\nOutput:",
inputVariables: ["adjective"],
});
源码逻辑(few_shot.ts 第 167-180 行):
private async getExamples(inputVariables: InputValues): Promise<InputValues[]> {
if (this.examples !== undefined) {
return this.examples; // 静态示例直接返回
}
if (this.exampleSelector !== undefined) {
return this.exampleSelector.selectExamples(inputVariables); // 动态选择
}
throw new Error("One of 'examples' and 'example_selector' should be provided");
}
12.5.3 FewShotChatMessagePromptTemplate
用于聊天场景的 Few-Shot 模板,输出 BaseMessage[] 而非字符串:
import { FewShotChatMessagePromptTemplate } from "@langchain/core/prompts";
const fewShotChatPrompt = new FewShotChatMessagePromptTemplate({
examples: [
{ input: "happy", output: "sad" },
{ input: "tall", output: "short" },
],
examplePrompt: ChatPromptTemplate.fromMessages([
["human", "{input}"],
["ai", "{output}"],
]),
inputVariables: [],
});
const messages = await fewShotChatPrompt.formatMessages({});
// [HumanMessage("happy"), AIMessage("sad"), HumanMessage("tall"), AIMessage("short")]
12.6 PipelinePromptTemplate — 模板组合
源码位置: libs/langchain-core/src/prompts/pipeline.ts
PipelinePromptTemplate 将多个子模板的输出作为变量传递给最终模板,实现模板的模块化组合:
import { PipelinePromptTemplate } from "@langchain/core/prompts";
import { PromptTemplate } from "@langchain/core/prompts";
const introPrompt = PromptTemplate.fromTemplate("You are impersonating {person}.");
const examplePrompt = PromptTemplate.fromTemplate(
"Here's an example:\nQ: {example_q}\nA: {example_a}"
);
const startPrompt = PromptTemplate.fromTemplate("Now answer:\nQ: {input}\nA:");
const finalPrompt = PromptTemplate.fromTemplate(
"{introduction}\n{example}\n{start}"
);
const pipeline = new PipelinePromptTemplate({
pipelinePrompts: [
{ name: "introduction", prompt: introPrompt },
{ name: "example", prompt: examplePrompt },
{ name: "start", prompt: startPrompt },
],
finalPrompt,
});
const result = await pipeline.format({
person: "Elon Musk",
example_q: "What's your favorite car?",
example_a: "Tesla",
input: "What's your favorite social media site?",
});
核心流程(pipeline.ts 第 124-150 行):
- 遍历每个
pipelinePrompt,用当前所有值格式化其输出 - 将输出结果作为新变量名加入
allValues - 最终用
allValues格式化finalPrompt
computeInputValues() 方法自动剔除中间变量名,只保留真正需要用户提供的输入变量。
12.7 ImagePromptTemplate — 图片提示
源码位置: libs/langchain-core/src/prompts/image.ts
用于构造包含图片的多模态提示:
import { HumanMessagePromptTemplate } from "@langchain/core/prompts";
// 在 HumanMessagePromptTemplate.fromTemplate 中使用 image_url
const messageTemplate = HumanMessagePromptTemplate.fromTemplate([
{ text: "Describe this image:" },
{ image_url: "{image_url}" },
]);
const message = await messageTemplate.format({
image_url: "https://example.com/cat.jpg",
});
// HumanMessage({ content: [
// { type: "text", text: "Describe this image:" },
// { type: "image_url", image_url: { url: "https://example.com/cat.jpg" } }
// ]})
ImagePromptTemplate 继承 BasePromptTemplate,其 format() 方法渲染 template 中的变量,返回 ImageContent(包含 url 和可选的 detail 字段)。
12.8 消息模板类型体系
_StringImageMessagePromptTemplate 是内部基类,为每种消息角色提供了模板子类:
| 模板类 | 生成的消息类型 | 使用场景 |
|---|---|---|
HumanMessagePromptTemplate | HumanMessage | 用户输入模板 |
AIMessagePromptTemplate | AIMessage | AI 回复模板 |
SystemMessagePromptTemplate | SystemMessage | 系统指令模板 |
ChatMessagePromptTemplate | ChatMessage | 自定义角色模板 |
这些类都支持 fromTemplate() 静态方法,接受字符串或多模态内容数组。
12.9 实战练习
练习 1:带历史的对话模板
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are {role}. Respond in {language}."],
new MessagesPlaceholder({ variableName: "chat_history", optional: true }),
["human", "{question}"],
]);
const messages = await prompt.formatMessages({
role: "a math tutor",
language: "Chinese",
chat_history: [
new HumanMessage("What is 1+1?"),
new AIMessage("1+1 = 2"),
],
question: "What is 2+2?",
});
练习 2:Few-Shot + ChatPromptTemplate 组合
import { FewShotChatMessagePromptTemplate, ChatPromptTemplate } from "@langchain/core/prompts";
const examplePrompt = ChatPromptTemplate.fromMessages([
["human", "{input}"],
["ai", "{output}"],
]);
const fewShot = new FewShotChatMessagePromptTemplate({
examples: [
{ input: "2+2", output: "4" },
{ input: "3+3", output: "6" },
],
examplePrompt,
inputVariables: [],
});
const fullPrompt = ChatPromptTemplate.fromMessages([
["system", "You are a calculator. Follow the examples:"],
fewShot,
["human", "{question}"],
]);
const result = await fullPrompt.invoke({ question: "5+5" });
12.10 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | prompts/chat.ts | ChatPromptTemplate.fromMessages(), formatMessages(), MessagesPlaceholder |
| P0 | prompt_values.ts | ChatPromptValue, StringPromptValue, ImagePromptValue |
| P1 | prompts/few_shot.ts | FewShotPromptTemplate, FewShotChatMessagePromptTemplate |
| P1 | prompts/pipeline.ts | PipelinePromptTemplate, formatPipelinePrompts() |
| P2 | prompts/image.ts | ImagePromptTemplate |
| P2 | prompts/prompt.ts | PromptTemplate(基础字符串模板) |
| P2 | prompts/base.ts | BasePromptTemplate 抽象基类 |
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 掌握 ChatPromptTemplate.fromMessages() 创建模板和 {variable} 变量填充 |
| 🔵 中阶 | 理解 MessagesPlaceholder 如何与对话历史(第 10 课)配合动态注入消息 |
| 🟡 高阶 | 掌握 PipelinePromptTemplate 的多模板组合模式和 FewShotChatMessagePromptTemplate |
| 🟠 资深 | 分析 Prompt 作为 Runnable 的设计:输入是变量字典,输出经 PromptValue 中间层适配不同模型 |
| 🔴 架构 | 设计可复用的 Prompt 库:版本管理、模块化组合、partial() 预填充策略 |
下一课预告
第 13 课讲 ExampleSelector 与高级 Few-Shot——当有大量示例时,如何用 LengthBasedExampleSelector 和 SemanticSimilarityExampleSelector 智能选择最相关的几个?