从零开发 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,错误处理主要涉及两个部分:

  1. 前端错误处理:处理网络请求失败、响应异常、数据解析错误等情况。
  2. 后端错误处理:处理参数校验失败、大模型 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();
}

这段代码的执行过程如下:

  1. 使用 fetch() 发送请求。
  2. 获取服务器返回的 Response 对象。
  3. 检查 HTTP 状态是否表示成功。
  4. 如果状态异常,使用 throw 主动抛出错误。
  5. 如果响应正常,再解析 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 应用开发中的作用。

主要知识点包括:

  1. try...catch:捕获和处理异常。
  2. throw:主动抛出异常。
  3. finally:统一执行清理操作。
  4. fetch() 与 HTTP 错误:理解网络失败和 HTTP 错误状态之间的区别。
  5. response.ok:检查 HTTP 响应状态。
  6. FastAPI 的 HTTPException:向客户端返回结构化的 HTTP 错误响应。
  7. Streaming 异常处理:应对数据流中断及界面状态恢复。

其中最重要的一点是:

错误处理不是简单地隐藏报错,而是让程序在异常发生时仍然具有明确、可控的行为。

对于 KK Chat 来说,前端负责处理用户交互和请求反馈,后端负责处理模型调用及服务异常。只有两端协同工作,才能构建稳定、可靠的聊天应用。


KJ

项目:KK Chat

系列:《从零开发 AI Chatbot》

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