用 ESM 模块化搭建 DeepSeek LLM 调用,顺带用 Prompt 实现轻量 NLP 任务

最刚开始上手对接 DeepSeek 接口的时候,我图赶速度,把 API 密钥、OpenAI 实例、对话请求逻辑、业务 Prompt 全部揉在一个 JS 文件里。跑通第一条返回时还美滋滋,觉得单文件写起来省事。结果第二天要换模型、调整 API 地址,整段代码翻来覆去改;想复制到另一个新项目,还要手动剪切拆分代码,维护起来一团糟。

意识到不对之后,我重新梳理分层,拆分出了下面这四个核心文件,结构清爽太多:

第一步:先填环境变量,踩了密钥硬编码的大坑

第一版我直接把sk-xxxx密钥写在代码变量里,写完才后背一凉:一旦推到 Git 仓库,API 密钥直接泄露,分分钟被刷额度。立刻新建.env文件隔离敏感配置。

env 复制代码
# .env 只存配置,绝不提交代码仓库
DEEPSEEK_API_KEY=sk-xxxxxx
DEEPSEEK_API_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_API_MODEL=deepseek-v4-flash

一定要把.env写入.gitignore,不然配置会跟着代码上传;我第一次忘记忽略,提交前 git diff 看到配置文件吓出一身汗。

第二步:封装 LLM 客户端 client.mjs

这里是全局唯一的 OpenAI 实例,负责对接 DeepSeek 兼容 OpenAI 格式的接口。这里栽了默认导出的语法坑:一开始手贱写了两个export default,直接报语法错误,翻 ESM 规范才记死一个模块只能有 1 个默认导出。

javascript 复制代码
// client.mjs
import { OpenAI } from 'openai';
import dotenv from 'dotenv';

// 重点!这行必须放在实例化前面,晚一步process.env拿不到值,坑我半小时
dotenv.config();

// 初始化DeepSeek兼容客户端
const client = new OpenAI({
    apiKey: process.env.DEEPSEEK_API_KEY,
    baseURL: process.env.DEEPSEEK_API_BASE_URL,
});

// 默认导出整个客户端实例,全局复用单例
export default client;

// 顺带测试命名导出(后面main里解构用)
export const testNumA = 2;
export const testNumB = 3;

第三步:拆分业务请求方法 completions.mjs

把对话生成、图像生成(预留)的请求逻辑抽离,不和入口耦合,后续加流式输出、重试逻辑只改这一个文件就行。用命名导出,可以灵活按需引入多个函数。

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

// 文本对话完成核心方法
export async function getCompletion(prompt) {
    const response = await client.chat.completions.create({
        model: process.env.DEEPSEEK_API_MODEL,
        messages: [{ role: 'user', content: prompt }]
    });
    return response.choices[0].message.content;
}

// 预留画图接口,后续扩展DeepSeek绘图模型直接补逻辑
export async function getImage(prompt) {
    
}

第四步:程序唯一入口 main.mjs

单点入口调度所有逻辑,所有 NLP 业务、循环批量推理、测试 demo 全部写在这里。这里刚好能用上 ES6 解构、展开、rest 这些语法,顺便巩固企业级 JS 写法。先贴简化骨架,再放完整 NLP 业务代码:

javascript 复制代码
// main.mjs 全局唯一启动入口
import { getCompletion } from "./completions.mjs";

async function main(){
    // 所有业务代码、批量推理、Prompt调试全写在这
}

// 必须调用执行
main();

完整的业务代码里我测了五类 Prompt 驱动 NLP 能力:情感分类、信息抽取、文本摘要、主题匹配,对比传统机器学习不用标注训练,写提示词就能跑通基础 NLP 任务。

javascript 复制代码
import { getCompletion } from "./completions.mjs";

async function main(){
    // 测试1:电商评论情感+多维度信息提取
    const lamp_review_zh = '我需要一盏漂亮的卧室灯,这款灯具有额外的储物功能,价格也不算太高。\
    我很快就收到了它。在运输过程中,我们的灯绳断了,但是公司很乐意寄送了一个新的。\
    几天后就收到了。这款灯很容易组装。我发现少了一个零件,于是联系了他们的客服,他们很快就给我寄来了缺失的零件!\
    在我看来,Lumina 是一家非常关心顾客和产品的优秀公司!';

    // 精准结构化输出Prompt,直接返回JSON方便程序解析
    const extractPrompt = `
    从评论文本中识别以下项目:
    - 情绪(正面或负面)
    - 是否表达了愤怒?(布尔值true/false)
    - 评论商品
    - 品牌名称
    文本用三个反引号包裹,严格输出JSON对象,键名:sentiment、angry、product、company,无信息填"未知"
    评论文本:```${lamp_review_zh}```
    `;
    const extractRes = await getCompletion(extractPrompt);
    console.log('多维度信息提取结果:\n', extractRes, '\n');

    // 测试2:批量多商品评论摘要
    const prod_review_zh = `这个熊猫公仔是我给女儿的生日礼物,她很喜欢,去哪都带着。公仔很软,超级可爱,面部表情也很和善。但是相比于价钱来说,它有点小,同价位能买更大的,快递提前一天送达`;
    const review_3 = `电动牙刷牙医推荐,电池续航强,但刷头尺寸太小,50美元价位性价比高,替换原装刷头贵,通用平替划算`;
    const reviewList = [prod_review_zh, review_3];
    
    console.log('批量评论摘要:');
    for(let review of reviewList){
        const sumPrompt = `总结下面评论,不超过30个字:```${review}````;
        const sumRes = await getCompletion(sumPrompt);
        console.log('-', sumRes);
    }

    // 测试3:文本主题匹配判断
    const topicList = ['美国国家航空航天局', '地方政府', '员工满意度'];
    const newsText = `NASA员工满意度95%排名公共部门第一,社保管理局满意度仅45%`;
    const topicPrompt = `给主题列表,判断每个是否属于文本话题,按顺序输出0/1逗号分隔。主题:${topicList.join(',')} 文本:${newsText}`;
    const topicRes = await getCompletion(topicPrompt);
    console.log('\n主题匹配0/1结果:', topicRes);
}

