第2章:Prompt Engineering 与模板系统深度实践
前言
大家好,我是鲫小鱼。是一名不写前端代码
的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!
🎯 本章学习目标
- 掌握 Prompt Engineering 的核心原则与方法论(指令式、少样本、思维链、反思等)
- 熟练使用 LangChain.js 的 Prompt 模板系统(
PromptTemplate
、ChatPromptTemplate
、FewShotPromptTemplate
等) - 能够将 Prompt 与
Runnable
、Callback
、Memory
、OutputParser
组合为稳定的可复用链路 - 学会结构化输出(JSON/Zod Schema)与健壮的错误处理策略
- 建立 Prompt 评测与 A/B 测试工作流,掌握迭代优化方法
- 通过两个实战项目,完成从「问题 → 设计 → 编码 → 评测 → 上线」的闭环
📖 理论基础:Prompt Engineering 核心理念(约 30%)
2.1 为什么需要 Prompt Engineering
- 大模型具备强泛化能力,但输出稳定性受上下文、提示措辞、约束条件影响极大
- Prompt 的质量决定了结果的可靠性、可控性与成本(token)
- 工程化的 Prompt 可复用、可测试、可监控、可演进
2.2 基本类型与策略
- 指令式(Instruction):清晰角色、任务、约束、步骤
- 少样本(Few-shot):示例驱动,降低歧义,提高风格一致性
- 思维链(CoT):显式"先思考后作答",提升推理质量
- 反思(Reflexion):要求模型自检与修正,降低幻觉率
- 分而治之(Decompose):复杂任务拆解为子任务流水线
- 工具化(Tool-Use):与外部工具/检索融合,提升事实性
2.3 好 Prompt 的 4 要素(RICE)
- Role(角色定位):模型扮演谁(专家/审校/产品经理)
- Instruction(任务指令):做什么、产出什么格式
- Context(上下文):必要背景、约束、风格、领域词汇
- Example(示例):正反示例、边界案例
2.4 可测试与可维护
- 模板化:变量化可控输入,便于不同调用场景复用
- 结构化输出:避免纯自然语言,降低解析成本
- 评测指标:正确率、可读性、一致性、引用率、成本/时延
- 版本迭代:Prompt 版本号、Changelog、灰度策略
🧩 LangChain.js Prompt 模板体系(约 15%)
2.5 常用类与能力
PromptTemplate
:文本模板 → 可注入变量ChatPromptTemplate
:消息式模板(system
/human
/ai
等)FewShotPromptTemplate
:少样本示例自动拼接PipelinePromptTemplate
:子模板管道化组合MessagesPlaceholder
:与Memory
协作,注入历史对话OutputParser
:搭配 JSON/Zod 解析为结构化对象
2.6 模板设计准则
- 单一职责:每个模板聚焦一个目标
- 边界清晰:说明输入变量、约束、输出格式
- 可观测性:为评测与追踪预留标记(如 version、taskId)
💻 基础代码实践(约 20%)
2.7 指令式 + 变量注入
typescript
// 文件:src/ch02/basic-instruction.ts
import { PromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
import * as dotenv from "dotenv";
dotenv.config();
const prompt = PromptTemplate.fromTemplate(`
你是一个{role}。请用{style}风格解答:
问题:{question}
要求:
- 使用分点说明
- 控制在{maxTokens}字以内
- 若不确定,请直接说"我需要更多上下文"
`);
const model = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0.5 });
const chain = prompt.pipe(model).pipe(new StringOutputParser());
export async function run() {
const result = await chain.invoke({
role: "Web 性能专家",
style: "简洁务实",
question: "如何优化首屏渲染?",
maxTokens: 200,
});
console.log(result);
}
if (require.main === module) {
run();
}
2.8 ChatPromptTemplate + 多消息角色
typescript
// 文件:src/ch02/chat-prompt.ts
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const chatPrompt = ChatPromptTemplate.fromMessages([
["system", "你是资深前端教练,善于用类比解释复杂概念。"],
["human", "请用类比解释 {topic},并提供一个代码示例。"],
]);
const model = new ChatOpenAI({ modelName: "gpt-3.5-turbo" });
const chain = chatPrompt.pipe(model).pipe(new StringOutputParser());
export async function run() {
const text = await chain.invoke({ topic: "虚拟 DOM" });
console.log(text);
}
if (require.main === module) { run(); }
2.9 FewShotPromptTemplate(少样本)
typescript
// 文件:src/ch02/few-shot.ts
import { FewShotPromptTemplate, PromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const examplePrompt = PromptTemplate.fromTemplate(
"用户:{input}\n分类:{label}"
);
const examples = [
{ input: "页面加载很慢", label: "性能问题" },
{ input: "按钮点击没反应", label: "交互缺陷" },
{ input: "接口经常 500", label: "后端故障" },
];
const fewShot = new FewShotPromptTemplate({
examples,
examplePrompt,
prefix: "请根据用户诉求给出标签(性能问题/交互缺陷/后端故障):",
suffix: "用户:{input}\n分类:",
inputVariables: ["input"],
});
const chain = fewShot.pipe(new ChatOpenAI()).pipe(new StringOutputParser());
export async function run() {
const out = await chain.invoke({ input: "首屏白屏 3 秒" });
console.log(out);
}
if (require.main === module) { run(); }
2.10 PipelinePromptTemplate(管道模板)
typescript
// 文件:src/ch02/pipeline.ts
import { PromptTemplate, PipelinePromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const partA = PromptTemplate.fromTemplate(
"请将主题扩写为 3 个小标题:{topic}"
);
const partB = PromptTemplate.fromTemplate(
"基于小标题生成提纲,风格:{style}\n小标题:{headings}"
);
const pipeline = new PipelinePromptTemplate({
finalPrompt: partB,
pipelinePrompts: [
{ name: "headings", prompt: partA },
],
});
const chain = pipeline.pipe(new ChatOpenAI()).pipe(new StringOutputParser());
export async function run() {
const result = await chain.invoke({ topic: "前端监控系统", style: "专业简练" });
console.log(result);
}
if (require.main === module) { run(); }
🧱 结构化输出与 OutputParser(约 10%)
2.11 JSON 输出与严格约束
typescript
// 文件:src/ch02/json-output.ts
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { JsonOutputParser } from "@langchain/core/output_parsers";
type Plan = { steps: { title: string; details: string }[] };
const prompt = PromptTemplate.fromTemplate(`
你是项目规划助手。请输出严格的 JSON:
{
"steps": [
{ "title": string, "details": string }
]
}
主题:{topic}
`);
const model = new ChatOpenAI({ temperature: 0 });
const parser = new JsonOutputParser<Plan>();
const chain = prompt.pipe(model).pipe(parser);
export async function run() {
const result = await chain.invoke({ topic: "前端监控平台搭建" });
console.log(result.steps.map(s => `- ${s.title}`).join("\n"));
}
if (require.main === module) { run(); }
2.12 Zod Schema 强类型解析
typescript
// 文件:src/ch02/zod-output.ts
import { z } from "zod";
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
const schema = z.object({
title: z.string(),
tags: z.array(z.string()).max(5),
estimateHours: z.number().min(1).max(80),
});
const parser = StructuredOutputParser.fromZodSchema(schema);
const prompt = PromptTemplate.fromTemplate(`
基于需求生成任务卡片:
需求:{requirement}
请严格输出:
{format_instructions}
`);
const chain = prompt.pipe(new ChatOpenAI({ temperature: 0 })).pipe(parser);
export async function run() {
const out = await chain.invoke({
requirement: "实现文章阅读进度统计与收藏功能",
format_instructions: parser.getFormatInstructions(),
});
console.log(out);
}
if (require.main === module) { run(); }
🔗 与 Runnable、Memory、Callback 协作(约 10%)
2.13 Runnable 组合与复用
typescript
// 文件:src/ch02/runnable-compose.ts
import { RunnableLambda, RunnableSequence } from "@langchain/core/runnables";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const normalize = new RunnableLambda((input: { q: string }) => ({
q: input.q.trim().slice(0, 300),
}));
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是严谨的技术作家,输出规范化 Markdown"],
["human", "请回答:{q}"],
]);
const chain = RunnableSequence.from([
normalize,
prompt,
new ChatOpenAI({ temperature: 0.2 }),
new StringOutputParser(),
]);
export async function run() {
const md = await chain.invoke({ q: " 讲讲CSR/SSR/SSG 区别 " });
console.log(md);
}
if (require.main === module) { run(); }
2.14 Memory 注入历史对话(滑动窗口)
typescript
// 文件:src/ch02/memory-window.ts
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { ConversationSummaryMemory } from "langchain/memory"; // 或 @langchain/community 中的记忆实现
import { RunnableSequence } from "@langchain/core/runnables";
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是持续对话的技术顾问,回答要简洁并引用上下文"],
new MessagesPlaceholder("history"),
["human", "{input}"],
]);
const model = new ChatOpenAI({ temperature: 0 });
const memory = new ConversationSummaryMemory({ llm: model as any, memoryKey: "history" });
const chain = RunnableSequence.from([
async (input: { input: string }) => ({ ...input, history: await memory.loadMemoryVariables({}) }),
prompt,
model,
async (output) => { await memory.saveContext({}, { output }); return output; },
]);
export async function chatOnce(text: string) {
const res = await chain.invoke({ input: text });
console.log(res);
}
if (require.main === module) {
(async () => {
await chatOnce("我们刚才讨论了哪些优化点?");
await chatOnce("继续说说 CSS 层面的优化。");
})();
}
2.15 Callback:日志与流式观测
typescript
// 文件:src/ch02/callbacks.ts
import { ChatOpenAI } from "@langchain/openai";
import { ConsoleCallbackHandler } from "@langchain/core/callbacks/console";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const prompt = ChatPromptTemplate.fromMessages([
["system", "你是输出严格 JSON 的助手"],
["human", "给出 3 个性能优化建议,输出 JSON 数组。"],
]);
const model = new ChatOpenAI({
modelName: "gpt-3.5-turbo",
callbacks: [new ConsoleCallbackHandler()],
verbose: true,
});
export async function run() {
const res = await prompt.pipe(model).invoke({});
console.log(res.content);
}
if (require.main === module) { run(); }
🧪 Prompt 评测与 A/B 测试(约 5%)
2.16 评测清单与指标
- 准确性(是否回答正题)
- 可读性(结构、格式、术语友好度)
- 一致性(相同输入的稳定输出)
- 事实性(是否引用可信来源,RAG 结合时引用率)
- 成本与时延(token 使用、响应时间)
2.17 LangSmith 与回归样本集
- 建立固定问题集(golden set),覆盖主流程与边界
- 为每次 Prompt 版本变更做回归评测
- 收集线上真实问题,持续补充样本
2.18 A/B 实战样例(伪代码)
typescript
// A/B 两套 Prompt 模板,线上分流 20%/80%
// 记录满意度、转化率、人工标注得分
🚀 实战项目一:FAQ RAG Chat(Prompt 为核心)(约 15%)
2.19 目标
- 为产品 FAQ/文档提供问答;要求:结构化答案、来源引用、低幻觉
- 用 Prompt 规范化回答格式;与向量检索(Vector/RAG)配合
2.20 项目结构
bash
src/
ch02/
rag-faq/
ingest.ts # 文档加载与向量化
retriever.ts # 检索器
prompt.ts # 回答模板(含引用)
answer.ts # 组合链路
server.ts # API/CLI 入口
2.21 关键代码(节选)
typescript
// 文件:src/ch02/rag-faq/prompt.ts
import { ChatPromptTemplate } from "@langchain/core/prompts";
export const answerPrompt = ChatPromptTemplate.fromMessages([
["system", `你是严谨的 FAQ 智能助手。请基于"检索到的片段"回答,若无答案请说不知道。
必须输出以下 JSON:
{
"answer": string,
"citations": [{"source": string, "chunkId": string}],
"confidence": number // 0-1
}`],
["human", `用户问题:{question}
检索片段:\n{chunks}\n请回答。`],
]);
typescript
// 文件:src/ch02/rag-faq/answer.ts
import { RunnableSequence } from "@langchain/core/runnables";
import { answerPrompt } from "./prompt";
import { ChatOpenAI } from "@langchain/openai";
import { JsonOutputParser } from "@langchain/core/output_parsers";
type QA = { answer: string; citations: { source: string; chunkId: string }[]; confidence: number };
export function buildQAChain(retriever: (q: string) => Promise<string>) {
const model = new ChatOpenAI({ temperature: 0 });
const parser = new JsonOutputParser<QA>();
return RunnableSequence.from([
async (input: { question: string }) => ({
question: input.question,
chunks: await retriever(input.question),
}),
answerPrompt,
model,
parser,
]);
}
typescript
// 文件:src/ch02/rag-faq/retriever.ts(示意)
// 实际可用 Chroma/Pinecone 等向量库
export async function simpleRetriever(q: string): Promise<string> {
return `# chunk-01 来自 docs/intro.md\nLangChain.js 是构建 LLM 应用的框架...`;
}
2.22 Prompt 要点
- 严格 JSON 输出,便于前端渲染
- 含"若无答案请直说"的防幻觉护栏
- 引用列表(citations)强制结构
- 引入
confidence
便于排序/过滤
2.23 运行与评测
- 建立 30+ 常见问题作为 gold set
- A/B 不同语气/结构提示,观察答非所问率
- 记录引用命中率与人工标注分
🛠️ 实战项目二:Prompt 驱动的「需求 → 任务卡片」生成器(约 15%)
2.24 目标
- 输入自然语言需求,自动生成结构化任务卡(标题、标签、预估工时、验收标准)
- 满足结构化输出、可复核、可回填的工程要求
2.25 项目结构
css
src/
ch02/
tasks/
schema.ts
prompt.ts
chain.ts
eval.ts
cli.ts
2.26 核心代码(节选)
typescript
// 文件:src/ch02/tasks/schema.ts
import { z } from "zod";
export const TaskSchema = z.object({
title: z.string().min(6),
tags: z.array(z.string()).max(5),
estimateHours: z.number().min(1).max(80),
acceptance: z.array(z.string()).min(1),
});
export type Task = z.infer<typeof TaskSchema>;
typescript
// 文件:src/ch02/tasks/prompt.ts
import { PromptTemplate } from "@langchain/core/prompts";
export const taskPrompt = PromptTemplate.fromTemplate(`
请把下面需求转成任务卡片(严格 JSON):
需求:{requirement}
返回:
{
"title": string,
"tags": string[],
"estimateHours": number,
"acceptance": string[]
}
`);
typescript
// 文件:src/ch02/tasks/chain.ts
import { ChatOpenAI } from "@langchain/openai";
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { TaskSchema } from "./schema";
import { taskPrompt } from "./prompt";
const parser = StructuredOutputParser.fromZodSchema(TaskSchema);
const model = new ChatOpenAI({ temperature: 0 });
export async function generateTask(requirement: string) {
const res = await taskPrompt
.pipe(model)
.pipe(parser)
.invoke({ requirement });
return res;
}
typescript
// 文件:src/ch02/tasks/eval.ts (简易评测)
import { generateTask } from "./chain";
const cases = [
"为博客增加全文搜索,支持标签过滤和高亮",
"新增图片上传,自动压缩并生成 WebP",
];
(async () => {
for (const c of cases) {
const out = await generateTask(c);
const ok = !!out.title && out.estimateHours > 0 && out.acceptance.length > 0;
console.log("case:", c, ok ? "✅" : "❌", out);
}
})();
2.27 质量要点
- 严格 Schema 校验,异常直接可见
- 失败样例回收成回归集,持续改进 Prompt
- 允许少量温度,保证多样性但不过度发散
⚙️ 性能、成本与健壮性(约 5%)
2.28 优化建议
- 模板复用与缓存:避免重复渲染模板与不必要的调用
- 限制输出长度与格式:降低 token 使用与解析成本
- 超时/重试/指数退避:应对网络抖动与临时错误
- 失败降级:为空时返回兜底文案或引导收集更多上下文
2.29 回退策略(Guardrails)
- 结构化输出失败 → 自动重试 + 降级纯文本 + 错误上报
- 检索为空 → 提示继续提问或缩小范围
- 敏感/越权问题 → 明确拒答并给出替代建议
🔍 与前端/产品工程协作要点(约 5%)
2.30 前端集成
- 流式输出:打字机效果、取消/重试按钮
- 结构化渲染:基于 JSON 直接渲染卡片/表格/引用
- 错误兜底:可视化错误提示与重试引导
2.31 产品落地
- 指标看板:满意度、人工接管率、拒答率、成本
- 版本切换:Prompt 版本灰度与快速回滚
- 合规安全:敏感词治理与数据最小化
📚 资源与延伸
- LangChain.js 文档(JS):
https://js.langchain.com/
- LangGraph:
https://langchain-ai.github.io/langgraph/
- LangSmith:
https://docs.smith.langchain.com/
- 提示工程最佳实践合集:
https://learnprompting.org/
- OpenAI 指南:
https://platform.openai.com/docs/guides/prompt-engineering
📦 附:示例项目最小可运行清单
bash
mkdir -p src/ch02 && cd $_
# 将上述 *.ts 文件放入对应目录后:
npm i @langchain/core @langchain/openai @langchain/community zod dotenv
npm i -D tsx typescript @types/node
echo '{
"compilerOptions": {"target":"ES2020","module":"commonjs","esModuleInterop":true,"strict":true},
"include": ["src/**/*"]
}' > tsconfig.json
json
// package.json 片段
{
"scripts": {
"ch02:basic": "tsx src/ch02/basic-instruction.ts",
"ch02:chat": "tsx src/ch02/chat-prompt.ts",
"ch02:few": "tsx src/ch02/few-shot.ts",
"ch02:pipe": "tsx src/ch02/pipeline.ts",
"ch02:json": "tsx src/ch02/json-output.ts",
"ch02:zod": "tsx src/ch02/zod-output.ts"
}
}
✅ 本章小结
- 系统化掌握了 Prompt 的核心方法与工程化落地
- 熟练运用 LangChain.js 模板体系与结构化输出
- 学会将 Prompt 与 Runnable/Memory/Callback 组合
- 建立 Prompt 评测与 A/B 测试的迭代闭环
- 完成两个实战项目,从 0 到 1 走通产品化路径
🎯 下章预告
下一章《Memory 系统与对话状态管理》中,我们将深入:
- 短期/长期记忆策略的权衡
- 滑动窗口与摘要记忆的组合
- 与向量记忆(VectorStore)协同,打造可检索的对话系统
- 记忆的一致性、成本与隐私安全
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!!