你是否曾经为了一个情感分类任务,花费数周时间标注数据、训练模型、调参?现在,这一切都可以在几分钟内完成。本文将带你领略Prompt工程的魅力,并用ES6模块化搭建一个生产可用的NLP推理系统。

 

一、为什么传统NLP开发正在被颠覆?

传统机器学习开发流程:

  • 数据采集与清洗(2-3天)

  • 人工标注(1-2周)

  • 特征工程与模型选择(3-5天)

  • 训练调参(2-3天)

  • 部署上线(2-3天)

总计:数周到数月

而使用LLM + Prompt:

  • 编写Prompt(5分钟)

  • 接入API(5分钟)

  • 完成!✨

这不是夸张,而是正在发生的技术变革。让AI(大语言模型)来理解语言,我们只需要学会“提问”即可。

二、项目模块化搭建:像搭建乐高一样组织代码

为什么要模块化?

❌ 反模式:所有代码写在同一个文件
main.mjs (3000行) 
├── API密钥配置
├── 调用逻辑
├── 错误处理
├── 20个不同的Prompt
└── 输出解析
✅ 模块化架构:
├── client.mjs      # LLM客户端(单例)
├── completion.mjs  # 通用调用封装
└── main.mjs        # 业务逻辑入口

核心模块解析

1. client.mjs - 客户端单例

import { OpenAI } from "openai";
import dotenv from 'dotenv';

dotenv.config();

// 创建全局唯一的LLM客户端
const client = new OpenAI({
    apiKey: process.env.DEEPSEEK_API_KEY,
    baseURL: process.env.DEEPSEEK_API_BASE_URL,
});

export default client; // 默认导出

为什么需要单例? 避免重复初始化,节省资源,统一管理API配置。

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;
}

3. main.mjs - 业务入口

import { getCompletion } from "./completion.mjs";

async function main() {
    // 业务逻辑:情感分析、信息提取等
    const result = await getCompletion("你的Prompt");
    console.log(result);
}

main();

模块化的三大收益:

  • 维护性:每个文件职责单一

  • 可复用性getCompletion可以在任何地方调用

  • 可测试性:可以单独测试每个模块

三、ES6核心特性:让代码更优雅

ES6(ECMAScript 2015)是JavaScript的一次重大升级,让这门语言真正具备了开发大型企业级应用的能力。

1. let/const与块级作用域

// ❌ var的问题:变量提升导致诡异bug
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 输出 3,3,3
}

// ✅ let/const:块级作用域
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 输出 0,1,2
}

// const的注意事项
const config = { apiKey: "123" };
config.apiKey = "456"; // ✅ 允许:修改对象属性
// config = {}; // ❌ 不允许:改变内存指向

2. Rest参数与Spread展开运算符

这两个运算符看起来很像(都是...),但作用完全不同:

运算符 位置 作用 类比
Rest 函数参数/解构左侧 收集剩余元素 打包
Spread 数组/对象字面量右侧 展开元素 拆包

Rest运算符(收集/打包):

// 收集函数剩余参数
function logTags(tag1, tag2, ...otherTags) {
    console.log(tag1, tag2);      // 'AI', 'NLP'
    console.log(otherTags);       // ['LLM', 'Prompt', 'Transformer']
}

// 解构中收集剩余数组元素
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second);  // 1, 2
console.log(rest);           // [3, 4, 5]

// 解构中收集剩余对象属性
const { name, ...others } = { name: '张三', age: 20, city: '北京' };
console.log(name);   // '张三'
console.log(others); // { age: 20, city: '北京' }

Spread运算符(展开/拆包):

// 数组合并
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];  // [1,2,3,4,5,6]

// 数组复制(浅拷贝)
const original = [1, 2, 3];
const copy = [...original];  // 新数组,互不影响

// 对象合并
const user = { name: '李四' };
const address = { city: '上海' };
const profile = { ...user, ...address, age: 25 };
// { name: '李四', city: '上海', age: 25 }

// 函数调用时展开
const numbers = [10, 20, 30];
Math.max(...numbers);  // 等价于 Math.max(10,20,30)

实际应用场景:

// 日志记录函数(支持任意参数)
function log(...args) {
    console.log('[LOG]', ...args);
}
log('用户登录', 'user123', '2024-01-01');

// 不可变数据更新(React风格)
const oldState = { count: 10, user: 'tom' };
const newState = { ...oldState, count: 11 }; // 只修改count

3. 解构赋值:优雅地提取数据

// 对象解构:按属性名提取
const user = { name: '詹姆斯', age: 20, city: '洛杉矶' };
const { name, city } = user;
console.log(name, city); // 詹姆斯 洛杉矶

// 数组解构:按位置提取
const rgb = [255, 128, 0];
const [red, green, blue] = rgb;
console.log(red); // 255

// 交换变量(无需临时变量)
let a = 1, b = 2;
[a, b] = [b, a];
// a=2, b=1

// 嵌套解构
const response = {
    data: { user: { id: 1, profile: { nickname: '小王' } } }
};
const { data: { user: { profile: { nickname } } } } = response;
console.log(nickname); // '小王'

四、NLP任务实战:从零到一构建推理系统

任务一:情感分类(Sentiment Analysis)

业务场景:电商评论分析、舆情监控、客服工单分类

