ByteFisher AI 工程化深度实战(一):AI 应用工程全景与架构选型

本系列是《ByteFisher AI 编程实战》的工程化续篇。前一个系列讲的是”怎么用好 AI 编程工具”,从概念到工具再到 Agent 应用,覆盖了 21 个主题。但那些内容停留在”能用”的层面:调通一次 DeepSeek API、跑通一个 RAG 演示、写完一个 Agent 示例,距离”生产级”还差着十万八千里。

本系列 12 篇要解决的是另一类问题:当 AI 能力真正成为业务系统的一部分时,怎么把它做成可靠、可观测、可度量、可控成本的工程。

和纯理论文章不同,本系列全程以博客真实运行的 ByteBot(AI 问答助手)和 钓鱼日记自动生成流水线 作为落地案例。每篇文章都会拿着真实代码、真实数据说话,给出改造前后的对比。本篇是总纲,先把工程全景讲清楚。

一、LLM 应用工程的四层架构

任何一个把大语言模型接进生产环境的系统,无论业务形态如何,都可以抽象成四层:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌────────────────────────────────────────────────┐
│ 应用层 UI / 渠道 / 会话 / 产品逻辑 │
│ (Next 主题悬浮球、Vercel API、前端检索) │
├────────────────────────────────────────────────┤
│ 编排层 意图识别 / Prompt / RAG / Agent / 工作流 │
│ (工具调用、多 Agent 协作、流程控制) │
├────────────────────────────────────────────────┤
│ 模型层 DeepSeek / GPT / 本地模型 / 路由 / 成本 │
│ (流式、重试、限流、缓存、量化推理) │
├────────────────────────────────────────────────┤
│ 数据层 文章索引 / 向量库 / TiDB / 会话记忆 / 评测 │
│ (posts-index.json、fish-dashboard、Langfuse) │
└────────────────────────────────────────────────┘

理解这四层,是工程化的第一步。绝大多数”AI 应用不稳定”的问题,根源都不在模型本身,而在四层的边界上:

  • 模型层问题:Token 成本失控、超时、限流、偶发幻觉、长上下文丢信息。
  • 编排层问题:Prompt 写死、检索质量差、上下文爆炸、Agent 死循环。
  • 应用层问题:会话状态管理混乱、并发竞争、无降级方案、无埋点。
  • 数据层问题:索引过期、无评测集、无法度量回答质量。

本系列 12 篇,本质上是自底向上 + 自顶向下各扫一遍:0203 篇修模型层和上下文,0405 篇做检索与评测,06 篇补本地推理,07~10 篇做编排与 Agent,11 篇加可观测性,12 篇收口成完整闭环。

二、ByteBot 现状架构解剖

先看博客里真正跑着的 ByteBot 是什么样子。它在生产环境由两部分组成。

2.1 前端:source/js/ai-assistant.js(999 行)

前端承担了三件事:UI 交互、本地会话管理、客户端检索兜底

关键机制一览:

机制 代码位置 说明
会话管理 maxHistoryTurns: 6 + sessionStorage 每次请求只带最近 6 轮,防止上下文爆炸
Token 预估 estimateTokens() 中文按 1.5 字/token、英文按 4 字符/token 估算
客户端 BM25 rankArticles() 本地用 BM25 算法给文章排序,k1=1.5, b=0.75
意图识别 isBlogRelatedQuestion() 关键词表 + 正则双重判定
降级兜底 showFallbackRecommendations() API 挂了时,用本地索引推荐文章
链接校验 validateLinks() 回答里的链接必须存在于索引中,防幻觉链接

特别注意 truncateMessages():它从尾部往前累计 token,超过 3000 就截断,但永远保留第一组 user/assistant。这是最简单粗暴的”上下文工程”——后面第 03 篇会升级它。

2.2 后端:api/chat.js(526 行,Vercel Serverless)

后端是一个无状态的 serverless 函数,核心链路:

