做了 8 年前端,简历改漂亮后面一个挂一个:差的不是前端,是这 3 块底子

做了 8 年前端,简历改漂亮后面一个挂一个:差的不是前端,是这 3 块底子

今年四月中,一个做了 8 年前端的同学来找我。他被裁了,简历投了两周,约面反馈还不错,但连着挂了四家。

他把简历发我看。项目写得很满,React 组件库、可视化大屏、低代码搭建平台,每个都有量化结果。按前端岗的标准,这份简历没问题。

问题出在他转的方向上。他面的是 AI 应用开发岗,面试官问的却是另外一批问题。

「你这个 Agent,工具调用超时了怎么办?」 「模型返回的结果,谁来判断能不能直接写库?」 「你说首屏加载快了 40%,那 40% 是怎么测出来的?」

他一个都答不上来。原因很直接:他过去八年积累的经验里,没有这三件事。

我们后来做的事很笨:没有继续改简历,也没有再投,而是花了一个多月补底子。六月初他拿到第一个 offer,之后再没断过。

这篇文章我把那三块底子拆开讲。前端同学转 AI 时,最容易漏掉的就是这三块后端侧的东西。

一、第一块:数据链路,知道一次请求从哪进到哪出

前端同学对「请求」的理解,通常停在 fetch 发出去、拿到 JSON 渲染页面这一层。中间那段是黑盒,平时也不需要打开。

AI 应用开发的岗位要求正好相反。面试官想知道的是黑盒里面长什么样。

一个问答请求进到系统,大致要过这么几跳:

环节 干什么 这一跳最容易出的问题
入口鉴权 确认是谁在调、有没有配额 把用户身份交给模型判断
意图识别 判断这次该走哪个分支 识别错了没有兜底路径
检索(RAG) 把相关知识捞出来 召回为空时直接把空上下文喂给模型
工具调用 查库、调外部接口 超时不设上限,一次抖动拖垮整条链路
生成 模型产出结果 流式输出中断后前端白屏
落库与审计 存下来、留痕 只有结果没有过程,出问题查不回去

这张表看着基础,但面试官追问的永远是某一跳里面的细节。比如「工具调用超时了怎么办」,标准答案不是「加重试」,而是三件事分开说:

  • 重试按错误类型分:网络抖动可以重试,参数错误重试一百次也没用;
  • 重试有上限且带抖动:固定 3 次、间隔按指数退避加随机量,避免同时打爆下游;
  • 重试都失败要有兜底:给一个业务上能用的降级结果,而不是把错误抛给用户。

这三句说出来,面试官就知道你真的在生产上接过这种活。

二、第二块:工程边界,知道哪些事绝对不能交给模型

这是前端同学最容易踩的一块。前端写惯了「用户点什么就发生什么」,转到 AI 之后很容易顺势写成「模型说什么就发生什么」。

我给学员定的边界只有一句话:模型负责产出意图和参数,判断和执行必须在业务代码里。

具体到代码,是这样分层的:

go 复制代码
package agent

import (
	"context"
	"errors"
	"fmt"
	"time"
)

// ToolSpec 是工具登记信息。模型只能调用登记过的工具,
// 能不能执行由 Allow 决定,而不是由模型说了算。
type ToolSpec struct {
	Name    string
	Timeout time.Duration
	Allow   func(userID string) bool
	Run     func(ctx context.Context, args map[string]any) (any, error)
}

// reservedArgs 是模型传参里不允许出现的字段名。
// 身份和权限只由服务端填,模型给的一律拒绝。
var reservedArgs = map[string]bool{
	"userID": true, "user_id": true, "role": true, "permission": true,
}

type Router struct {
	tools map[string]ToolSpec
}

// NewRouter 在登记阶段就把不完整的工具挡掉,
// 免得运行到一半才发现 Allow 或 Run 是 nil。
func NewRouter(specs ...ToolSpec) (*Router, error) {
	r := &Router{tools: make(map[string]ToolSpec, len(specs))}
	for _, s := range specs {
		if s.Name == "" || s.Allow == nil || s.Run == nil {
			return nil, fmt.Errorf("incomplete tool spec: %q", s.Name)
		}
		r.tools[s.Name] = s
	}
	return r, nil
}

// Execute 的 userID 由服务端会话提供,不从模型入参里取。
func (r *Router) Execute(ctx context.Context, userID, name string, args map[string]any) (any, error) {
	spec, ok := r.tools[name]
	if !ok {
		return nil, fmt.Errorf("tool not registered: %s", name)
	}
	if !spec.Allow(userID) {
		return nil, errors.New("permission denied")
	}
	for k := range args {
		if reservedArgs[k] {
			return nil, fmt.Errorf("reserved arg from model: %s", k)
		}
	}
	timeout := spec.Timeout
	if timeout <= 0 {
		timeout = 3 * time.Second
	}
	cctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	type result struct {
		v   any
		err error
	}
	ch := make(chan result, 1)
	go func() {
		v, err := spec.Run(cctx, args)
		ch <- result{v, err}
	}()

	select {
	case res := <-ch:
		return res.v, res.err
	case <-cctx.Done():
		// 走到这里只代表上层放弃等待,工具那个 goroutine 还在跑。
		// 工具内部必须自己 select ctx.Done() 才能真正退出。
		return nil, fmt.Errorf("tool %s timeout after %s", name, timeout)
	}
}

