2.前端使用Node + LangChain提示词工程应用实践

108 阅读10分钟

Prompt templates(提示词模板)

语言模型以文本作为输入 - 这个文本通常被称为提示词(prompt)。

在开发过程中,对于提示词通常不能直接硬编码提供给模型使用,而是通过提示词模板进行维护,类似开发过程中遇到的短信模板、邮件模板等等。

64b8936c0bccdc569ca15c504a302d55_u=1578766093,829081525&fm=253&fmt=auto&app=120&f=JPEG_w=1302&h=500.webp

到底什么是提示词模板?

提示词模板的核心是「固定逻辑 + 可变参数」:

  • 固定部分:包含对任务目标、输出格式、约束条件、角色设定等通用要求的描述(比如 “你是一名前端工程师,需遵循 ES6 规范编写代码”);
  • 可变参数:通过占位符(如 {变量名}{变量名})预留的个性化输入项(比如 {功能需求}{代码语言});
  • 使用时仅需替换变量值,即可快速生成符合要求的完整提示词,无需每次从头梳理指令逻辑。
  • 一个提示词模板可以包含下面内容:
    • 发给大语言模型(LLM)的指令。
    • 一组问答示例,以提醒AI以什么格式返回请求。
    • 发给语言模型的问题。

举个基础示例(前端代码生成模板):

import { PromptTemplate } from '@langchain/core/prompts';
const cotPromptTemplate = PromptTemplate.fromTemplate(`
角色:你是一名资深前端工程师,熟悉React和TypeScript,遵循组件化开发规范。
任务:根据以下需求编写可复用的{componentType}组件,要求:
1. 使用{codeLanguage}编写,符合ESLint规范;
2. 包含完整的类型定义和注释;
3. 输出格式:先写组件代码,再补充使用示例和注意事项。
需求详情:{functionalRequirements}
`)
const result = await cotPromptTemplate.format({
    componentType: 'button',
    codeLanguage: 'React',
    functionalRequirements: '支持自定义尺寸、颜色,点击触发回调'
})
console.log(result)

使用时只需替换 组件类型(如 “按钮”)、代码语言(如 “TypeScript”)、功能需求(如 “支持自定义尺寸、颜色,点击触发回调”),就能快速生成标准化的组件开发提示词。

模板输出结果:

角色:你是一名资深前端工程师,熟悉React和TypeScript,遵循组件化开发规范。 任务:根据以下需求编写可复用的button组件,要求:

  1. 使用React编写,符合ESLint规范;
  2. 包含完整的类型定义和注释;
  3. 输出格式:先写组件代码,再补充使用示例和注意事项。 需求详情:支持自定义尺寸、颜色,点击触发回调

聊天消息提示词模板(chat prompt template)

聊天消息提示词模板(Chat Prompt Template)是适配 LLM 聊天场景的标准化提示词结构,核心作用是定义角色规则、约束输出风格、衔接上下文,让 LLM 输出符合预期的聊天回复。

一.Chat Prompt Template 核心结构

所有聊天模板都围绕「3 个核心模块」设计,可灵活组合:

模块作用示例占位符
系统提示定义 LLM 角色、回复规则、语气风格(核心,决定对话基调){system_role}
对话上下文多轮聊天的历史记录(保证连贯性){chat_history}
用户当前输入用户最新发送的消息(核心输入){user_input}
输出约束(可选)限制回复长度、格式、语言等(避免输出失控){output_constraint}

二.通用 & 分场景 Chat Prompt Template

1.基础通用聊天模板(适配大部分场景)

适用于无特殊要求的日常闲聊,兼顾自然度和可控性:

{system_role}

【对话历史】
{chat_history}

【用户当前消息】
{user_input}

{output_constraint}

变量替换示例

  • system_role:你是一个友好的聊天伙伴,语气轻松自然,回复简洁(1-3 句话),不使用专业术语,像和朋友聊天一样。
  • chat_history:用户:今天下班吃什么?AI:可以试试楼下的牛肉面,味道超赞~
  • user_input:那家牛肉面会不会很辣?
  • output_constraint:回复控制在 20 字以内,只回答辣度相关问题,不推荐其他食物。
2.专业客服模板(电商 / 售后场景)

强调「问题解决导向 + 标准化话术 + 同理心」:

{system_role}

【核心规则】
1. 优先安抚用户情绪,再解决问题;
2. 未明确的信息(如订单号、商品型号)主动询问,但仅问1个问题/次;
3. 无法解答时,引导联系人工客服,不编造答案。

【历史对话】
{chat_history}

【用户当前咨询】
{user_input}

【输出要求】
- 语气:耐心、专业、有同理心;
- 长度:30-50 字;
- 格式:先回应情绪 → 解答问题 → 确认是否需要进一步帮助。

变量替换示例

  • system_role:你是电商售后客服,负责解答商品退换货、物流、价格相关问题。
