给 AI 智能体能力包一层 BFF,前端只调一个接口

3 阅读2分钟

最近重构了一个带 AI 功能的后台,前端同学跟我吐槽:调个智能体要先拿 token、再传一堆参数、还得自己拼会话上下文,文档翻得头大。我听完决定给他们包一层 BFF,结论是:前端最后只调一个接口。

BFF 解决的是「关注点错配」

Backend For Frontend 这词听着玄,本质就一句话:在前端和一堆后端能力之间,加一个专为前端服务的薄层。

之前前端要做的事:

  • 自己存 / 传 conversationId 维护多轮上下文
  • 自己处理鉴权 token 的获取和刷新
  • 智能体返回的字段一堆 snake_case,前端还要手动转 camelCase
  • 上游限流了、超时了,前端各种 if-else 兜底

这些全是后端的活儿,却漏到了前端。BFF 就是把这些收回来。

一个接口长啥样

我的设计是前端只认一个 POST /bff/assistant/ask,请求体干净到只有两个字段:

// 前端视角:清爽
interface AskRequest {
  sessionId: string  // 前端只需维护这一个
  message: string
}

BFF 内部干的脏活:

app.post('/bff/assistant/ask', async (req, res) => {
  const { sessionId, message } = req.body

  // 1. 上下文:BFF 自己从 Redis 取历史,前端不用管
  const history = await ctxStore.get(sessionId)

  // 2. 鉴权:BFF 持有上游 key,前端永远拿不到
  const upstreamPayload = buildPayload(history, message)

  // 3. 调上游智能体能力
  const raw = await callAgent(upstreamPayload)

  // 4. 字段裁剪 + 命名转换,只把前端要的字段吐出去
  const clean = pick(raw, ['answer', 'references', 'usage'])

  // 5. 顺手把这轮存回上下文
  await ctxStore.append(sessionId, message, clean.answer)

  res.json(toCamel(clean))
})

前端拿到的就是规规整整的 { answer, references, usage },多轮、鉴权、转换全在 BFF 里消化了。

真实的取舍:别把 BFF 写成大杂烩

吹了半天,说点不好的。BFF 最容易腐化成一个啥逻辑都往里塞的垃圾桶,时间一长就成了新的巨石。我的纪律是:BFF 只做编排和裁剪,不做业务决策。比如「这个用户能不能用 AI 功能」这种权限判断,我放在更下游的领域服务里,BFF 只负责把它的结果透传,不自己实现一套。

另一个坑:BFF 加了一跳,链路追踪得跟上。我吃过一次亏——出问题分不清是 BFF 慢还是上游慢。后来给每个请求带个 traceId 一路透传,排查才有了抓手。

这套为啥成立

成立的前提,是上游那个智能体能力本身就是个规整的 HTTP 接口——我用的是那类零代码搭智能体、直接发布成 API 的平台,能力是现成的,我才有底气在前面包薄薄一层。换句话说,讯飞这种 MaaS 把「模型 + 编排」打包成接口,BFF 才能只管编排不管算力。

你们的 BFF 边界划在哪?评论区掰扯掰扯。