从零开始搭一个 AI 项目:环境配置、API 调用与异步编程
摘要:这篇文章记录了我从零搭建一个 Node.js AI 项目的完整过程:初始化项目、安装依赖、配置环境变量、调用大模型 API,并理解了 ES6 模块化、async/await、进程对象等概念。最终成功让程序回复了我的“hello”。
📑 目录
- 第一步:初始化项目
- 第二步:安装依赖
- 第三步:配置环境变量与 .gitignore
- 第四步:编写代码——模块化与 API 调用
- 第五步:理解 async/await 与执行顺序
- 第六步:让开发更顺滑——nodemon 热更新
- 一点总结:AIGC 工程化流程
- 互动讨论
第一步:初始化项目
想在后端调用大模型 API,首先得有一个 Node.js 项目。在空文件夹里打开终端,执行:
bash
npm init -y
-y 表示所有选项都用默认值,快速生成 package.json。这个文件记录了项目名称、版本、依赖等信息。
小知识:AI / Agent 项目几乎都是后端项目,因为 API Key 不能暴露在前端。
第二步:安装依赖
接下来安装两个核心库:
bash
pnpm i openai dotenv
- openai:OpenAI 官方 SDK,已经成为调用大模型 API 的事实标准。DeepSeek 等厂商兼容 OpenAI 的接口格式,所以可以用同一个库。
- dotenv:用来读取
.env文件中的环境变量,比如 API Key。
为什么用 pnpm?
传统的 npm 每个项目都会把依赖包复制一份,占用大量空间。pnpm 使用硬链接,依赖只安装一次,不同项目可以共享,大大节省磁盘空间。可以通过npm install -g pnpm全局安装 pnpm。
安装完成后,package.json 的 dependencies 里会多出这两项。注意:依赖不需要上传到 Git 仓库,所以项目里应该有一个 .gitignore 文件,把 node_modules 和 .env 都忽略掉。
第三步:配置环境变量与 .gitignore
在项目根目录创建 .env 文件,内容格式如下:
text
DEEPSEEK_API_KEY=你的真实密钥
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
.env是环境变量的配置文件,绝对不能上传到远程仓库。- 环境变量的 key 通常使用大写字母。
dotenv库默认读取根目录下的.env文件,并把变量注入到process.env对象中。
process 是 Node.js 的全局对象,代表当前运行的进程。只要程序还在运行,就能通过 process.env 拿到环境变量。这就像前端有 document 对象一样,后端有 process。
同时创建 .gitignore,写入:
text
node_modules/
.env
这样 Git 就不会追踪这些敏感或庞大的文件。
第四步:编写代码——模块化与 API 调用
我创建了一个 index.mjs 文件。为什么是 .mjs 后缀?因为我想使用 ES6 的 import 语法导入模块。
传统的 Node.js 默认使用 CommonJS(require)。要使用 import,有两种方法:
- 文件后缀改为
.mjs - 或者在
package.json中设置"type": "module"
我在 package.json 里加了 "type": "module",所以直接用 .js 也可以。但为了清晰,还是用了 .mjs。
代码如下:
javascript
import dotenv from 'dotenv'
import { OpenAI } from 'openai'
dotenv.config()
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_BASE_URL,
});
const main = async () => {
console.log('程序开始运行');
const result = await client.chat.completions.create({
model: 'deepseek-chat',
messages: [{ role: 'user', content: 'hello' }]
});
console.log(result.choices[0].message.content);
console.log('程序结束');
}
main();
逐段解释:
dotenv.config():读取.env并注入process.env。new OpenAI({...}):实例化客户端,传入 API Key 和 base URL。main是一个入口函数。Node.js 不像 Python 那样可以直接写顶层await(除非在.mjs中使用顶层await),所以通常包装一个 async 函数并调用。await client.chat.completions.create(...):发起请求,等待响应返回。
运行 node index.mjs,终端会输出大模型的回复。我收到了类似“Hello! How can I help you today?”的消息。
第五步:理解 async/await 与执行顺序
在代码里,await 让程序等待 API 响应回来,才继续执行后面的 console.log('程序结束')。如果没有 await,程序会立刻往下走,打印“程序结束”,等响应回来后再打印结果——顺序就乱了。
为什么需要 async/await?
JavaScript 代码的编写顺序和执行顺序有时不一样:
- 变量提升(hoisting)会导致声明提前。
- 异步任务(如
setTimeout、网络请求)会放到任务队列里,等待同步代码执行完才执行。
async/await 是 ES8 引入的语法,目的是阻塞代码执行流程,让异步代码看起来像同步代码,增强可读性。一个参考标准:执行时间超过 100ms 的操作通常就是异步的。
setTimeout是异步的,API 请求也是异步的。而console.log是同步的。
第六步:让开发更顺滑——nodemon 热更新
每次修改代码都要手动重新运行 node index.mjs,很麻烦。安装 nodemon 可以监听文件变化,自动重启进程:
bash
npm install -g nodemon
nodemon index.mjs
之后每次保存文件,nodemon 都会重新运行,省去了手动操作的步骤。
一点总结:AIGC 工程化流程
回顾整个搭建过程,我总结出一套标准的 AI 项目工程化流程:
- 初始化项目:
npm init -y - 安装依赖:
pnpm i openai dotenv - 配置环境变量:创建
.env和.gitignore - 编写入口文件(如
index.mjs),使用import引入模块 - 实例化 client:传入
process.env中的 Key 和 BaseURL - 编写 main 入口函数,使用
async/await控制异步流程 - 调用 chat completion API,处理返回结果
这一步一步走下来,我从一个只会前端 fetch 的小白,变成了能自己写后端 AI 调用脚本的初学者。虽然简单,但打通了环境配置→模块化→API 调用→异步控制的完整链路。
互动讨论
- 为什么 API Key 不能写死在代码里,而要用
.env文件? 如果不小心提交到 GitHub 会有什么后果? .mjs和.js后缀在使用import时有什么区别? 你更习惯哪种方式?process对象除了env,还有哪些常用属性或方法? (比如process.cwd()、process.argv)async/await和.then()相比,各有什么优缺点? 你在什么场景下会选择Promise链?- 如果调用 API 时网络出错,上面的代码会直接崩溃。如何优雅地处理异常?
📌 一点心得:从零搭建项目往往比直接复制代码更能暴露知识的盲区。遇到
.mjs报错、dotenv读取不到变量、await失效——这些坑踩一遍,理解就深一层。