prompt 毫无疑问是 AI Agent 中最核心的部分:我们调用大模型完成各种功能,都是在 prompt 里描述的;类似 RAG 查询向量数据库,查到的文档也是放在 prompt 里给到大模型。
但这节不讲 prompt 怎么写——在公司里有专门的产品部门负责写 prompt——我们要学的是如何管理它,比如 prompt 之间的组合、prompt 里的示例的管理等。
之前都是直接写字符串,而实际上可以通过 Prompt Template 的 API 来动态的管理。
新建一个项目测试这些 API:
mkdir prompt-template-test
cd prompt-template-test
npm init -y
pnpm install dotenv @langchain/core @langchain/openaiPromptTemplate:占位符模板
创建 src/prompt-template1.mjs:
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { PromptTemplate } from '@langchain/core/prompts';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const naiveTemplate = PromptTemplate.fromTemplate(`
你是一名严谨但不失人情味的工程团队负责人,需要根据本周数据写一份周报。
公司名称: {company_name}
部门名称: {team_name}
直接汇报对象: {manager_name}
本周时间范围: {week_range}
本周团队核心目标:
{team_goal}
本周开发数据(Git 提交 / Jira 任务):
{dev_activities}
请根据以上信息生成一份【Markdown 周报】,要求:
- 有简短的整体 summary(两三句话)
- 有按模块/项目拆分的小结
- 用一个 Markdown 表格列出关键指标(字段示例:模块 / 亮点 / 风险 / 下周计划)
- 语气专业但有一点人情味,适合作为给老板和团队抄送的周报。
`);
const prompt = await naiveTemplate.format({
company_name: '星航科技',
team_name: '数据智能平台组',
manager_name: '刘总',
week_range: '2025-03-10 ~ 2025-03-16',
team_goal: '完成用户画像服务的灰度上线,并验证核心指标是否达标。',
dev_activities:
'- 阿兵:完成用户画像服务的 Canary 发布与回滚脚本优化,提交 27 次\n' +
'- 小李:接入埋点数据,打通埋点 → Kafka → DWD → 画像服务的全链路,提交 22 次\n' +
'- 小赵:完善画像服务的告警与 Dashboard,新增 8 个告警规则,提交 15 次',
});
console.log('格式化后的提示词:');
console.log(prompt);
const stream = await model.stream(prompt);
console.log('\nAI 回答:');
for await (const chunk of stream) {
process.stdout.write(chunk.content);
}我们让 AI 生成周报,要给它一些上下文信息。定义了一个 Prompt Template,其中有一些占位符,用的时候通过 format 方法传入占位符的具体内容。之后给大模型填充数据之后的 prompt 来生成回答。
完全可以换一组内容来生成新的 prompt——模板定义一次,反复复用。之前我们是直接拼接字符串,而现在用 Prompt Template 的 API 来管理。
PipelinePromptTemplate:组件化组合
现在都是整个的 prompt,实际上可能需要按照角色、背景、任务、格式等来拆分管理 prompt,这样用的时候再组合。这就需要 PipelinePromptTemplate,创建 src/pipeline-prompt-template.mjs:
import 'dotenv/config';
import { PipelinePromptTemplate, PromptTemplate } from '@langchain/core/prompts';
// A. 人设模块
const personaPrompt = PromptTemplate.fromTemplate(
`你是一名资深工程团队负责人,写作风格:{tone}。
你擅长把枯燥的技术细节写得既专业又有温度。\n`
);
// B. 背景模块
const contextPrompt = PromptTemplate.fromTemplate(
`公司:{company_name}
部门:{team_name}
直接汇报对象:{manager_name}
本周时间范围:{week_range}
本周部门核心目标:{team_goal}\n`
);
// C. 任务模块
const taskPrompt = PromptTemplate.fromTemplate(
`以下是本周团队的开发活动(Git / Jira 汇总):
{dev_activities}
请你从这些原始数据中提炼出:
1. 本周整体成就亮点
2. 潜在风险和技术债
3. 下周重点计划建议\n`
);
// D. 格式模块
const formatPrompt = PromptTemplate.fromTemplate(
`请用 Markdown 输出周报,结构包含:
1. 本周概览(2-3 句话的 Summary)
2. 详细拆分(按模块或项目分段)
3. 关键指标表格,表头为:模块 | 亮点 | 风险 | 下周计划
注意:
- 尽量引用一些具体数据(如提交次数、完成的任务编号)
- 语气专业,但可以偶尔带一点轻松的口吻,符合 {company_values}。
`
);
// E. 最终组合 Prompt(把上面几个模块拼在一起)
const finalWeeklyPrompt = 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: finalWeeklyPrompt,
});
const pipelineFormatted = await pipelinePrompt.format({
tone: '专业、清晰、略带幽默',
company_name: '星航科技',
team_name: 'AI 平台组',
manager_name: '王总',
week_range: '2025-02-03 ~ 2025-02-09',
team_goal: '完成智能周报 Agent 的 MVP 版本,并打通 Git / Jira 数据源。',
dev_activities:
'- Git: 58 次提交,3 个主要分支合并\n' +
'- Jira: 完成 12 个 Story,关闭 7 个 Bug\n' +
'- 关键任务:完成智能周报 Pipeline 设计、实现 Prompt 拆分、接入 ExampleSelector',
company_values: '「极致、开放、靠谱」的价值观',
});
console.log('PipelinePromptTemplate 组合后的 Prompt:');
console.log(pipelineFormatted);我们创建 PipelinePromptTemplate,指定了一个 finalPrompt 最终的 prompt,以及它组合的所有 pipelinePrompts。(上面那个 inputVariables 参数可以忽略,占位符变量会自动识别出来,不用手动声明。)
这样组合有什么好处呢?当 prompt 大了之后优势就明显了——每一部分都可以单独维护和复用。
比如我们把这个 personaPrompt、contextPrompt 导出,用于创建另一个 prompt(比如"季度 OKR 回顾邮件"场景),只需要自己写任务模块和格式模块,人设+背景直接复用周报的:
import { PromptTemplate, PipelinePromptTemplate } from '@langchain/core/prompts';
import { personaPrompt, contextPrompt } from './pipeline-prompt-template.mjs';
// 1. 本场景自己的任务说明模块
const okrReviewTaskPrompt = PromptTemplate.fromTemplate(`
以下是本季度与你所在团队相关的关键事实与数据(OKR 进展、重要事件等):
{okr_facts}
请你基于这些信息,整理一份发给 {manager_name} 的【季度 OKR 回顾邮件】,重点包含:
1. 本季度整体达成情况(相对 OKR 的完成度)
2. 关键成果与亮点
3. 暴露出的主要问题 / 风险
4. 下季度的改进方向与优先级建议
`);
// 2. 本场景自己的格式要求模块
const okrReviewFormatPrompt = PromptTemplate.fromTemplate(
`请用 Markdown 写这封邮件,结构建议为:
1. 邮件开头(1-2 句话的问候 + 本邮件目的)
2. 本季度整体概览
3. 逐条 OKR 的回顾(可分小节)
4. 主要问题 / 风险
5. 下季度计划与请求支持
语气保持专业、克制但真诚。`
);
// 3. 用 PipelinePromptTemplate 组合成最终 Prompt
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: PromptTemplate.fromTemplate(
`{persona_block}
{context_block}
{task_block}
{format_block}
现在请生成本次的【季度 OKR 回顾邮件】:`
),
});跑一下:创建季度总结的 prompt,前面的角色 + 背景部分直接复用周报的。
partial:预填固定变量
这么多 inputVariables,其实有的是比较固定的,比如公司名、经理名等。可以用 partial 来预填入一些变量,生成新的 PromptTemplate:
import { pipelinePrompt } from "./pipeline-prompt-template.mjs";
const pipelineWithPartial = await pipelinePrompt.partial({
company_name: '星航科技',
company_values: '「极致、开放、靠谱」的价值观',
tone: '偏正式但不僵硬',
});
// 用的时候就只需要填其他变量
const partialFormatted = await pipelineWithPartial.format({
team_name: 'AI 平台组',
manager_name: '刘东',
week_range: '2025-02-10 ~ 2025-02-16',
team_goal: '上线周报 Agent 到内部试用环境,并收集反馈。',
dev_activities: '- 小明:完成 Git/Jira 集成封装\n- 小红:实现 Prompt 配置化加载',
});这里我们把 PromptTemplate 预填入了一些固定的变量,然后新的 PromptTemplate 用的时候就只需要填入其他变量就好了。
ChatPromptTemplate:messages 数组方式
PromptTemplate 产出的就是一个字符串,实际上我们更多是用 SystemMessage、HumanMessage、AIMessage、ToolMessage 的 messages 数组来调大模型——这种就需要 ChatPromptTemplate 了。
创建 src/chat-prompt-template.mjs:
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate } from '@langchain/core/prompts';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const chatPrompt = ChatPromptTemplate.fromMessages([
[
'system',
`你是一名资深工程团队负责人,擅长用结构化、易读的方式写技术周报。
写作风格要求:{tone}。
请根据后续用户提供的信息,帮他生成一份适合给老板和团队同时抄送的周报草稿。`,
],
[
'human',
`本周信息如下:
公司名称:{company_name}
团队名称:{team_name}
直接汇报对象:{manager_name}
本周时间范围:{week_range}
本周团队核心目标:
{team_goal}
本周开发数据(Git 提交 / Jira 任务等):
{dev_activities}
请据此输出一份 Markdown 周报,结构建议包含:
1. 本周概览(2-3 句话)
2. 详细拆分(按项目或模块分段)
3. 关键指标表格(字段示例:模块 / 亮点 / 风险 / 下周计划)
语气专业但有人情味。`,
],
]);
const chatMessages = await chatPrompt.formatMessages({
tone: '专业、清晰、略带鼓励',
company_name: '星航科技',
team_name: '智能应用平台组',
manager_name: '王总',
week_range: '2025-05-05 ~ 2025-05-11',
team_goal: '完成内部 AI 助手灰度上线,并确保核心链路稳定。',
dev_activities:
'- 小李:完成 AI 助手工单流转能力,对接客服系统,提交 25 次\n' +
'- 小张:接入日志检索和知识库查询,提交 19 次',
});
console.log('ChatPromptTemplate 生成的消息:');
console.log(chatMessages);
const response = await model.invoke(chatMessages);
console.log('\nAI 生成的周报草稿:');
console.log(response.content);参数是一个二维数组,数组第一个元素是 message 类型('system'、'human' 等)。实际上还是 ChatPromptTemplate 用得多,也就是 messages 数组的方式管理 prompt。
ChatPromptTemplate 还有另一种写法:分别用 SystemMessagePromptTemplate、HumanMessagePromptTemplate 等创建具体的 PromptTemplate,然后组合到 fromMessages 的参数数组里,结果一样。
那 PipelinePromptTemplate 和 ChatPromptTemplate 怎么结合用呢?其实 PipelinePromptTemplate 的 finalPrompt 也可以传 ChatPromptTemplate,然后用 formatPromptValue 拿到填好变量的 messages 数组:
const finalChatPrompt = ChatPromptTemplate.fromMessages([
[
'system',
`你是一名资深工程团队负责人,擅长把复杂的技术细节总结成结构化、易读的周报。
下面是一些已经预先整理好的信息块,请你综合理解后,再根据用户补充的信息生成周报。`,
],
[
'human',
`人设与写作风格:
{persona_block}
团队与本周背景:
{context_block}
任务与输入数据:
{task_block}
输出格式要求:
{format_block}
现在请基于以上信息,直接输出最终的周报内容。`,
],
]);
const weeklyChatPipelinePrompt = new PipelinePromptTemplate({
pipelinePrompts: [
{ name: 'persona_block', prompt: personaPrompt }, // 复用⼈设
{ name: 'context_block', prompt: contextPrompt }, // 复用背景
{ name: 'task_block', prompt: weeklyTaskPrompt }, // 本文件自己的任务模块
{ name: 'format_block', prompt: weeklyFormatPrompt }, // 本文件自己的格式模块
],
// 注意:这里的 finalPrompt 是 ChatPromptTemplate,而不是普通 PromptTemplate
finalPrompt: finalChatPrompt,
});
// 用 formatPromptValue 拿到填充变量后的 messages 数组
const promptValue = await weeklyChatPipelinePrompt.formatPromptValue({...});
console.log(promptValue.toChatMessages());MessagesPlaceholder:插入对话历史
ChatPromptTemplate 的 input variables 只能填充一些占位符的值。那如果我是想插入一段聊天记录呢?这种就要用 MessagesPlaceholder 了,创建 src/messages-placeholder.mjs:
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
// 定义一个包含 MessagesPlaceholder 的 ChatPromptTemplate
const chatPromptWithHistory = ChatPromptTemplate.fromMessages([
[
'system',
`你是一名资深工程效率顾问,善于在多轮对话的上下文中给出具体、可执行的建议。`,
],
// 这里用 MessagesPlaceholder 来承载「之前的多轮对话」
new MessagesPlaceholder('history'),
[
'human',
`这是用户本轮的新问题:{current_input}
请结合上面的历史对话,一并给出你的建议。`,
],
]);
// 构造一个模拟的历史对话 + 当前输入
const historyMessages = [
{ role: 'human', content: '我们团队最近在做一个内部的周报自动生成工具。' },
{ role: 'ai', content: '听起来不错,可以先把数据源(Git / Jira / 运维)梳理清楚,再考虑 Prompt 模块化设计。' },
{ role: 'human', content: '我们已经把 Prompt 拆成了「人设」「背景」「任务」「格式」四块。' },
{ role: 'ai', content: '很好,接下来可以考虑把这些模块做成可复用的 PipelinePromptTemplate。' },
];
const formattedMessages = await chatPromptWithHistory.formatPromptValue({
history: historyMessages,
current_input: '现在我们想再优化一下多人协同编辑周报的流程,有什么建议?',
});
console.log('包含历史对话的消息数组:');
console.log(formattedMessages.toChatMessages());这里用 MessagesPlaceholder 来插入一段对话历史,变量名是 history。我们平时用 ChatPromptTemplate 比较多,所以需要用到 MessagesPlaceholder 插入对话历史的场景也比较多。
FewShotPromptTemplate:带示例的模板
最后还有 FewShotPromptTemplate,也就是生成一些带少量示例的 prompt。创建 src/fewshot-prompt-template.mjs:
import 'dotenv/config';
import { FewShotPromptTemplate, PromptTemplate } from '@langchain/core/prompts';
// 2. 定义 few-shot 示例模板(单条示例长什么样)
const examplePrompt = PromptTemplate.fromTemplate(
`用户输入:{user_requirement}
期望周报结构:{expected_style}
模型示例输出片段:
{report_snippet}
---`
);
// 3. 准备几条示例数据(few-shot examples)
const examples = [
{
user_requirement: '重点突出稳定性治理,本周主要在修 Bug 和清理技术债,适合发给偏关注风险的老板。',
expected_style: '语气稳健、偏保守,多强调风险识别和已做的兜底动作。',
report_snippet:
`- 支付链路本周共处理线上 P1 Bug 2 个、P2 Bug 3 个,全部在 SLA 内完成修复;\n` +
`- 针对历史高频超时问题,完成 3 个核心接口的超时阈值和重试策略优化;\n` +
`- 清理 12 条重复/噪音告警,减少值班同学 30% 的告警打扰。`,
},
{
user_requirement: '偏向对外展示成果,希望多写一些亮点,适合发给更大范围的跨部门同学。',
expected_style: '语气积极、突出成果,对技术细节做适度抽象。',
report_snippet:
`- 新上线「订单实时看板」,业务侧可以实时查看核心转化漏斗;\n` +
`- 首次打通埋点 → 数据仓库 → 实时服务链路,为后续精细化运营提供基础能力;\n` +
`- 和产品、运营一起完成 2 场内部分享,会后收到 15 条正向反馈。`,
},
];
// 4. 把示例封装成 FewShotPromptTemplate
const fewShotPrompt = new FewShotPromptTemplate({
examples,
examplePrompt,
prefix:
`下面是几条已经写好的【周报示例】,你可以从中学习语气、结构和信息组织方式:\n`,
suffix:
`\n基于上面的示例风格,请帮我写一份新的周报。` +
`\n如果用户有额外要求,请在满足要求的前提下,尽量保持示例中的结构和条理性。`,
inputVariables: [],
});
const fewShotBlock = await fewShotPrompt.format({});
console.log(fewShotBlock);我们先有 PromptTemplate 创建了示例的 prompt 模版,然后创建了 2 个示例,加上前缀、后缀。这就是生成少量示例的 FewShotPromptTemplate,它可以结合其他 PromptTemplate 一起用,通过 PipelinePromptTemplate 组合到一块。
但如果示例特别多呢?消耗 token 岂不是很多?如果我不同的 query,需要选一些不同的示例来加入 prompt 呢?或者根据长度限制来选择示例呢?这种就要用 ExampleSelector 的 API 了,也就是示例选择器。
ExampleSelector:示例选择器
LengthBasedExampleSelector:按长度选择
创建 src/example-selector1.mjs:
import 'dotenv/config';
import { FewShotPromptTemplate, PromptTemplate } from '@langchain/core/prompts';
import { LengthBasedExampleSelector } from '@langchain/core/example_selectors';
const examplePrompt = PromptTemplate.fromTemplate(
`用户需求:{user_requirement}
周报片段示例:
{report_snippet}
---`
);
// 构造一批「长度差异明显」的示例,方便观察选择效果
const examples = [
{
user_requirement: '本周主要在做基础设施稳定性治理,想突出风险控制。',
report_snippet:
`- 核心链路共处理 P1 级别故障 1 起,P2 故障 2 起,均在 SLA 内完成处置;\n` +
`- 对 5 个高风险接口补充了限流与熔断策略,覆盖 80% 高峰流量;\n` +
`- 新增 6 条针对延迟抖动的告警规则,减少漏报风险。`,
},
{
user_requirement: '偏向对外展示成果,多写一些亮点和业务价值。',
report_snippet:
`- 上线「实时订单看板」,支持业务实时查看转化漏斗;\n` +
`- 打通埋点 → 数据仓库 → 实时服务的闭环,支撑后续精细化运营;\n` +
`- 完成 2 场内部分享,会后收到 15 条正向反馈。`,
},
{
user_requirement: '只是想要一个非常简短的周报,两三句话就够了,主要告诉老板「一切稳定」即可。',
report_snippet: `本周整体运行平稳,未发生重大事故,核心指标均在预期范围内。`,
},
{
user_requirement: '需要一份比较详细的技术周报,涵盖研发、测试、上线、监控等各个环节,篇幅可以略长。',
report_snippet:
`- 研发:完成结算服务重构第一阶段,拆分出 3 个独立子服务,接口延迟较旧架构下降约 35%;\n` +
`- 测试:补齐 20+ 条关键路径自动化用例,整体用例数量提升到 180 条,回归时间从 2 天缩短到 0.5 天;\n` +
`- 上线:采用灰度 + Canary 策略,期间监控到 2 次轻微指标抖动,均在 5 分钟内回滚处理;\n` +
`- 监控:新增 8 条核心告警和 3 个 SLO 指标,后续会结合值班反馈继续收敛噪音告警。`,
},
];
// 创建 LengthBasedExampleSelector
const exampleSelector = await LengthBasedExampleSelector.fromExamples(examples, {
examplePrompt,
// 这里简单地用字符长度近似控制,真实项目中可以配合 token 估算
maxLength: 700,
getTextLength: (text) => text.length,
});
// 基于 selector 构建 FewShotPromptTemplate
const fewShotPrompt = new FewShotPromptTemplate({
examplePrompt,
exampleSelector,
prefix: '下面是一些不同风格和长度的周报片段示例,你可以从中学习语气和结构:\n',
suffix:
'\n\n现在请根据上面的示例风格,为下面这个场景写一份新的周报:\n' +
'场景描述:{current_requirement}\n' +
'请输出一份适合发给老板和团队同步的 Markdown 周报草稿。',
inputVariables: ['current_requirement'],
});
// 演示:给定一个较长的需求,让 selector 自动选出合适的示例
const finalPrompt = await fewShotPrompt.format({
current_requirement: '我们本周在做「内部 AI 助手」项目,既有稳定性保障(处理线上问题),也有新功能上线(接入知识库、日志检索)。',
});
console.log(finalPrompt);我们创建了 LengthBasedExampleSelector,根据长度来选择示例。长度计算是 maxLength 减去调用 format 的所有字段的长度,剩下的就是示例的长度,根据这个来选择。这个长度默认是字符长度,如果你要根据 token 多少来计算的话,改一下 getTextLength 参数即可。
SemanticSimilarityExampleSelector:按语义选择
除了根据长度选择示例,也支持根据语义选择相近的示例——这个也是基于向量数据库做的。
先写一些示例到 Milvus 里(src/weekly-report-examples-writer-milvus.mjs,结构和前面 ebook-writer 类似:建集合 weekly_report_examples,字段 id/scenario/report_snippet/vector,把 8 个周报场景示例向量化插入)。
然后创建 src/example-selector2.mjs:
import 'dotenv/config';
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
import { FewShotPromptTemplate, PromptTemplate } from '@langchain/core/prompts';
import { SemanticSimilarityExampleSelector } from '@langchain/core/example_selectors';
import { Milvus } from '@langchain/community/vectorstores/milvus';
const COLLECTION_NAME = process.env.MILVUS_COLLECTION_NAME ?? 'weekly_report_examples';
const VECTOR_DIM = 1024;
const model = new ChatOpenAI({
temperature: 0,
model: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDINGS_MODEL_NAME,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
dimensions: VECTOR_DIM,
});
const examplePrompt = PromptTemplate.fromTemplate(
`用户场景:{scenario}
生成的周报片段:
{report_snippet}
---`
);
// 连接 Milvus,并基于已存在的集合创建向量库
const vectorStore = await Milvus.fromExistingCollection(embeddings, {
collectionName: COLLECTION_NAME,
clientConfig: {
address: process.env.MILVUS_ADDRESS ?? 'localhost:19530',
},
// 与写入脚本中创建的索引保持一致
indexCreateOptions: {
index_type: 'IVF_FLAT',
metric_type: 'COSINE',
params: { nlist: 1024 },
search_params: { nprobe: 10 },
},
});
const exampleSelector = new SemanticSimilarityExampleSelector({
vectorStore,
k: 2, // 每次只选出语义上最相近的 2 条示例
});
// 用 selector 构建 FewShotPromptTemplate
const fewShotPrompt = new FewShotPromptTemplate({
examplePrompt,
exampleSelector,
prefix: '下面是一些不同类型的周报示例,你可以从中学习语气和结构(系统会自动从 Milvus 选出和当前场景最相近的示例):\n',
suffix:
'\n\n现在请根据上面的示例风格,为下面这个场景写一份新的周报:\n' +
'场景描述:{current_scenario}\n' +
'请输出一份适合发给老板和团队同步的 Markdown 周报草稿。',
inputVariables: ['current_scenario'],
});
// 两个语义不同的场景
const currentScenario1 = '我们本周主要是在清理历史技术债:重构老旧的订单模块、补齐核心接口的单测,同时也完善了一些文档。整体没有对外大范围发布的新功能。';
const currentScenario2 = '本周完成新一代运营看板的首批功能上线,重点打通埋点和实时数仓链路,并面向运营和市场同学做了多场宣讲。';
console.log('\n===== 场景 1:技术债清理为主 =====\n');
const finalPrompt1 = await fewShotPrompt.format({ current_scenario: currentScenario1 });
console.log(finalPrompt1);
console.log('\n\n===== 场景 2:新功能首发 + 对外宣传 =====\n');
const finalPrompt2 = await fewShotPrompt.format({ current_scenario: currentScenario2 });
console.log(finalPrompt2);这里我们不再直接用 Milvus 的 SDK 的 API,而是用 @langchain/community 里封装的 Milvus:
pnpm install @langchain/community @zilliz/milvus2-sdk-node跑一下可以看到:场景 1(技术债清理)自动选出了语义相近的技术债示例,场景 2(新功能首发)自动选出了对外宣传的示例——不同的 query 会选中不同的示例,既省 token 又更贴合场景。
FewShotChatMessagePromptTemplate:对话形式
有 FewShotPromptTemplate 自然就有 FewShotChatMessagePromptTemplate,也就是对话形式的 prompt:
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from '@langchain/core/prompts';
// few-shot 示例:每条示例是「human 问 + ai 答」的聊天片段
const EXAMPLES = [
{
input: '本周主要推进支付稳定性治理,做了事故处置、告警优化和演练。',
output:
'- 本周围绕支付链路稳定性开展治理工作:完成 1 起 P1 事故与 2 起 P2 事故的排查与修复,均在 SLA 内完成;\n' +
'- 梳理并合并冗余告警规则 8 条,新建 4 条基于 SLO 的告警,大幅降低无效告警噪音;\n' +
'- 组织 1 次故障应急演练,验证支付核心链路的应急预案可行性。',
},
{
input: '本周交付了新运营看板,并给业务同学做了多场分享。',
output:
'- 上线新一代「运营实时看板」,支持业务实时查看关键转化指标和漏斗数据;\n' +
'- 衔接埋点、数据仓库与可视化链路,为后续精细化运营提供统一数据口径;\n' +
'- 面向市场和运营团队组织 2 场产品培训,帮助非技术同学理解看板核心能力。',
},
];
// 把上面的结构映射为 FewShotChatMessagePromptTemplate 可用的 examples
const fewShotExamples = new FewShotChatMessagePromptTemplate({
examplePrompt: ChatPromptTemplate.fromMessages([
['human', '下面是本周的工作概述:\n{input}\n\n请帮我整理成适合发在团队周报里的要点列表。'],
['ai', '{output}'],
]),
examples: EXAMPLES,
exampleSeparator: '\n\n',
inputVariables: [],
});
// 把 few-shot 示例和最终用户输入组合成一个完整的 ChatPromptTemplate
const chatPrompt = ChatPromptTemplate.fromMessages([
['system', '你是一名资深技术负责人,请根据给定的工作内容,参考上面的示例,帮我写一段结构清晰、重点突出的周报片段。'],
['system', '下面是一些参考示例,请重点学习它们的「表达方式和结构」,而不是照搬具体内容:'],
fewShotExamples,
['human', '这是我本周的实际工作内容,请帮我整理成周报:\n{current_work}'],
]);
const messages = await chatPrompt.formatMessages({
current_work: '本周完成了订单模块的一轮重构,拆分了历史遗留的大文件,并补齐了核心路径的单测;同时修复了两起线上性能问题。',
});
console.log(messages);和 FewShotPromptTemplate 的区别只不过是现在示例变成了 ai 和 human 的对话历史了。
常见问题
prompt 如何给产品部门管理? 产品会根据需求给出 prompt,代码里自己拆分 Prompt Template 维护;需求沟通的时候会告诉产品,产品会在 prompt 里留出空来。
API 太多了记不住? 这节 API 多但是不难理解,过一遍就行,不用记——知道有啥是干啥的就行。
SemanticSimilarityExampleSelector 语义比较的输入是什么? 是示例里的 scenario 字段(向量化该字段文本做相似度匹配),不是拼接好的完整 prompt。
上下文会不会爆掉? 示例选少了省 token,选多了占上下文——这涉及到 memory 管理,前面那节讲过了。
总结
这节我们学了 Prompt Template 相关的 API,主要有这些:
- PromptTemplate:提示词模版,可以填入占位符变量
- ChatPromptTemplate:对话形式(messages 数组)的提示词模版
- FewShotPromptTemplate:生成带示例的提示词模版
- FewShotChatMessagePromptTemplate:生成带示例的提示词模版,对话形式
- LengthBasedExampleSelector:根据长度选择合适的示例
- SemanticSimilarityExampleSelector:选择语义相近的示例(基于向量数据库)
- PipelinePromptTemplate:合并多个 Prompt Template 成一个大的 Prompt Template
- MessagesPlaceholder:在 ChatPromptTemplate 中插入对话历史
- partial:预填固定变量生成新的模板
有了这些 prompt template 的 API,就可以用组件化的方式来管理 prompt 了,用的时候再组合。
