Prompt 驱动 NLP:从 ES6 模块化到文本推理实战

Prompt 驱动 NLP:从 ES6 模块化到文本推理实战

从 Prompt Engineering 的基础技巧,到真正用代码搭建一个可运行的 NLP 系统,现在让我们往 AI 全栈的方向前进。之前文章中我提到过调优单条 Prompt 的写法,现在让我们直接落地到工程化:用 ES6 模块化组织代码,再围绕情感分析、信息提取、主题推断和文本总结这四个经典 NLP 任务,写出一套真正能跑起来的推理系统。

这篇文章记录了我学习的过程和代码实践,覆盖了 ES6 模块化、Prompt 做 NLP 任务的核心思路,以及多个可运行的代码示例。

从基础的代码素养说起:为什么需要模块化

一个问题:如果所有代码都堆在一个文件里,会发生什么?你想优化一下你们部门常用的一个工具函数,结果发现页面的渲染出现了问题、你们部门要对项目进行优化,n 个人一起修改同一个文件,Conflict 直接爆炸、你完成了一个小功能,接下来你要测试它,没办法,你只能启动整个项目,所以我们应当意识到------当项目里要维护鉴权、路由、多种模型调用时,单文件简直就是灾难。

理解模块化不是为了"装高级",而是为了维护性、可读性和复用性import from 引入、export default 导出,让代码随时可以像乐高积木一样拆出来复用。

项目的模块化搭建

接下来我会用一个小项目来进行接下来的内容,项目结构非常清晰,只有三个核心文件:

  • main.mjs:单点入口,负责业务逻辑(鉴权、路由都在这里)
  • client.mjs:只负责提供 LLM Client 对象
  • completions.mjs:封装调用逻辑,提供 getCompletions 等方法

client.mjs:只做一件事

javascript 复制代码
import 'dotenv/config';
import OpenAI from 'openai';
// 负责提供 LLM Client 对象
const client = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
    baseURL: process.env.OPENAI_BASE_URL,
    model: process.env.OPENAI_MODEL
});
// export const a = 2;  // 直接导出
// export const b = 3;
export default client; // 默认导出 一个文件只能有一个默认导出

这里注意一下:注释里留了 export const a = 2 这种直接导出的写法,和 export default client 形成对比。默认导出和直接导出的不同是------默认导出每个文件只能有一个 ,而直接导出可以有多个。在 main.mjs 里引入时,直接导出是一个对象 {key:value},所以可以用 key 来访问。

completions.mjs:封装调用逻辑

javascript 复制代码
import client from './client.mjs';

export async function getCompletions(prompt) {
    const response = await client.chat.completions.create({
        model: process.env.OPENAI_MODEL,
        messages: [
            {
                role: "user",
                content: prompt
            }
        ]
    });
    return response.choices[0].message.content;
}

export async function getImages(prompt) {
    return await client.images.create({
    });
}

这里 getImages 还只是一个空壳,后续可以扩展到文生图能力。

main.mjs:简洁的入口

javascript 复制代码
// 让我们的入口文件简洁
import {getCompletions} from './completions.mjs';

async function main() {
    // AI 全栈
    // 企业里 LLM 接入 NLP 能力
    // 情感推理与信息提取
    // ...
}

main();

理解了这套分层之后,我们可以发现入口文件确实可以变得很干净。因此我们要注意,AI 全栈开发不只是会调 API,更重要的是代码组织能力

ES6 语法特性:企业级开发的基石

在搭项目的过程中,我们顺带把 ES6 的几个核心语法过一下。ES6 是 JS 在 2015 年推出的重大更新,目标是让 JS 成为一个适合企业级项目开发的语言。

let 与 const:告别声明提升的坑

  • 解决了声明提升 Bug
  • 支持块级作用域
  • letconst 都不能重复声明
  • const 对于简单数据类型不能赋值,复杂数据类型的属性可以重新赋值,但是不能改变复杂数据类型指向的内存地址(类型)

解构赋值:性能与优雅兼得

我用了一个具体的例子来解释解构赋值的意义:

