从零开发 AI Chatbot(KK Chat):Error Handling 错误处理机制
从零开发 AI Chatbot(KK Chat):Error Handling 错误处理机制
一、前言
在之前的开发中,我们已经学习了 OpenAI SDK、FastAPI、Fetch API、Promise、Async/Await 等知识,并逐步完善了 KK Chat 的聊天流程。
正常情况下,一次 AI 对话大致经过以下步骤:
用户发送消息
|
v
前端发送 HTTP 请求
|
v
FastAPI 接收请求
|
v
调用大模型 API
|
v
返回 AI 回复
|
v
前端渲染消息
但是,真实的网络环境并不总是稳定的。
例如:
- 网络连接失败,前端无法访问后端。
- FastAPI 服务没有启动。
- 大模型 API 返回错误状态码。
- API Key 无效,导致身份验证失败。
- 返回的数据格式不符合预期。
- Streaming 过程中连接意外中断。
如果程序没有处理这些异常,用户可能只能看到控制台报错,甚至发现发送按钮一直处于禁用状态。
因此,今天我们来学习 Error Handling(错误处理机制),让 KK Chat 在发生异常时,能够及时反馈错误,并尽可能恢复到正常状态。
二、什么是 Error Handling?
Error Handling 是指程序在运行过程中发现错误或异常时,通过捕获、判断和处理等方式,控制程序后续行为的机制。
需要注意,错误处理并不意味着能够消除所有错误,而是让程序在错误发生时具备可预期的处理方式。
例如:
发起 AI 请求
|
v
请求成功?
/ \
是 否
| |
v v
显示回复 捕获异常
|
v
记录错误
|
v
提示用户
|
v
恢复界面
对于 KK Chat,错误处理主要涉及两个部分:
- 前端错误处理:处理网络请求失败、响应异常、数据解析错误等情况。
- 后端错误处理:处理参数校验失败、大模型 API 调用异常、服务端内部错误等情况。
前后端都需要处理异常,因为前端无法完全控制后端,而后端也无法保证网络和客户端始终正常。
三、JavaScript 中的 try…catch
在 JavaScript 中,try...catch 是最常见的异常处理结构。
1. 基本语法
try {
// 可能发生异常的代码
} catch (error) {
// 异常发生后执行的代码
}
try 用于执行可能发生异常的代码。
如果执行过程中抛出异常,程序就会进入对应的 catch 代码块。
例如:
try {
JSON.parse("invalid json");
} catch (error) {
console.error("解析失败:", error);
}
这里传入的字符串不是合法的 JSON,因此 JSON.parse() 会抛出异常,程序进入 catch。
如果没有捕获这个异常,它就可能继续向调用方传播,最终表现为未处理的异常。
2. catch 中的 error 是什么?
error 是捕获到的异常对象,常见的 Error 对象包含:
error.name:错误类型。error.message:错误描述。error.stack:调用堆栈信息,通常用于调试。
例如:
try {
throw new Error("AI 请求失败");
} catch (error) {
console.log(error.name);
console.log(error.message);
}
输出结果:
Error
AI 请求失败
这里的 throw 用于主动抛出异常,new Error() 则创建了一个错误对象。
需要注意,try...catch 不能自动捕获所有异步错误。例如,普通的 try...catch 无法直接捕获稍后由独立定时器回调抛出的异常。对于 Promise 和异步函数,需要正确使用 await、.catch() 等方式处理错误。
四、finally:确保请求结束后恢复状态
上一阶段学习 Loading State 时,我们使用了 isLoading 控制加载状态。
当请求成功时,需要关闭加载提示;当请求失败时,也必须关闭加载提示。
这时就可以使用 finally。
try {
// 执行请求
} catch (error) {
// 处理异常
} finally {
// 无论成功还是失败,都执行必要的清理操作
}
例如:
async function sendMessage() {
setLoading(true);
try {
await requestAI();
} catch (error) {
console.error("请求失败:", error);
} finally {
setLoading(false);
}
}
这里的 requestAI() 代表项目中的异步请求函数。
由于使用了 await,当 Promise 成功完成或以异常结束时,程序才会继续执行后续逻辑,随后进入 finally 完成清理。
因此,finally 特别适合:
- 恢复发送按钮。
- 关闭加载动画。
- 清理临时状态。
- 结束一次请求对应的 UI 生命周期。
关键点:不要只在请求成功时恢复界面状态。
否则,请求失败后,界面可能一直停留在加载中。
五、Fetch API 如何处理错误?
这是前端调用 AI 接口时非常重要的知识点。
1. 网络错误与 HTTP 错误并不相同
很多初学者会认为,只要服务器返回错误状态码,fetch() 就会自动进入 catch。
实际上并不是这样。
fetch() 通常会在网络请求无法正常完成时拒绝 Promise,例如连接失败或请求被中断。
但当服务器返回 HTTP 400、401 或 500 等状态码时,fetch() 通常仍然会正常返回一个 Response 对象。
因此,我们需要主动检查 response.ok。
2. 正确检查 HTTP 响应
async function requestAI() {
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
message: "你好"
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
这段代码的执行过程如下:
- 使用
fetch()发送请求。 - 获取服务器返回的
Response对象。 - 检查 HTTP 状态是否表示成功。
- 如果状态异常,使用
throw主动抛出错误。 - 如果响应正常,再解析 JSON 数据。
其中,response.ok 在 HTTP 状态码处于 200–299 范围内时为 true。
通过主动检查状态码,就可以让 HTTP 错误进入统一的异常处理流程。
需要注意,HTTP 请求成功并不意味着业务处理一定成功。例如,服务器也可能在 HTTP 200 响应中返回业务错误,因此仍然需要根据接口约定检查响应内容。
六、为 KK Chat 建立完整的请求处理流程
将前面学到的知识组合起来,就可以构建一个更完整的请求处理流程。
async function sendMessage(message) {
if (isLoading) {
return;
}
setLoading(true);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ message })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
// 根据项目的响应结构显示 AI 回复
renderAssistantMessage(data);
} catch (error) {
console.error("AI 请求失败:", error);
showErrorMessage("请求失败,请检查网络或稍后重试。");
} finally {
setLoading(false);
}
}
这里的 isLoading、setLoading()、renderAssistantMessage() 和 showErrorMessage() 是示意性的项目函数,需要根据 KK Chat 现有代码实现或替换。
这段代码体现了一个完整的异常处理思路:
- 请求前检查状态,避免重复提交。
- 请求过程中显示加载状态。
- 检查 HTTP 响应。
- 解析数据并渲染消息。
- 捕获异常并反馈给用户。
- 最后统一恢复界面状态。
实际项目还需要结合已有的消息渲染逻辑,避免错误提示被当作正常的 AI 回复处理。
七、后端 FastAPI 如何处理异常?
前端可以捕获请求错误,但后端也需要处理自身的异常。
例如,FastAPI 在调用大模型接口时,可能遇到网络超时、身份验证失败、额度不足等问题。
可以使用 HTTPException 返回符合 HTTP 语义的错误响应:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/example")
async def example():
try:
result = await call_ai_model()
return {"result": result}
except TimeoutError:
raise HTTPException(
status_code=504,
detail="模型请求超时"
)
这里的 call_ai_model() 是示意函数,需要替换为项目中真实的大模型调用逻辑。
当模型调用超时并被对应的异常类型捕获后,FastAPI 就会返回 HTTP 504 响应。
需要注意,不同 SDK 和网络客户端的超时异常类型可能不同,实际项目应根据使用的库捕获相应异常,不能假设所有超时都会抛出 Python 内置的 TimeoutError。
对于其他异常,可以根据情况返回合适的 HTTP 状态码,或使用统一的异常处理器。
同时,不建议直接把完整的内部异常堆栈、API Key 或其他敏感信息返回给用户。详细错误应记录在服务端日志中,用户只需要获得安全、清晰的提示。
八、常见错误类型与处理思路
| 错误类型 | 可能原因 | 处理思路 |
|---|---|---|
| 网络请求失败 | 网络中断、服务不可达 | 提示用户检查网络,并允许重试 |
| HTTP 401 | 身份验证失败 | 检查 API Key 或身份凭证 |
| HTTP 429 | 请求频率或额度限制 | 根据服务端信息提示用户,必要时采用退避重试 |
| HTTP 500 | 服务端内部异常 | 记录日志,向用户展示通用错误提示 |
| JSON 解析失败 | 返回内容格式不符合预期 | 检查响应格式与后端接口约定 |
| 请求超时 | 模型响应过慢或网络延迟 | 配置合理的超时时间,必要时允许用户重试 |
| Streaming 中断 | 连接关闭或数据读取失败 | 结束当前流处理,恢复界面状态并提示用户 |
需要注意,这张表提供的是常见排查方向。具体原因仍然需要结合 HTTP 状态码、服务端日志和实际异常信息判断。
九、Streaming 场景下的错误处理
KK Chat 已经涉及 Streaming,因此还需要考虑流式响应中的异常。
普通请求通常在获得完整响应后再处理数据,而 Streaming 会在读取过程中持续接收数据片段。
因此,错误可能发生在:
- 建立连接之前。
- 接收部分数据之后。
- 解析某个数据片段时。
- 读取数据流的过程中。
例如,AI 已经输出了一部分内容,但连接突然中断。
此时,前端不能简单地认为这是一条完整回复。
更合理的处理流程是:
开始读取数据流
|
v
持续接收数据
|
v
读取是否正常结束?
/ \
是 否
| |
v v
完成回复 处理异常
|
v
记录错误状态
|
v
恢复界面
对于 Streaming,需要在读取循环之外设置适当的异常处理,并在 finally 中清理加载状态等资源。
如果需要支持用户主动停止生成,还可以进一步研究 AbortController,通过取消请求来终止前端的读取操作。
十、今天的收获
通过今天的学习,我进一步理解了错误处理在 AI 应用开发中的作用。
主要知识点包括:
try...catch:捕获和处理异常。throw:主动抛出异常。finally:统一执行清理操作。fetch()与 HTTP 错误:理解网络失败和 HTTP 错误状态之间的区别。response.ok:检查 HTTP 响应状态。- FastAPI 的
HTTPException:向客户端返回结构化的 HTTP 错误响应。 - Streaming 异常处理:应对数据流中断及界面状态恢复。
其中最重要的一点是:
错误处理不是简单地隐藏报错,而是让程序在异常发生时仍然具有明确、可控的行为。
对于 KK Chat 来说,前端负责处理用户交互和请求反馈,后端负责处理模型调用及服务异常。只有两端协同工作,才能构建稳定、可靠的聊天应用。
KJ
项目:KK Chat
系列:《从零开发 AI Chatbot》
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)