最小 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>
);
四条状态纪律:
- 等待中禁止再次提交:禁用按钮是界面防线,
if (status === "loading") return是逻辑防线,两道都要有。 - 一次请求只落一种终态:success 和 error 互斥,状态机保证不会同时出现。
- 失败信息要能读:告诉用户「能做什么」(重试、检查配置),不是甩一个错误码。
- 成功不等于正确:模型输出是生成文本(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 课的教学路由),前端代码原样可用。
人工核验要有清单而不是口号:事实对不对、和站内课程内容一致吗、有没有编造不存在的概念。本站助手把命中的课程引用一并返回,就是为了让你有东西可对。
动手任务
- 在实验区观察日志:等待中再次触发 submit 会被忽略(
[ignored]),确认防线生效;把防线删掉再跑一次,看会发什么。 - 本机把实验里的
fakeExplain换成真实/api/ai/simple调用,四种状态各触发一遍(含改错密钥触发失败态)。 - 用真实模型连问三个术语,逐条核验解释是否准确,记录至少一处不准确之处——这个记录就是你「核验习惯」的起点。
常见错误
- 只设计成功态:失败无提示、转圈不消失。等待、成功、失败三种都要有明确出口。
- 用多个布尔拼状态:
isLoading + isError会出现「又在加载又出错」的非法组合;单一status字段穷举四态即可。 - 等待中不设防:连点三次发三个请求,结果互相覆盖,后返回的覆盖先返回的(R26 课的竞态问题在 AI 场景重现)。
- 把输出当权威答案直接展示:不加核验提醒,用户记了错误笔记你都不知道。
检查与复盘
自查清单:
- 能默写四态与全部状态迁移条件
- 能说出防重复提交的两道防线分别挡什么
- 对每次展示的模型输出,都有核验动作或核验提醒
参考资料
- React 官方文档:Managing State —— 用状态而非条件堆叠组织界面
- MDN:fetch ——
res.ok检查与 try/catch - OpenAI 文档:API Reference - Chat —— 请求与响应形状
- 下一课 A07 在这个工具的基础上打磨请求本身:提示词结构。
读完这一课了?
标记完成后,工作台会安排接下来的学习。