javascript 复制代码
// 解构赋值
// 如果只是需要变量值,是不需要解构的
// 需要一个额外的变量来进行接收的时候,才需要解构
let {name,age} = {"name":"詹姆斯","age":20};
console.log(name,age);
let obj = {"name":"詹姆斯","age":20};
console.log(obj.name);
console.log(obj.age);
// name  obj.name 两者的查找效率与性能有差异
// 如果要查找 obj.name 需要先查找 obj 对象,再查找 name 属性
// 如果要查找 name 变量,直接查找 name 变量即可

这里有一个小点:nameobj.name 的查找效率不一样 。如果要查找 obj.name,需要先查找 obj 对象,再查找 name 属性;而直接查找 name 变量就一步到位。这让我意识到,解构赋值不只是代码好看,性能也更好。而作为解释性语言本身性能就不好的 JS ,我们更应该注意一下类似的可以提升效率的地方。

还有数组解构和 ... reset(收集)运算符的用法:

javascript 复制代码
// 数组解构,按照顺序解构,reset(...) 操作符 将余下的收集起来
let [coach,...players] = ['詹姆斯','麦迪','姚明'];
console.log(coach,players);
let [hrCoach,...hrPlayers] = ['詹姆斯','麦迪','姚明'];
let allPlayers = [...players,...hrPlayers];
console.log(allPlayers);

... 在这里既可以做 rest(收集余下元素),也可以做 spread(展开数组)。这种灵活性在后续的代码拼接和参数传递中非常实用。

一句话总结:ES6 让 JS 从一个脚本语言变成了能扛大型项目的企业级开发语言。

NLP 任务:Prompt 即系统

理解了代码结构之后,接下来我们进入核心主题------用 Prompt 做 NLP 任务开发。现在和之前相比构建一个 NLP 系统已经十分简单了:

仅用几分钟,我们就可以构建多个用于对文本进行推理的系统,而以前需要熟练的机器学习人员数天到数周的时间。

这就是 Prompt Engineering 带来的平等化------不需要训练模型,不需要调参,只需要写好 Prompt,就能完成以下四类经典 NLP 任务:

  • 情感分类 Sentiment Analysis:正面/负面/中性,在电商等行业非常重要,可用于客户服务、预警、产品质检
  • 信息提取 Information Extraction:从文本中抽取出结构化的关键信息
  • 主题推断:识别文本讨论的核心主题
  • 文本总结 Summarization:对长文本进行总结,提取关键信息,减少工作量------老板、行政岗、小编都需要

接下来,我按照顺序逐渐介绍这些任务。

实战一:情感分类与信息提取

我们用的测试文本是一段关于卧室灯的中文评论:

javascript 复制代码
const lamp_review_zh = '我需要一盏漂亮的卧室灯,这款灯具有额外的储物功能,价格也不算太高。\
  我很快就收到了它。在运输过程中,我们的灯绳断了,但是公司很乐意寄送了一个新的。\
  几天后就收到了。这款灯很容易组装。我发现少了一个零件,于是联系了他们的客服,他们很快就给我寄来了缺失的零件!\
  在我看来,Lumina 是一家非常关心顾客和产品的优秀公司!'

1. 基础情感分类

最开始写的是一个直接的分类 Prompt:

javascript 复制代码
// 写一个 prompt 来分类这个评论的情感是正面还是负面
const prompt = `
以下用三个反引号分隔的产品评论的情感是什么?
评论文本\`\`\`${lamp_review_zh}\`\`\`
`;

2. Few-shot 引导模型输出固定格式

但模型可能会输出一大段解释,而我们只想要一个词。这时候可以用 Few-shot,通过示例来引导模型:

javascript 复制代码
// few shot 通过示例来引导模型分类情感
const prompt = `
以下用三个反引号分隔的产品评论的情感是什么?
用一个单词回答(正面/负面/中性)
评论文本\`\`\`${lamp_review_zh}\`\`\`
`;

3. 多情感项识别

如果不止要一个标签,而是想要更细粒度的情感列表:

