昨天心血来潮想跑通 DeepSeek 的 API demo,本以为复制几行代码就能跑起来,结果从模块化报错、密钥读不到,到异步输出顺序乱套,连踩三个坑,折腾了快一小时才理顺。今天趁着热乎劲,把整个爬坑过程和吃透的知识点全记下来,纯干货无废话。
先被 ESM 模块化坑麻了
最开始我随手新建了 index.js,直接抄了官方的 import 语法引入 openai 库,信心满满敲下 node index.js,结果控制台直接甩给我一片红:
当时控制台就报不能在模块外使用 import 语法,我当场懵了。
我第一反应:代码抄错了?反复核对了好几遍,语法一点问题没有。后来翻了 Node 文档才明白,Node 默认用的是老的 CommonJS 模块化(require 语法) ,而现在的 AI SDK(比如 openai)全是基于 ES6 推出的 ESM 模块化写的,俩规范不兼容!
解决办法超简单,二选一:
- 文件名改成
.mjs后缀,直接标识这是 ESM 模块 - 在
package.json里加一句"type": "module"
我嫌改配置麻烦,直接把文件重命名为 index.mjs,一跑,报错直接消失,就这么简单。
说实话,以前总觉得模块化是个抽象概念,这次踩坑才真切感受到:ESM 就是现代 JS 的文件引入规范,是前端 / 后端一体化的标配,老的 CommonJS 慢慢要被淘汰了。
密钥明文写代码?差点酿成大祸
模块化问题解决后,我图省事,直接把 API 密钥和接口地址写死在代码里:
// 错误示范!纯纯找死行为
const client = new OpenAI({
apiKey: "sk-5d846ef4c7de4fa089010bfd7db13852",
baseURL: "https://api.deepseek.com/v1",
})
代码跑通的瞬间我就慌了:这要是顺手提交到 Git,密钥直接泄露,账号分分钟被盗刷!
这时候才想起 AIGC 开发的铁律:密钥绝对不能明文写在代码里,必须用环境变量管理。
于是我安装了 dotenv 库,专门读取 .env 文件里的环境变量,这里必须提一下 process 对象:我理解的 process 就是 Node 进程的专属管家,我们用 node index.mjs 启动程序,本质是开启了一个 Node 进程,内存、CPU、环境变量全归它管,process.env 就是存环境变量的储物柜。
dotenv 的作用就是:把 .env 文件里的键值对,一键塞进 process.env 这个储物柜里。
正确姿势(直接复制可用)
- 新建
.env文件,严格按KEY=VALUE格式写(别加空格!)
DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxx
DEEPSEEK_API_BASE_URL=https://api.deepseek.com/v1
- 安装依赖,编写代码
// index.mjs
import dotenv from 'dotenv'
import { OpenAI } from 'openai'
// 注意这一行!坑了我10分钟,忘了写就读不到.env变量
dotenv.config()
// 从进程的环境变量里取密钥,安全!
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_API_BASE_URL,
})
- 最后一步!新建
.gitignore文件,把.env加进去,彻底杜绝密钥上传:
.env
node_modules/
小细节:.env 里的 key 建议全大写,这是行业约定,看着也清晰。
异步代码顺序乱套?async/await 救场
解决完密钥问题,我兴高采烈写了调用逻辑,结果输出顺序直接离谱:
程序开始运行
程序结束
你好!有什么可以帮你的?
先输出结束,再出 AI 回复?完全不符合预期。
原因很简单:API 请求是异步任务,JS 不会等它执行完,会先跑后面的同步代码。以前用回调函数嵌套能解决,但代码丑到爆炸,直到 ES8 出了 async/await。
这玩意我愿称之为异步代码的救世主,说白了就是:用同步的写法,写异步的逻辑,加个 await 就能卡住流程,等 API 返回结果再继续执行。
最终可运行的完整 demo
import dotenv from 'dotenv'
import { OpenAI } from 'openai'
dotenv.config()
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_API_BASE_URL,
})
// async 标记这是异步函数,内部可以用await
const main = async ()=> {
console.log('程序开始运行')
// await 等待AI接口返回结果,再执行下一行
const response = await client.chat.completions.create({
model: 'deepseek-chat',
messages: [{ role: 'user', content: '你好' }]
})
// 输出AI的回复内容
console.log(response.choices[0].message.content)
console.log('程序结束')
}
// 执行入口函数
main()
运行后输出顺序完美:
程序开始运行
你好!有什么可以帮助你的吗?
程序结束
顺手优化了开发效率,顺便聊聊 pnpm 和 nodemon
跑通代码后,我又顺手优化了下开发流程,两个神器必须安利:
- pnpm之前一直用 npm,安装依赖慢还占硬盘,pnpm 是全局缓存依赖,不同项目软链接共用,省空间还快。全局安装:
npm install -g pnpm以后装依赖直接用pnpm i,香到离谱。 - nodemon改一次代码就要重启一次 node,太麻烦。全局安装
npm install -g nodemon,启动命令改成nodemon index.mjs,文件修改自动重启进程,开发效率直接拉满。
扒了下源码,理清核心设计
其实吃透这些知识点后,我特意翻了 openai 库的源码和 Node 规范,才算彻底通透:
- AI 相关的 SDK 全是 ESM 规范,所以 Node 项目必须用模块化,这是硬性要求;
process是 Node 独有的全局对象,浏览器里根本没有,这也是AIGC 项目本质是后端 Node 项目的原因;async/await不是黑魔法,就是 Promise 的语法糖,只是让异步代码可读性提升了 10 倍。
把我踩的坑,摆成错误 vs 正确对照表
表格
| 错误姿势 | 后果 | 正确姿势 |
|---|---|---|
| .js 后缀 + import 语法 | 直接报错,无法运行 | 改.mjs 或 package.json 加 type:module |
| 明文写 API 密钥 | 密钥泄露,账号被盗 | .env + dotenv + .gitignore |
| 不用 await 调用 AI 接口 | 代码执行顺序混乱 | async 函数 + await 等待结果 |
聊到这也差不多了,其实这次折腾下来,核心就记住三件事:第一,做 AIGC/Agent 开发,全是 Node 后端项目,ESM 模块化是标配;第二,密钥安全是底线,环境变量 + 忽略文件是必须操作;第三,调用 AI 接口必用 async/await,异步逻辑别硬写回调。
这些工具和规范也不是万能的,比如写个超小的测试脚本,不用 pnpm、不用 nodemon 也完全没问题,适合自己的场景就好。
搞懂这些踩坑点的朋友,不妨留言说说你在 AI 开发中还踩过什么奇葩坑,咱们互相避坑~