1
2
3
4
5
6
7
8
9
客户端 POST /api/chat
→ sanitizeMessages(): 角色白名单 + 单条4000字符 + 总计12000字符 + 最近12条
→ buildBlogMessages():
getPostsIndex(): 读取 public/api/posts-index.json(本地优先,生产走远端,TTL 5分钟)
classifyIntent(): 意图识别 → blog / casual
rankArticles(): BM25 排序取前 8
buildAdaptiveSystemPrompt(): 动态组装 system prompt
→ 调 DeepSeek API(deepseek-chat, temperature 0.7, max_tokens 2000)
→ SSE 流式回传(空闲 15 秒超时, 整体 30 秒超时)

最值得一提的设计是 buildAdaptiveSystemPrompt()system prompt 不是写死的,而是根据当前问题意图和检索结果动态拼接的——意图是博客相关时,注入文章候选列表并强调”严禁编造”;是闲聊时,则切换成”别强行推荐文章”模式。这已经算是非常朴素的”提示词路由”。

2.3 数据层:api/posts-index.json

Hexo 构建时生成的静态索引,共 260 篇文章,每篇含 title / url / date / tags / categories / headings / summary / textForSearch 等字段。前端和后端都依赖它做检索。

它的定位是一个轻量级 BM25 全文索引——把中文拆成 bi-gram 和 tri-gram 再算 IDF。这个方案对 260 篇的规模完全够用,但检索质量天花板很低,第 04/05 篇会把这里升级成真正的混合检索 + 向量方案。

三、逐层痛点诊断

对照四层架构,ByteBot 现状的每一层都有明确短板。把痛点列出来,就是整个系列要填的坑。

3.1 模型层

  1. 无重试机制fetch 一次失败直接返回错误,Vercel 无冷启动重试、无 429 退避。高峰期 DeepSeek 偶发 429/5xx,用户只能手动点重试。
  2. 无缓存:相同问题每次都重新调用 API 付费。ByteBot 的高频问题(”有哪些 Unity 教程”)重复消费 Token。
  3. 超时一刀切:所有请求固定 30 秒,长回答(多篇文章推荐 + 解释)经常在流式中间被掐断。
  4. 成本不可见:没有任何埋点统计每轮对话消耗的 Token 和费用。无法度量就无法优化

3.2 编排层

  1. Prompt 是”打补丁”式演进chat.jsai-assistant.js 里各维护一套 system prompt,逻辑重复且版本不可追溯。没有 Prompt 版本管理,改坏了无法回滚。
  2. 无结构化输出:DeepSeek 返回纯 Markdown 字符串,靠前端 marked + DOMPurify 渲染。一旦模型输出格式漂移(比如链接写进代码块、标题层级错乱),前端就要打补丁——preLinkArticles()validateLinks()norm() 这些函数都是为了”修正模型的乱输出”而存在。
  3. 检索是单路 BM25:无语义、无重排、无查询改写。”五子棋”搜”棋类游戏”永远搜不到。

3.3 应用层

  1. 会话记忆在 sessionStorage:换设备即失忆,无持久化。
  2. 无并发控制语义debounceInterval: 1000 只是防抖,用户连点仍可能发出重复请求。
  3. 无埋点追踪:只有 localStorage 里三个计数器(opens/sends/errors),看不到单次对话的质量。

3.4 数据层

  1. 无评测集:没有人记录”哪些问题是读者真实会问的”、期望答案是什么,回答质量完全不可量化。
  2. 索引与线上文章脱节:posts-index 是构建时快照,新增文章后依赖 CI 重建,期间 ByteBot 会推荐已下线的旧文章或漏掉新文章。

这些痛点不是 ByteBot 独有的,任何从 demo 走向生产的 AI 应用都会踩一遍。本系列 12 篇就是按上面 3.1~3.4 逐条修复。

四、编排框架选型

做 Agent/多 Agent 之前,必须先回答”要不要引入框架、引入哪个”。2026 年的生态下,主流选择有四类。

4.1 四类方案对比