3. 技术问答模板(适配前端 / LLM 开发场景)

强调「准确性 + 步骤化 + 简洁性」,贴合你的技术背景:

{system_role}

【回答规则】
1. 技术问题优先给出「步骤化解决方案」,再补充注意事项;
2. 涉及代码的,给出可运行的极简示例(不超过 5 行);
3. 仅回答前端/LLM 开发相关问题,其他问题告知无法解答。

【历史对话】
{chat_history}

【用户技术问题】
{user_input}

【输出约束】
- 语言:简洁易懂,避免冗余;
- 格式:分点说明(最多 3 点),代码用 ```js 包裹。

变量替换示例

  • system_role:你是资深前端工程师,擅长 Node.js、LangChain、Ollama 相关开发。
4. 多轮对话优化模板(带上下文裁剪)

解决长对话上下文超限问题,仅保留关键历史:

{system_role}

【上下文规则】
- 仅保留和当前问题相关的历史对话(无关内容已裁剪);
- 回复时无需提及「上下文已裁剪」,自然衔接即可。

【关键对话历史】
{chat_history}

【用户当前输入】
{user_input}

【输出要求】
- 回复需基于历史对话,避免重复提问;
- 长度:1-4 句话,语气连贯。
5. 结构化输出模板(需 JSON / 固定格式)

适用于需要机器解析回复的场景(如对接业务系统):

{system_role}

【输出格式强制要求】
必须返回 JSON 格式,包含以下字段:
- role: 固定值 "assistant";
- content: 回复内容(字符串);
- confidence: 回答置信度(0-1,如不确定则为 0.5);
- need_follow: 是否需要追问(true/false)。

【对话历史】
{chat_history}

【用户输入】
{user_input}

【禁止】
不输出任何 JSON 外的内容,包括注释、说明文字。

示例输出

{
  "role": "assistant",
  "content": "Node.js 读取本地文件可使用 fs 模块,示例:fs.readFile('test.txt', (err, data) => {console.log(data);})",
  "confidence": 0.95,
  "need_follow": false
}

三、LangChain 中使用 Chat Prompt Template

结合 LangChain 的 ChatPromptTemplate 类,可快速封装模板并对接 Ollama 模型,示例如下:

import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOllama } from "@langchain/ollama";

// 1. 定义聊天提示词模板(以技术问答为例)
const chatPrompt = ChatPromptTemplate.fromMessages([
  // 系统提示(角色 + 规则)
  ["system", `你是资深前端工程师,擅长 Node.js、LangChain、Ollama 开发。
   回答规则:
   1. 技术问题优先给出步骤化解决方案;
   2. 代码示例不超过 5 行;
   3. 回复控制在 50 字以内。`],
  // 对话上下文(多轮时自动填充)
  ["placeholder", "{chat_history}"],
  // 用户当前输入
  ["user", "{user_input}"]
]);

// 2. 初始化 Ollama 模型
const model = new ChatOllama({
  model: "qwen3:0.6b",
  temperature: 0.2
});

// 3. 组装模板 + 调用模型
async function runChatTemplate() {
  // 填充模板变量
  const prompt = await chatPrompt.format({
    chat_history: "用户:Node.js 怎么读文件?AI:用 fs 模块的 readFile 方法~",
    user_input: "readFile 是异步的吗?有没有同步方法?"
  });

  // 调用模型获取回复
  const response = await model.invoke(prompt);
  console.log("模型回复:", response);
}

// 运行测试
runChatTemplate();

image.png

四、模板设计关键技巧

  1. 角色明确:系统提示中清晰定义 LLM 的身份(如「前端工程师」「电商客服」),避免回复偏离定位;
  2. 规则具体:不用模糊表述(如「回答准确」),而是明确约束(如「代码示例不超过 5 行」);
  3. 上下文可控:多轮对话时裁剪无关历史,避免上下文窗口超限(如 Qwen3:4B 上下文 32K,需控制历史长度);
  4. 变量解耦:将「角色、历史、输入」拆分为独立变量,便于动态替换和维护。

使用示例选择器

这里我们使用SemanticSimilarityExampleSelector类。该类根据与输入的相似性选择小样本示例。它使用嵌入模型计算输入和小样本示例之间的相似性,然后使用向量数据库执行相似搜索,获取跟输入相似的示例。

提示:这里涉及向量计算、向量数据库,在AI领域这两个主要用于数据相似度搜索,例如:查询相似文章内容、相似的图片、视频等等,这里先简单了解下就行。

.env文件配置

# Ollama 配置
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_EMBED_MODEL=nomic-embed-text:latest
OLLAMA_THINK_MODEL=deepseek-r1:1.5b

# Chroma 向量数据库配置
CHROMA_URL=http://localhost:8000
CHROMA_COLLECTION_NAME=few-shot-examples
CHROMA_PERSIST_DIR=./chroma-data
import * as dotenv from 'dotenv';
dotenv.config();
import { Document } from '@langchain/core/documents';
import { SemanticSimilarityExampleSelector } from '@langchain/core/example_selectors';
import { Chroma } from '@langchain/community/vectorstores/chroma'; // Chroma 向量存储
import { ChatOllama, OllamaEmbeddings } from '@langchain/ollama'; // 本地嵌入模型
import { PromptTemplate } from '@langchain/core/prompts'; // 可选:用于构建提示词
import { CallbackManager } from '@langchain/core/callbacks/manager';
const embeddings = new OllamaEmbeddings({
  model: process.env.OLLAMA_EMBED_MODEL, // 轻量嵌入模型
  baseUrl: process.env.OLLAMA_BASE_URL, // Ollama 服务地址
});
// 1. 初始化回调管理器(打印每个步骤的日志)
const callbackManager = CallbackManager.fromHandlers({
  // 监听 LLM 生成环节(推理的核心:模型正在生成回答)
  handleLLMStart: (params) => {
    console.log('\n=== 推理环节:模型开始生成回答 ===');
    console.log('输入给模型的 Prompt:', params);
  },
  // 监听 LLM 生成结束(推理完成,即将返回回答)
  handleLLMEnd: (output) => {
    console.log('模型原始输出:', output);
    console.log('\n=== 推理环节:模型生成结束 ===');
  },
});
// 2 准备示例数据(用于筛选的样本,格式:对象数组,含输入/输出等字段)
const examples = [
  {
    input: '如何在 Node.js 中安装 LangChain?',
    output: '执行命令:npm install @langchain/core @langchain/community',
    category: '安装问题', // 可选:元数据,用于过滤
  },
  {
    input: 'Chroma 如何持久化数据?',
    output: '启动 Chroma 时指定 --path 目录(本地)或挂载 Docker 卷(容器)',
    category: 'Chroma 问题',
  },
  {
    input: 'SemanticSimilarityExampleSelector 作用是什么?',
    output: '根据查询的语义相似度,从示例集中筛选最相关的样本(用于 Few-Shot)',
    category: 'ExampleSelector 问题',
  },
  {
    input: 'Ollama 如何启动服务?',
    output: '终端执行命令:ollama serve(默认端口 11434)',
    category: 'Ollama 问题',
  },
];

// 3. 创建 Chroma 向量存储(存储示例数据的向量)
const vectorStore = await Chroma.fromDocuments(
  // 将示例数据转为 Document 类(Chroma 要求输入为 Document 数组)
  examples.map(
    (example) =>
      new Document({
        pageContent: JSON.stringify(example), // 存储示例完整信息(JSON 字符串)
        metadata: { category: example.category }, // 元数据(用于后续过滤)
      })
  ),
  embeddings, // 嵌入模型(自动将示例转为向量)
  {
    collectionName: process.env.CHROMA_COLLECTION_NAME, // Chroma 集合名称(类似数据库表名) 若 Chroma 已存在同名集合(collectionName),会直接复用,不会重复创建
    url: process.env.CHROMA_URL, // Chroma 服务地址
    persistDirectory: process.env.CHROMA_PERSIST_DIR, // 本地 Chroma 时可选(指定数据存储路径)
  }
);
// 4. 实例化 SemanticSimilarityExampleSelector(核心步骤)
const exampleSelector = new SemanticSimilarityExampleSelector({
  vectorStore, // 绑定 Chroma 向量存储(示例数据存在这里)
  k: 2, // 最多返回 2 个最相似的示例
  inputKeys: ['input'], // 用于计算相似度的「查询字段」(与示例的 input 对应)
});

// 5. 筛选相似示例(核心功能)
const userQuery = 'Chroma 数据怎么持久化到本地?'; // 用户查询

await Promise.all(examples.map((item) => exampleSelector.addExample(item)));
const selectedExamples = await exampleSelector.selectExamples({ input: userQuery });

// 6. 结合 LLM 实现 Few-Shot 回答(完整落地场景)
// 构建提示词模板(包含筛选出的示例)
const promptTemplate = PromptTemplate.fromTemplate(`
	参考:{examples}
	问题:{question}
	`);

// 格式化示例(转为自然语言文本)
const formattedExamples = selectedExamples.map((ex) => `问:${ex.input}\n答:${ex.output}`).join('\n\n');

//  初始化 LLM(使用环境变量配置)
const llm = new ChatOllama({
  model: process.env.OLLAMA_THINK_MODEL,
  streaming: true,
  callbacks: callbackManager,
});

// 执行链(提示词 + LLM)
const chain = promptTemplate.pipe(llm);
const response = await chain.invoke({
  examples: formattedExamples,
  question: userQuery,
});

console.log('\n=== LLM 最终回答 ===');
console.log(response.content);

image.png