1098 lines
28 KiB
Markdown
1098 lines
28 KiB
Markdown
---
|
||
title: 前端接入 DeepSeek 大语言模型技术实现
|
||
id: '364'
|
||
cover: '/images/deepseek.webp'
|
||
categories:
|
||
- 分享
|
||
tags:
|
||
- DeepSeek
|
||
- 大语言模型
|
||
- JavaScript
|
||
- 流式响应
|
||
- SSE
|
||
- Server-Sent Events
|
||
date: 2025-04-17 12:48:36
|
||
---
|
||
|
||
## 前言
|
||
|
||
随着人工智能技术的快速发展,大语言模型(LLM)已经成为前端开发中不可或缺的一部分。DeepSeek作为国内领先的大语言模型,提供了强大的对话能力和API接口,使得前端开发者可以轻松地将AI对话功能集成到自己的应用中。
|
||
|
||
本文将从前端开发者的角度,详细介绍如何接入DeepSeek大语言模型,实现一个完整的对话系统,包括API调用、流式响应处理、错误处理、用户体验优化等关键技术点。
|
||
|
||
## 1. DeepSeek API 概述
|
||
|
||
DeepSeek提供了RESTful API接口,支持多种编程语言调用。对于前端开发者来说,最常用的是对话补全(Chat Completion)接口。
|
||
|
||
### 1.1 API 基础信息
|
||
|
||
* **基础URL**: `https://api.deepseek.com`
|
||
* **认证方式**: Bearer Token
|
||
* **主要端点**: `/v1/chat/completions`
|
||
* **支持模型**: `deepseek-chat`, `deepseek-coder`等
|
||
|
||
### 1.2 请求格式
|
||
|
||
```javascript
|
||
const requestBody = {
|
||
model: "deepseek-chat",
|
||
messages: [{
|
||
role: "system",
|
||
content: "你是一个有帮助的AI助手"
|
||
},
|
||
{
|
||
role: "user",
|
||
content: "你好,请介绍一下你自己"
|
||
}
|
||
],
|
||
stream: true, // 启用流式响应
|
||
temperature: 0.7,
|
||
max_tokens: 1000
|
||
};
|
||
```
|
||
|
||
## 2. 前端实现方案
|
||
|
||
### 2.1 环境准备
|
||
|
||
首先,我们需要获取API密钥并设置项目环境:
|
||
|
||
```bash
|
||
# 安装必要的依赖
|
||
npm install axios event-source-parser
|
||
# 或使用fetch API(现代浏览器内置)
|
||
```
|
||
|
||
### 2.2 基础API调用
|
||
|
||
#### 非流式响应(一次性返回)
|
||
|
||
```javascript
|
||
async function callDeepSeekAPI(messages, apiKey) {
|
||
try {
|
||
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${apiKey}`
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'deepseek-chat',
|
||
messages: messages,
|
||
stream: false,
|
||
temperature: 0.7
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`API请求失败: ${response.status}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
return data.choices[0].message.content;
|
||
} catch (error) {
|
||
console.error('调用DeepSeek API失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 流式响应(实时显示)
|
||
|
||
流式响应可以提供更好的用户体验,让用户看到AI思考的过程:
|
||
|
||
```javascript
|
||
async function streamDeepSeekResponse(messages, apiKey, onChunk, onComplete) {
|
||
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${apiKey}`
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'deepseek-chat',
|
||
messages: messages,
|
||
stream: true,
|
||
temperature: 0.7,
|
||
max_tokens: 2000
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`API请求失败: ${response.status}`);
|
||
}
|
||
|
||
const reader = response.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let buffer = '';
|
||
|
||
try {
|
||
while (true) {
|
||
const {
|
||
done,
|
||
value
|
||
} = await reader.read();
|
||
if (done) break;
|
||
|
||
buffer += decoder.decode(value, {
|
||
stream: true
|
||
});
|
||
const lines = buffer.split('\n');
|
||
buffer = lines.pop(); // 保留未完成的行
|
||
|
||
for (const line of lines) {
|
||
if (line.trim() === '') continue;
|
||
if (line.startsWith('data: ')) {
|
||
const data = line.slice(6);
|
||
if (data === '[DONE]') {
|
||
onComplete();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const parsed = JSON.parse(data);
|
||
const content = parsed.choices[0]?.delta?.content || '';
|
||
if (content) {
|
||
onChunk(content);
|
||
}
|
||
} catch (e) {
|
||
console.warn('解析流数据失败:', e);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} finally {
|
||
reader.releaseLock();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.3 使用EventSource处理SSE
|
||
|
||
对于更简单的SSE处理,可以使用EventSource API:
|
||
|
||
```javascript
|
||
function createDeepSeekEventSource(messages, apiKey, onMessage, onError) {
|
||
const eventSource = new EventSourcePolyfill(
|
||
'https://api.deepseek.com/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${apiKey}`
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'deepseek-chat',
|
||
messages: messages,
|
||
stream: true
|
||
}),
|
||
pollingInterval: 0
|
||
}
|
||
);
|
||
|
||
eventSource.onmessage = (event) => {
|
||
if (event.data === '[DONE]') {
|
||
eventSource.close();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const data = JSON.parse(event.data);
|
||
const content = data.choices[0]?.delta?.content || '';
|
||
if (content && onMessage) {
|
||
onMessage(content);
|
||
}
|
||
} catch (error) {
|
||
console.error('解析消息失败:', error);
|
||
}
|
||
};
|
||
|
||
eventSource.onerror = (error) => {
|
||
console.error('EventSource错误:', error);
|
||
if (onError) onError(error);
|
||
eventSource.close();
|
||
};
|
||
|
||
return eventSource;
|
||
}
|
||
```
|
||
|
||
## 3. 对话状态管理
|
||
|
||
### 3.1 消息历史管理
|
||
|
||
```javascript
|
||
class ChatManager {
|
||
constructor() {
|
||
this.messages = [];
|
||
this.systemPrompt = "你是一个有帮助的AI助手,请用中文回答用户的问题。";
|
||
this.maxHistoryLength = 20;
|
||
}
|
||
|
||
addMessage(role, content) {
|
||
this.messages.push({
|
||
role,
|
||
content,
|
||
timestamp: Date.now()
|
||
});
|
||
|
||
// 限制历史记录长度,避免token超限
|
||
if (this.messages.length > this.maxHistoryLength) {
|
||
// 保留系统消息和最近的对话
|
||
const systemMessage = this.messages.find(m => m.role === 'system');
|
||
const recentMessages = this.messages.slice(-this.maxHistoryLength + 1);
|
||
this.messages = systemMessage ? [systemMessage, ...recentMessages] : recentMessages;
|
||
}
|
||
}
|
||
|
||
getConversationHistory() {
|
||
return [{
|
||
role: 'system',
|
||
content: this.systemPrompt
|
||
},
|
||
...this.messages.filter(m => m.role !== 'system')
|
||
];
|
||
}
|
||
|
||
clearHistory() {
|
||
this.messages = this.messages.filter(m => m.role === 'system');
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 Token计数与限制
|
||
|
||
```javascript
|
||
class TokenManager {
|
||
// 简单的中文token估算(实际应该使用tiktoken库)
|
||
static estimateTokens(text) {
|
||
// 中文大致按字符数估算,英文按单词数
|
||
const chineseChars = text.match(/[\u4e00-\u9fa5]/g) || [];
|
||
const englishWords = text.match(/[a-zA-Z]+/g) || [];
|
||
return chineseChars.length + englishWords.length * 0.75;
|
||
}
|
||
|
||
static truncateMessages(messages, maxTokens = 4000) {
|
||
let totalTokens = 0;
|
||
const truncated = [];
|
||
|
||
// 从最新消息开始添加,直到达到token限制
|
||
for (let i = messages.length - 1; i >= 0; i--) {
|
||
const message = messages[i];
|
||
const messageTokens = this.estimateTokens(message.content) + 10; // 加上role的token
|
||
|
||
if (totalTokens + messageTokens > maxTokens) {
|
||
break;
|
||
}
|
||
|
||
truncated.unshift(message);
|
||
totalTokens += messageTokens;
|
||
}
|
||
|
||
return truncated;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 4. 用户体验优化
|
||
|
||
### 4.1 打字机效果实现
|
||
|
||
```javascript
|
||
class TypewriterEffect {
|
||
constructor(element, options = {}) {
|
||
this.element = element;
|
||
this.speed = options.speed || 50; // 毫秒/字符
|
||
this.cursorChar = options.cursorChar || '▌';
|
||
this.isTyping = false;
|
||
this.currentText = '';
|
||
}
|
||
|
||
async type(text) {
|
||
if (this.isTyping) {
|
||
await this.stop();
|
||
}
|
||
|
||
this.isTyping = true;
|
||
this.currentText = '';
|
||
|
||
for (let i = 0; i < text.length; i++) {
|
||
if (!this.isTyping) break;
|
||
|
||
this.currentText += text[i];
|
||
this.element.textContent = this.currentText + this.cursorChar;
|
||
await this.delay(this.speed);
|
||
}
|
||
|
||
if (this.isTyping) {
|
||
this.element.textContent = this.currentText;
|
||
this.isTyping = false;
|
||
}
|
||
}
|
||
|
||
async stop() {
|
||
this.isTyping = false;
|
||
this.element.textContent = this.currentText;
|
||
}
|
||
|
||
delay(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 错误处理与重试机制
|
||
|
||
```javascript
|
||
class APIRetryManager {
|
||
constructor(maxRetries = 3, baseDelay = 1000) {
|
||
this.maxRetries = maxRetries;
|
||
this.baseDelay = baseDelay;
|
||
}
|
||
|
||
async withRetry(apiCall, shouldRetry = (error) => true) {
|
||
let lastError;
|
||
|
||
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
||
try {
|
||
return await apiCall();
|
||
} catch (error) {
|
||
lastError = error;
|
||
|
||
if (!shouldRetry(error) || attempt === this.maxRetries - 1) {
|
||
break;
|
||
}
|
||
|
||
// 指数退避
|
||
const delay = this.baseDelay * Math.pow(2, attempt);
|
||
console.log(`请求失败,${delay}ms后重试...`, error);
|
||
await this.delay(delay + Math.random() * 1000); // 添加随机抖动
|
||
}
|
||
}
|
||
|
||
throw lastError;
|
||
}
|
||
|
||
delay(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
const retryManager = new APIRetryManager();
|
||
try {
|
||
const response = await retryManager.withRetry(
|
||
() => callDeepSeekAPI(messages, apiKey),
|
||
(error) => {
|
||
// 只对网络错误和5xx错误重试
|
||
return error.message.includes('Network') ||
|
||
(error.status && error.status >= 500);
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('所有重试尝试都失败了:', error);
|
||
}
|
||
```
|
||
|
||
## 5. 安全考虑
|
||
|
||
### 5.1 API密钥安全
|
||
|
||
**重要:永远不要在前端代码中硬编码API密钥!**
|
||
|
||
```javascript
|
||
// 错误做法 ❌
|
||
const API_KEY = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
|
||
|
||
// 正确做法 ✅
|
||
// 1. 使用后端代理
|
||
async function callDeepSeekThroughProxy(messages) {
|
||
const response = await fetch('/api/deepseek-proxy', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
messages
|
||
})
|
||
});
|
||
return response.json();
|
||
}
|
||
|
||
// 2. 使用环境变量(构建时注入)
|
||
const API_KEY = process.env.REACT_APP_DEEPSEEK_API_KEY;
|
||
|
||
// 3. 使用认证token(用户登录后获取)
|
||
async function getApiToken() {
|
||
const user = await auth.getCurrentUser();
|
||
return user.getIdToken();
|
||
}
|
||
```
|
||
|
||
### 5.2 输入验证与过滤
|
||
|
||
```javascript
|
||
function sanitizeUserInput(input) {
|
||
// 移除潜在的恶意内容
|
||
let sanitized = input
|
||
.trim()
|
||
.replace(/[<>]/g, '') // 移除HTML标签
|
||
.substring(0, 5000); // 限制长度
|
||
|
||
// 检查是否包含敏感信息
|
||
const sensitivePatterns = [
|
||
/password\s*[:=]/i,
|
||
/credit\s*card/i,
|
||
/\d{16}/, // 信用卡号
|
||
// ... 其他敏感模式
|
||
];
|
||
|
||
for (const pattern of sensitivePatterns) {
|
||
if (pattern.test(sanitized)) {
|
||
throw new Error('输入包含敏感信息,请修改后重试');
|
||
}
|
||
}
|
||
|
||
return sanitized;
|
||
}
|
||
```
|
||
|
||
## 6. 性能优化
|
||
|
||
### 6.1 请求防抖与节流
|
||
|
||
```javascript
|
||
class Debouncer {
|
||
constructor(delay = 300) {
|
||
this.delay = delay;
|
||
this.timeoutId = null;
|
||
}
|
||
|
||
debounce(fn) {
|
||
return (...args) => {
|
||
clearTimeout(this.timeoutId);
|
||
this.timeoutId = setTimeout(() => fn(...args), this.delay);
|
||
};
|
||
}
|
||
}
|
||
|
||
// 在搜索建议场景中使用
|
||
const searchDebouncer = new Debouncer(500);
|
||
const debouncedSearch = searchDebouncer.debounce(async (query) => {
|
||
if (query.length < 2) return;
|
||
|
||
const suggestions = await getSearchSuggestions(query);
|
||
updateSuggestions(suggestions);
|
||
});
|
||
```
|
||
|
||
### 6.2 响应缓存
|
||
|
||
```javascript
|
||
class ResponseCache {
|
||
constructor(maxAge = 5 * 60 * 1000) { // 5分钟
|
||
this.cache = new Map();
|
||
this.maxAge = maxAge;
|
||
}
|
||
|
||
getKey(messages) {
|
||
// 基于消息内容生成缓存键
|
||
return JSON.stringify(messages);
|
||
}
|
||
|
||
get(messages) {
|
||
const key = this.getKey(messages);
|
||
const cached = this.cache.get(key);
|
||
|
||
if (cached && Date.now() - cached.timestamp < this.maxAge) {
|
||
return cached.data;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
set(messages, data) {
|
||
const key = this.getKey(messages);
|
||
this.cache.set(key, {
|
||
data,
|
||
timestamp: Date.now()
|
||
});
|
||
|
||
// 清理过期缓存
|
||
this.cleanup();
|
||
}
|
||
|
||
cleanup() {
|
||
const now = Date.now();
|
||
for (const [key, value] of this.cache.entries()) {
|
||
if (now - value.timestamp > this.maxAge) {
|
||
this.cache.delete(key);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7. 完整示例:React组件实现
|
||
|
||
```jsx
|
||
import React, { useState, useRef, useEffect } from 'react';
|
||
import './ChatInterface.css';
|
||
|
||
const ChatInterface = ({ apiKey }) => {
|
||
const [messages, setMessages] = useState([]);
|
||
const [input, setInput] = useState('');
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const messagesEndRef = useRef(null);
|
||
|
||
const scrollToBottom = () => {
|
||
|
||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||
|
||
};
|
||
|
||
useEffect(() => {
|
||
|
||
scrollToBottom();
|
||
|
||
}, [messages]);
|
||
|
||
const handleSubmit = async (e) => {
|
||
|
||
e.preventDefault();
|
||
if (!input.trim() || isLoading) return;
|
||
|
||
const userMessage = input.trim();
|
||
setInput('');
|
||
|
||
// 添加用户消息
|
||
setMessages(prev => [...prev, {
|
||
role: 'user',
|
||
content: userMessage,
|
||
timestamp: new Date().toISOString()
|
||
}]);
|
||
|
||
setIsLoading(true);
|
||
|
||
try {
|
||
// 调用DeepSeek API
|
||
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${apiKey}`
|
||
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'deepseek-chat',
|
||
messages: [
|
||
{ role: 'system', content: '你是一个有帮助的AI助手' },
|
||
...messages.map(m => ({ role: m.role, content: m.content })),
|
||
{ role: 'user', content: userMessage }
|
||
],
|
||
stream: false,
|
||
temperature: 0.7
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error( `API请求失败: ${response.status}` );
|
||
}
|
||
|
||
const data = await response.json();
|
||
const aiResponse = data.choices[0].message.content;
|
||
|
||
// 添加AI回复
|
||
setMessages(prev => [...prev, {
|
||
role: 'assistant',
|
||
content: aiResponse,
|
||
timestamp: new Date().toISOString()
|
||
}]);
|
||
} catch (error) {
|
||
console.error('对话失败:', error);
|
||
setMessages(prev => [...prev, {
|
||
role: 'system',
|
||
content: `抱歉,出现错误: ${error.message}` ,
|
||
timestamp: new Date().toISOString(),
|
||
isError: true
|
||
}]);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
|
||
};
|
||
|
||
return (
|
||
|
||
<div className="chat-container">
|
||
<div className="messages-container">
|
||
{messages.map((msg, index) => (
|
||
<div
|
||
key={index}
|
||
className={ `message ${msg.role} ${msg.isError ? 'error' : ''}` }
|
||
>
|
||
<div className="message-header">
|
||
<span className="role">{msg.role === 'user' ? '你' : 'AI助手'}</span>
|
||
<span className="timestamp">
|
||
{new Date(msg.timestamp).toLocaleTimeString()}
|
||
</span>
|
||
</div>
|
||
<div className="message-content">
|
||
{msg.content}
|
||
</div>
|
||
</div>
|
||
))}
|
||
<div ref={messagesEndRef} />
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit} className="input-form">
|
||
<div className="input-container">
|
||
<textarea
|
||
value={input}
|
||
onChange={(e) => setInput(e.target.value)}
|
||
placeholder="输入你的问题..."
|
||
disabled={isLoading}
|
||
rows={3}
|
||
className="message-input"
|
||
/>
|
||
<button
|
||
type="submit"
|
||
disabled={isLoading || !input.trim()}
|
||
className="send-button"
|
||
>
|
||
{isLoading ? '思考中...' : '发送'}
|
||
</button>
|
||
</div>
|
||
<div className="input-hint">
|
||
按 Enter 发送,Shift + Enter 换行
|
||
</div>
|
||
</form>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ChatInterface;
|
||
```
|
||
|
||
## 8. 进阶功能实现
|
||
|
||
### 8.1 多轮对话上下文管理
|
||
|
||
在实际应用中,我们需要维护对话的上下文,确保AI能够理解整个对话流程:
|
||
|
||
```javascript
|
||
class ConversationContext {
|
||
constructor(maxContextLength = 10) {
|
||
this.context = [];
|
||
this.maxContextLength = maxContextLength;
|
||
}
|
||
|
||
addExchange(userMessage, aiResponse) {
|
||
this.context.push({
|
||
user: userMessage,
|
||
ai: aiResponse,
|
||
timestamp: Date.now()
|
||
});
|
||
|
||
// 保持上下文长度
|
||
if (this.context.length > this.maxContextLength) {
|
||
this.context = this.context.slice(-this.maxContextLength);
|
||
}
|
||
}
|
||
|
||
getContextSummary() {
|
||
if (this.context.length === 0) return '';
|
||
|
||
return this.context
|
||
.map((exchange, index) =>
|
||
`第${index + 1}轮对话:
|
||
用户:${exchange.user}
|
||
AI:${exchange.ai}`
|
||
)
|
||
.join('\n\n');
|
||
}
|
||
|
||
clear() {
|
||
this.context = [];
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 实时语音输入集成
|
||
|
||
结合Web Speech API,可以实现语音输入功能:
|
||
|
||
```javascript
|
||
class VoiceInputManager {
|
||
constructor() {
|
||
this.recognition = null;
|
||
this.isListening = false;
|
||
this.onResult = null;
|
||
this.onError = null;
|
||
|
||
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
|
||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||
this.recognition = new SpeechRecognition();
|
||
this.recognition.continuous = false;
|
||
this.recognition.interimResults = true;
|
||
this.recognition.lang = 'zh-CN';
|
||
|
||
this.recognition.onresult = (event) => {
|
||
const transcript = Array.from(event.results)
|
||
.map(result => result[0].transcript)
|
||
.join('');
|
||
|
||
if (this.onResult) {
|
||
this.onResult(transcript);
|
||
}
|
||
};
|
||
|
||
this.recognition.onerror = (event) => {
|
||
if (this.onError) {
|
||
this.onError(event.error);
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
startListening() {
|
||
if (!this.recognition) {
|
||
throw new Error('浏览器不支持语音识别');
|
||
}
|
||
|
||
if (!this.isListening) {
|
||
this.recognition.start();
|
||
this.isListening = true;
|
||
}
|
||
}
|
||
|
||
stopListening() {
|
||
if (this.recognition && this.isListening) {
|
||
this.recognition.stop();
|
||
this.isListening = false;
|
||
}
|
||
}
|
||
|
||
toggleListening() {
|
||
if (this.isListening) {
|
||
this.stopListening();
|
||
} else {
|
||
this.startListening();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.3 文件上传与处理
|
||
|
||
DeepSeek支持文件上传功能,可以处理图像、PDF、Word等文档:
|
||
|
||
```javascript
|
||
async function uploadAndProcessFile(file, apiKey) {
|
||
// 检查文件类型和大小
|
||
const validTypes = ['image/jpeg', 'image/png', 'application/pdf', 'text/plain'];
|
||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||
|
||
if (!validTypes.includes(file.type)) {
|
||
throw new Error('不支持的文件类型');
|
||
}
|
||
|
||
if (file.size > maxSize) {
|
||
throw new Error('文件大小超过10MB限制');
|
||
}
|
||
|
||
// 创建FormData
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('purpose', 'vision'); // 或 'assistants'
|
||
|
||
// 上传文件
|
||
const uploadResponse = await fetch('https://api.deepseek.com/v1/files', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${apiKey}`
|
||
},
|
||
body: formData
|
||
});
|
||
|
||
if (!uploadResponse.ok) {
|
||
throw new Error(`文件上传失败: ${uploadResponse.status}`);
|
||
}
|
||
|
||
const uploadData = await uploadResponse.json();
|
||
const fileId = uploadData.id;
|
||
|
||
// 使用文件进行对话
|
||
const chatResponse = await fetch('https://api.deepseek.com/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${apiKey}`
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'deepseek-chat',
|
||
messages: [{
|
||
role: 'user',
|
||
content: [{
|
||
type: 'text',
|
||
text: '请分析这个文件的内容'
|
||
},
|
||
{
|
||
type: 'file',
|
||
file_id: fileId
|
||
}
|
||
]
|
||
}]
|
||
})
|
||
});
|
||
|
||
return chatResponse.json();
|
||
}
|
||
```
|
||
|
||
## 9. 测试与调试
|
||
|
||
### 9.1 单元测试示例
|
||
|
||
使用Jest进行单元测试:
|
||
|
||
```javascript
|
||
// chatManager.test.js
|
||
import {
|
||
ChatManager
|
||
} from './chatManager';
|
||
|
||
describe('ChatManager', () => {
|
||
let chatManager;
|
||
|
||
beforeEach(() => {
|
||
chatManager = new ChatManager();
|
||
});
|
||
|
||
test('应该正确添加消息', () => {
|
||
chatManager.addMessage('user', '你好');
|
||
expect(chatManager.messages).toHaveLength(1);
|
||
expect(chatManager.messages[0].role).toBe('user');
|
||
expect(chatManager.messages[0].content).toBe('你好');
|
||
});
|
||
|
||
test('应该限制历史记录长度', () => {
|
||
for (let i = 0; i < 25; i++) {
|
||
chatManager.addMessage('user', `消息${i}`);
|
||
}
|
||
expect(chatManager.messages.length).toBeLessThanOrEqual(20);
|
||
});
|
||
|
||
test('应该包含系统提示', () => {
|
||
const history = chatManager.getConversationHistory();
|
||
expect(history[0].role).toBe('system');
|
||
expect(history[0].content).toContain('AI助手');
|
||
});
|
||
});
|
||
|
||
// apiClient.test.js
|
||
import {
|
||
callDeepSeekAPI
|
||
} from './apiClient';
|
||
|
||
describe('API客户端', () => {
|
||
beforeEach(() => {
|
||
global.fetch = jest.fn();
|
||
});
|
||
|
||
test('应该正确处理API响应', async () => {
|
||
const mockResponse = {
|
||
ok: true,
|
||
json: async () => ({
|
||
choices: [{
|
||
message: {
|
||
content: '你好,我是DeepSeek助手'
|
||
}
|
||
}]
|
||
})
|
||
};
|
||
|
||
global.fetch.mockResolvedValue(mockResponse);
|
||
|
||
const result = await callDeepSeekAPI(
|
||
[{
|
||
role: 'user',
|
||
content: '你好'
|
||
}],
|
||
'test-key'
|
||
);
|
||
|
||
expect(result).toBe('你好,我是DeepSeek助手');
|
||
expect(global.fetch).toHaveBeenCalledWith(
|
||
'https://api.deepseek.com/v1/chat/completions',
|
||
expect.any(Object)
|
||
);
|
||
});
|
||
|
||
test('应该处理API错误', async () => {
|
||
global.fetch.mockResolvedValue({
|
||
ok: false,
|
||
status: 429
|
||
});
|
||
|
||
await expect(callDeepSeekAPI([], 'test-key'))
|
||
.rejects
|
||
.toThrow('API请求失败: 429');
|
||
});
|
||
});
|
||
```
|
||
|
||
### 9.2 调试技巧
|
||
|
||
1. **网络请求调试**:
|
||
|
||
```javascript
|
||
// 添加请求拦截器进行调试
|
||
const originalFetch = window.fetch;
|
||
window.fetch = async function(...args) {
|
||
console.log('请求开始:', args[0], args[1]);
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
const response = await originalFetch.apply(this, args);
|
||
console.log('请求完成:', {
|
||
url: args[0],
|
||
status: response.status,
|
||
time: Date.now() - startTime
|
||
});
|
||
return response;
|
||
} catch (error) {
|
||
console.error('请求失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
```
|
||
|
||
2. **性能监控**:
|
||
|
||
```javascript
|
||
class PerformanceMonitor {
|
||
constructor() {
|
||
this.metrics = [];
|
||
}
|
||
|
||
startMeasurement(name) {
|
||
return {
|
||
name,
|
||
startTime: performance.now(),
|
||
end: () => {
|
||
const duration = performance.now() - this.startTime;
|
||
this.metrics.push({
|
||
name,
|
||
duration
|
||
});
|
||
console.log(`${name}: ${duration.toFixed(2)}ms`);
|
||
return duration;
|
||
}
|
||
};
|
||
}
|
||
|
||
getReport() {
|
||
return this.metrics.reduce((acc, metric) => {
|
||
if (!acc[metric.name]) {
|
||
acc[metric.name] = {
|
||
count: 0,
|
||
total: 0,
|
||
avg: 0
|
||
};
|
||
}
|
||
acc[metric.name].count++;
|
||
acc[metric.name].total += metric.duration;
|
||
acc[metric.name].avg = acc[metric.name].total / acc[metric.name].count;
|
||
return acc;
|
||
}, {});
|
||
}
|
||
}
|
||
```
|
||
|
||
## 10. 部署与优化
|
||
|
||
### 10.1 生产环境配置
|
||
|
||
```javascript
|
||
// config.js
|
||
const config = {
|
||
development: {
|
||
apiBaseUrl: 'https://api.deepseek.com',
|
||
logLevel: 'debug',
|
||
enableMock: true
|
||
},
|
||
production: {
|
||
apiBaseUrl: 'https://api.deepseek.com',
|
||
logLevel: 'error',
|
||
enableMock: false,
|
||
features: {
|
||
streaming: true,
|
||
fileUpload: true,
|
||
voiceInput: false // 生产环境可能需要额外权限
|
||
}
|
||
}
|
||
};
|
||
|
||
export default config[process.env.NODE_ENV || 'development'];
|
||
```
|
||
|
||
### 10.2 CDN与缓存策略
|
||
|
||
```html
|
||
<!-- 使用CDN加速 -->
|
||
<script src="https://cdn.jsdelivr.net/npm/event-source-polyfill@1.0.0/dist/eventsource.min.js"></script>
|
||
|
||
<!-- Service Worker缓存 -->
|
||
<script>
|
||
if ('serviceWorker' in navigator) {
|
||
navigator.serviceWorker.register('/sw.js').then(registration => {
|
||
console.log('ServiceWorker注册成功:', registration.scope);
|
||
}).catch(error => {
|
||
console.log('ServiceWorker注册失败:', error);
|
||
});
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 10.3 监控与告警
|
||
|
||
```javascript
|
||
// 错误监控
|
||
window.addEventListener('error', (event) => {
|
||
// 发送错误到监控服务
|
||
fetch('/api/monitor/error', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
message: event.message,
|
||
filename: event.filename,
|
||
lineno: event.lineno,
|
||
colno: event.colno,
|
||
error: event.error?.stack,
|
||
timestamp: new Date().toISOString()
|
||
})
|
||
});
|
||
});
|
||
|
||
// 性能监控
|
||
const observer = new PerformanceObserver((list) => {
|
||
for (const entry of list.getEntries()) {
|
||
if (entry.duration > 1000) { // 超过1秒的API调用
|
||
console.warn('慢API调用:', entry.name, entry.duration);
|
||
}
|
||
}
|
||
});
|
||
observer.observe({
|
||
entryTypes: ['resource']
|
||
});
|
||
```
|
||
|
||
## 总结
|
||
|
||
通过本文的介绍,我们全面探讨了前端接入DeepSeek大语言模型的技术实现。从基础的API调用到高级功能如流式响应、对话状态管理、用户体验优化、安全考虑、性能优化等,我们覆盖了构建一个完整AI对话系统所需的关键技术点。
|
||
|
||
---
|
||
*示例代码基于DeepSeek API v1版本,实际使用时请参考最新官方文档*
|