普通视图

发现新文章,点击刷新页面。
今天 — 2025年8月25日技术

LangChain.js 完全开发手册(二)Prompt Engineering 与模板系统深度实践

作者 鲫小鱼
2025年8月25日 11:26

第2章:Prompt Engineering 与模板系统深度实践

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

🎯 本章学习目标

  • 掌握 Prompt Engineering 的核心原则与方法论(指令式、少样本、思维链、反思等)
  • 熟练使用 LangChain.js 的 Prompt 模板系统(PromptTemplateChatPromptTemplateFewShotPromptTemplate 等)
  • 能够将 Prompt 与 RunnableCallbackMemoryOutputParser 组合为稳定的可复用链路
  • 学会结构化输出(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 指令式 + 变量注入

// 文件: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 + 多消息角色

// 文件: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(少样本)

// 文件: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(管道模板)

// 文件: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 输出与严格约束

// 文件: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 强类型解析

// 文件: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 组合与复用

// 文件: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 注入历史对话(滑动窗口)

// 文件: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:日志与流式观测

// 文件: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 实战样例(伪代码)

// A/B 两套 Prompt 模板,线上分流 20%/80%
// 记录满意度、转化率、人工标注得分

🚀 实战项目一:FAQ RAG Chat(Prompt 为核心)(约 15%)

2.19 目标

  • 为产品 FAQ/文档提供问答;要求:结构化答案、来源引用、低幻觉
  • 用 Prompt 规范化回答格式;与向量检索(Vector/RAG)配合

2.20 项目结构

src/
  ch02/
    rag-faq/
      ingest.ts         # 文档加载与向量化
      retriever.ts      # 检索器
      prompt.ts         # 回答模板(含引用)
      answer.ts         # 组合链路
      server.ts         # API/CLI 入口

2.21 关键代码(节选)

// 文件: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请回答。`],
]);
// 文件: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,
  ]);
}
// 文件: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 项目结构

src/
  ch02/
    tasks/
      schema.ts
      prompt.ts
      chain.ts
      eval.ts
      cli.ts

2.26 核心代码(节选)

// 文件: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>;
// 文件: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[]
}
`);
// 文件: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;
}
// 文件: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

📦 附:示例项目最小可运行清单

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
// 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)协同,打造可检索的对话系统
  • 记忆的一致性、成本与隐私安全

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

前端监控实战:从性能指标到用户行为,我是如何搭建监控体系的

作者 ErpanOmer
2025年8月25日 11:17
还在当一线开发的时候,我最怕半夜接到电话,说:线上出问题了!!!。 那时候我们对线上环境几乎是两眼一抹黑。一个功能发布后,它在线上跑得快不快、有没有报错、用户到底喜不喜欢用,我们一概不知。出了问题,只

CSS Float(浮动)

作者 xianxin_
2025年8月25日 09:42
CSS float属性指定盒子是否应该浮动。在CSS的float属性是定位性质。它用于将一个元素向左或向右推,允许其他元素环绕它,通常用于图像和布局。 CSS的浮动元素 您可以将元素向左或向右浮动,但
❌
❌