用 Prompt 做 NLP 任务开发 —— 从零搭建一个模块化的 LLM NLP 系统
前言
在传统的 NLP(自然语言处理)领域,构建一个情感分类、信息提取或文本摘要系统,往往需要熟练的机器学习工程师花费数天甚至数周的时间——收集标注数据、训练模型、调参、部署……门槛相当高。
而如今,随着大语言模型(LLM)的成熟,我们仅用几分钟、几十行代码,就能搭建起一套功能强大的 NLP 系统。本文将通过一个实际的项目示例,带你一步步了解如何使用 Prompt 工程来完成常见的 NLP 任务。
一、项目结构:模块化思想
先来看看这个小项目的目录结构:
nlp.demo/
├── readme.md # 项目说明
├── client.mjs # LLM 客户端对象
├── completion.mjs # 任务完成函数
├── main.mjs # 入口文件:文本摘要
└── main2.mjs # 入口文件:情感分析 & 信息提取
这个结构体现了一个非常重要的工程思想——模块化:
| 文件 | 职责 |
|---|---|
client.mjs | 负责创建并导出 LLM 客户端实例(鉴权、配置) |
completion.mjs | 封装通用的任务函数(发请求、拿结果) |
main.mjs / main2.mjs | 单点入口,编写具体业务逻辑(Prompt + 调用) |
为什么要模块化?
- 可维护性:配置变更只需改
client.mjs,不会影响业务代码。 - 可复用性:
getCompletion函数一次编写,多个入口文件都能引入使用。 - 关注点分离:鉴权、请求封装、业务逻辑各司其职,代码清晰易懂。
二、核心模块拆解
2.1 client.mjs —— 初始化 LLM 客户端
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;
这里做了三件事:
- 使用
dotenv加载环境变量(.env文件),敏感信息(API Key)不会硬编码在代码中。 - 创建 OpenAI 兼容的客户端实例(这里接入的是 DeepSeek,只要是 OpenAI 兼容接口的模型都可以用同样的方式接入)。
- 通过
export default默认导出,方便其他模块引入。
小贴士:
export default一个模块只能有一个,而export(命名导出)可以有多个。如果模块只有一个核心对象需要对外暴露,用默认导出最简洁。
2.2 completion.mjs —— 封装通用请求函数
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;
}
这个函数是整个系统的核心"发动机":
- 接收一个
prompt(提示词),发送给 LLM。 - 返回模型生成的文本内容。
async/await异步处理,不阻塞主线程。
有了这个通用函数,后续无论做什么 NLP 任务,只需要专注于写好 Prompt 就够了。
三、ES6 语法特性速览
项目使用了 ES6(ES2015)模块语法,这也是现代 JavaScript 开发的基础。快速回顾几个关键特性:
3.1 let / const —— 块级作用域
// let 和 const 支持块级作用域,解决了 var 的声明提升问题
const apiKey = 'xxx'; // 简单数据类型不可重新赋值
let count = 0; // 可以重新赋值
// const 修饰的对象可以修改属性,但不能改变指向
const obj = { name: '姚明' };
obj.city = '上海'; // ✅ 允许
obj = {}; // ❌ 报错,不能改变内存地址
3.2 解构赋值 —— 优雅提取数据
// 对象解构
let { name, city } = { name: '姚明', city: '上海' };
console.log(name, city); // 姚明 上海
// 数组解构 + rest 运算符
let [coach, ...players] = ['111', '222', '333', '444', '555'];
console.log(coach); // '111'
console.log(players); // ['222', '333', '444', '555']
解构赋值不仅代码优雅,性能也比逐属性访问更好。
3.3 展开运算符(Spread)
let players1 = ['222', '333', '444'];
let players2 = ['123', '234', '345'];
let allPlayers = [...players1, ...players2];
// ['222', '333', '444', '123', '234', '345']
3.4 ESM 模块系统
import client from './client.mjs'; // 默认导入
import { getCompletion } from './completion.mjs'; // 命名导入
export default client; // 默认导出
export async function getCompletion() {} // 命名导出
四、NLP 任务实战
下面我们来看看如何用 Prompt 完成四大经典 NLP 任务。核心思路:你不需要训练模型,你只需要设计好 Prompt。
4.1 情感分类(Sentiment Analysis)
判断一段文本的情感倾向——正面、负面或中性。这在电商、客服预警、产品质检等场景中非常重要。
const prompt = `
以下用三个反引号分隔的产品评论的情感是什么?
用一个单词回答,正面或负面。
评论文本:```${lamp_review_zh}```
`;
还可以进一步细化——识别具体情绪类型:
const prompt = `
识别以下评论的作者表达的情感。
包含不超过五个项目。
将答案格式化为以逗号分隔的单词列表。
评论文本:```${lamp_review_zh}```
`;
// → 满意、感激、开心、认可、信任
const prompt = `
以下用三个反引号分隔的产品评论是否表达了愤怒情感?
给出是或否的答案。
评论文本:```${lamp_review_zh}```
`;
4.2 信息提取(Information Extraction)
从非结构化文本中提取结构化信息,比如商品名、品牌等:
const prompt = `
从评论文本中识别以下项目:
- 评论者购买的商品
- 制造该商品的公司
评论文本用三个反引号分隔。将你的响应格式以"物品"和"品牌"为键的JSON对象。
如果信息不存在,请使用"未知"作为值。
评论文本:```${lamp_review_zh}```
`;
// → {"物品": "卧室灯", "品牌": "Lumina"}
进阶:一次提取多个维度并格式化输出:
const prompt = `
从评论文本中识别以下项目:
- 情绪(正面或负面)
- 是否表达了愤怒?(是或否)
- 评论者购买的商品
- 制造该物品的公司
将你的响应格式化为JSON对象,以"sentiment"、"anger"、"product"、"brand"为键。
如果信息不存在,请使用"未知"作为值。
将anger值格式化为布尔值。
评论文本:```${lamp_review_zh}```
`;
4.3 主题推断(Topic Detection)
判断一段文本在讨论哪些主题:
const prompt = `
确定一下给定文本中讨论的五个主题。
每个主题用1-2个单词概括。
输出时用逗号分隔。
给定文本:```${story_zh}```
`;
// → 员工满意度, NASA, 调查结果, 政府承诺, 工作环境
4.4 文本摘要(Summarization)
对长文本进行概括,提取关键信息——这个功能对行政岗、编辑、管理者特别实用:
const prompt = `
你的任务是从电子商务网站上的产品评论中提取相关信息。
请对三个反引号之间的文本进行概括,最多20个字符。
评论文本:```${review}```
`;
还可以让摘要聚焦于特定维度:
const prompt = `
你的任务是从电子商务网站上生成一个产品评论的简短摘要。
请对三个反引号之间的文本进行概括,最多30个词汇,并且聚焦在产品价格和质量上。
评论文本:```${prod_review_zh}```
`;
五、Prompt 设计技巧总结
从上面的示例中可以提炼出几个 Prompt 设计的关键原则:
| 原则 | 说明 | 示例 |
|---|---|---|
| 明确角色 | 告诉模型它要完成什么任务 | "你的任务是从产品评论中提取相关信息" |
| 限定格式 | 指定输出格式,便于后续解析 | "用JSON对象回答"、"用一个单词回答" |
| 用分隔符 | 把输入文本用 ``` 或 """ 包裹,避免混淆 | 评论内容 |
| 控制长度 | 限制输出字数或条目数 | "最多20个字符"、"不超过五个项目" |
| 处理缺失 | 告诉模型数据缺失时怎么做 | "如果信息不存在,请使用'未知'" |
六、完整工作流
把整个流程串起来:
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ main.mjs │────▶│ completion.mjs │────▶│ client.mjs │
│ (业务逻辑) │ │ getCompletion() │ │ OpenAI API │
│ │ │ │ │ │
│ 1.准备文本 │ │ 接收 prompt │ │ DeepSeek │
│ 2.设计Prompt │ │ 调用 LLM │ │ LLM 模型 │
│ 3.处理结果 │◀────│ 返回结果 │◀────│ │
└─────────────┘ └──────────────────┘ └─────────────┘
- 在
client.mjs中配置好 API Key 和 Base URL,创建客户端。 - 在
completion.mjs中封装通用的getCompletion(prompt)函数。 - 在
main.mjs中编写 Prompt,调用getCompletion,拿到结果。
七、结语
"仅用几分钟,我们就可以构建多个用于对文本进行推理的系统,而以前需要熟练的机器学习人员数天到数周的时间。"
这不仅仅是一句口号,而是正在发生的技术变革。Prompt 工程让 NLP 能力变得前所未有的平民化:
- 不需要 收集标注数据
- 不需要 训练模型
- 不需要 懂机器学习算法
- 只需要 会写 Prompt,会调 API
而一个良好的模块化架构(像本项目这样),则让这套系统更容易维护、扩展和复用。随着你对 Prompt 工程的深入理解,你可以轻松地接入更复杂的 NLP 流程——多轮对话、链式推理、工具调用等等。
这就是 AI 时代的软件开发——你不再是训练模型,而是在设计指令。