javascript 复制代码
const prompt = `
识别以下用三个反引号分隔的产品评论的作者表达的情感
包含不超过五个项目。
将答案格式化为以逗号分隔的单词列表
评论文本\`\`\`${lamp_review_zh}\`\`\`
`;

4. 愤怒检测

在客服预警场景中,判断用户是否表达愤怒非常重要:

javascript 复制代码
const prompt = `
识别以下用三个反引号分隔的产品评论是否表达了愤怒
给出是或否的答案
评论文本\`\`\`${lamp_review_zh}\`\`\`
`;

5. 信息提取:商品与品牌

从非结构化评论中提取结构化信息,直接输出 JSON:

javascript 复制代码
const prompt = `
从评论文本中识别以下项目
- 评论者购买的商品
- 制造该商品的公司
评论文本用三个反引号分隔。将你的响应格式以"物品(product)"和"品牌(brand)"为键的 JSON 对象。
如果信息不存在,请使用"未知"作为值。
评论文本\`\`\`${lamp_review_zh}\`\`\`
`;

6. 多任务合并:一次提取所有字段

最让我惊讶的是,可以把多个提取任务合并成一条 Prompt:

javascript 复制代码
const prompt = `
从评论文本中识别以下项目
- 情绪(正面/负面)
- 是否表达了愤怒(是/否)
- 评论者购买的商品
- 制造该商品的公司
评论文本用三个反引号分隔。
将您的响应格式化为 JSON 对象,以"情感(sentiment)"、"愤怒(anger)"、"物品(product)"、"品牌(brand)"为键。
如果信息不存在,请使用"未知"作为值。
让你的回应尽可能简短。
将 anger 值格式化为布尔值
评论文本\`\`\`${lamp_review_zh}\`\`\`
`;

这里有两个技巧:一是用 JSON 格式约束模型输出,方便后续代码解析;二是在 Prompt 里显式声明数据类型(比如 anger 要布尔值),能显著提升输出稳定性。

实战二:主题推断

测试文本是一段关于 NASA 员工满意度调查的新闻:

javascript 复制代码
const story_zh = `
在政府最近进行的一项调查中,要求公共部门的员工对他们所在部门的满意度进行评分。
调查结果显示,NASA 是最受欢迎的部门,满意度为 95%。

一位 NASA 员工 John Smith 对这一发现发表了评论,他表示:
"我对 NASA 排名第一并不感到惊讶。这是一个与了不起的人们和令人难以置信的机会共事的好地方。我为成为这样一个创新组织的一员感到自豪。"

NASA 的管理团队也对这一结果表示欢迎,主管 Tom Johnson 表示:
"我们很高兴听到我们的员工对 NASA 的工作感到满意。
我们拥有一支才华横溢、忠诚敬业的团队,他们为实现我们的目标不懈努力,看到他们的辛勤工作得到回报是太棒了。"

调查还显示,社会保障管理局的满意度最低,只有 45%的员工表示他们对工作满意。
政府承诺解决调查中员工提出的问题,并努力提高所有部门的工作满意度。
`;

主题推断的 Prompt 设计思路很清晰------要求模型给出五个主题,每个主题用 1-2 个单词概括,输出时用逗号分隔:

javascript 复制代码
const prompt = `
确定一下给定文本中讨论的五个主题
每个主题用1-2个单词概括.
输出时用逗号分隔.
给定文本\`\`\`${story_zh}\`\`\`
`;

这种输出格式非常适合直接入库或者做标签系统。

除此之外,这里还留了一个更细粒度的主题判断版本(在 main2.mjs 的注释中):给定一个主题列表,让模型判断每个主题是否在文本中出现,输出 0 或 1:

javascript 复制代码
// const topicList = [
//     '美国国家航空航天局',
//     '地方政府',
//     '工程',
//     '员工满意度',
//     '联邦政府'
// ]

// const prompt = `
// 判断主题列表中的每一项是否是给定文本中的一个话题,
// 以列表的形式给出答案,每个主题用 0 或 1 。
// 主题列表:\`\`\`${topicList.join(',')}}\`\`\`
// 给定文本:\`\`\`${story_zh}\`\`\`
// `;

