本文基于「灶台导航」小程序集成 DeepSeek API 的实际经验,详解 HTTP 调用、JSON 模式、超时重试、错误降级等核心要点。
一、为什么选择 DeepSeek?
在为小程序接入 AI 能力时,我们对比了多个方案:
| 方案 | 优势 | 劣势 |
|---|---|---|
| 微信同声传译插件 | 官方支持 | 能力有限 |
| 腾讯云 AI | 国内稳定 | 价格较高 |
| OpenAI API | 效果好 | 国内访问不稳定 |
| DeepSeek API | 国产、便宜、效果好 | 相对较新 |
最终选择 DeepSeek,原因:
- 国产服务,访问稳定
- 价格实惠(约为 GPT 的 1/10)
- 中文效果好
- API 兼容 OpenAI 格式
二、API 基础配置
2.1 获取 API Key
- 访问 DeepSeek 开放平台
- 注册账号并登录
- 创建 API Key
- 复制保存 Key(只显示一次)
2.2 云函数配置
javascript
// cloudfunctions/chat/index.js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
// DeepSeek API 配置
const DEEPSEEK_CONFIG = {
baseUrl: 'https://api.deepseek.com',
apiKey: '你的apiKey',
model: 'deepseek-chat',
temperature: 0.7,
maxTokens: 512 // 减少输出token加速响应
}
安全建议:API Key 应存储在云函数环境变量中,而非硬编码:
javascript
// 从环境变量读取
const DEEPSEEK_CONFIG = {
apiKey: process.env.DEEPSEEK_API_KEY,
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-chat'
}
3.3 安装 HTTP 请求库
json
// cloudfunctions/chat/package.json
{
"name": "chat",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~2.6.3",
"got": "^11.8.6"
}
}
使用 got@11.x 版本(支持 CommonJS)。
三、基础调用实现
3.1 简单对话
javascript
// cloudfunctions/chat/index.js
const cloud = require('wx-server-sdk')
const got = require('got')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const DEEPSEEK_CONFIG = {
apiKey: process.env.DEEPSEEK_API_KEY,
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-chat'
}
exports.main = async (event, context) => {
const { message } = event
try {
const response = await got.post(`${DEEPSEEK_CONFIG.baseUrl}/chat/completions`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEEPSEEK_CONFIG.apiKey}`
},
json: {
model: DEEPSEEK_CONFIG.model,
messages: [
{
role: 'system',
content: '你是一个智能菜谱助手,帮助用户解答烹饪相关问题。'
},
{
role: 'user',
content: message
}
]
},
responseType: 'json'
})
const reply = response.body.choices[0].message.content
return {
errCode: 0,
data: {
reply: reply
},
errMsg: 'success'
}
} catch (err) {
console.error('DeepSeek API 调用失败:', err)
return {
errCode: -1,
data: null,
errMsg: 'AI 服务暂时不可用'
}
}
}
3.2 JSON 模式输出
当需要 AI 返回结构化数据时,使用 JSON 模式:
javascript
const response = await got.post(`${DEEPSEEK_CONFIG.baseUrl}/chat/completions`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEEPSEEK_CONFIG.apiKey}`
},
json: {
model: DEEPSEEK_CONFIG.model,
messages: [
{
role: 'system',
content: '你是一个菜谱推荐助手,根据用户需求推荐菜谱。返回 JSON 格式数据。'
},
{
role: 'user',
content: `请推荐适合家庭晚餐的菜谱,返回格式:
{
"recipes": [
{ "name": "菜名", "description": "简介", "difficulty": "难度" }
]
}`
}
],
response_format: { type: 'json_object' } // 强制 JSON 输出
},
responseType: 'json'
})
// 解析 JSON
const replyText = response.body.choices[0].message.content
const result = JSON.parse(replyText)
四、超时与重试
4.1 设置超时
javascript
const response = await got.post(url, {
// ... 其他配置
timeout: {
request: 30000 // 30 秒超时
}
})
4.2 重试机制
javascript
/**
* 带重试的 API 调用
*/
async function callDeepSeekWithRetry(messages, maxRetries = 3) {
let lastError
for (let i = 0; i < maxRetries; i++) {
try {
const response = await got.post(`${DEEPSEEK_CONFIG.baseUrl}/chat/completions`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEEPSEEK_CONFIG.apiKey}`
},
json: {
model: DEEPSEEK_CONFIG.model,
messages: messages
},
responseType: 'json',
timeout: {
request: 30000
}
})
return response.body
} catch (err) {
lastError = err
console.warn(`第 ${i + 1} 次调用失败:`, err.message)
// 指数退避
if (i < maxRetries - 1) {
await sleep(1000 * Math.pow(2, i))
}
}
}
throw lastError
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
五、错误降级
5.1 什么是降级?
当 AI 服务不可用时,使用本地数据库的预设回复或关键词匹配作为后备方案。
用户请求
│
▼
调用 DeepSeek API
│
├── 成功 → 返回 AI 回复
│
└── 失败 → 降级方案
│
▼
关键词匹配本地数据
│
▼
返回预设回复
5.2 降级实现
javascript
// cloudfunctions/chat/index.js
/**
* DeepSeek API 调用
*/
async function callDeepSeekAPI(history, userMessage, userContext, ragContext = '', ragRecipes = []) {
// 1. 构建 system prompt(基础 + RAG 上下文)
let systemContent = SYSTEM_PROMPT
if (ragContext) {
systemContent += '\n\n' + ragContext
}
const messages = [
{ role: 'system', content: systemContent }
]
// 添加历史对话(只取 role 和 content,去掉 timestamp 等额外字段)
for (const item of history) {
if (item.role === 'user' || item.role === 'assistant') {
messages.push({ role: item.role, content: item.content })
}
}
// 添加当前用户消息(拼接上下文)
const fullMessage = userMessage + userContext
messages.push({ role: 'user', content: fullMessage })
// 2. 发送 HTTP 请求到 DeepSeek API
try {
const response = await got.post(`${DEEPSEEK_CONFIG.baseUrl}/chat/completions`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEEPSEEK_CONFIG.apiKey}`
},
json: {
model: DEEPSEEK_CONFIG.model,
messages: messages,
temperature: DEEPSEEK_CONFIG.temperature,
max_tokens: DEEPSEEK_CONFIG.maxTokens,
response_format: { type: 'json_object' } // 强制 JSON 输出
},
timeout: {
request: TIMEOUT_CONFIG.deepseekApi // 使用配置的超时时间
}
})
// 3. 解析 DeepSeek 响应
const responseBody = JSON.parse(response.body)
const aiContent = responseBody.choices[0].message.content
console.log('[chat] DeepSeek 响应:', aiContent.substring(0, 200))
// 4. 解析 AI 返回的 JSON
const parsed = parseAIReply(aiContent)
// 5. 将 AI 推荐菜谱的 recipeId 替换为真实数据库 _id
if (parsed.recommendations && parsed.recommendations.length > 0) {
parsed.recommendations = await resolveRecipeIds(parsed.recommendations, ragRecipes)
}
// 同样处理 cookData 中的 recipeIds
if (parsed.cookData && parsed.cookData.recipeIds && parsed.cookData.recipeIds.length > 0) {
parsed.cookData.recipeIds = await resolveRecipeIdsToIdList(parsed.cookData.recipeIds, ragRecipes)
}
return parsed
} catch (err) {
console.error('[chat] DeepSeek API 调用失败:', err.message)
// 超时或网络错误时使用本地兜底方案
if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT' || err.message.includes('timeout')) {
console.warn('[chat] API超时,降级为本地关键词匹配方案')
} else {
console.warn('[chat] API错误,降级为本地关键词匹配方案')
}
return await fallbackResponse(userMessage)
}
}
/**
* 降级匹配
*/
async function fallbackResponse(userMessage) {
const msg = userMessage.toLowerCase()
// 常见问候语直接回复
const greetings = ['你好', 'hello', 'hi', '嗨', 'hey', '早上好', '下午好', '晚上好', '在吗']
if (greetings.some(g => msg.includes(g))) {
return {
reply: '你好!我是灶台导航助手。告诉我你想做什么菜,或者说说家里有什么食材,我来帮你推荐~',
action: 'ask',
recommendations: [],
cookData: null
}
}
......
}
六、上下文管理
6.1 多轮对话
保持对话上下文,实现连续交互:
javascript
// 存储对话历史
let conversationHistory = []
async function chatWithHistory(message) {
// 添加用户消息
conversationHistory.push({
role: 'user',
content: message
})
// 限制历史长度(避免 token 过多)
const maxHistory = 10
if (conversationHistory.length > maxHistory) {
conversationHistory = conversationHistory.slice(-maxHistory)
}
const response = await got.post('https://api.deepseek.com/chat/completions', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`
},
json: {
model: 'deepseek-chat',
messages: [
{ role: 'system', content: '你是智能菜谱助手' },
...conversationHistory
]
},
responseType: 'json'
})
const reply = response.body.choices[0].message.content
// 添加 AI 回复到历史
conversationHistory.push({
role: 'assistant',
content: reply
})
return reply
}
6.2 使用数据库存储历史
javascript
// cloudfunctions/chat/index.js
exports.main = async (event, context) => {
const { message, sessionId } = event
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
// 获取历史对话
let history = []
if (sessionId) {
const sessionRes = await db.collection('chat_sessions')
.where({
_id: sessionId,
_openid: openid
})
.get()
if (sessionRes.data.length > 0) {
history = sessionRes.data[0].messages || []
}
}
// 构建消息列表
const messages = [
{ role: 'system', content: '你是智能菜谱助手' },
...history.slice(-10), // 最近 10 条
{ role: 'user', content: message }
]
// 调用 AI
const response = await callDeepSeek(messages)
// 保存历史
const newHistory = [
...history,
{ role: 'user', content: message },
{ role: 'assistant', content: response }
]
if (sessionId) {
await db.collection('chat_sessions').doc(sessionId).update({
data: { messages: newHistory, updateTime: db.serverDate() }
})
} else {
const createRes = await db.collection('chat_sessions').add({
data: {
_openid: openid,
messages: newHistory,
createTime: db.serverDate()
}
})
}
return {
errCode: 0,
data: {
reply: response,
sessionId: sessionId || createRes._id
}
}
}
七、流式响应(小程序端)
由于小程序云函数不支持流式返回,可以采用轮询或分段返回的方式。
7.1 分段返回方案
javascript
// 云函数中生成完整回复后返回
// 前端逐字显示
// 前端实现打字机效果
function typeWriter(text, element, speed = 50) {
let index = 0
element.text = ''
const timer = setInterval(() => {
if (index < text.length) {
element.text += text[index]
index++
} else {
clearInterval(timer)
}
}, speed)
}
八、总结
DeepSeek API 集成要点:
| 要点 | 说明 |
|---|---|
| 配置管理 | API Key 存环境变量 |
| 超时重试 | 设置超时 + 指数退避重试 |
| 错误降级 | AI 失败时使用关键词匹配 |
| 上下文管理 | 数据库存储对话历史 |
| 成本控制 | 缓存 + 限制历史长度 |
通过合理的架构设计,可以让小程序稳定地接入 AI 能力。
后续还可以添加RAG检索生成功能,将RAG检索生成的context加入提问词中优化回答效果
javascript
if (ragContext) {
systemContent += '\n\n' + ragContext
}
作者:「倒灶了队」
项目:灶台导航 - 微信小程序
更新时间:2026-04-18