这段代码里藏了四个边界,面试时都能单独拎出来讲:

  1. 白名单:没登记的工具名,模型说了也不执行;
  2. 权限在服务侧判定 :userID 由服务端会话传入,模型如果在 args 里塞 userID、role 这类字段,直接报错拒绝;
  3. 超时按工具单独设:不共用全局超时,慢工具才不会因为快工具的阈值被误杀;
  4. 超时要自己兜 :context.WithTimeout 只把截止时间传下去,取消是协作式的。工具内部不 select ctx.Done(),超时就拦不住,所以这里在 Execute 里用 select 等结果和等 cctx.Done() 二选一,超时就返回错误,上层的重试和兜底才有东西可接。

上面四个分支我都实跑过:漏填 Allow 的工具在 NewRouter 阶段就被拒;无权用户返回 permission denied;模型塞 userID 返回 reserved arg from model: userID;一个 50 毫秒超时、内部睡 200 毫秒的工具,实测 50 毫秒返回 timeout 错误而不是傻等到底。

有些同学会问:模型返回的结果能不能直接写库?我的答案一直是不能。写库、删数据、算金额、判定权限,这四类一律走业务代码,模型最多给个建议值,然后被校验。

这位同学当时的问题就在这。他的项目里,模型输出一段 JSON,前端拿到就直接渲染、直接提交。功能跑得通,但面试官一问「如果模型被人诱导输出了越权字段呢」,他就接不住了。

三、第三块:数字溯源,简历上每个数字都说得清怎么来的

第三块最容易被忽略,也最致命。

他的简历里写着「首屏加载优化 40%」。面试官问了一句「这 40% 是怎么测的」,他答的是「优化前后各点几次,感觉快了」。

问题不在数字真假,在他没有测量口径。

我让他做的一件事是:把简历上每一个数字,都补上三个字段。

数字 怎么测的 测了多少次 在什么条件下测的
首屏加载 40% Lighthouse 跑分 + 真实用户埋点 P75 30 次取中位数 4G 网络、中端机型、冷启动
召回准确率 0.82 200 条标注集上跑评测 3 轮取平均 固定切片版本、固定 embedding 模型
单次成本 0.03 元 服务端埋点累加 token 后折算 连续 7 天全量 含重试,不含人工介入的场景

上表是补完口径之后的写法示例,不是该学员的实测值,填的时候请换成自己的数。

补完之后他自己的感受是:有三个数字他不敢写了,因为测不出来。这三个最后从简历里删掉了。

剩下来的数字反而好讲了。面试官追问的时候,他能正面回答口径,而不是绕。

四、为什么是「补」而不是「改」

这位同学一开始想要的是「再投一波」。他想不通:简历明明改好了,约面反馈也不错,为什么就是不过。

我跟他说的是另外一句话:

着急我能理解,但标准不能降低。面试可以密集一些,能过关就过关;达不到内部标准,就讲清楚哪里需要补、为什么没过关,没过关的阶段继续学。

这不是劝他慢下来,是告诉他顺序错了。简历解决的是「有没有面试」,底子解决的是「能不能通过」。前一个已经解决了,再改一百遍也没用。

我们后来定的节奏是:面试不停,白天照常面,晚上把当天被问住的题拆成一块底子补上。这一个多月里他补的就是上面这三块,没有多,也没有少。

六月初的第一个 offer 是在杭州。之后又面了几家,最后去了北京。具体薪资我不写,只能说比他被裁前的水平高出一大截。

五、前端同学转 AI,真正能复用的是什么

写到这里要说一句公平话:前端八年的经验不是废的。

前面讲的三块里,第一块数据链路,前端同学理解起来其实比纯后端更快,因为你们天天在和「请求」打交道,只是以前不用管黑盒里面。第三块数字溯源,做过性能优化的前端本来就有埋点习惯,差的是把「我感觉快了」换成「P75 是多少」。

真正缺的只有第二块,工程边界。这一块前端几乎没有训练场景,因为浏览器里不存在「别人给我的指令要不要执行」这个问题。服务器端有。

所以如果你也是前端转 AI,我的建议是:数据链路补一遍,数字溯源养成习惯,工程边界从头学。第三块花的时间会比你想象的长。


你现在在做 AI 项目的时候,有没有哪一处是「模型说什么就执行什么」的?评论区说说,我挑典型的单独写一篇。

相关推荐
极派科技1 小时前
凌晨收到告警:MySQL CPU 100%,几千台 IoT 设备同时上报把系统打崩了
后端
南方者1 小时前
AI卡在授权?ToDesk手机点一下,不用跑回电脑
人工智能·智能手机·远程工作·远程控制
SimonKing1 小时前
数据不出本机!本地全文检索+知识图谱,可以试试这个开源项目:KnowSeek
后端·程序员
坐吃山猪1 小时前
从 Prompt 到 Harness:AI 能力边界扩展的完整脉络
人工智能·prompt
我的愿望是成为富婆1 小时前
2027校招AI产品经理能力清单:数据科学与大数据技术专业如何准备
大数据·人工智能·产品经理
空空dream1 小时前
03 | 更新:Ollama 换成 llama.cpp构建更小的离线镜像
人工智能·语言模型·llama
单向箔1 小时前
08|一套可复制的 AI Coding 流程:从需求到提交怎么走
人工智能·prompt·ai编程
寻林写彡1 小时前
从技术报告到面试入口:安全岗位简历如何用问题、关键技术与量化结果重写项目经历
面试·开源
AI棒棒牛1 小时前
YOLO26 改进:融合 LW-DETR 思想的轻量 TokenMixer,让 Neck 从局部堆叠走向局部-全局协同!嘎嘎创新!
人工智能·深度学习·yolo·目标检测