这个版本就是为了提出:同样的推断任务,输出格式可以根据业务需求灵活调整。 要标签就用 0/1,要概览就用关键词列表。

实战三:文本总结与批量处理

如果说前面的任务是在理解文本"里面有什么",那么文本总结就是在回答"这段话说了什么"。

单条评论的多种总结策略

这是一段关于熊猫公仔的中文评论:

javascript 复制代码
const prod_review_zh = `
这个熊猫公仔是我给女儿的生日礼物,她很喜欢,去哪都带着。
公仔很软,超级可爱,面部表情也很和善。但是相比于价钱来说,
它有点小,我感觉在别的地方用同样的价钱能买到更大的。
快递比预期提前了一天到货,所以在送给女儿之前,我自己玩了会。
`

我来展示三种不同的总结策略,每种聚焦点不同,输出就会不同。

第一种是通用摘要

javascript 复制代码
// const prompt = `
// 您的任务是从电子商务网站上生成一个产品评论的简短摘要。
// 请对三个反引号之间的文本进行概括,最多30个词汇。
// 评论文本:\`\`\`${prod_review_zh}\`\`\`
// `;

第二种是聚焦运输

javascript 复制代码
// const prompt = `
// 您的任务是从电子商务网站上生成一个产品评论的简短摘要。
// 请对三个反引号之间的文本进行概括,最多30个词汇。
// 并且聚焦在产品运输上。
// 评论文本:\`\`\`${prod_review_zh}\`\`\`
// `;

第三种是聚焦价格和质量

javascript 复制代码
const prompt = `
您的任务是从电子商务网站上生成一个产品评论的简短摘要。
请对三个反引号之间的文本进行概括,最多30个词汇。
并且聚焦在产品价格和质量上。
评论文本:\`\`\`${prod_review_zh}\`\`\`
`;

关键点:总结不是越少越好,而是越"对症"越好。同样的评论,物流部门关心快递,产品部门关心质量,运营部门关心整体口碑------Prompt 里的聚焦指令,直接决定了总结的服务对象。

批量处理多条评论

最后展示一下如何用 for...of 循环批量处理多条评论:

javascript 复制代码
const review_2 = `
我想为我的卧室找一个漂亮的灯,这款灯还有额外的存储空间,价格也不太高。\
购买后很快就收到了,两天就送到了。但在运输过程中,灯的拉链断了,公司态度\
很好,发来了一条新的。新的拉链也在几天内就到了。这个灯非常容易装配。后来,我\
发现缺少一个部分,所以我联系了他们的客户支持,他们很快就给我寄来了缺失的部件\
!我觉得这是一家非常关心他们的客户和产品的好公司。
`

// review for an electric toothbrush
const review_3 = `
我的牙科卫生师推荐我使用电动牙刷,这就是我购买这款牙刷的原因。目前为止,我发现电池的\
续航时间颇为令人印象深刻。在初次充电并在第一周保持充电器插头插入以调节电池状态之后,我\
已经将充电器拔掉,并在过去的3周里,每天两次刷牙都使用同一次充电。然而,这款牙刷的刷头实\
在太小了。我见过的婴儿牙刷都比这个大。我希望牙刷头能做得更大一些,搭配不同长度的刷毛更好\
地清洁牙齿间缝,因为现有的无法做到这一点。总的来说,如果你能以大约50美元的价格购入这款电动\
牙刷,那它就物超所值。厂家配套的替换刷头价格相当昂贵,但你可以买到价格更为合理的通用款。\
使用这款牙刷让我感觉像每天都去看了牙医一样,我的牙齿感觉洁净如新!
`

