你熟悉的 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用户说的话
assistantAI 之前说过的话(用来维持上下文)

关键点: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 })

动手作业

  1. 跑通上面的示例
  2. 改成流式输出,看效果
  3. 实现一个命令行聊天:反复 readline 读用户输入,维护 messages 数组,用流式打印 AI 回复

参考资料

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:调用大模型 API:从 fetch 到流式响应

本文链接:https://www.sshipanoo.com/blog/ai/ai-for-frontend/02-调用大模型API/

本文最后一次更新为 天前,文章中的某些内容可能已过时!