const lamp_review_zh = `我需要一盏漂亮的卧室灯,这款灯具有额外的储物功能,价格也不算太高。
我很快就收到了它。在运输过程中,我们的灯绳断了,但是公司很乐意寄送了一个新的。
在我看来,Lumina 是一家非常关心顾客和产品的优秀公司!`;

// 基础版本:判断情感
const prompt = `
以下用三个反引号分隔的产品评论的情感是什么?
用一个单词回答:正面 或 负面
评论文本: \`\`\` ${lamp_review_zh} \`\`\`
`;
// 输出:正面

Few-shot学习(小样本学习):给模型提供示例,提高准确率

const prompt = `
判断评论的情感(正面/负面/中性)。

示例1: 
评论:"这个产品太差劲了,根本不值这个价格"
情感:负面

示例2:
评论:"物流很快,包装完好,商品符合预期"
情感:正面

现在请判断:
评论:"${lamp_review_zh}"
情感:
`;

任务二:信息提取(Information Extraction)

业务场景:从非结构化文本中提取结构化数据

const prompt = `
从评论文本中识别以下项目:
- 情绪(正面或负面)
- 是否表达了愤怒?(是或否)
- 评论者购买的商品
- 制造该物品的公司

评论用三个反引号分隔。
将您的响应格式化为JSON对象,以 "sentiment"、"anger"、"product"、"brand"为键。
评论文本: \`\`\` ${lamp_review_zh} \`\`\`
`;

// 输出:
{
  "sentiment": "正面",
  "anger": false,
  "product": "卧室灯",
  "brand": "Lumina"
}

关键技巧

  • 明确指定输出格式(JSON)

  • 要求使用布尔值而非"是/否"

  • 处理缺失信息(使用"未知")

任务三:主题推断(Topic Inference)

单文本主题识别

const story_zh = `在政府最近进行的一项调查中,要求公共部门的员工对他们所在部门的满意度进行评分。
调查结果显示,NASA 是最受欢迎的部门,满意度为95%。
一位NASA员工John Smith对这一发现发表了评论...`;

const prompt = `
确定以下给定文本中讨论的五个主题。
每个主题用1-2个单词概括,用逗号分隔。
给定文本:${story_zh}
`;
// 输出:政府调查, NASA满意度, 员工评论, 管理层回应, 最低满意度

多主题分类(判断主题列表中的哪些出现在文本中):

const topicList = ['美国国家航空航天局', '地方政府', '工程', '员工满意度', '联邦政府'];

const prompt = `
判断主题列表中的每一项是否是给定文本中的一个话题,
以列表的形式给出答案,每个主题用0或1。
主题列表:${topicList.join(", ")}
给定文本:${story_zh}
`;
// 输出:[1, 0, 0, 1, 1]
// 表示:NASA✓、员工满意度✓、联邦政府✓

任务四:文本总结(Text Summarization)

业务场景:新闻摘要、会议纪要、邮件总结

const prod_review_zh = `这个熊猫公仔是我给女儿的生日礼物,她很喜欢,去哪都带着。
公仔很软,超级可爱,面部表情也很和善。但是相比于价钱来说,它有点小。
快递比预期提前了一天到货。`;

// 通用摘要(限制字数)
const prompt = `
对评论文本进行概括,最多30个词汇。
评论文本:\`\`\` ${prod_review_zh} \`\`\`
`;
// 输出:女儿喜欢的熊猫公仔柔软可爱,但价格偏贵尺寸较小,快递提前一天送达。

// 聚焦特定维度:运输体验
const prompt_freight = `
聚焦在产品运输上,最多30个词汇。
评论文本:\`\`\` ${prod_review_zh} \`\`\`
`;
// 输出:快递比预期提前一天到货,包装完好,运送速度令人满意。

高级技巧:批量处理多篇评论

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);
}
// 输出:
// 熊猫公仔可爱但小
// 灯好,服务佳,补件快
// 电动牙刷续航好刷头小
// 价格波动大,质量下滑

五、Prompt工程的最佳实践

基于实战经验,总结出以下黄金法则:

技巧 示例 作用
使用分隔符 \``文本```` 清晰区分指令和内容
指定输出格式 用JSON格式输出 便于程序解析
Few-shot示例 提供2-3个例子 提高准确率
约束输出长度 最多30个词汇 控制token消耗
处理边界情况 不存在则使用"未知" 增强鲁棒性
拆分复杂任务 先分类→再提取 提高成功率

六、总结与展望

通过本文,我们实现了:

  1. ✅ 模块化架构:ES6模块让代码清晰可维护

  2. ✅ 4大NLP任务:情感分类、信息提取、主题推断、文本总结

  3. ✅ 企业级特性:环境变量管理、错误处理、批量处理

下一步可以探索

  • 链式Prompt(多步推理)

  • 对话式上下文管理

  • RAG(检索增强生成)

  • Agent工具调用

写在最后:Prompt工程不是简单地“问问题”,而是像编写代码一样设计指令。掌握这门“AI方言”,你的开发效率将提升一个数量级。

本文代码均已在本机测试通过,可以直接复制使用。只需在.env文件中配置好API密钥即可运行。


互动时间:你尝试过用Prompt解决哪些NLP问题?遇到了什么有趣的挑战?欢迎在评论区分享交流!

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