我是怎么稀里糊涂在浏览器里做出一套 LLM 编排系统的

从架构角度回看 Litseller 如何用 GPT API、React、提示词和浏览器端编排,生成一个图书目录的结构化内容。

我是怎么稀里糊涂在浏览器里做出一套 LLM 编排系统的
由 React、提示词和一连串务实的工程判断拼出来的浏览器端 LLM 编排流程

两年前,我做了 Litseller。

那会儿我没在想什么 LLM 编排,也没在想这类系统的架构。我只是在解决一个具体问题:怎么快速生成图书目录用的结构化内容。

如今回头看,我明白那本质上就是一套完整的 LLM 编排系统。只不过它没实现在后端,而是直接跑在浏览器里。

它究竟是什么

这一点得说准确。

Litseller 不是一个 LLM 服务。

它是一个带目录的普通 Web 应用,我只是在它的后台里嵌了一个 LLM,当作生成数据的工具。

全部编排都发生在一条简单的流程里:编辑界面、GPT API、JSON、校验、写回后端。

  • 没有队列。
  • 没有 worker。
  • 没有服务端编排。
  • 没有复杂的基础设施。

一切都建立在 React、提示词和一串串请求之上。

架构

系统分成三层。

  • 前端:Next.js 后台、编辑器,以及全部 LLM 逻辑。
  • 后端:.NET API、校验和持久化。
  • 存储:SQL Server 和 S3。
LLM 逻辑根本没放在后端。调用直接从浏览器发出。

生成流水线

我没有用一个大请求,而是搭了一条流水线。

  1. 确认模型是否知道这本书。
  2. 必要时把书名问清楚。
  3. 选一个分类。
  4. 生成主要信息。
  5. 挑选内容区块,比如梗概、引文、主题等。
  6. 逐块单独生成。
  7. 拼装 JSON。
  8. 把内容翻成其他语言。
  9. 校验结果。
  10. 保存最终数据。

这已经是真正的编排了,只是没有一个独立的编排服务。

为什么分块效果好

按区块生成内容,是当时最见效的决定之一。

我没让模型一口气生成整个图书页面。

  • 梗概单独生成。
  • 人物单独生成。
  • 引文单独生成。
  • 主题单独生成。

这让我对质量的把控更好,能只重做其中某一块,JSON 更稳定,出错也更少。

实际上,它变成了盖在 LLM 输出之上的一层手工版本控制。

提示词设计

提示词很简单,但是有结构。

  • 严格的 JSON 格式。
  • 清楚的指令。
  • 尽量少的玄学。

上下文是显式传进去的:书名、作者、分类、语言,以及前面的区块。

  • 没有对话记忆。
  • 没有复杂状态。
  • 没有工具调用。

最有争议的一个决定

API key 存在 localStorage 里。

原因很简单:编辑器不是对外的界面,用户极少,首要目标是快点上线,而后端编排会让系统复杂得多。

这是一个有意识的决定。风险我清楚,也接受。

另外,访问是受限的,并且通过 Cloudflare 手工做了防护。

薄弱之处

今天老实看,薄弱的地方一目了然。

  • API key 放在客户端。
  • 没有集中的限流。
  • 没有对调用的集中管控。
  • 没有重试或退避机制。
  • 没有像样的 schema 校验。
  • 错误处理很弱。
  • 没有可观测性。
  • 浏览器里塞了太多复杂逻辑。
它是一个不错的 MVP,但不是能扛生产的 LLM 平台。

可取之处

与此同时,这套东西是能跑的,也确实产出了成果。

  • 开发非常快。
  • 基础设施极少。
  • 灵活度高。
  • 通过界面完全可控。
  • 手工微调很方便。
  • 生成是模块化的。
  • 真正在生产里跑的工作流。
LLM 是一件工具,不是系统的核心。

今天我会怎么改

如果今天重做一遍,我会改掉架构。

  • 把 LLM 调用挪进后端网关。
  • 把 API key 从客户端拿掉。
  • 加上队列和重试。
  • 引入严格的 JSON schema 校验。
  • 补上日志和链路追踪。
  • 实现限流。
  • 把编排从界面里拆出来。

最大的收获

对我来说最有意思的是:我并没有去设计一套 LLM 系统。

我只是在解决一个问题。

直到后来我才意识到,自己搭出来的正是今天被称作 LLM 编排的那种架构。

有时候,好的工程判断一开始看着像混乱和直觉。要等到后来,它们才变成一套清晰的架构,可以被有意识地一点点改好。

模型内部发生了什么

以上都是围绕一个已经能用的模型做编排。有一个互动页面讲的是另一面:一个滑块带模型走过一千个训练步,从随机数字一路到预测。

语言模型是怎么学会的: 互动示意图

阅读更多