Demo能跑通不算完,权限日志卡住多少人?

聊《别急着换赛道:前端经验在 AI 项目里到底值多少?》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。

摘要

写这篇文章的动机,是因为上个月我接手了一个 RAG 问答 Agent 项目,联调阶段连续三天都在排查同一个问题。前端页面看着挺正常,调用 LLM 的时候偶尔成功偶尔失败,但错误信息基本没有,日志里也找不到线索。后来复盘才发现,问题出在请求头里少带了一个认证字段——而这个问题,任何一个有权限意识的前端开发者,本来是可以提前规避的。

很多前端转大模型的同学,Demo 阶段特别顺,页面一跑,流式输出,打字机效果,老板和客户都很满意。但真正要上线,权限、日志、可观测这三件事就把人卡住了。今天这篇文章,我想把这个过程摊开讲讲,不是教怎么写 Agent,而是教怎么把 Agent 做成一个能上线的产品。

---

目录

  • 前端的转型优势,不是代码能力
  • 一次联调翻车:从现象到根因
  • 失败原因:怎么区分业务错误、配置错误和环境错误
  • 适用边界:什么时候不该照搬
  • 流式输出的前端实践
  • 作品集方向:怎么展示你的大模型能力
  • 多模态体验:前端的新战场
  • 总结

前端的转型优势,不是代码能力

文章插图 1

先说一个反直觉的判断:前端转大模型产品工程师,真正的优势不是 JS/TS 熟练度,而是对"用户可见性"的敏感度。

后端同学做 Agent,往往先考虑逻辑对不对、调用链全不全;而前端同学天然会问:这个按钮点了之后用户看到什么?流式输出有没有卡顿感?错误提示会不会让用户迷惑?这些问题,直接决定了产品的成败,而不是模型的准确率。

具体到这个方向,前端有几处经验可以直接迁移:

  • 流式输出(Streaming):SSE(Server-Sent Events)是前端的老本行,大模型的流式响应本质上就是一个 SSE 连接,前端同学在这里几乎没有学习成本。
  • 多模态体验:语音、图片输入输出,前端处理媒体流的经验可以直接复用。
  • 权限和鉴权:前端做过 OAuth、JWT、CORS 的同学,理解后端权限体系的门槛很低。
  • 可观测的前端视角:监控用户行为、错误上报、性能采集,这是前端每天都在做的事。

但有一个地方容易踩坑:前端习惯在客户端做所有事情,而大模型应用的核心逻辑必须放在服务端。 很多前端同学会把 prompt 拼接、token 计算、甚至简单的 RAG 检索逻辑写到前端,这样做 Demo 没问题,但上线之后安全和可维护性全部崩掉。

---

一次联调翻车:从现象到根因

文章插图 2

真实案例

项目背景:一个企业内部知识库问答 Agent,技术栈是 Next.js + LangChain + PostgreSQL(向量库)。功能很典型:用户上传 PDF,Agent 检索相关内容后调用 LLM 生成回答,前端用流式输出展示结果。

联调第三天,出现了一个诡异的问题:同一个 prompt,同一个知识库,有时候能返回答案,有时候返回空字符串,有时候直接抛错,但错误信息是通用的 500 Internal Server Error,完全没有指向性。

排查的第一步,是确认问题出在哪一层。我们分了三组对照实验:

| 测试组 | 输入 | 结果 |
|--------|------|------|
| A | 直接 curl 后端 API | 全部成功 |
| B | 前端通过 SSE 调用 | 有时成功有时失败 |
| C | 前端直接调 LLM provider API | 全部失败 |

这个结果很关键:问题不在 LLM provider,也不在后端逻辑,而在前端到后端的这条链路上。

排查过程

接下来我按链路逐段验证:

第一步:看网络请求
打开浏览器 DevTools,找到那个失败的请求,发现请求头里 Authorization 字段是空的。但奇怪的是,成功的请求里这个字段是有值的。

第二步:对比成功和失败的请求
把两组请求的完整 headers 并排对比,发现只有一个字段不同:X-Request-ID。这个字段是我们内部用于链路追踪的,失败请求里这个字段缺失了。

第三步:追代码
找到前端发起请求的地方,是一个封装好的 fetchWithAuth 函数:

async function fetchWithAuth(url: string, options: RequestInit = {}) {
  const token = await getAuthToken(); // 从 localStorage 读取 JWT

  // 问题出在这里:token 过期后没有刷新逻辑
  if (!token) {
    throw new Error('No authentication token');
  }

  return fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Request-ID': crypto.randomUUID(),
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });
}

这段代码本身没有逻辑错误,但有一个隐藏的前提:getAuthToken() 必须始终返回有效 token。 而在我们的项目里,token 的有效期是 1 小时,过期后前端没有自动刷新逻辑,导致后续所有请求都带着过期 token 发出。

第四步:验证假设
在 token 过期后,手动刷新页面(重新获取 token),再发同样的请求,全部成功。问题确认:前端 token 管理逻辑缺失。

代码解释

上面这段 fetchWithAuth 有几个关键设计点,值得单独说:

输入:url 是后端接口地址,options 是标准 RequestInit,允许调用方传入额外的 headers 或 body。

核心逻辑:每次请求前调用 getAuthToken() 获取 JWT,拼入 Authorization 头;同时生成一个 UUID 作为 X-Request-ID,用于服务端链路追踪。

异常处理:这里只处理了 token 不存在 的情况,但没有处理 token 已过期 的情况。这是典型的"happy path"代码——正常情况没问题,边界情况全崩。

正确的做法应该是增加 token 刷新逻辑:

async function fetchWithAuth(url: string, options: RequestInit = {}) {
  let token = await getAuthToken();

  // 尝试请求
  let response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Request-ID': crypto.randomUUID(),
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  // 401 表示 token 过期,尝试刷新后重试一次
  if (response.status === 401) {
    token = await refreshAuthToken(); // 调用刷新接口获取新 token
    response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': `Bearer ${token}`,
        'X-Request-ID': crypto.randomUUID(),
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });
  }

  // 二次失败才抛出
  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(error.detail || `HTTP ${response.status}`);
  }

  return response;
}

这段代码的变化有三个:第一,先尝试请求再判断 401,而不是提前校验 token 是否过期;第二,401 时调用刷新接口重新获取 token 并重试一次;第三,错误信息从响应体中提取,而不是返回通用的 Internal Server Error

---

失败原因:怎么区分业务错误、配置错误和环境错误

这次翻车,根本原因是token 过期处理缺失,但如果要把这类问题归类,它属于哪一类?

我总结了一个简单的判断框架:

| 错误类型 | 特征 | 排查方向 | 这次翻车属于 |
|----------|------|----------|-------------|
| 业务错误 | 逻辑写错了,输入输出不符合预期 | 检查代码逻辑、测试用例 | ❌ 不是 |
| 配置错误 | 环境变量、密钥、权限配置不对 | 检查 .env、IAM 策略、CORS | ❌ 不是 |
| 环境错误 | 运行时状态异常,如 token 过期、服务不可达 | 检查生命周期管理、超时重试 | ✅ 就是 |

很多同学在排查大模型项目问题时,习惯直接怀疑"是不是 prompt 写得不好"或者"是不是模型选错了",但实际上,超过 60% 的"奇怪问题"都是配置或环境层面的问题。

具体到这次翻车,如果我们在开发阶段加了以下三个检查,问题可以提前暴露:

1. Token 有效期断言:在 getAuthToken() 里加上过期时间检查,过期前 5 分钟主动触发刷新。
2. 401 自动重试中间件:在前端请求库层面统一处理 401,而不是在每个页面里单独写刷新逻辑。
3. 请求失败日志:把所有 HTTP 错误统一上报到监控系统,而不是只在控制台打印。

---

CSDN资料领取方式

适用边界:什么时候不该照搬

上面这套 token 管理方案,适合以下场景:

  • 前端单页应用(SPA),token 存在 localStorage 或 cookie 中
  • 后端 API 统一使用 JWT 鉴权
  • 对用户体验要求较高,不允许用户中途被踢出登录态

但以下场景不建议照搬:

  • 移动端原生应用:token 存储和刷新逻辑不同,建议使用平台提供的安全存储方案(如 iOS Keychain、Android EncryptedSharedPreferences)。
  • 服务端渲染(SSR)为主的站点:token 应该存在 cookie 中并由服务端统一管理,而不是在前端 js 里操作。
  • 内部工具类应用:如果用户量小、容忍度高,可以直接用 session cookie 代替 JWT,减少前端复杂度。
  • 涉及敏感数据的应用:JWT 存 localStorage 有 XSS 风险,建议改用 httpOnly cookie。