// review for a blender
const review_4 = `
他们还在11月把17件套系统以大约$49的优惠价格销售,几乎是五折。但不明原因(轻易就可以归咎于价格欺诈)\
在到了12月第二周,同一套系统的价格一下儿飙升到了$70-$89之间。11件套系统的价格也从之前的优惠价$29上\
升了大概$10。看上去还算公道,但如果你仔细观察底部,会发现刀片锁定的部位相比几年前的版本要略逊一筹,所\
以我打算非常小心翼翼地使用(例如,我会将像豆子、冰块、大米之类的硬质食材先用搅拌机压碎,然后调到我需要\
的份量,再用打发刀片研磨成更细的粉状,制作冰沙时我首选交叉刀片,如果需要更细腻些或者少些浆糊状,我会换成\
平刀)。在制作果昔时,把将要用的水果和蔬菜切片冷冻是个小技巧(如果你打算用菠菜,要先稍微焖炖软,再冷冻,\
制作雪葩时,用一个小到中号的食品加工器就行)这样就不用或者很少加冰块到你的果昔了。大约一年后,电机开始发出\
一些可疑的声音。我联系了客服,但保修期已经过期,所以我只好另购一台。友情提示:这类产品的整体质量都在下滑,\
所以他们更多的是利用品牌知名度和消费者的忠诚度来保持销售。我在两天之后就收到了它。
`
javascript 复制代码
const reviews = [prod_review_zh, review_2, review_3, review_4];
for(let review of reviews){
    const prompt = `
    你的任务是从电子商务网站上的产品评论中获取相关信息。
    请对三个反引号之间的文本进行概括,最多20个词汇。
    评论文本:\`\`\`${review}\`\`\`
    `
    const response = await getCompletions(prompt);
    console.log(response , '\n');
}

这四条评论各有特点:熊猫公仔是标准短评,灯具评论涉及售后,电动牙刷是长文的优缺点分析,搅拌机则是一篇接近小作文的深度体验。用同一套 Prompt 批量处理时,我发现评论越长,20个词的压缩率就越高,信息损失也越明显。这让我意识到,实际业务中可能需要根据文本长度动态调整摘要词数限制。

我现在怎么理解它

实践之后,我对 AI 全栈开发有了更具体的感知。以前我觉得调 OpenAI API 就是写个 fetch,但现在我发现,真正的工程化是从代码结构开始的client.mjs 隔离配置,completions.mjs 封装调用,main.mjs 专注业务------这种分层思维是从小项目走向企业级开发的必经之路。

在 NLP 任务层面,我最大的收获是:Prompt 本身就是一种"轻量级模型训练"。不需要标注几千条数据,不需要微调参数,只需要在 Prompt 里把任务描述清楚、输出格式约束好,就能完成情感分类、信息提取、主题推断和文本总结。这背后的本质,是 LLM 已经把语言理解的能力预训练好了,我们只是在调用它的推理能力。

当然,我也清楚这种方式的边界------如果领域非常垂直、准确率要求极高,微调或者 RAG 仍然是必要的。但在大多数通用文本处理场景中,Prompt 驱动已经足够解决问题。

学习 Prompt 做 NLP,不只是学习怎么写 Prompt,更重要的是学习如何把 LLM 的推理能力嵌入到工程流程中,让它真正为业务服务。

相关推荐
AI_yangxi1 小时前
短视频矩阵系统供应商
大数据·人工智能·矩阵
harykali1 小时前
Datawhale Hello-ROCm学习:初探Gemma4 #AMDev #Datawhale
人工智能·llm
行者-全栈开发1 小时前
CVE-2026-33017:Langflow AI工作流平台未授权RCE漏洞深度剖析与紧急修复指南
人工智能·rce·漏洞修复·ai安全·langflow·cvss 10.0·cve-2026-33017
happyprince1 小时前
05_verl-配置系统详解
人工智能·架构·强化学习
JieE2121 小时前
树与二叉树--JS实例
javascript·数据结构
txg6661 小时前
FuzzGPT:用大语言模型生成“极端边界程序”的深度学习框架 Fuzzing 新范式
人工智能·深度学习·安全·网络安全·语言模型
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 44 - 47)
开发语言·人工智能·经验分享·笔记·python
zhy295631 小时前
【DNN】基于llama.cpp的Qwen3-0.6B量化部署微调
人工智能·lora·dnn·llama·qwen3