你熟悉的 fetch,加上一点新玩法
好消息:调 AI 和调 REST API 没区别
所有主流大模型都提供 HTTP API,并且几乎都兼容OpenAI 的协议(这是事实标准)。所以你学会调 OpenAI,就等于同时学会了调 DeepSeek、通义千问、Kimi、vLLM 自部署服务……
// 这就是全部秘密——对,就一个 POST 请求
await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [{ role: 'user', content: '你好' }],
}),
})初始化项目
mkdir ai-hello && cd ai-hello
npm init -y
npm pkg set type=module
npm i openai dotenv
echo "DEEPSEEK_API_KEY=sk-xxxxx" > .env
这里我们用 openai 官方 SDK,但指向 DeepSeek 的端点——因为它兼容 OpenAI 协议,成本只有 OpenAI 的 1/50。
Hello, LLM
// index.js
import OpenAI from 'openai'
import 'dotenv/config'
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: 'https://api.deepseek.com/v1',
})
const res = await client.chat.completions.create({
model: 'deepseek-chat',
messages: [
{ role: 'system', content: '你是一个毒舌但很有用的助手。' },
{ role: 'user', content: '用三行代码告诉我数组去重' },
],
})
console.log(res.choices[0].message.content)node index.js
你就得到了你的第一个 AI 回复。
理解 messages 协议
这是整个 AI 应用的基石,三种角色:
| role | 作用 |
|---|---|
system | 系统指令,定义 AI 的身份/行为/规则。只在开头出现一次 |
user | 用户说的话 |
assistant | AI 之前说过的话(用来维持上下文) |
关键点:LLM 本身是无状态的!
// 错误理解:以为模型会"记住"上一次对话
await client.chat.completions.create({
messages: [{ role: 'user', content: '我叫小红' }],
})
await client.chat.completions.create({
messages: [{ role: 'user', content: '我叫什么?' }],
// 它完全不知道
})
// 正确:你要把历史对话全部传回去
const history = [
{ role: 'user', content: '我叫小红' },
{ role: 'assistant', content: '你好小红!' },
{ role: 'user', content: '我叫什么?' },
]
你可以把 messages 数组理解为一个"聊天记录",每次都要把完整的记录发给模型。这也是为什么长对话会越来越贵——Token 数量累积。
常用参数
await client.chat.completions.create({
model: 'deepseek-chat',
messages,
// 生成的随机性。0 = 几乎确定,1 = 有创造力。写代码建议 0~0.3,写文案 0.7~1
temperature: 0.7,
// 最大生成 Token 数,防止模型啰嗦烧钱
max_tokens: 1000,
// 遇到这些字符就停止生成
stop: ['\n\n##'],
// 开启流式输出,下面会讲
stream: false,
})流式输出:ChatGPT 那种打字机效果
同步等完整响应的问题是:长回答要等 10+ 秒才能看到东西,用户体验极差。解决方案是流式(Streaming),本质是基于 HTTP 的 Server-Sent Events (SSE)。
const stream = await client.chat.completions.create({
model: 'deepseek-chat',
messages: [{ role: 'user', content: '写一首春天的诗' }],
stream: true, // 关键
})
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content || ''
process.stdout.write(delta) // 一片一片打印
}
底层发生了什么(如果你想深究):服务端 Content-Type: text/event-stream,每生成几个 Token 就推一条 data: {...} 消息过来,直到 data: [DONE]。SDK 帮你把这些 chunk 包成异步迭代器了。
在浏览器里调用?不行!
// 千万别这样
const client = new OpenAI({
apiKey: 'sk-xxx',
dangerouslyAllowBrowser: true, // 名字已经在警告你了
})
API Key 是钱。暴露在前端 = 任何人打开 DevTools 就能偷走,半小时能给你花几百刀。
正确姿势:始终走自己的后端中转。Next.js 的 API Route、Cloudflare Workers、Vercel Functions、Express 都行。后端拿 Key 调 LLM,再把结果转发给前端。
[浏览器] ──► [你的后端] ──► [LLM API]
▲
Key 藏这里
第 9 篇会用 Next.js + Vercel AI SDK 演示完整流程。
错误处理速查
try {
const res = await client.chat.completions.create({...})
} catch (err) {
if (err.status === 401) // API Key 错了
if (err.status === 429) // 限流,等一下再试
if (err.status === 400) // 参数错误或触发内容安全
if (err.status === 500) // 服务端炸了,重试
}
生产环境一定要加指数退避重试。SDK 内置了,默认 2 次:new OpenAI({ maxRetries: 3 })。
动手作业
- 跑通上面的示例
- 改成流式输出,看效果
- 实现一个命令行聊天:反复
readline读用户输入,维护messages数组,用流式打印 AI 回复
参考资料
- OpenAI Node SDK — 文档和示例都在这
- DeepSeek API 文档 — 性价比之王
- OpenAI API Reference — 所有参数的权威说明
- MDN: Server-Sent Events — 理解流式的底层机制
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:调用大模型 API:从 fetch 到流式响应
本文链接:https://www.sshipanoo.com/blog/ai/ai-for-frontend/02-调用大模型API/
本文最后一次更新为 天前,文章中的某些内容可能已过时!