仅用几分钟,我们就可以构建多个用于对文本进行推理的系统,而以前这需要熟练的机器学习人员数天到数周的时间。这就是 Prompt 工程带来的效率革命。
一、为什么要模块化?
NLP 项目天然适合模块化:调用 LLM 的逻辑、客户端配置、业务 prompt 都是独立的部分。模块化带来两个核心好处:
- 维护性和可读性:每个文件只做一件事,职责单一
- 好复用 :通过
import / export在任意地方引入
本项目的模块化结构如下:
| 文件 | 职责 | 说明 |
|---|---|---|
| main.mjs | 单点入口 | 业务逻辑、prompt 编写、结果输出 |
| client.mjs | 封装 client 对象 | LLM 客户端实例(apiKey、baseURL) |
| completion.mjs | 完成任务的函数 | 封装 getCompletion(prompt) 统一调用 |
js
// client.mjs ------ 只负责创建客户端
import { OpenAI } from 'openai';
import dotenv from 'dotenv';
dotenv.config();
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_API_BASE_URL,
});
export default client; // 默认导出,一个模块只能有一个
js
// completion.mjs ------ 只负责调用 LLM
import client from './client.mjs';
export async function getCompletion(prompt) {
const response = await client.chat.completions.create({
model: process.env.DEEPSEEK_MODEL,
messages: [{ role: "user", content: prompt }]
})
return response.choices[0].message.content;
}
js
// main.mjs ------ 只负责业务逻辑
import { getCompletion } from './completion.mjs';
async function main() {
const prompt = `...你的 prompt...`;
const response = await getCompletion(prompt);
console.log(response);
}
main();
关键点 :main.mjs 里看不到任何 API Key、模型名、HTTP 请求细节,全是业务 prompt。这就是模块化的优势。
二、ESM 模块化语法
ES6(2015 年发布)的目标是让 JS 成为企业级大型项目开发语言。模块化(ESM)是其中最重要的特性之一,让 NLP 项目可以拆成 client / completion / main 三个独立文件。
1. 为什么需要它们
JS 默认每个文件是"封闭"的,文件 A 里写的变量、函数,文件 B 看不到。如果想让文件 B 用文件 A 的东西,必须:
- 文件 A 主动暴露 出来 →
export - 文件 B 主动引入 进来 →
import
就像写笔记:写完要发到掘金 (export),别人才能打开掘金看(import)。
2. 两种导出方式
javascript
// ===== 方式一:默认导出(default)------ 一个文件只能有一个 =====
// client.mjs
const client = new OpenAI({ ... });
export default client;
// 引入时:不需要花括号,名字随便起
import client from './client.mjs'; // 叫 client
import whatever from './client.mjs'; // 叫 whatever 也行,拿到的还是同一个东西
// ===== 方式二:命名导出 ------ 可以有多个 =====
// completion.mjs
export async function getCompletion(prompt) { ... }
export async function getImage(prompt) { ... }
// 引入时:必须用花括号,名字必须对得上
import { getCompletion } from './completion.mjs'; // ✅ 只拿一个
import { getCompletion, getImage } from './completion.mjs'; // ✅ 拿多个
import { getCompletion as chat } from './completion.mjs'; // ✅ 重命名为 chat
// ===== 方式三:混合(default + 命名) =====
// client.mjs
export default client;
export const a = 2;
export const b = 3;
// 引入时:default 在外,命名在花括号
import client, { a, b } from './client.mjs';
3. 结合项目理解
项目里三个文件就是通过 import/export 串起来的:
javascript
// client.mjs(生产者)------ 提供一个 client 对象
const client = new OpenAI({ apiKey, baseURL });
export default client; // 默认导出"主打产品"
// completion.mjs(中间消费者 + 生产者)
import client from './client.mjs'; // 拿 client.mjs 的默认导出,命名为 client
// './client.mjs' 中的 ./ 表示同目录
export async function getCompletion(prompt) { // 命名导出一个函数
const response = await client.chat.completions.create({ ... });
return response.choices[0].message.content;
}
// main.mjs(最终消费者)
import { getCompletion } from './completion.mjs'; // 花括号按名字拿
async function main() {
const response = await getCompletion(prompt); // 直接用
}
main();
串联关系:
client.mjs说:"我提供一个 client 对象(默认)"completion.mjs说:"我要 client,我提供 getCompletion 函数(命名)"main.mjs说:"我要 getCompletion"
每个文件只管自己的事,互不干扰,这就是模块化。
4. 记忆口诀
| 关键词 | 作用 | 谁用 |
|---|---|---|
export |
暴露给别人 | 写东西的人(生产者) |
import |
拿别人的东西 | 用东西的人(消费者) |
export default |
暴露"主打产品"(唯一) | 生产者 |
import xxx from |
拿主打产品(不用花括号) | 消费者 |
import { xxx } from |
拿指定名字的东西(要花括号) | 消费者 |
一句话理解 :export 是**"我提供什么",import 是 "我要用什么"**。default 是默认提供的那个,花括号 {} 是"按名字挑"。
补充:命名导入
import { getCompletion }本质就是解构赋值 ,和const { a, b } = obj是同一套语法思想。ES6 的其他语法特性(let/const、rest/spread、解构赋值、模板字符串等)见单独的《ES6 语法特性》笔记。
三、NLP 四大任务
NLP(自然语言处理)在 LLM 出现前是机器学习的"高端局",现在用 Prompt 几行代码就能搞定。
1. 情感分类(Sentiment Analysis / Classification)
场景:电商评论、客服工单、舆情预警、产品质检
任务:判断文本是 正面 / 负面 / 中性
js
const prompt = `
以下用三个反引号分隔的产品评论是什么?
用一个单词回答:正面 或 负面
评论文本:\`\`\` ${lamp_review_zh} \`\`\`
`
进阶:识别多种情绪、限制数量、格式化输出
js
const prompt = `
识别以下用三个反引号分隔的产品评论的作者表达的情感。
包含不超过5个项目。
将答案格式化为以逗号分隔的单词列表。
评论文本:\`\`\` ${lamp_review_zh} \`\`\`
`
愤怒检测(一个特殊的二分类):
js
const prompt = `
识别以下用三个反引号分隔的产品评论是否表达了愤怒?
给出是或否的答案。
评论文本:\`\`\` ${lamp_review_zh} \`\`\`
`
2. 信息提取(Information Extraction)
场景:从非结构化文本中提取结构化字段(商品、品牌、价格等)
js
const prompt = `
从评论文本中识别以下项目:
- 情绪(正面或负面)
- 是否表达了愤怒?(是或否)
- 评论者购买的商品
- 制造该物品的公司
评论用三个反引号分隔,
将您的响应格式化为JSON对象,以"sentiment"、"anger"、"product"、"brand"为键。
如果信息不存在,请使用"未知"作为值。
让你的回应尽可能简短。
将 anger 值格式化为布尔值
评论文本:\`\`\` ${lamp_review_zh} \`\`\`
`
关键技巧:要求输出 JSON 格式,方便后续程序解析处理。
3. 主题推断(Topic Inference)
场景:长文章自动打标签、新闻分类
任务一:推断文本讨论了哪些主题
js
const prompt = `
确定以下给定文本中讨论的五个主题。
每个主题用1-2个单词概括。
输出时用逗号分隔。
给定文本:${story_zh}
`
任务二:判断给定主题列表中哪些在文本中出现(零样本分类)
js
const prompt = `
判断主题列表中的每一项是否是给定文本中的一个话题,
以列表的形式给出答案,每个主题用0 或 1。
主题列表:${topicList.join(",")}
给定文本:${story_zh}
`
${topicList.join(",")}中的,是 join 的分隔符,把数组['NASA','政府']转成字符串"NASA,政府",方便嵌入 prompt。
4. 文本总结(Summarization)
场景:老板看长报告、行政整理会议纪要、小编做内容摘要
基础版:
js
const prompt = `
您的任务是从电商网站上生成一个商品评论的简要摘要。
请对三个反引号之间的评论文本进行概括,最多30个词汇。
评论文本:\`\`\`${prod_review_zh}\`\`\`
`
聚焦版(用 prompt 引导关注点):
js
// 聚焦运输
const prompt = `
...并且聚焦在产品运输上。
评论文本:\`\`\`${prod_review_zh}\`\`\`
`
// 聚焦价格和质量
const prompt = `
...并且聚焦在产品价格和质量上。
评论文本:\`\`\`${prod_review_zh}\`\`\`
`
批量处理 (结合 for...of):
js
const reviews = [review_1, review_2, review_3, review_4];
for (let review of reviews) {
const prompt = `
你的任务是从电子商务网站上的产品评论中提取相关信息。
请对三个反引号之间的评论文本进行概括,最多20个字符。
评论文本:\`\`\`${review}\`\`\`
`
const response = await getCompletion(prompt);
console.log(response, '\n');
}
for(let a of b): 这是 ES6 的 for...of 循环 ,用来遍历 可迭代对象 (数组、字符串等),直接拿到值。
四、Prompt 工程的 5 个关键技巧
从上面的案例可以总结出写好 prompt 的通用模式:
1. 用分隔符清晰区分指令和文本
js
评论文本:\`\`\` ${review} \`\`\`
常用分隔符:```````````、###、---、<tag></tag>。避免模型把文本内容误解为指令。
2. 明确约束输出格式
- "用一个单词回答"
- "最多30个词汇"
- "以逗号分隔的单词列表"
- "格式化为 JSON 对象,以 xxx 为键"
约束越明确,输出越稳定可预测。
3. 限定数量和长度
包含不超过5个项目。
最多20个字符。
防止模型啰嗦。
4. 处理未知情况
arduino
如果信息不存在,请使用"未知"作为值。
避免模型瞎编(幻觉)。
5. 角色化与聚焦
你的任务是从电商网站上生成一个商品评论的简要摘要...并且聚焦在产品运输上。
通过"任务定义 + 聚焦方向"控制输出方向,同一份文本可以多次复用产出不同视角的摘要。
五、完整调用链路
scss
.env (DEEPSEEK_API_KEY / BASE_URL / MODEL)
↓
client.mjs → 创建 OpenAI client 实例(默认导出)
↓
completion.mjs → getCompletion(prompt) 封装调用逻辑(命名导出)
↓
main.mjs → 编写 prompt,调用 getCompletion,console.log 结果
一次完整调用的核心代码:
js
const response = await client.chat.completions.create({
model: process.env.DEEPSEEK_MODEL,
messages: [{ role: "user", content: prompt }]
})
return response.choices[0].message.content;
messages:消息数组,每条有role(system/user/assistant)和contentchoices[0].message.content:模型返回的文本内容model:从环境变量读取,便于切换模型
六、总结
| 维度 | 传统机器学习 | Prompt 工程 |
|---|---|---|
| 开发周期 | 数天 ~ 数周 | 几分钟 |
| 人员要求 | 熟练 ML 工程师 | 会写 prompt 的开发者 |
| 数据需求 | 大量标注数据 | 零样本 / 少样本即可 |
| 部署成本 | 训练 + 部署模型 | 调用 API |
核心思想:LLM 把 NLP 从"训练模型"变成了"写 prompt"。开发者不需要懂神经网络,只需要会清晰表达任务目标、约束输出格式,就能构建出强大的文本推理系统。
模块化的价值 :业务逻辑(prompt)和工程实现(client、API 调用)分离,换模型只改 .env,换任务只改 main.mjs 里的 prompt,互不影响。这就是 AI 全栈开发的基本范式。