main();

顺带梳理这套架构用到的 ES6 企业级语法

之前写小脚本习惯用 var,拆模块化之后才发现 ES6 是支撑大型 JS 项目的根基,几个高频用法我整理了实操对比:

  1. 变量声明 let/const拥有块级作用域,不存在 var 的变量提升污染;const 引用地址锁死,对象 / 数组内部值可以修改,基础类型完全不可改。
  2. 解构赋值(性能比逐行取值更好)
javascript 复制代码
// 对象解构
const info = {name:"张三",age:18};
const {name,age} = info;

// 数组解构 + rest剩余运算符
const [coach, ...team] = ['范甘迪','姚明','麦迪'];
// 展开运算符合并数组
const allTeam = [...team, ['科比','加索尔']];
  1. ESM 模块化核心规则
  • export default:一个文件仅 1 个,默认导出,导入时可自定义名称
  • export xxx:命名导出,可多个,导入必须花括号匹配名称
  • 文件后缀.mjs代表强制 ESM 模式;如果不想写后缀,在package.json"type":"module"

跑通整套项目的前置依赖

初始化安装两行命令,缺一不可:

bash 复制代码
# 安装LLM兼容客户端 + 环境变量读取工具
npm install openai dotenv

如果 Node 版本低于 14,顶层不能直接写 await,必须全部包裹在 async function 里调用,我本地 v16 刚好兼容无压力。

踩过的几个致命小坑复盘

  1. ESM 与 CommonJS 冲突 :一开始没加"type":"module",node 默认走 require 规范,识别import直接抛错,要么改 package 配置,要么全部文件后缀改为.mjs
  2. dotenv 执行顺序颠倒 :把new OpenAI写在dotenv.config()前面,process.env全是 undefined,客户端初始化失败;
  3. 默认导出重复 :同一个文件写两次export default,JS 直接语法终止;
  4. 异步函数忘记调用 :写了 async main 函数,最后没执行main(),程序一动不动,无报错无输出。

最后梳理三个核心收获

  1. 分层模块化的本质就是单一职责:配置、客户端实例、请求方法、业务入口完全隔离,改一处不牵连全局,复用性拉满;
  2. 兼容 OpenAI 协议的大模型(DeepSeek、通义千问兼容版等),客户端封装逻辑可以 100% 平移,只换环境变量里的 key 和 baseURL;
  3. Prompt 能搞定简单 NLP 任务(分类、抽取、摘要、匹配),零训练成本快速落地业务;但高并发、万级精准标注场景还是得微调模型。

这套方案适合快速验证 LLM 业务原型,不用搭复杂后端框架就能跑通 NLP 推理。搞懂之后你可以试着加流式输出、请求超时重试,有调整思路可以留条消息聊聊你的改动方案。

相关推荐
jrjrgood1 小时前
现货黄金和黄金期货的区别有哪些?如何投资?
大数据·人工智能·区块链
属于自己的天空1 小时前
确认弹窗太多?一次配好 Claude Code 权限,安心让 AI 干活
人工智能
dearxue1 小时前
这一次,我们一起把AI的复杂一口吃掉
人工智能·后端
行者-全栈开发1 小时前
深度解析 WWDC 2026:苹果 AI 全栈技术架构与落地实现路径
人工智能·架构·wwdc
企业老板ai培训1 小时前
2026中小企业AI应用落地白皮书:从AI短视频矩阵到数字人获客的破局增长趋势
人工智能·矩阵·音视频
SEO_juper2 小时前
博客文章黄金结构:开头 1 句痛点 + 3 小标题 + 对比 + 总结 + 下载
人工智能·博客·外贸·geo·独立站·跨境电商独立站·文章结构
双翌视觉2 小时前
工业AI视觉检测中的“小样本困境”
人工智能·计算机视觉·视觉检测
CoderIsArt2 小时前
声纹识别与音频AI领域
人工智能·音视频
tedcloud1232 小时前
HyperFrames部署教程:用HTML生成MP4视频
前端·数据库·人工智能·html·音视频