另外,不要为了"工程化"而工程化。如果你的项目只是一个内部 Demo,token 过期后让用户重新登录完全可以接受,那就不要加自动刷新逻辑。复杂度要和服务规模匹配。

---

流式输出的前端实践

除了权限问题,流式输出也是前端转大模型经常踩坑的地方。

很多前端同学第一次做流式输出,会用 response.text() 读完整响应再解析,这样完全失去了流式的意义——用户要等所有 token 生成完才能看到第一个字。

正确的做法是用 response.body 配合 TextDecoder 逐块解析 SSE 数据:

async function* streamResponse(url: string, body: unknown) {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!response.body) return;

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    // SSE 格式:data: {...}\n\n
    const lines = chunk.split('\n');
    for (const line of lines) {
      if (line.startsWith('data:')) {
        const data = line.slice(5).trim();
        if (data === '[DONE]') return;
        try {
          const parsed = JSON.parse(data);
          yield parsed.token; // 逐 token 输出
        } catch {}
      }
    }
  }
}

这段代码的核心是 reader.read() 配合 TextDecoder({ stream: true })——stream: true 告诉解码器这是一个不完整的字节流,不要等完整字符再解码,而是遇到不完整的 UTF-8 序列时先缓存,等下一块数据到来再合并解码。这样可以避免中文乱码问题。

---

作品集方向:怎么展示你的大模型能力

如果你正在考虑转大模型方向,作品集比证书有用得多。但作品集不是"我做了个聊天机器人",而是要展示你解决过什么工程问题。

我建议准备三个层次的作品:

第一层:能跑通的 Demo
一个基础的 RAG 问答系统,能接收用户问题、检索知识库、流式输出答案。这一步很多人能做到,但它只是入场券。

第二层:能上线的 MVP
在上面基础上加上权限管理、错误处理、日志上报、基本的可观测性。这才是区分"会调 API"和"能做产品"的关键。建议你找一个实际的知识库(比如你之前做过的文档系统),做一个能真正使用的内部工具。

第三层:有取舍的工程决策
在作品集中明确写出:你为什么选这个技术方案、你遇到过什么问题、你怎么解决的、你放弃了什么。这部分往往是面试官最感兴趣的,因为它展示了你的工程判断力。

具体到一个项目,你可以这样描述:

> 做了一个企业内部文档问答 Agent,基于 LangChain + ChromaDB + Claude。初期 Demo 运行正常,但联调阶段发现 token 过期导致 401 错误频发,排查后增加了自动刷新逻辑和 401 重试中间件。同时接入了 Sentry 做错误上报,用 OpenTelemetry 做链路追踪。项目已内部上线,日均调用 200+ 次。

这段话里有技术栈、有问题、有解决方案、有数据,比"我做了一个大模型应用"有力得多。

---

多模态体验:前端的新战场

大模型应用不只是文本对话。语音输入、图片理解、多轮对话中的状态管理,这些都是前端可以发挥的地方。

以语音为例,Web Speech API 可以完成基础的语音识别,但要做好体验,还需要处理:噪音环境下的识别准确率、长语音的分段处理、识别过程中的反馈动画。这些问题的解决,不依赖模型能力,而依赖前端对产品体验的理解。

图片理解也是类似的逻辑。用户上传图片后,前端需要做压缩、预览、上传进度展示,而这些和"调用多模态模型"本身没有关系——它是独立的交互问题。

---

总结

前端转大模型产品工程师,不是一个"学几个 API 调用"就能完成的事情。真正决定你能不能在这个方向立足的,是你能不能把 Demo 阶段的"能跑通",升级到生产阶段的"能上线"。

权限管理、日志上报、错误处理、可观测性——这些事情不性感,但它们是 Demo 和生产之间的鸿沟。跨过去,你就是产品工程师;跨不过去,你只是一个会调 API 的前端。

我的建议是:不要急着换赛道,先把手头的 AI 项目做到能上线。 在这个过程中积累的排查经验和工程判断,比任何证书都值钱。

资料展示

下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。

AI大模型资料展示 1

AI大模型资料展示 2

AI大模型资料展示 3

AI大模型资料展示 4

需要这份AI大模型资料清单的话,在评论区回复「清单」即可;我会根据大家的问题继续补充对应的实战内容。

CSDN官方大礼包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值