方案 代表 编程模型 学习成本 可控性 适用规模
图编排框架 LangGraph 显式状态图(节点+边+检查点) 复杂状态流、需持久化
角色协作框架 CrewAI 声明式(Role/Goal/Task/Crew) 多角色协作、任务分解
低代码平台 Dify / Coze 可视化编排 + 内置 RAG 快速搭建、非深度定制
自研编排 手写 async 流程 完全自由 最高 流程简单、强领域定制

4.2 选型决策树

1
2
3
4
5
6
7
流程是否需要显式状态管理 / 分支循环 / 断点续跑?
├─ 是 → 状态复杂吗?
│ ├─ 复杂(多分支、需持久化、需流式) → LangGraph
│ └─ 简单(顺序 + 少量判断) → 自研 async 流程即可
└─ 否 → 是否多个角色协作?
├─ 是(策划→写作→校对→SEO) → CrewAI
└─ 否(单 Agent 单任务) → 直接调 API + 函数调用

我的判断:ByteBot 的核心流程(意图→检索→生成→渲染)目前是顺序结构,自研 + 轻量状态机完全够用,强行上 LangGraph 是过度设计;但博客的钓鱼日记流水线(策划→写作→校对→SEO 多角色)和多 Agent 协作场景,恰恰是 CrewAI/LangGraph 的主场。所以本系列 07~10 篇会把两个框架都剖析一遍,再回到”是否引入”的决策上。

4.3 一个容易被忽视的原则

框架只解决流程控制,不解决质量。 无论选哪个框架,检索质量、Prompt 质量、评测闭环仍然是决定性因素。框架选型的正确姿势是:先想清楚”流程图长什么样”,再选能把这个图画出来的最小框架——这个原则贯穿 07~10 篇。

五、本系列 12 篇改造路线图

主题 对应痛点 落地案例
02 调用层工程化 3.1 全部 ByteBot 接入 DeepSeek 的重试/缓存/成本
03 上下文工程与结构化输出 3.1/3.2 会话记忆升级 + 检索结果结构化
04 RAG 优化(上):检索质量 3.2 检索 260 篇文章的混合检索知识库
05 RAG 优化(下):评测迭代 3.4 无评测 RAGAS + 真实评估集
06 本地模型量化与推理优化 3.1 降级 本地模型做 ByteBot 兜底
07 LangGraph 源码剖析 编排选型 文章分析 Agent
08 CrewAI 源码剖析 编排选型 钓鱼流水线改写
09 自研 MCP Server 工具层 TiDB 鱼获查询 MCP
10 多 Agent 编排工程化 编排工程化 钓鱼日记多 Agent 流水线
11 可观测性与评测 3.3 无埋点 Langfuse 追踪 ByteBot
12 综合实战:ByteBot v2 全量收敛 完整重构 + 前后对比数据

路线图的设计逻辑是先打地基再盖楼:0205 篇先把模型层、上下文、检索、评测这四块地基打牢;06 篇补兜底;0710 篇在中层做编排能力;11 篇从观测角度给前面所有工作装上”仪表盘”;12 篇一次性收口。

六、本章小结

  • LLM 应用工程可抽象为应用层/编排层/模型层/数据层四层,90% 的不稳定问题出在层与层的边界,而非模型本身。
  • ByteBot 的现状:客户端 BM25 检索 + 动态 system prompt + SSE 流式 + 会话截断,已经具备了朴素的工程雏形,但缺少重试/缓存/结构化输出/评测/观测这五块生产级拼图。
  • 编排框架选型决策树:复杂状态流用 LangGraph,多角色协作用 CrewAI,简单顺序流程用自研,追求快速验证用 Dify。
  • 框架只解决流程控制,不解决质量;质量来自检索、Prompt、评测三个闭环。
  • 本系列 12 篇按”地基→中层→观测→收口”的顺序推进,每篇都绑定博客真实项目。

下一篇进入第一块地基:调用层工程化。我会基于 api/chat.js 的真实代码,把重试、限流、缓存、成本建模逐项落地,并给出”为什么 serverless 函数更需要这些”的分析。


本系列持续更新中,每周一篇。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
ByteFisher AI工程化深度实战教程 系列
第 1/4 篇
分享: