博客.
博客.
(联系方式)

保持联系

如果你想联系我,无论是讨论合作机会、分享想法,还是只是打个招呼,都可以通过以下方式找到我。

(最后更新)

版权所有 / 2026

(快捷键)C
返回学习路线
(03 · AI Native)2026年8月

落地实践

收藏了 19 章知识点,不如亲手做完一个真实项目。这篇收官长文用「给博客加一个 AI 助手」做贯穿示例:怎么选题不烂尾、19 个知识点逐个落地并验收、能跑起来的 RAG 代码、上线清单和复盘节奏。

落地实践

为什么收官章是「动手做」

学到这里,你脑子里应该已经装了不少概念:Loop、RAG、Eval、流式、护栏……但有个残酷的事实:没在自己的项目里踩过坑的知识,都不算你的。 看十篇讲 RAG 的文章,不如亲手被「检索回来的 chunk 根本答非所问」折磨一次。道理在纸上是自洽的,一落到真实数据、真实用户、真实成本上,到处都是从书里看不来的毛边。

所以这一章不讲新知识,只讲一件事:怎么把前面 19 章真正用起来。我会用一个贯穿示例——给这个博客站点加一个 AI 助手——展示每一步怎么做决策。这个助手能回答读者关于站内文章的问题、推荐文章、陪读者聊技术选型。麻雀虽小,五脏俱全:它有检索、有生成、有工具、有 eval、有上线和迭代,整条路线的每个知识点都能在里面找到位置。


第一步:选定一个真实项目

练手项目最大的陷阱是「假」:假数据、假用户、假需求,做完往 GitHub 一扔,再也不碰。这样的项目练不出工程直觉。选项目有三个硬判据:

  • 小而完整:能在 2~4 周内做出端到端闭环(用户输入 → AI 处理 → 用户拿到结果)。「完整」比「大」重要,因为只有完整才会逼你面对部署、成本、脏数据这些真问题
  • 有真实用户:哪怕用户只有你自己和三个朋友。真实用户会提真问题、制造真 badcase,这是任何合成数据都替代不了的
  • 你自己在乎:你是它的重度用户,好不好用你有直觉判断,迭代有内在动力

「给博客加 AI 助手」刚好三条全中:博客是你自己的站点(在乎)、读者是真实用户、问答+推荐的闭环一周内能跑起来。类似的候选还有:给自己的笔记软件加语义搜索、给团队内部文档站加问答机器人、给自己常逛的社区做摘要 Agent。

先砍出一个 MVP 边界:第一版只做「基于站内文章的问答 + 文章推荐」,流式输出,不做多轮记忆、不做登录、不做生成式 UI。边界写在纸上,后续任何「顺便加个 XX」的念头都先记下来而不是立刻做。

反例:什么样的项目会烂尾

判据从正面说完了,再看四个典型的烂尾画像,每一个都对应违反了一条判据——

  • 宏大叙事型:「我要做一个 AI 驱动的个人操作系统 / 全自动炒股 Agent」。三个月过去还在搭架构,闭环永远「下周就能跑通」。死因是违反「小而完整」——项目大到你在热情耗尽之前根本看不到端到端的那条细线,而没有闭环就没有任何可验证的东西
  • 玩具数据型:拿 Kaggle 上清洗好的数据集做「智能客服」,demo 演示完美,接真实数据第一天就被打穿——真实用户不会按数据集分布提问。死因是违反「有真实用户」,假数据给你的是假信心
  • 无人使用型:「给我自己的照片集做个 AI 相册」,做完发了个朋友圈集了二十个赞,然后没人再用,包括你自己。没有真实用户就没有反馈流,没有反馈流就没有迭代的理由,项目自然死亡
  • 技术驱动型:「我想学 LangGraph / 多 Agent,所以找个项目套一下」。需求是后贴的,功能是围绕框架特性而不是用户问题设计的,学完了项目也就失去了存在意义。技术应该是手段,一旦它变成目的,项目就成了练习册,而不是作品

判断方法很简单:问自己「三个月后谁还在用它?」。如果答案里没有具体的人(哪怕只有你自己),换个选题。


第二步:把 19 个知识点逐个映射进项目

这是本章的核心。很多人学完一条路线的问题是「知识在天上,项目在地上」——下面这张映射表把它们钉在一起。左列是前面章节的知识点,中列是它在博客 AI 助手里具体落在哪,右列是验收标准——怎么算这个知识点真的落地了,而不是「我看过文档」:

章节知识点在博客 AI 助手里的落点验收标准
LLM 基本原理理解上下文窗口限制:一篇长文 + 对话历史塞不下,所以必须做检索而非全文灌入能算出单次问答的 token 构成,说清为什么不能全文灌入
模型选型问答主链路用便宜快的小模型(GPT-4o-mini / DeepSeek 级),只在用户反馈「答得差」时升级重试有一张不同模型在 eval 集上的质量/成本/延迟对比记录
Prompt Engineering系统提示词明确角色(「只基于提供的文章片段回答」),规定引用格式与拒答策略诱导编造类 eval 样本全部通过,拒答时不胡编
上下文工程每轮只携带最近 N 条消息 + 本轮检索结果,超长时摘要压缩历史连续 10 轮对话不爆窗口、不丢主题,有压缩策略在生效
Agent 核心概念判断选型:问答是 Workflow(检索→生成,固定两步),「帮我规划阅读路径」才上 Agentic Loop能向人讲清哪条链路是 Workflow、哪条是 Loop,以及为什么
规划与推理策略单轮问答几乎不需要任务分解;只在「帮我规划阅读路径」这类多步请求上,先让模型输出一份显式计划(读哪几篇、什么顺序、为什么),再逐条执行MVP 阶段论证了「为什么不需要」并写下触发条件:当请求平均需要 3 步以上工具调用时,才引入 plan-then-execute
工具调用两个原子工具:search_articles(query)get_article(slug),粒度对齐「一次原子操作」工具 schema 有描述和参数约束,模型在 20 条测试里选对工具 ≥ 90%
MCP把博客内容检索封装成 MCP Server,让 Claude Code 等外部 Agent 也能查你的博客在 Claude Code 里真的连上并成功查到一篇文章
记忆与状态会话级短期记忆存数据库;读者偏好(如「只看前端文章」)作为长期记忆写入刷新页面会话可恢复;偏好能在后续回答中被引用
RAG 实战全文核心:markdown 按小节 chunking,向量 + 关键词混合检索,答案带引用链接检索相关类 eval 样本的召回命中率 ≥ 80%,引用可点通
多 Agent 系统MVP 阶段不用——但预留接口,未来可拆出「检索 Agent」和「写作风格 Agent」说得出「为什么现在不用」的理由,而不是没考虑过
Computer Use明确不用:博客内容有 API 和文件系统可以直接读写,让模型截图点鼠标是拿最不可靠的接口干最可靠的事能讲清 Computer Use 的适用边界(无 API 的遗留 GUI 软件),并证明博客场景不属于它
主流框架用 Vercel AI SDK(和 Next.js 同生态),不裸写 SSE,也不上重型编排流式与工具调用没有手写协议解析,代码里没有框架之外的胶水
Agent 工程化建一个 30 条的 eval 问题集;每次改 prompt 或 chunking 策略后跑一遍对比eval 跑分有历史记录,能回答「这次改动是变好了还是变差了」
什么是 AI Native助手不是「加了 AI 的搜索框」,而是读者表达意图(「我搞不懂 RAG」)直接得到答案和导读新用户不看说明书就能用自然语言完成一次有效问答
AI Native 交互对话式 UI + 流式逐字输出 + 答案内嵌可点击的文章卡片(生成式 UI 的最小形态)首 token < 2s,回答中的文章以卡片呈现且可点击跳转
LLM 应用架构Next.js Route Handler + SSE 流式;模型 API key 只在服务端前端源码和网络面板里都搜不到 key,流式无代理层断裂
可靠性设计检索为空时明确说「博客里没写过」;API 超时降级为非流式;输出做引用校验三种故障注入测试(空检索/超时/断流)都有明确用户可见结果
案例拆解抄 Cursor 的作业:上下文只给「相关的」,答案永远附来源,让用户可以验证每条回答都有出处,且抽样 10 条人工核对无张冠李戴

注意表里的一个模式:每个知识点的落点都伴随着一次「做或不做」的判断。MVP 阶段多 Agent 不用上、记忆只做两层、模型只用一档——落地实践教你的不只是「怎么用」,更是「什么时候不用」。验收标准那一列同理:它的作用是防止「我以为我用了」,每一行都必须能被第三方验证。

争议:一步到位,还是小步快跑?

做这个项目时你会遇到第一个路线之争,值得把正反方都摆出来:

  • 正方(一步到位):AI 应用的质量是系统性的——检索、prompt、模型互相耦合,先上糙版本会让早期用户形成「这东西不行」的印象,而第一印象不可逆。况且 eval 集、日志、护栏迟早要做,晚做不如早做,免得返工
  • 反方(小步快跑):你对「什么叫行」的理解,只有在真实用户用过之后才是对的。闭门调三个月,很可能把 80% 的精力花在没人关心的维度上。早一天上线,就早一天开始积累真实的 badcase,而 badcase 才是质量改进的唯一可靠燃料

我的判断:链路一步到位,质量小步快跑。 端到端的细线(能问、能答、有引用)要一次搭通,不要分期——没有闭环的项目没有反馈;但链路上每一环的质量(检索准度、prompt 打磨、交互细节)必须靠上线后的真实数据迭代,不要试图在上线前调到完美。一句话:完整性提前,完美度延后。

进阶方向:造一个 Coding Agent

博客助手上线并稳定迭代一个月后,如果你想要更硬核的第二个项目,造一个能读你代码库、帮你改 bug 的 Coding Agent 是天然的下一站——它几乎复用你在博客助手里练过的每一块肌肉,但每一块都要求你练得更深。它同样满足选题三判据:用户是你自己,闭环就是「提需求 → 改代码 → 测试通过」,而你天天都在乎它好不好用。

有三个地方是博客助手没教过你的:

代码库索引。表面上是把 RAG 章的切块与检索搬过来,但切块单位变了:markdown 按「小节」切,代码要按「函数 / 类」切——按固定行数硬切会把一个函数拦腰截断,检索回来全是半成品。MVP 阶段可以取巧:按空行和函数签名行做启发式切分,够跑;等「召回的片段缺定义」这类 badcase 多起来,再上线 AST 感知的切块。检索还是老配方:关键词打底,同义问题多了再加 embedding。

diff 编辑策略。这是 Coding Agent 独有的核心决策——模型说「改这里」,你怎么落到文件上?两条主流路线,失败模式完全不同:

  • search/replace 块:模型输出「找到这段原文、替换成这段新文」,工具做精确匹配后替换。省 token、改动可审计;但失败模式是匹配不上——模型凭记忆复述的代码和文件里的真实内容在缩进、空行、注释上差一点点,整个替换就失败,需要有「模糊匹配 + 失败重述」的兜底
  • 整文件覆写:模型直接输出改完后的完整文件。永远不会匹配失败;但失败模式更阴——悄悄丢代码(长文件生成到一半开始偷懒省略)、token 成本随文件长度线性涨、diff 噪音大到无法 review

实践结论:小文件覆写、大文件 search/replace,并且每次落盘前先做语法校验(比如用对应语言的 parser 跑一遍),不通过就退回让模型重写——这是可靠性设计章节在 Coding 场景的具体形态。

diff 编辑两条路线对比:search/replace 块省 token 但可能匹配不上,整文件覆写不会匹配失败但可能悄悄丢代码

测试反馈回路。这才是 Coding Agent 的引擎,也是它和聊天机器人的本质区别:代码世界自带裁判。跑测试 → 读报错 → 改 → 再跑,这个闭环的每一圈都有一个客观的通过/失败信号,不需要人盯着打分。正因为有这个信号,Agentic Loop 在 Coding 场景才真正成立——模型可以在没有人干预的情况下自己收敛。反过来,这也是你 eval 的天然素材:一组「带 failing test 的真实 issue」就是现成的评测集,测试通过就是验收标准。

最后是一张能力复用对照,帮你看清哪些是搬家、哪些是开荒:

能力从博客助手迁移?
RAG(切块/检索/引用)直接迁移,切块单位从「小节」换成「函数」
Prompt 工程(角色/拒答/格式约束)直接迁移,约束力要求更高——输出必须能被工具解析
eval 与 badcase 复盘节奏直接迁移,且评测信号更强(测试通过与否)
流式 UI 与成本/预算控制直接迁移,几乎不用改
工具设计新挑战:工具数量从 2 个涨到十几个,粒度决定 Loop 步数
长 Loop 的上下文管理新挑战:几十轮工具调用后上下文必然爆炸,要做压缩与隔离
执行安全新挑战:Agent 能跑命令,沙箱和权限从「可选」变「必须」

做完这两个项目你会发现:知识点还是那 19 个,但每个的深度都被重新犁了一遍。这就是「做项目」和「看路线」的差别。


第三步:搭出最小闭环

核心链路就三步:检索相关文章片段 → 连同问题一起发给模型 → 流式返回答案并附引用。整个「RAG」不需要向量数据库,几十行关键词打分就够 MVP 用了——这是刻意的:先用最简单的实现把闭环跑通,把升级检索留给你上线后第一次迭代(那时候你手里有真实 badcase,知道该往哪个方向升级)。

博客 AI 助手最小闭环:读者提问经关键词检索取回片段,连同提示词发给模型生成,流式回答附引用卡片,onFinish 落日志

先是检索模块,切块 + 打分,零外部依赖:

// lib/rag.ts —— 最小 RAG:按小节切块,关键词重叠度打分
import fs from "node:fs";
import path from "node:path";

export interface Chunk {
  slug: string;
  title: string;
  text: string;
}

let cache: Chunk[] | null = null;

// 英文按词,中文按单字 + 二元组,粗糙但零依赖、可解释
function tokenize(s: string): string[] {
  const words = s.toLowerCase().match(/[a-z0-9]+/g) ?? [];
  const han = s.match(/[\u4e00-\u9fff]/g) ?? [];
  const bigrams = han.slice(1).map((c, i) => han[i] + c);
  return [...words, ...han, ...bigrams];
}

function loadChunks(): Chunk[] {
  if (cache) return cache;
  const dir = path.join(process.cwd(), "content/posts");
  cache = fs.readdirSync(dir).flatMap((file) => {
    const slug = file.replace(/\.md$/, "");
    const md = fs.readFileSync(path.join(dir, file), "utf8");
    const title = md.match(/^#\s+(.+)$/m)?.[1] ?? slug;
    return md
      .split(/^##\s+/m) // 按二级标题切块,保住语义边界
      .filter((s) => s.trim().length > 50)
      .map((text) => ({ slug, title, text: text.trim() }));
  });
  return cache;
}

export async function searchArticles(query: string, topK = 5): Promise<Chunk[]> {
  const q = new Set(tokenize(query));
  return loadChunks()
    .map((c) => {
      const tokens = tokenize(c.title + "\n" + c.text);
      const hits = tokens.filter((t) => q.has(t)).length;
      // 除以长度平方根做归一:长块不天然占便宜
      return { ...c, score: hits / Math.sqrt(tokens.length) };
    })
    .filter((c) => c.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

然后是接口层:检索先行(这是 Workflow 不是 Agent——路径固定),流式输出,引用来源随响应头下发给前端渲染文章卡片,同时在 onFinish 里落日志,为复盘积累燃料:

// app/api/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { searchArticles } from "@/lib/rag";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const question: string = messages[messages.length - 1].content;

  const chunks = await searchArticles(question, 5);

  const result = streamText({
    model: openai("gpt-4o-mini"),
    system: [
      "你是本博客的 AI 助手。只根据下面编号的文章片段回答。",
      "在用到某片段的句子末尾标注 [^序号],序号对应片段编号。",
      "片段里没有答案,就直接说博客里没写过,不要编造。",
      "",
      ...chunks.map((c, i) => `[${i + 1}] 《${c.title}》\n${c.text}`),
    ].join("\n\n"),
    messages,
    onFinish: ({ text }) => {
      // 每次问答落日志:问题、命中片段、答案 —— 复盘迭代的燃料
      console.log(JSON.stringify({
        question,
        hits: chunks.map((c) => c.slug),
        answer: text,
      }));
    },
  });

  // 引用来源随响应头下发,前端据此把 [^n] 渲染成可点击的文章卡片
  const sources = chunks.map((c) => ({ slug: c.slug, title: c.title }));
  return result.toDataStreamResponse({
    headers: { "x-sources": encodeURIComponent(JSON.stringify(sources)) },
  });
}

前端一个输入框 + 一个消息列表,消费流式响应,读 x-sources 头渲染引用卡片,总共不到一百行。第一天的目标就是让这个闭环跑通,哪怕检索还是最糙的关键词匹配。闭环先通,再逐段优化——这比「先把 RAG 调到完美再接前端」的错误顺序快十倍,因为你永远先有东西可以给真人试。

什么时候该把关键词检索升级成向量检索或混合检索?不要凭感觉,等 badcase 说话:当你发现「检索未召回」类 badcase 里大量是同义不同词造成的(用户问「怎么省钱」,文章写的是「成本控制」),关键词匹配就到天花板了,这时候上 embedding 才有明确收益;而如果 badcase 主要是「片段缺上下文」,该改的是切块而不是检索器。让数据替你选升级方向,而不是让教程替你选。


第四步:从 Demo 到上线的检查清单

Demo 和上线之间隔着一条鸿沟,鸿沟里全是「非功能性」问题。这份清单每项都附了不做的后果——多数线上事故不是因为不知道要做,而是觉得「应该没事」:

功能正确性

  • 准备 20~30 条 eval 问题集(覆盖:能答的、答不了的、诱导编造的、站外问题),每次改动后跑一遍 —— 不做:调优变玄学,你分不清是改好了还是运气好
  • 答案里的引用链接都能点通、确实来自被引文章 —— 不做:引用成了新型幻觉,比不引用更伤信任
  • 长对话(10 轮以上)不爆上下文、不丢主题 —— 不做:深度用户(最有价值的那批)恰好最先踩到

可靠性

  • 检索为空 / 模型超时 / 流中断,三种异常都有明确的用户可见文案 —— 不做:用户面对白屏或转圈,默认结论是「这产品坏了」,而不是「网络抖动」
  • 模型接口有重试与超时熔断,失败时降级为静态提示 —— 不做:上游一次抖动变成你的一次宕机
  • 断开后重发不产生重复扣费或脏状态(幂等) —— 不做:月底账单里有一笔说不清的重复消耗

成本与性能

  • 算过单笔问答成本(chunks token 数 × 单价),设每日预算告警 —— 不做:被刷接口时,你是看账单才知道的
  • 首 token 延迟 < 2s(流式) —— 不做:用户在第 3 秒关掉页面,你的答案质量再好也没人看到
  • 高频问题有缓存 —— 不做:同一个「这个博客用什么技术栈」每天付一百次模型费

安全

  • API key 只在服务端,前端零暴露 —— 不做:key 被扒走后几小时内被打满,且账单算你的
  • 提示词注入防御:用户输入与文章片段都被视为「数据」,不能覆盖系统指令 —— 不做:你的助手可以被诱导说出任何话,截图传播时署名是你的博客
  • 有频率限制 —— 不做:你成了别人免费的 GPT 代理

上线后

  • 每次问答落日志(问题、命中 chunks、用户反馈) —— 不做:没有数据,迭代全靠拍脑袋,上面所有优化的前提都不存在
  • 有反馈入口(👍/👎),badcase 能一键归集 —— 不做:用户想帮你改进都找不到入口,只能默默流失

这份清单的价值不在「全做完」,而在每一项你都做过一次决策:做、不做、还是记下以后做。明知故犯叫权衡,不知而犯才叫事故。


第五步:复盘与迭代

上线不是终点,是迭代的起点。真正有效的迭代节奏是数据驱动的小循环

  1. 每周看 badcase:把 👎 和日志里明显答歪的问答捞出来,按下表分类归因
  2. badcase 进 eval 集:每个确认的 badcase 都变成 eval 集的一条新样本
  3. 一次只改一个变量:改 chunking 就跑 eval,改 prompt 也跑 eval,但不要同时改——否则没法归因
  4. 每月回看映射表:哪些知识点真用上了?哪些判断(比如「不用多 Agent」)被验证或推翻了?写下来

badcase 驱动的迭代循环:收集 badcase、分类归因、进 eval 集、单变量修改、跑 eval 对比、上线后再收集新 badcase

badcase 分类是复盘里最见功力的部分。类别直接决定修法,分错类就会改错地方:

类别典型症状根因层修法
检索未召回答案说「没写过」,但其实写过检索改切块、加混合检索、上向量
召回但切烂引用了对的片段,但片段缺上下文,答得片面切块调 chunk 边界与重叠
提示词失控该拒答的瞎编、引用格式乱、口吻不对Prompt补系统指令 + 加对应 eval 样本
问题超范围问的根本不是博客内容产品明确拒答 + 引导,不算质量事故
能力天花板检索和 prompt 都对,模型就是答不好模型记录样本,换更强模型验证,别在 prompt 上死磕

eval 集的维护也要有节奏,否则它会腐烂:初始用你想到的高频问题写 20~30 条;每周把确认的 badcase 入库,eval 集应该单调增长,只增不删(过时样本标注停用而不是删除,留着防回归);每次改动跑全量对比,通过率下降必须说清原因才能合入;每月审查一次分布,哪类问题占比过高就补其他类,防止你只对某一类过拟合。冻结 v1 上线时的跑分作为基线,三个月后回看,那条曲线就是这个项目给你最真实的成绩单。

最后,复盘最容易被跳过的部分是写下来。不写的复盘只是感觉,写下来的复盘才能沉淀成下次选项目时的直觉。


常见误区

  • 先做宏大架构再动手:上来就设计多 Agent、插件系统、多模型路由——MVP 阶段这些全是负债,先让闭环跑起来
  • 用假数据自嗨:拿三条精心准备的 demo 问题测试当然全对,真实用户的第一句话就能把你打回原形,所以要尽早上线给真人用
  • 跳过 eval 直接调:没有 eval 集的调优是玄学,你分不清是改好了还是运气好
  • 功能蔓延:做着做着想加语音输入、图片理解、微信机器人……记住 MVP 边界,新想法先进 backlog
  • 上线即遗忘:项目最大的价值在上线后的迭代过程,不在上线那一刻

术语表

名词定义一句话直觉常见混淆
MVP能完成端到端闭环的最小产品版本砍到再砍就没有「完整体验」为止不是「半成品」:功能少但闭环完整
闭环用户输入到拿到结果的完整链路一圈能转起来,哪怕转得糙不是「流程图好看」:能跑才叫闭环
badcase真实使用中产出错误/低质结果的案例用户用脚投票投出来的错题不是 bug:模型「正常发挥」但结果差也算
eval 集用于回归验证的质量评测样本集合项目的错题本 + 模拟考卷不是测试集:会随 badcase 持续增长
验收标准判断某个能力是否真正落地的可验证条件第三方能照着检查,不靠自我感觉不是需求描述:需求说做什么,验收说怎么算做完
单变量调优每次只改一个因素并对比效果像对照实验,改一个量才知道是谁起的作用不是效率低:同时改多个才是真的慢
基线冻结的历史版本跑分,用于对比成绩单上的第一条刻度线不是目标:基线用来防退化,不是用来达标的
降级核心依赖故障时切换到更弱但可用的方案飞机引擎坏一个,滑翔也要落地不是失败:用户无感知地拿到次优结果才是降级
幂等同一操作执行多次与一次效果相同重试的保险丝不是重试本身:重试是行为,幂等是行为安全的前提
提示词注入用户或外部内容试图覆盖系统指令的攻击别人往你助手耳朵里塞小纸条不只是「越狱」:被检索文档里的恶意内容也算
引用校验验证答案引用的来源真实且相关让每句话能拿出证据不等于消除幻觉:校验的是出处,内容仍可能错
迭代基于反馈的小步改进循环改一点、量一次、再改不是返工:返工是推翻重来,迭代是在闭环上生长

参考材料


小结

这一章没有新知识,只有一个方法论:选一个你真实在乎的小项目,把 19 个知识点逐个钉进去并给出验收标准,用检查清单从 Demo 扛到上线,再用 badcase 驱动的迭代让它真正变好。 路线走完了,但你的第一个真实项目才刚开始——那才是学习的本体。