项目实训(2) -AI接入!DeepSeek 接口初体验与消息循环
昨天搭了个聊天框的壳子,今天总算要来点实际的了——让 AI 真正说上话。
后端同学把 DeepSeek 的内部接口文档甩过来了,一个 URL,加上要传的 model 名称,还有一些请求体格式的约定。核心就是传一个 messages 数组,包含用户和 AI 的历史对话。
对接 API 的尝试:
我一般用 axios,但想想 fetch 也挺香的,原生,少个依赖。就用 fetch 吧。
// 在 AIChatWindow.vue 的 <script setup> 里
// ... (之前的代码) ...
const DEEPSEEK_API_URL = 'https://backend-provided.internal.api/deepseek-chat'; // 后端给的URL
const MODEL_NAME = 'deepseek-coder'; // 或者他们指定的模型
const sendMessage = async () => { // 改成 async
if (!userInput.value.trim() || isLoading.value) return;
const userMessageText = userInput.value;
const newUserMessage: Message = {
id: Date.now().toString(),
text: userMessageText,
sender: 'user',
timestamp: new Date(),
};
messages.value.push(newUserMessage);
userInput.value = '';
isLoading.value = true;
// 构造发送给API的对话历史
// API期望的格式可能是 [{ role: 'user', content: '你好'}, { role: 'assistant', content: '你好,有什么可以帮您?'}]
// 我们自己的 Message 接口需要转换一下
const apiMessages = messages.value.map(msg => ({
role: msg.sender === 'user' ? 'user' : 'assistant',
content: msg.text,
}));
// 通常,最后一条是当前用户发的消息
// 有些模型可能只需要最近几轮对话,或者有token限制,这个要注意
try {
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 'Authorization': 'Bearer YOUR_API_KEY' // 如果需要API Key的话,但内部接口可能不需要
},
body: JSON.stringify({
model: MODEL_NAME,
messages: apiMessages,
// stream: false, // 先不用流式,简单点
}),
});
if (!response.ok) {
// 这里要处理错误,比如 response.statusText
const errorData = await response.json().catch(() => ({ detail: 'AI服务返回非OK状态,且无法解析错误详情' }));
console.error('AI API Error:', response.status, errorData);
throw new Error(errorData.detail || `AI 服务错误,状态码: ${response.status}`);
}
const data = await response.json();
// 解析返回的数据,DeepSeek返回的格式可能类似 OpenAI
// { choices: [{ message: { role: 'assistant', content: 'AI的回复内容' } }] }
const aiReplyText = data.choices?.[0]?.message?.content;
if (aiReplyText) {
const aiResponseMessage: Message = {
id: Date.now().toString() + '-ai',
text: aiReplyText.trim(),
sender: 'ai',
timestamp: new Date(),
};
messages.value.push(aiResponseMessage);
} else {
throw new Error('AI回复内容为空或格式不正确');
}
} catch (error: any) {
console.error('调用AI接口失败:', error);
const errorMessage: Message = { // 把错误信息也显示在聊天框里,方便调试
id: Date.now().toString() + '-error',
text: `抱歉,AI开小差了... (${error.message || '未知错误'})`,
sender: 'ai', // 也可以用一个 'system' sender
timestamp: new Date(),
};
messages.value.push(errorMessage);
} finally {
isLoading.value = false;
// 滚动到底部
scrollToBottom();
}
};
const messageListWrapperRef = ref<HTMLElement | null>(null); // 给List的父组件加个ref
const scrollToBottom = () => {
// nextTick 确保DOM更新后再滚动
nextTick(() => {
if (messageListWrapperRef.value) {
messageListWrapperRef.value.scrollTop = messageListWrapperRef.value.scrollHeight;
}
});
};
// 在 <div class="message-list-wrapper"> 上加上 ref="messageListWrapperRef"
// 并且在 messages.value.push 之后都调用 scrollToBottom()
// 比如在 finally 里调用一次就行
遇到的问题和思考:
- API 格式对齐:后端给的 DeepSeek 接口文档虽然说了基于 OpenAI 兼容,但实际请求体和响应体总有些细微差别。
role是user/assistant还是别的?content字段名叫啥?这些都得仔细核对。我这里先按常见的user/assistant和content来写。 - 错误处理:网络请求总可能失败。AI 服务本身也可能返回错误(比如限流、内容审查、输入无效等)。
response.ok要判断,错误信息也要尽量展示出来,不然用户只会看到一直在转圈圈。 - Loading 状态:AI 响应不是瞬时的,必须有个 loading 状态,防止用户重复点击发送。
- 自动滚动:新消息来了,聊天窗口应该自动滚到底部。这个用
nextTick和scrollHeight、scrollTop可以实现。 - 上下文传递:目前是把
messages.value里所有的历史记录都传给 API了。如果对话太长,会不会超出 API 的 token 限制?或者影响响应速度?这是个隐患。后端同学说这个 DeepSeek 接口对上下文长度有优化,但具体限制没细说。先跑起来再说,后面可能要考虑截断或者做摘要。 - CORS:果然像后端同学说的,直接调他们给的内部 URL,没有遇到浏览器报跨域错误。看来他们在网关或者服务本身解决了。前端省心了!
成果: 经过一番折腾(主要是字段名对来对去),AI 终于能在我的聊天框里回复我的消息了!虽然回复还很“通用”,没有针对“面试”这个场景做任何优化,但起码证明了通路是OK的。 看到自己发的消息和AI的回复在列表里交替出现,还是有点小激动的。
下一步,就要开始琢磨怎么引导 AI 进行有意义的“面试”对话了。这就要涉及到“提示工程”(Prompt Engineering)了。感觉又是一个新世界的大门。
#AI模拟面试 #DeepSeek #API对接 #Vue3 #FetchAPI #前端开发 #小激动