1. 转岗三个月,日常研发的技术栈其实很“朴素”
三个月前我还在一家传统软件公司写业务后端,每天和订单、权限、报表打交道。当时听说AI应用开发很热,岗位多、薪资涨幅也明显,我就动了转岗的念头。说实话,最焦虑的不是“大模型会不会取代我”,而是“我能不能跟上那帮天天讲Agent、RAG、微调的人”。
真正入职做AI应用开发之后,我发现自己被打脸了。前两周我诚惶诚恐,以为每天要训练模型、调权重、研究显存优化。结果实际工作是:写一个对话接口,把用户的输入整理成请求体,带上历史消息,发给大模型HTTP接口,再接收流式返回,传给前端逐字渲染。再复杂一点,就是做一个知识库问答,先向量化文档,存到向量数据库,用户提问时先检索再拼Prompt。其中80%的技术栈,我在大学课程和当初报的网课里都见过对应章节。
我先用一张表说明“我以为的技术栈”和“实际用的技术栈”之间的差距。
| 日常研发动作 | 实际用到的技术 | 课程里的对应章节 |
|---|---|---|
| 登录、会话保持 | Cookie / Session / Token | HTTP协议、Web开发基础 |
| 大模型逐字回复 | SSE、Fetch API、AbortController | 数据流、事件驱动、网络编程 |
| 用户停止生成 | 中断请求、释放连接 | 异步编程、资源管理 |
| 多轮对话上下文 | 滑动窗口、token裁剪 | 数据结构、内存管理 |
| 知识库检索 | 向量检索、倒排索引 | 数据库索引、搜索原理 |
| 多Agent协作 | 状态机、消息队列、工作流 | 操作系统进程/线程、设计模式 |
| 提示词模板化 | 模板引擎、配置管理 | 软件工程、模板模式 |
| 接口稳定性 | 重试、超时、限流、熔断 | 分布式系统、网络可靠性 |
和我一起转岗的同事也发现,大家在课上学过的知识点并没有过期,只是换了个包装出现在日常任务里。最典型的例子就是SSE。我读书时学HTTP轮询、长连接、数据流,当时觉得抽象,考试一过就忘。到了AI应用开发场景,大模型回答不可能一次性返回,必须用流式输出。你只要把“流”这个概念理解透了,再看SSE协议就是水到渠成。
所以我越来越觉得,所谓“AI应用开发”,本质上是“用AI能力解决业务问题”的应用开发,它的底座仍然是计算机基础。课程里讲过的道理,一个都没少,只是以前没有具体业务场景去承载,学的时候觉得悬浮,做的时候才发现全都能落地。
2. 当初上课“听不懂”的那些原理,现在成了排查问题的地图
很多人转岗的时候有个误区:把大量时间花在背新名词上,LangChain、LlamaIndex、AutoGPT学了无数个,真遇到线上问题却一脸懵。我自己就踩过这个坑,后来发现,每次排查问题的思路,最后都回到了课程里讲过的计算机基础原理。
先说一个真实场景。我做的第一个功能是“流式问答”,需求简单:用户发一条消息,大模型开始回答,页面上文字要一个一个字蹦出来,不能等全部生成完才显示。听起来很简单,实际跑起来发现两个问题。
第一个问题是“前端怎么拿到增量数据”。我一开始用普通POST请求等完整返回,用户反馈体验很差,一条长回答要等七八秒才出现。后来我想到课程里讲过的HTTP流式响应,服务端可以持续向客户端推送数据块。顺着这个思路去查,发现SSE就是干这个的。EventSource的标准行为是单向文本流,虽然课程不是专门讲SSE,但“HTTP响应体是一个可以持续写入的数据流”这个概念,上课时确实反复强调过。概念一旦在脑子里有了地图,查文档都快很多。
第二个问题是“用户点停止,请求并没有真正停下”。我最初以为只要前端断开连接就行,结果服务端的大模型请求还在继续跑,白白消耗token费用。后来查资料发现,前端需要使用AbortController来真正取消Fetch请求,同时后端也要监听客户端断开事件,主动取消上游模型调用。这里涉及的就是异步编程和资源释放。课程里讲“取消异步操作”“释放文件句柄”“处理超时”时我都觉得是死记硬背,到真实项目里,每一条都变成了钱和体验。
还有一次排查“多轮对话越聊越慢”,也是靠课程里的滑动窗口概念解决的。模型输入长度有限,对话一长就得把历史消息裁剪,只保留最近几轮。很多人以为这是Prompt工程,其实背后的思路就是“有限容量下的缓存淘汰策略”,LRU、FIFO,操作系统课和数据结构课都讲过。你去看那些常见的上下文管理工具,核心思路和淘汰算法没有本质区别,只是把内存换成了token。
再说Agent。现在很多人一说Agent就觉得神秘,什么规划、推理、工具调用。我自己拆解下来,多步任务执行就是一套状态机:一开始是“等待用户输入”,然后进入“规划任务”,再到“调用工具”,之后回到“评估结果”,出错则进入“重试或失败”。这个状态流转图,软件工程课讲状态模式的时候讲过无数遍。谁掌握了状态建模,谁写Agent编排就顺手;谁只背框架API,换个场景就抓瞎。
我不否认新东西的存在,模型推理、Embedding、向量检索确实是这些年才普及的。但这些新东西大部分是“基础概念的组合”:向量检索是基于索引和排序,Embedding本质是特征表示,模型调用是远程API。课程没有直接讲“怎么把大模型接到网页上”,但课程讲了网络通信、数据处理、状态管理、异常处理,这些组件攒在一起就是AI应用开发。
3. 从“听过大纲”到“写出生产代码”:流式对话功能的完整复盘
光说原理容易飘,我拿自己做过的“实时渲染大模型回答”功能完整复盘一遍,看看课程知识是怎么一步步变成生产代码的。
3.1 需求拆解:从一句话到技术方案
产品经理丢过来一句话:“希望用户在对话框提问之后,答案像打字机一样显示出来,最好能中途停止。”接到需求,第一件事不是写代码,而是拆成几个子问题:
- 大模型的回答是异步生成的,一次性返回会让用户等很久,要考虑数据分块传输。
- 前端不能等全部数据到达再渲染,要支持增量更新页面。
- 用户可能点“停止”,前端要能中断请求,服务端要能释放资源。
- 如果中途断网或服务端报错,要有重试或提示机制。
这四个子问题,每一个都能落到课程知识点上:HTTP流、事件驱动、异步取消、错误处理。我自己复盘的时候发现,能做到这一步本身就需要“把业务问题翻译成技术问题”的能力,这不是某个框架教你的,而是算法课、软件工程课反复训练过的。
3.2 后端实现:用SSE把数据“流”出去
我用的后端是Python FastAPI,大模型接口是标准HTTP流式接口。核心代码大概长这样:
from fastapi import FastAPI from fastapi.responses import StreamingResponse import httpx app = FastAPI() async def generate_answer(prompt: str): async with httpx.AsyncClient() as client: async with client.stream("POST", "https://api.example.com/v1/chat", json={ "model": "some-model", "messages": [{"role": "user", "content": prompt}], "stream": True }) as resp: async for line in resp.aiter_lines(): if line.strip(): yield f"data: {line}\n\n" @app.post("/chat/stream") async def chat_stream(prompt: str): return StreamingResponse(generate_answer(prompt), media_type="text/event-stream")这里每一步都是课程内容。async for是异步迭代器,操作系统课程讲IO多路复用;StreamingResponse是响应体流式写出,网络编程课讲数据流时提过;media_type设置成text/event-stream是SSE的协议标识,协议分析课常用的思路是“先看报文头”。
当时我查资料时才发现,SSE本质上就是HTTP长连接,服务端往同一个响应体里持续写数据,客户端不断读。和课程里讲的TCP字节流模型几乎一致。所以后端实现并没有用到什么神奇的黑科技,只是把“响应”从一次性对象变成了持续数据流。
3.3 前端实现:增量渲染和用户中断
前端这边我用的是原生JavaScript加一点点React。关键是两部分。
第一个是接收流式数据。用fetch而不是EventSource,因为EventSource不能自定义请求头,也难处理POST请求,而实际项目里我们的会话鉴权放在Header里。这里就用到课程里讲的“协议选型要看业务约束”:标准方案不满足需求时,要能从底层原理推导出替代方案。
const controller = new AbortController(); const response = await fetch("/api/chat/stream", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ prompt: userInput }), signal: controller.signal }); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n\n"); buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { const data = JSON.parse(line.slice(6)); setAnswerText(prev => prev + data.choices[0].delta.content); } } }reader.read()返回一个Promise,每读到一块数据就更新一次页面,这就是“增量渲染”。信号量AbortController用来处理用户点击“停止”按钮,触发中断。
这里有个特别容易踩的坑:流式数据的切分不能简单按行处理,因为一个数据块可能被TCP拆成多次到达,也可能一次到达多个数据块。我的做法是先维护一个缓冲区,等到完整的分隔符出现再解析。这个思路在课程里对应的是“粘包和拆包”问题,计算机网络实验课做过类似的字节流解析练习。当时觉得无聊,现在才知道这就是真实生产的常态。
3.4 中断请求:不只是前端关掉页面
如果只是前端把请求停掉,后端还在继续把上游数据取回来,资源就白白浪费了。需要在后端也监听客户端断开事件。FastAPI里可以用request.is_disconnected(),或者在生成器里捕获异常来停止上游调用。
@app.post("/chat/stream") async def chat_stream(prompt: str, request: Request): async def event_source(): async with httpx.AsyncClient() as client: async with client.stream(...) as resp: async for line in resp.aiter_lines(): if await request.is_disconnected(): break yield f"data: {line}\n\n" return StreamingResponse(event_source(), media_type="text/event-stream")这里对应的课程知识是“资源管理和协作式取消”:一方停止消费,生产者应该察觉并停止生产,否则就是内存泄漏和钱的问题。并发编程课讲线程协作、中断标志位时,例子用的是循环打印,现在换成了流式接口,底层逻辑一模一样。
3.5 复盘:代码行行都能找到出处
整个功能写下来,我发现自己没发明任何新技术。前端用fetch处理流、后端用异步生成器、协议用SSE、中断用AbortController,全部是标准库里就有的能力。那为什么课程教过还是觉得不会?因为课程只讲“HTTP请求有GET和POST”,没讲“怎么用POST把用户问题发给大模型”;只讲“事件流”,没讲“逐字渲染需要把JSON按行切分”。但如果你连HTTP和事件流都不熟,遇到这类需求就完全无从下手。
4. 真正拉开差距的,是课程里没细讲的“工程化组合拳”
如果说前面这些还属于“课程基本盘”,那接下来这部分,是转岗三个月里我觉得最值得总结的东西:课程教了零件,但没教怎么把零件组装成一套可靠系统。AI应用开发的门槛不在于某个单一技术点,而在于一堆技术点的组合与取舍。
4.1 提示词和上下文管理:接口调用的“内存管理”
很多人以为写Prompt就是会说话,其实在生产环境里,Prompt是被当成代码管理的。我们要维护一个模板仓库,每个模板包含系统指令、用户案例、工具说明、输出格式。这就像课程里讲的“配置与代码分离”,只是配置内容变成了自然语言。
更麻烦的是上下文管理。对话系统每轮都要把历史消息塞给模型,但模型输入长度有限,所以需要在“保留关键信息”和“控制token成本”之间做取舍。我通常会按角色把消息分组:系统提示、历史问答、检索片段、当前问题,然后按优先级裁剪。说白了就是做内存淘汰,只是淘汰单位变成了消息条数或字符数。操作系统课的LRU思想在这里完全能用上。
4.2 缓存和去重:减少重复调用才是省钱关键
大模型接口按token计费,同样的用户问题,如果不在缓存层拦一下,公司账上会很难看。我们团队做法很简单:把“用户ID + 问题哈希”作为Key,结果写入Redis并设置过期时间;完全一样的问题在过期时间内直接命中缓存,不再调用模型。
另外还有一个容易忽略的坑:用户快速点击两次提交,可能同时发出两个一模一样请求。我们加了幂等键(Idempotency Key),后端拿到相同的Key就复用第一次请求的结果。这就是分布式系统课程里讲的幂等设计,我以前只在支付系统里用过,没想到在AI应用里也这么普遍。
4.3 并发与限流:别让模型接口被浏览器打爆
页面一上线,用户量稍微上来,问题就变了:模型服务有QPS限制,超过会返回429;模型本身响应慢,如果所有请求都同步等待,后端线程池很快耗尽。我们做了三层保护:网关层做IP限流,应用层做用户维度并发控制,服务层做降级和排队。
控制并发时,我第一时间想到的是信号量和令牌桶。课程里讲生产者消费者模型时,我写过基于队列的任务调度Demo。现在做的事情是同样的逻辑:维护一个异步队列,设置最大并发数为5,多余请求排队等待;当队列超过100,直接返回“系统繁忙”。这些代码用asyncio.Queue和Semaphore就能实现,几乎是把课程作业搬上线。
4.4 测试和回滚:不可能每次都调真模型
有一件事很多转岗新人没意识到:测试阶段不能每次都调真实大模型,因为慢、贵、且答案不确定。我们组采用的是分层测试策略:
- 单元测试:用Mock对象模拟模型返回值,只测业务逻辑。
- 集成测试:用固定Prompt和固定输出样本,跑通全链路。
- 线上灰度:先让5%用户走新模型版本,对比回答质量和延迟。
这里用到的能力包括接口Mock、断言、配置开关、灰度发布。软件工程课讲测试金字塔、讲Mock的时候,我从来没认真听过。现在发现,在AI应用里,Mock比传统后端更重要,因为你根本无法预测模型今天会给出什么答案,不稳定输出只能是可测试的。
4.5 可观测性:一条请求要能定位到“哪一环”出问题
传统后端排查慢,最多是SQL慢查询。AI应用出现“回答很慢”时,你得分清楚慢在哪一环:是用户网络慢,还是模型排队慢,还是Prompt里带的知识库检索太久,还是前端渲染卡了。
我们最后给每条请求都生成一个trace_id,进入网关时创建,传给后端服务、模型调用、检索服务,最后带到前端响应头。申请一个完整请求链路,我们能看到:
| 环节 | 耗时 | 说明 |
|---|---|---|
| 鉴权 | 5ms | Redis会话校验 |
| Prompt拼接 | 2ms | 内存操作 |
| 向量检索 | 80ms | 查询向量数据库 |
| 模型调用 | 3200ms | 大模型流式生成 |
| 前端渲染 | 1800ms | 与模型生成并行 |
没有这套链路追踪,线上用户说“慢”,你根本不知道从哪查起。这个思想在微服务课程和运维课程里都提到过,我当时觉得那是架构师才用得上的东西,现在发现自己这种小开发也要天天用。
4.6 课程没讲的部分:成本预算、模型选型、产品判断
当然,课程也不是万能的。真正让我觉得大学没教的,是“怎么判断这个问题该不该用大模型”。比如用户问“订单为什么没发货”,你完全可以用规则引擎或查数据库直接回答,没必要调大模型;而用户问“帮我写一封给客户的道歉邮件”,才需要大模型生成。
模型选型也是一样:简单分类任务用小模型便宜又稳定,开放问答用大模型效果好但贵。课程里不会教你按价格和效果做技术选型,这需要实际项目的数据积累。但从应用开发的角度,懂业务、懂成本、懂取舍,恰恰是转岗之后能走多远的分水岭。
5. 给想转岗的同学的三个月实操清单
我知道很多人在犹豫要不要转岗AI应用开发,也很多人问“是不是要先学Python、再学PyTorch、再读论文”。从我三个月的经验来看,这条路最大的误区就是把时间花在“学最新框架”上。我的建议是,用三个月时间扎扎实实做一个最小可用的AI应用,每一步都回到基础概念上找答案。
下面的清单是我自己重新走一遍会采用的安排。不需要全部参考,但有两条主线:一是把HTTP流式交互吃透,二是把一个带知识库的问答应用完整落地。
| 周期 | 学习主题 | 动手项目 | 对应课程基础 |
|---|---|---|---|
| 第1周 | HTTP协议、SSE、WebSocket | 用Python写一个文件下载接口,观察响应头 | 计算机网络 |
| 第2周 | 大模型API调用与流式输出 | 后端调用大模型,通过SSE转发给前端 | 网络编程、进程管理 |
| 第3周 | 前端增量渲染与用户中断 | React页面逐字显示回答,按钮中断请求 | 事件驱动、异步编程 |
| 第4周 | 提示词模板与会话管理 | 实现多轮对话,裁剪历史消息 | 数据结构、内存管理 |
| 第5周 | 向量检索与RAG | 上传PDF,检索相关片段并生成回答 | 数据结构索引、搜索排序 |
| 第6周 | 缓存、限流、日志 | 为问答服务加上Redis缓存和QPS限制 | 操作系统、分布式基础 |
| 第7-8周 | Agent工作流 | 写一个简单Agent,能调用天气/计算器工具 | 状态机、设计模式、事件驱动 |
| 第9-12周 | 工程化打磨 | 加测试、加可观测性、部署上线 | 软件工程、运维基础 |
每个阶段我都建议用“最小化实现”来验证理解。比如学SSE,别去背协议规范,直接跑一个本地服务,用curl看返回的数据块长什么样;学向量检索,别急着上生产级向量数据库,先用文本相似度加一个朴素实现跑通流程;学Agent,别一上来就套LangChain,先用原生代码写完一个“如果天气大于30度,就调用穿衣建议工具”的小状态机。
这样做的原因是:AI应用开发里,框架更新迭代太快,一个月前兴起的库,一个月后可能就被替代。但底层的HTTP流、协议解析、内存管理、并发控制、状态建模不会变。你把基础打牢,再去看新框架的文档,会发现自己能很快看懂它在解决什么问题;如果你根基不牢,就会永远处于“这个新东西还没学会”的焦虑里。
我面试别人的时候也发现一个规律:候选人简历写满LangChain、LlamaIndex,但一问“SSE和WebSocket什么区别”,就开始含糊;而另外一个候选人虽然只写过一个原生API调用,但能把HTTP流式传输、连接复用、断线重连讲得清清楚楚,最后我反而更倾向后者。道理很简单:前者只是用过工具,后者是理解了工具背后的原理,而AI应用开发每天都会出现新工具,原理不变,变的是封装方式。
6. 三个月的真实体感:别神话AI应用开发,也别低估基础
如果让我用一句话总结这三个月:AI应用开发没有想象中那么高不可攀,但也绝不是搭积木那么简单。
说它没有想象中高不可攀,是因为一切最终都落在“API调用 + 数据流 + 状态管理 + 工程规范”这些基础能力上。我第一天上班时,连LangChain的Agent概念都要查半天,现在已经能在不依赖框架的前提下手写一个简单工作流,核心就是循环调用模型、解析输出、执行工具,再决定是否继续。这套循环看起来像是一门新课,其实每个环节都是老知识。
说它不简单,是因为真实环境里有太多意外。模型返回JSON格式不固定,需要容错解析;用户消息里包含恶意Prompt,需要做注入防护;每次模型升级,都可能导致历史能力回退,需要做回归测试。这些都是单一课程不会教,只有在真实业务里踩过坑才会懂的细节。
我个人的体会是:如果你是零基础转岗,不要被“三个月上岸”这种话冲昏头。先把一门语言练熟,把HTTP协议抓包看明白,把数据库增删改查搞利索,再谈“AI应用开发学习路线”。如果你已经有后端或前端经验,千万别因为羡慕AI岗位就把自己的老本行扔掉,反而是老本行里积累的工程能力会成为你最快上手AI应用开发的底气。
最后分享一个对我帮助很大的小技巧:每周写工作总结时,不要把“调大模型接口”当作一件孤立的任务,而是描述成“通过SSE流式接口解决长时间任务请求的增量渲染,结合AbortController实现用户中断语义”。这样写下来,你会发现自己其实做了很多事,而每一件事都能映射回一门课程、一个原理。面试时就是这么讲的,评委听得懂,也不会觉得你在背新名词。
转岗三个月,最大的变化不是技能清单变长了,而是我对“学会”的判断标准变了。以前我学完一门课,觉得考试过了就是学会;现在我觉得,能在真实场景里把一个抽象概念用出来的那一刻,才叫真正学会了。AI应用开发这个方向,恰恰给了我很多次“重新学会”的机会。