
模拟面试里最常出现的一幕是这样的。我问「你做了 6 年 Go现在想转 AI那你最近一次面试面试官问的第一个技术问题是什么」对方通常会先愣一下然后说「问得挺杂的我也记不太清了。」记不清本身就是答案。一个准备了两个月的人如果连被问了什么都复述不出来说明这两个月的准备没有落在能被追问的地方。我带过的学员里有一位情况最典型6 年 Go地产 SaaS 背景普通本科主动裸辞出来转 AI。裸辞之后的两个月招聘平台上打招呼的人不少真正能约到面试的没几家面完的基本都是回去等通知。他自己给的结论是自己零 AI 经验岗位不匹配。我听完他复述的那三分钟面试给出的判断是另一回事卡住他的不是简历是简历下面那三块底子。一、他把四个问题当成了一个问题裸辞两个月零 offer最容易让人归错因。他归的是「没有 AI 经验」我拆出来的是四层而且这四层的补法完全不同。卡点他自己的判断面试官实际在看什么他后来具体补了什么业务匹配度低以前做地产 SaaS跟 AI 不搭边你过去解决的那类问题在我这儿还成不成立把交易链路那一段改写成幂等与回滚的约束并点明它在多轮编排里同样成立零 AI 经验没做过真正的 AI 项目你那个项目能不能扛住三层追问没有换项目就在原来那个问答机器人上补超时、重试、trace项目细节挖不深做过的东西讲不出来讲到第二层你是真做过还是在背每一步都带上耗时和重试次数追问到第三层还有东西可讲面试技巧不足一紧张就答乱数字有没有口径结论有没有边界每个数字配一句测法说不出测法的直接从简历里删掉这四层里第二层和第三层是同一件事的两面所以我给他的清单是三块底子不是四条建议。二、底子一业务匹配度不是「我学过」是「我做过的事在你这儿等于什么」他简历上最显眼的一句大意是负责地产 SaaS 核心交易链路的后端开发。这句在他原来的行业里是好句子到了 AI 岗的面试官眼里是一句跟他要面的岗位无关的话。问题不在于他没做过事在于他没有做过翻译。地产 SaaS 的交易链路本质上就是一件 AI 岗很认的事多环节、有状态、不能重跑、出错要能回滚。这不就是现在做 Agent 编排时最难的那部分吗工具调用要幂等多轮对话要保持状态失败要能定位到具体哪一步这些他干了六年。翻译这件事有固定三步不靠灵感第一步把项目里的业务名词剥掉只留约束。地产 SaaS 是业务名词多环节、有状态、不能重跑、要能回滚才是约束。第二步把约束翻译成目标岗位的行话。多环节对应编排有状态对应会话与记忆不能重跑对应幂等要能回滚对应失败补偿。第三步补一句这个约束在 AI 项目里会怎么坑人。比如多环节有状态链路最常见的问题是第二步失败了第一步已经写进库里的数据怎么办。这句话一出来面试官就知道他不是来背概念的。三步做完一个老项目就能讲出新东西而且不需要凭空造 AI 经验。这里要插一句反面例子因为它最能说明「归错因」有多贵。另一位学员是 8 年前端转 AI Agent简历改完之后约面反馈很好但面一个挂一个。他的第一反应是简历还不够漂亮于是又改了两版结果还是挂。真正的原因跟他改简历这件事一点关系都没有面试官问「你这个 Agent 的请求链路怎么走」他只能讲到调框架那一层再往下就空了。一个人以为自己缺 AI 项目一个人以为自己缺简历包装两个人都在补最上面那一层缺口都在下面。三、底子二项目深度一个能被追问三层的小项目胜过三个跑不通的大项目这是最花时间的一块也是他真正缺的那一块。他自学阶段做的是「接一个大模型 API做个问答机器人」。这类项目的问题不是简单是它没有地方可以被追问。面试官问请求链路怎么走答到「调 API、拿结果、返回前端」就到头了再问失败怎么退答加个重试再问重试几次、超时多久、依据是什么就卡住。我让他不要换大项目就在原来那个问答机器人上加三样东西超时、重试、trace。加完之后同一个项目能讲的东西完全不一样了。以下代码需 Go 1.21用到标准库 log/slog。packagemainimport(contexterrorsfmtlog/slogtime)typeStepstruct{NamestringBudget time.Duration// 这一步的总预算所有尝试加起来不超过它Attempt time.Duration// 单次尝试的超时必须明显小于 BudgetRetryintBackoff time.Duration}func(s Step)Run(ctx context.Context,traceIDstring,fnfunc(context.Context)(string,error))(string,error){// 总预算建在循环外面面试官问「这一步最多等多久」答案是一个数不是一次乘法ctx,cancel:context.WithTimeout(ctx,s.Budget)defercancel()varlastErrerrorfori:0;is.Retry;i{start:time.Now()// 单次超时建在循环里面只吃掉这一次尝试的时间剩下的预算留给重试actx,acancel:context.WithTimeout(ctx,s.Attempt)out,err:fn(actx)acancel()args:[]any{slog.String(trace_id,traceID),slog.String(step,s.Name),slog.Int(attempt,i1),slog.Duration(elapsed,time.Since(start)),}iferrnil{slog.Info(step ok,args...)returnout,nil}lastErrerr// 只有「单次超时且总预算还没用完」才值得重试iferrors.Is(err,context.DeadlineExceeded)ctx.Err()nil{slog.Warn(attempt timeout,append(args,slog.String(err,err.Error()))...)ifis.Retry{break}// 退避立刻重试只会把一次抖动放大成连续两次超时select{case-ctx.Done():return,fmt.Errorf(step %s out of budget: %w,s.Name,ctx.Err())case-time.After(s.Backoff):}continue}// 参数错、鉴权错这类错误重试多少次都是同一个结果只会放大调用量。// 真实项目里还要把限流和 5xx 单独识别出来它们属于可重试。slog.Error(step failed,append(args,slog.String(err,err.Error()))...)return,err}return,fmt.Errorf(step %s failed after %d attempts: %w,s.Name,s.Retry1,lastErr)}funcmain(){// 整条链路先给一个总预算再往下分给每一步ctx,cancel:context.WithTimeout(context.Background(),25*time.Second)defercancel()// 一个最小的问答调用链先检索再让模型回答retrieval:Step{Name:retrieval,Budget:2*time.Second,Attempt:800*time.Millisecond,Retry:1,Backoff:100*time.Millisecond}model:Step{Name:model,Budget:20*time.Second,Attempt:8*time.Second,Retry:1,Backoff:200*time.Millisecond}traceID:demo-0001chunks,err:retrieval.Run(ctx,traceID,func(ctx context.Context)(string,error){// 示例实现真实项目里这里是向量召回select{case-ctx.Done():return,ctx.Err()case-time.After(200*time.Millisecond):returntop3 chunks,nil}})iferr!nil{fmt.Println(retrieval failed:,err)return}answer,err:model.Run(ctx,traceID,func(ctx context.Context)(string,error){// 示例实现真实项目里这里是流式模型调用select{case-ctx.Done():return,ctx.Err()case-time.After(300*time.Millisecond):returnanswer with chunks,nil}})fmt.Println(answer,err)}这段代码本身不值钱值钱的是它带来的三个能讲的点。第一超时是两层不是一层。这里说的两层是单个步骤内部的单次尝试有自己的超时整步有总预算重试花掉的是总预算里剩下的部分。整条链路还另外有一个总预算上面代码里是 25 秒往下再分给每一步。所以预算和重试次数不能各拍各的它们之间有个不等式总预算要大于等于「单次超时乘以尝试次数」加上「退避乘以重试次数」。上面示例里检索是 2 秒对 800 毫秒乘 2 加 100 毫秒模型是 20 秒对 8 秒乘 2 加 200 毫秒两边都留了余量。这几个数是示例你自己的项目要按自己的耗时分布重算。第二每个步骤的超时只跟这一步自己的耗时分布有关上下游之间没有大小关系。检索 800 毫秒、模型 8 秒是因为模型调用本身比检索慢一个量级不是因为它在后面。常见的定法是取该步骤耗时分布的 P95 再留一倍余量。第三每一步都带 trace 字段和耗时。线上出问题时你能回答是哪一轮、哪一步、耗时多少、重试了几次而不是回答我看看日志。这三句话讲出来面试官就知道你是真做过不是看过。四、底子三面试表达每个数字都要准备一句口径这一块最容易被忽略也最容易翻车。简历上写着「优化后响应速度明显提升」我通常会问一句这个提升是怎么测出来的对方答不上来最后落在「就是感觉快了」。一句话就把前面所有的准备都打折了。他的问题也属于这一类。后来我给他定的规则是简历上每一个数字都必须配一句怎么测出来的说不出来的数字就从简历里删掉。一个能用的数字至少经得起下面三句追问每一句都要提前准备好这个数是在什么环境上测的。本地单机、测试环境、还是线上灰度三者的结论差得远。测的时候用的什么数据、多少条。比如几十条手挑的样本和几千条真实分布得出来的数不是一个东西。有没有对照。没有对照组的提升等于没有提升你连跟谁比都说不清这个数就不该写进简历。三句话准备完你会发现简历上能留下的数字不多但留下来的每一个都能扛住追问。这比写满一页纸强。还有一条值得单独说模拟面试不是走流程是找缺口。我让他每次结束之后立刻把面试官问倒他的那一层原样记下来下一次从那一层开始准备。这里说的层指的是同一个问题往下追的深度第一层是你做了什么第二层是怎么做的第三层是这么定的依据是什么第四层是出了问题你怎么定位。两个月下来他一开始答到第二层就空了后来能扛住连续三层。这个变化在简历上是看不出来的。五、两个月之后发生了什么这三块底子的顺序不能乱。业务匹配度解决的是能不能被看见项目深度解决的是能不能扛住问面试表达解决的是讲出来的东西有没有说服力。先补第三块没有意义因为前两块没有内容可讲。那位 8 年前端的学员也是这样他停掉改简历、回头去补链路细节之后offer 才接连来。他按这个顺序补完之后变化来得比他预想的快。原本他的期望值定得挺保守觉得能拿个跟原来差不多的档位就不错了。结果后面一段时间里 offer 接连来了几个最后去的那家给到的比他原本的预期高出一截。入职那天他给我发消息说的还是这件事。六、如果你也在裸辞转 AI 的路上我想说一句可能会得罪人的话两个月零 offer 这个信号指向的通常不是这个赛道不行也不是你不适合而是你准备的东西跟面试官要考察的东西不在同一层。你可以先做一次自我检查不用找人把最近一次面试里被问到的三个问题写下来。如果写不出来说明你的准备还没落到能被追问的层面先补第二块底子。如果写得出来就对着每一个问题往下再问自己两层。答不到第二层的就是你的缺口所在。这个动作花不了半小时但比再投五十份简历有用得多。你最近一次面试是被问到哪一层就答不下去了评论区说说我挑几个典型的拆解。完整的《Go 后端转 AI 底子自检清单》我放在评论区置顶了。更多 Go 转 AI 的实战笔记https://wangzhongyang.com