A06第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

最小 AI 应用

最小 AI 应用 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完本课你能实现一个「解释工具」的最小闭环:一个输入框、一个按钮、四种状态(输入、等待、成功、失败),并对模型输出做人工核验。

问题场景

「输入一个术语,返回通俗解释」——界面很简单,第一次上线就翻车:用户连点按钮发出三个请求;请求失败页面毫无反应;成功了,用户把一条错误解释记进笔记。问题都不在「调用模型」,在「没有设计状态」。

核心解释

最小 AI 应用 = 四态状态机 + 一次受控调用。状态用单一字段穷举,不用一堆积布尔互相组合:

type Status = "idle" | "loading" | "success" | "error";

function Explainer() {
  const [status, setStatus] = useState<Status>("idle");
  const [term, setTerm] = useState("");
  const [reply, setReply] = useState("");
  const [error, setError] = useState("");

  async function explain() {
    if (status === "loading") return; // 防线一:等待中直接忽略
    setStatus("loading"); setError(""); setReply("");
    try {
      const res = await fetch("/api/ai/simple", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message: `用两句话解释「${term}」` }),
      });
      if (!res.ok) throw new Error(`请求失败(${res.status})`);
      const data = await res.json();
      setReply(data.reply);
      setStatus("success");
    } catch (err) {
      setError((err as Error).message);
      setStatus("error");
    }
  }
  // 界面渲染见下
}

渲染层对应四态各有出口:

return (
  <div>
    <input value={term} onChange={(e) => setTerm(e.target.value)} disabled={status === "loading"} />
    <button onClick={explain} disabled={status === "loading" || !term.trim()}>
      {status === "loading" ? "解释中…" : "解释"}
    </button>
    {status === "success" && (
      <div>
        <p>{reply}</p>
        <p style={{ color: "#888" }}>模型生成内容,请核验后再采用。</p>
      </div>
    )}
    {status === "error" && <p style={{ color: "crimson" }}>{error}(可修改后重试)</p>}
  </div>
);

四条状态纪律:

  1. 等待中禁止再次提交:禁用按钮是界面防线,if (status === "loading") return 是逻辑防线,两道都要有。
  2. 一次请求只落一种终态:success 和 error 互斥,状态机保证不会同时出现。
  3. 失败信息要能读:告诉用户「能做什么」(重试、检查配置),不是甩一个错误码。
  4. 成功不等于正确:模型输出是生成文本(A01 课),展示时附核验提醒;能附带来源(如本站的课程引用 refs)就一并给。

示例拆解

本课实验区里没有真实模型——fakeExplain 用 Promise 加延迟模拟一次调用的「耗时、成功、失败」:

function fakeExplain(word) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (word === "炸") reject(new Error("upstream")); // 模拟上游失败
      else resolve(`「${word}」的模拟解释(纯函数生成)`);
    }, 600);
  });
}

关键点:

  • 它和真实 fetch 的契约一致:返回 Promise,成功给结果、失败抛 Error。
  • 把 fakeExplain 换成真实请求,状态机代码一行不用改——这正是先设计状态再接模型的价值。
  • 想本地接真的:跑一个走 /api/ai/simple 的版本(A03 课的教学路由),前端代码原样可用。

人工核验要有清单而不是口号:事实对不对、和站内课程内容一致吗、有没有编造不存在的概念。本站助手把命中的课程引用一并返回,就是为了让你有东西可对。

动手任务

  1. 在实验区观察日志:等待中再次触发 submit 会被忽略([ignored]),确认防线生效;把防线删掉再跑一次,看会发什么。
  2. 本机把实验里的 fakeExplain 换成真实 /api/ai/simple 调用,四种状态各触发一遍(含改错密钥触发失败态)。
  3. 用真实模型连问三个术语,逐条核验解释是否准确,记录至少一处不准确之处——这个记录就是你「核验习惯」的起点。

常见错误

  • 只设计成功态:失败无提示、转圈不消失。等待、成功、失败三种都要有明确出口。
  • 用多个布尔拼状态:isLoading + isError 会出现「又在加载又出错」的非法组合;单一 status 字段穷举四态即可。
  • 等待中不设防:连点三次发三个请求,结果互相覆盖,后返回的覆盖先返回的(R26 课的竞态问题在 AI 场景重现)。
  • 把输出当权威答案直接展示:不加核验提醒,用户记了错误笔记你都不知道。

检查与复盘

自查清单:

  • 能默写四态与全部状态迁移条件
  • 能说出防重复提交的两道防线分别挡什么
  • 对每次展示的模型输出,都有核验动作或核验提醒

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 3 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

「输入术语、返回解释」的最小 AI 应用,状态设计符合课程口径的是?

Q2故障定位中等

bug 现场:「解释工具」上线后,用户连点三次「解释」按钮,发出三个请求。界面最终显示的解释与输入框里的术语对不上——显示的是第二个请求的结果,但用户以为点的是最后一个。原因与修法?

async function explain() {
  setStatus("loading");
  const res = await fetch("/api/ai/simple", {
    method: "POST",
    body: JSON.stringify({ message: term }),
  });
  const data = await res.json();
  setReply(data.reply);
  setStatus("success");
}
Q3多选简单(多选,全部选对才算正确)

最小 AI 应用进入 success 态展示模型输出时,下列做法符合「成功不等于正确」原则的有?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...