LangChain.js 提示词模板全解
系统梳理 LangChain.js 的 9 个 Prompt Template 相关 API——基础模板、对话模板、Few-Shot 示例、示例选择器与流水线组装,附带可直接照抄的最小代码示例。
Leo
2026.08.29 · Updated 2026.09.13
LangChain.js 提示词模板全解
提示词(Prompt)是 LLM 应用里最容易写坏、也最值得抽出来管理的部分。 硬编码的提示词没法复用、没法换变量、没法按场景动态调整;而 Prompt Template 就是把这些提示词变成"可以填变量的模板"——这是所有 Agent / RAG 应用的地基。
本文基于
@langchain/core的 prompts 与 example_selectors 模块,用最小可运行示例逐个拆解 9 个核心 API。
为什么要学 Prompt Template
三个理由,缺一个都值得学:
- 复用:人设、背景、任务、格式这些模块,写一次到处用。
- 动态:同一个模板,换公司名、换团队、换本周数据,就是一份新 Prompt。
- 可控:示例(Few-Shot)要不要带、带哪几条,可以由选择器按需决定,省 token 又提效果。
下面按"从简单到复杂"的顺序讲。
一、PromptTemplate:最朴素的占位符模板
它是所有模板的地基。用 {变量名} 占位,调用 format() 时填值。
import { PromptTemplate } from '@langchain/core/prompts';
const template = PromptTemplate.fromTemplate(`
你是一名工程团队负责人,请根据以下信息写一份周报。
公司名称:{company_name}
部门名称:{team_name}
本周核心目标:{team_goal}
开发数据:
{dev_activities}
请生成一份 Markdown 周报。
`);
const prompt = await template.format({
company_name: '星航科技',
team_name: '数据平台组',
team_goal: '完成用户画像服务灰度上线',
dev_activities: '- 阿兵:完成 Canary 发布,提交 27 次',
});
console.log(prompt); // 占位符全部被替换成真实值
要点:
fromTemplate()从字符串创建模板,自动识别{xxx}占位符。format()返回纯字符串,不调用模型。- 也可以直接用
new PromptTemplate({ template, inputVariables: [...] })显式声明变量。
二、ChatPromptTemplate:对话形式的模板
对话类模型(Chat 模型)的输入不是一段字符串,而是一个消息数组(system / human / ai 角色各说各话)。ChatPromptTemplate 就是用来组织这个数组的。
import { ChatPromptTemplate } from '@langchain/core/prompts';
const chatPrompt = ChatPromptTemplate.fromMessages([
['system', '你是一名资深技术负责人,写作风格:{tone}。'],
['human', `本周信息:公司 {company_name},团队 {team_name}。
本周开发数据:
{dev_activities}
请输出一份 Markdown 周报。`],
]);
// formatMessages() 返回消息数组,可直接丢给模型
const messages = await chatPrompt.formatMessages({
tone: '专业、清晰',
company_name: '星航科技',
team_name: 'AI 平台组',
dev_activities: '- 小李:完成工单流转,提交 25 次',
});
const response = await model.invoke(messages);
要点:
- 消息用
['system', 模板]、['human', 模板]这样的二元数组表示。 formatMessages()返回消息数组(区别于 PromptTemplate 的format()返回字符串)。- 对话模板比纯字符串模板更贴近真实 LLM 应用的输入结构。
三、MessagesPlaceholder:往对话里注入"历史记录"
多轮对话时,模型需要看到之前的对话。历史消息的数量和内容是运行时才知道的,不能写死模板里——这时用 MessagesPlaceholder 占一个"历史插槽"。
import {
ChatPromptTemplate,
MessagesPlaceholder,
} from '@langchain/core/prompts';
const chatPromptWithHistory = ChatPromptTemplate.fromMessages([
['system', '你是一名资深工程效率顾问,善于结合上下文给出建议。'],
// 历史对话从这里注入
new MessagesPlaceholder('history'),
['human', '这是本轮新问题:{current_input}\n请结合历史对话给出建议。'],
]);
const history = [
{ role: 'human', content: '我们团队在做周报自动生成工具。' },
{ role: 'ai', content: '建议先梳理 Git / Jira 数据源,再考虑 Prompt 模块化。' },
];
const messages = await chatPromptWithHistory.formatPromptValue({
history, // 任意数量的历史消息
current_input: '如何优化多人协同编辑周报的流程?',
});
要点:
MessagesPlaceholder('变量名')就是一个"运行时才能确定的动态消息区"。- 适合在 Agent 循环里,把历史消息列表直接填进去,实现多轮对话记忆。
四、partial():先填一部分,剩下的后面再填
有些字段是固定的(公司名、价值观),有些是每次变的(本周数据)。可以用 partial() 先把固定字段填好,得到一个"半成品模板"多次复用。
// 先把公司级公共字段定死
const basePrompt = await prompt.partial({
company_name: '星航科技',
company_values: '「极致、开放、靠谱」',
tone: '偏正式但不僵硬',
});
// 每次只需填本周数据
const reportA = await basePrompt.format({
team_name: 'AI 平台组',
week_range: '2025-02-10 ~ 2025-02-16',
dev_activities: '- 小明:完成 Git/Jira 集成封装',
});
const reportB = await basePrompt.format({
team_name: 'AI 工程效率组',
week_range: '2025-02-17 ~ 2025-02-23',
dev_activities: '- 阿俊:完成链路追踪接入',
});
要点:
partial()是部分填充,不是复制模板——返回的新模板只保留"还没填的"变量。- 适合"一个公司模板给多个团队/多周复用"的场景,明显减少重复代码。
五、FewShotPromptTemplate:让模型"看例子"再干活
模型很多时候"听命令"不如"看例子"学得快。Few-Shot 就是给模型喂几条(输入 → 理想输出)示例,让它模仿结构。
import {
FewShotPromptTemplate,
PromptTemplate,
} from '@langchain/core/prompts';
const examplePrompt = PromptTemplate.fromTemplate(
`用户需求:{user_requirement}
期望周报结构:{expected_style}
示例输出片段:
{report_snippet}
---`
);
const examples = [
{
user_requirement: '突出稳定性治理,适合发给偏关注风险的老板。',
expected_style: '语气稳健,多强调风险识别和兜底动作。',
report_snippet: '- 处理 P1 Bug 2 个、P2 Bug 3 个,均在 SLA 内修复;\n- 清理 12 条噪音告警。',
},
{
user_requirement: '偏向对外展示成果,适合发给跨部门同学。',
expected_style: '语气积极,突出成果,技术细节适度抽象。',
report_snippet: '- 上线「订单实时看板」,支持实时查看转化漏斗;\n- 完成 2 场内部分享。',
},
];
const fewShotPrompt = new FewShotPromptTemplate({
examples, // 示例数据
examplePrompt, // 单条示例长什么样
prefix: '下面是一些周报示例,请学习其中的语气和结构:\n',
suffix: '\n基于上面的示例风格,请帮我写一份新的周报。',
inputVariables: [],
});
const finalPrompt = await fewShotPrompt.format({});
要点:
- 四个关键成员:
examples(数据)、examplePrompt(单条示例的模板)、prefix(示例前的话)、suffix(示例后的任务指令)。 - 输出是字符串,示例被按模板展开拼进最终 Prompt。
六、FewShotChatMessagePromptTemplate:对话版的 Few-Shot
对话场景下,示例本身也是"human 问 + ai 答"的多轮消息。用这个模板,示例会以消息数组形式插进 ChatPromptTemplate。
import {
ChatPromptTemplate,
FewShotChatMessagePromptTemplate,
} from '@langchain/core/prompts';
const EXAMPLES = [
{
input: '本周主要推进支付稳定性治理。',
output: '- 完成 1 起 P1 事故与 2 起 P2 事故的排查修复;\n- 合并冗余告警 8 条。',
},
{
input: '本周交付了新运营看板。',
output: '- 上线「运营实时看板」,支持实时查看转化指标;\n- 面向运营组织 2 场培训。',
},
];
const fewShotExamples = new FewShotChatMessagePromptTemplate({
examplePrompt: ChatPromptTemplate.fromMessages([
['human', '本周工作概述:{input}\n请整理成周报要点。'],
['ai', '{output}'],
]),
examples: EXAMPLES,
inputVariables: [],
});
const chatPrompt = ChatPromptTemplate.fromMessages([
['system', '你是一名资深技术负责人,参考示例写周报片段。'],
fewShotExamples, // 示例直接作为一个消息块插进来
['human', '这是我本周的实际工作,请整理成周报:\n{current_work}'],
]);
const messages = await chatPrompt.formatMessages({
current_work: '本周完成订单模块重构,补齐核心单测,修复两起性能问题。',
});
要点:
examples里的input/output会映射到examplePrompt的{input}/{output}占位符。- 生成的消息数组可以直接
model.stream(messages),模型能同时"看到"示例和当前任务。
七、ExampleSelector:示例太多,怎么挑?
Few-Shot 很有效,但示例不能无限多——每个示例都占 token,示例多了既费钱又可能干扰模型。当示例库很大时,就需要"按需选几条最合适的"。
这就是 ExampleSelector(示例选择器)的用武之地。它不改变 FewShotPromptTemplate 的用法,只是把写死的 examples 换成动态的 exampleSelector。
7.1 LengthBasedExampleSelector:按长度选
根据输入内容的长短,自动选"长度合适"的示例——输入简单就选短示例,输入复杂就选长示例。
import { LengthBasedExampleSelector } from '@langchain/core/example_selectors';
const exampleSelector = await LengthBasedExampleSelector.fromExamples(examples, {
examplePrompt,
maxLength: 700, // 示例总长度上限
getTextLength: (text) => text.length, // 用字符长度近似(可换成 token 估算)
});
const fewShotPrompt = new FewShotPromptTemplate({
examplePrompt,
exampleSelector, // 用 selector 替换写死的 examples
prefix: '下面是一些周报示例:\n',
suffix: '\n请根据下面的场景写一份周报:\n场景:{current_requirement}',
inputVariables: ['current_requirement'],
});
const finalPrompt = await fewShotPrompt.format({
current_requirement: '本周既有稳定性保障,也有新功能上线……',
});
要点:
getTextLength可以自定义,真实项目建议换成 token 估算函数更准。- selector 会自动挑选若干条示例,使总长度不超过
maxLength。
7.2 SemanticSimilarityExampleSelector:按语义选(向量检索)
比"看长度"更聪明的是"看语义"——把示例先向量化存进向量库(如 Milvus),每次提问时检索出语义最接近的几条示例。这是 Few-Shot + RAG 的结合。
import { SemanticSimilarityExampleSelector } from '@langchain/core/example_selectors';
import { Milvus } from '@langchain/community/vectorstores/milvus';
// 1. 连上已存好示例向量的 Milvus
const vectorStore = await Milvus.fromExistingCollection(embeddings, {
collectionName: 'weekly_report_examples',
clientConfig: { address: 'localhost:19530' },
});
// 2. 建 selector:每次只取语义最相近的 2 条
const exampleSelector = new SemanticSimilarityExampleSelector({
vectorStore,
k: 2,
});
// 3. 用法和 LengthBased 完全一致
const fewShotPrompt = new FewShotPromptTemplate({
examplePrompt,
exampleSelector,
prefix: '下面是一些与当前场景最相近的周报示例:\n',
suffix: '\n请为下面的场景写一份周报:\n场景:{current_scenario}',
inputVariables: ['current_scenario'],
});
// 4. 换不同场景提问,模型会"看到"不同的示例
const promptForTechDebt = await fewShotPrompt.format({
current_scenario: '本周主要清理历史技术债:重构订单模块、补齐单测……',
});
const promptForLaunch = await fewShotPrompt.format({
current_scenario: '本周新功能首发,面向运营和市场做宣讲……',
});
要点:
- 示例需要提前向量化写入 Milvus(每条的
scenario字段转成向量)。 - 提问时,
scenario越相近的示例越容易被选中 → 不同问题拿到不同示例,省 token 又贴合场景。 - 示例库很大时,这是最推荐的选择器;Milvus 只是其中一种向量库实现。
八、PipelinePromptTemplate:模块化组装大 Prompt
Prompt 写长了很难维护。思想是:把大 Prompt 拆成人设 / 背景 / 任务 / 格式等独立模块,各自维护、各自复用,最后拼成一个最终 Prompt。
import { PipelinePromptTemplate, PromptTemplate } from '@langchain/core/prompts';
// A. 人设模块
const personaPrompt = PromptTemplate.fromTemplate(
'你是一名资深工程负责人,写作风格:{tone}。\n'
);
// B. 背景模块
const contextPrompt = PromptTemplate.fromTemplate(
'公司:{company_name}\n部门:{team_name}\n本周时间范围:{week_range}\n本周核心目标:{team_goal}\n'
);
// C. 任务模块
const taskPrompt = PromptTemplate.fromTemplate(
'以下是本周开发活动:\n{dev_activities}\n\n请提炼出:1. 亮点 2. 风险 3. 下周计划\n'
);
// D. 格式模块
const formatPrompt = PromptTemplate.fromTemplate(
'请用 Markdown 输出,包含:本周概览 / 详细拆分 / 关键指标表格(模块|亮点|风险|下周计划)。\n'
);
// E. 最终组装模板:用 {xxx_block} 引用各模块
const finalPrompt = PromptTemplate.fromTemplate(
`{persona_block}
{context_block}
{task_block}
{format_block}
现在请生成本周的最终周报:`
);
const pipelinePrompt = new PipelinePromptTemplate({
pipelinePrompts: [
{ name: 'persona_block', prompt: personaPrompt },
{ name: 'context_block', prompt: contextPrompt },
{ name: 'task_block', prompt: taskPrompt },
{ name: 'format_block', prompt: formatPrompt },
],
finalPrompt, // 最终把谁拼起来
inputVariables: [ // 汇总所有模块用到的变量
'tone', 'company_name', 'team_name', 'week_range',
'team_goal', 'dev_activities',
],
});
const prompt = await pipelinePrompt.format({
tone: '专业、清晰、略带幽默',
company_name: '星航科技',
team_name: 'AI 平台组',
week_range: '2025-02-03 ~ 2025-02-09',
team_goal: '完成智能周报 Agent 的 MVP 版本。',
dev_activities: '- Git: 58 次提交;- Jira: 完成 12 个 Story,关闭 7 个 Bug',
});
最有价值的一点:模块可以跨场景复用。
人设、背景这两个模块写好后,可以换"任务 + 格式"模块,组合出一个完全不同场景的 Prompt——比如把"周报"换成"季度 OKR 回顾邮件":
const okrReviewPipeline = new PipelinePromptTemplate({
pipelinePrompts: [
{ name: 'persona_block', prompt: personaPrompt }, // 复用人设
{ name: 'context_block', prompt: contextPrompt }, // 复用背景
{ name: 'task_block', prompt: okrReviewTaskPrompt }, // 换任务
{ name: 'format_block', prompt: okrReviewFormatPrompt }, // 换格式
],
finalPrompt: finalOkrPrompt,
inputVariables: ['tone', 'company_name', 'team_name', 'week_range', 'team_goal', 'okr_facts'],
});
要点:
pipelinePrompts里每个模块的name要对应finalPrompt里的{xxx_block}占位符。inputVariables要汇总所有模块的变量。- 模块化之后:人设/背景写一次,任务/格式按场景换——这是生产级 Prompt 管理的关键姿势。
一张图总结:怎么选
| API | 输出 | 核心场景 |
|---|---|---|
PromptTemplate | 字符串 | 基础占位符模板 |
ChatPromptTemplate | 消息数组 | 对话角色组织 |
MessagesPlaceholder | 消息插槽 | 注入历史对话 |
.partial() | 半成品模板 | 先填固定字段 |
FewShotPromptTemplate | 字符串 | 文本 Few-Shot |
FewShotChatMessagePromptTemplate | 消息数组 | 对话 Few-Shot |
LengthBasedExampleSelector | 选中的示例 | 按长度控制 token |
SemanticSimilarityExampleSelector | 选中的示例 | 按语义检索(Milvus) |
PipelinePromptTemplate | 字符串 | 模块化组装 |
最后
Prompt Template 是 LangChain 应用的第一层地基——几乎所有真实应用(RAG、Agent、记忆系统)最终都要拼 Prompt。把这 9 个 API 吃透,后面学什么都是站在它上面。
下一站建议:把 SemanticSimilarityExampleSelector 和 PipelinePromptTemplate 组合起来用——先按语义选出最合适的示例,再通过 Pipeline 拼出最终 Prompt,这就是一个"示例动态化 + 模块化"的准生产级提示词管线。
Leo
BloggerIndependent developer / Blogger and the maintainer of the original blog “大道至简”. Migrating years of posts and shiyu from WordPress to Next.js.
Reader comments
COMMENTS · 0Leave a comment