首页 / 资讯中心 / 文章详情

传统开发者转型AI应用开发:RAG、Agent与工程化闭环

传统开发者转型AI应用开发:RAG、Agent与工程化闭环 ★ FEATURED ARTICLE
这两年只要在技术社区里刷一会儿就会反复撞见类似“转到人工智能是我这辈子做过的最正确的决定”的标题。有人从 Java 后端转去做大模型应用有人从算法岗转到应用工程也有人从运维、测试转到 AI 平台方向。这类分享读多了容易产生两个错觉一个是以为门槛高得离谱得先把线性代数和概率论重刷一遍另一个是以为门槛低得离谱会调 API 就等于转型成功。这两种判断都不太对。真正发生变化的地方其实不在数学层而在工程接口层。过去做一个“能理解自然语言的业务功能”需要算法团队、标注数据、训练周期和上线评估链条长、成本高现在很多需求可以直接退化成“把业务上下文组织好交给一个模型再对输出做约束和校验”。这意味着传统开发者的核心能力——接口编排、状态管理、异常处理、数据建模、灰度发布——并没有作废只是换了一个落点。我见过最多的卡点不是学不会模型原理而是不知道第一个能跑起来的东西该长什么样。看完一堆科普知道 Transformer 有注意力机制知道 RAG 是检索增强但打开编辑器还是不知道从哪一行开始写。结果就是“概念都懂项目为零”简历上写不出任何可验证的东西。这篇文章不打算再讲一遍 AI 发展史而是把转型这件事拆成工程问题哪些旧经验可以直接复用哪些概念必须重新建立三个最小可运行闭环分别怎么落地以及在生产环境里最容易翻车的那几个地方到底怎么排查。读完你应该能自己跑通一条从调用模型到对外提供服务的完整链路并且知道哪些坑必须提前避开。1. 这句话真正值得拆解的地方1.1 门槛降低发生在哪一层把“转到人工智能”拆开看其实是三个完全不同的方向训练侧预训练、微调算法、分布式训练、应用侧把模型能力接进业务系统、平台侧推理服务、调度、监控、成本治理。绝大多数传统开发者的现实路径是应用侧其次是平台侧。训练侧对数学、算力、数据规模的要求依然很高并没有因为工具链进步而消失。应用侧之所以门槛明显下降是因为模型的输入输出被标准化成了文本或结构化数据。你不再需要自己设计特征工程也不需要维护一个复杂的模型服务你面对的更像一个“能力很强但不稳定、延迟较高、按量计费的下游服务”。这个描述听起来是不是很熟悉它和调用第三方支付、调用短信网关、调用地图服务在工程形态上高度相似只不过它的返回结果是非确定性的。非确定性是整件事的分水岭。传统接口你写下if (status 200)就敢继续往下走模型接口返回 200 只代表这次调用成功了内容对不对是另一回事。所以转型真正要补的不是深度学习理论而是围绕非确定性输出建立一整套约束、校验、兜底和评测机制。1.2 什么人适合走这条路什么人要谨慎从公开的转型分享和招聘要求看最适合的是有 2 年以上业务系统开发经验的人。你写过订单、支付、权限、工作流知道什么叫幂等、什么叫超时重试、什么叫降级这些在 AI 应用里全部用得上而且是稀缺的。很多纯算法背景的同学能把模型效果调上去但把服务做成能扛住线上流量、能回滚、能定位问题的形态反而是短板。需要谨慎的是两类人。一类是完全没有工程经验、指望靠几个提示词模板直接就业的这类岗位正在快速内卷因为门槛确实不高。另一类是排斥不确定性的如果你的习惯是“必须保证每次输出完全一致才敢上线”那在 AI 应用里会非常痛苦因为这个领域更像做概率系统你要做的是把错误率压到业务可接受的范围而不是消灭错误。另外一个容易被忽略的判断标准是你能不能接受“效果靠评测说话”。传统开发里一个功能对不对跑一遍用例就知道AI 应用里同一个问题问十次可能有八次好两次差你必须建立一套评测集才能判断改动是变好还是变坏。愿不愿意做这件有点枯燥的事基本决定了他能走多远。2. 能力迁移地图旧经验如何映射到新岗位很多人在转型期最大的浪费是把已有的经验清零重新学。实际上大部分工程能力是平移的只是命名和关注点变了。下面这张表是我认为最值得先建立起来的映射关系建议对照自己的技术栈找一找对应项。传统开发能力在 AI 应用开发中的对应位置迁移难度说明接口设计与服务编排工具调用、Agent 流程编排低本质还是定义输入输出契约数据库建模与查询优化向量库 schema、检索参数、混合检索中相似度检索的调优逻辑与索引优化不同业务逻辑抽象Prompt 与上下文结构设计中从写代码变成写约束需要重新找手感单元测试与 CI评测集、回归测试、AB 对比低思路一致但断言方式从相等变成阈值日志、监控、告警token 成本、首字延迟、失败率监控低指标换了方法论没变高并发与限流降级模型侧配额、排队、缓存、降级到小模型低上游从数据库变成第三方服务统计学与实验设计评测指标、阈值调优、显著性判断中高需要补但不必到能推导公式的程度从这张表能看出一个结论转型的难点集中在两个格子——“上下文结构设计”和“评测指标”。这两件事在传统开发里没有完全对应的训练必须专门花时间。而这两件事恰恰是决定 AI 应用好不好用的关键比会不会调 API 重要得多。还有一个反直觉的点代码能力不会被削弱反而更值钱。因为模型调用只是链路中的一环真正让应用可用的是前后那些看起来“很传统”的部分——参数校验、缓存、重试、超时、并发控制、幂等、审计日志。一个只会写 prompt 的人做不出能上线的系统一个能把这些工程件补齐的人哪怕 prompt 写得一般系统也能跑得住。3. 核心概念与选型RAG、微调、Agent 怎么选在动手之前有几个概念必须先对齐否则很容易在错误的方案上耗掉几周。这里不追求严谨定义重点说清楚“它解决什么问题、什么时候该用、什么时候不该用”。大语言模型LLM可以理解成一个“给定上文、预测下文的函数”。它的知识截止在训练数据的时间点它不知道你公司的内部文档也不保证说真话。Token 是计费和信息量的基本单位中文大致一个字到两个字一个 token具体取决于分词器。上下文窗口指的是单次请求能塞进去的最大 token 数超过就会报错或被截断——这是新手最容易踩的第一个坑。Embedding 是把文本映射成高维向量语义相近的文本在向量空间里距离更近向量检索就是靠这个特性工作。下面这张表是我建议的选型顺序从成本最低、见效最快开始排方案解决什么问题成本知识更新时效典型场景Prompt 与输出约束角色设定、格式规范、拒答边界最低即时所有场景的基础RAG 检索增强私有知识、可溯源、减少编造中分钟级到小时级文档问答、客服、内部助手长上下文直塞少量文档一次性分析按 token 线性计费即时合同审阅、单次报告解读微调固定风格、固定输出格式、特定任务高天级稳定任务、小模型降本Agent 工具调用需要查实时数据、执行动作中高实时订单查询、工单流转、运维助手一个常见的误区是“效果不好就去微调”。实际上大部分“答不准”的问题根因是知识缺失或者上下文没给对用 RAG 就能解决微调反而容易把模型带偏还会引入数据准备和版本管理的新麻烦。更稳妥的判断顺序是先确认 prompt 和上下文结构有没有问题再考虑加检索最后才考虑微调。4. 环境准备与项目骨架这里给出一套最小但完整的工程结构能跑通后面所有示例。运行环境建议 Linux 或 macOSWindows 用 WSL 会更顺一些。Python 建议 3.10 及以上因为代码里用到了dict[str, Any]这种类型标注写法。具体的库版本请以你所在团队的依赖规范为准本文重点演示通用思路不把版本号写死。# 创建虚拟环境并安装依赖 python -m venv .venv source .venv/bin/activate pip install -U pip pip install -r requirements.txt# 文件路径requirements.txt openai1.30.0 python-dotenv1.0.0 fastapi0.110.0 uvicorn0.27.0 numpy1.24.0# 项目目录结构 ai-demo/ ├── app/ │ ├── __init__.py │ ├── llm_client.py # 统一封装模型调用 │ ├── rag.py # 切分、向量化、检索 │ ├── rag_qa.py # 检索增强问答 │ ├── agent.py # 工具调用 │ └── main.py # FastAPI 服务入口 ├── data/ │ └── faq.txt # 示例知识库 ├── eval/ │ └── run_eval.py # 评测脚本 ├── .env └── requirements.txt密钥管理是最容易被忽视的一环。绝对不能把 API Key 写进代码再提交到仓库这是真实项目里最常见的安全事故。统一用环境变量注入.env只用于本地开发并且必须写进.gitignore。# 文件路径.env不要提交到版本库 OPENAI_API_KEYsk-your-key-here # 如果团队使用私有化部署或内部统一的模型网关把 base_url 指向内部地址 OPENAI_BASE_URLhttps://api.openai.com/v1 LLM_MODELgpt-4o-mini EMBEDDING_MODELtext-embedding-3-small# 文件路径.gitignore .venv/ .env data/store.json如果你的模型服务来自内部网关只需要改OPENAI_BASE_URL代码逻辑完全不用动。这也是为什么第一步就要做统一封装——把服务商差异收敛到一个文件里后面换模型才有余地。5. 第一个可运行闭环结构化输出与稳定调用第一个闭环不要贪心去做问答先做“稳定拿到结构化结果”。原因很实在大部分 AI 应用最终都要把模型输出交给下游程序处理如果输出格式不稳定后面全是补丁。这个能力一旦跑通你就已经越过了最容易劝退的那道坎。# 文件路径app/llm_client.py import json import logging import os import time from typing import Any from dotenv import load_dotenv from openai import OpenAI load_dotenv() logger logging.getLogger(__name__) logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), ) CHAT_MODEL os.getenv(LLM_MODEL, gpt-4o-mini) def chat_json(system_prompt: str, user_prompt: str, retries: int 3) - dict[str, Any]: 调用模型并强制返回 json 对象失败时按指数退避重试。 last_error: Exception | None None for attempt in range(1, retries 1): try: resp client.chat.completions.create( modelCHAT_MODEL, temperature0, response_format{type: json_object}, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], ) content resp.choices[0].message.content or {} return json.loads(content) except Exception as exc: last_error exc logger.warning(第 %s 次调用失败: %s, attempt, exc) time.sleep(2 ** attempt) raise RuntimeError(f模型调用失败已重试 {retries} 次) from last_error这段代码有三个关键点值得解释。第一temperature0并不能保证输出完全确定它只是尽量降低随机性所以要把它当成“减少波动”而不是“消除波动”。第二response_format{type: json_object}是一个真实的约束能力但它通常要求提示词里明确出现 json 字样否则可能报错因此 system prompt 必须写清“输出 json”。第三重试必须加退避因为限流和网络抖动是常态连续快速重试只会让情况更糟。写一个测试文件验证它是否可用# 文件路径app/demo_structured.py from app.llm_client import chat_json SYSTEM 你是一个工单分类器。请输出 json 对象格式为 {category: 退款/物流/账号/其他, urgency: 高/中/低, summary: 不超过 30 字的摘要} 不要输出任何解释性文字。 if __name__ __main__: text 我上周买的鞋子到现在还没发货客服也联系不上麻烦尽快处理 result chat_json(SYSTEM, text) print(result)运行python -m app.demo_structured预期输出类似{category: 物流, urgency: 高, summary: 下单一周未发货且客服失联}。如果报错提示 json 相关先检查 system prompt 里有没有 json 字样如果报鉴权错误先确认.env是否被正确加载python-dotenv默认从当前工作目录找文件在子目录里运行会读不到。6. 第二个闭环RAG 检索增强问答拿到结构化输出之后第二个要解决的问题是“模型不知道我的资料”。最直接的办法是把资料拼进提示词但很快就会撞上上下文窗口和成本的双重限制。RAG 的思路是把文档切成小块、转成向量存起来提问时先检索最相关的几块只把这几块塞进上下文。这样既控制了 token也能给出引用来源。# 文件路径app/rag.py import json import os from pathlib import Path import numpy as np from dotenv import load_dotenv from openai import OpenAI load_dotenv() client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), ) EMBEDDING_MODEL os.getenv(EMBEDDING_MODEL, text-embedding-3-small) STORE_PATH Path(data/store.json) def split_text(text: str, chunk_size: int 500, overlap: int 80) - list[str]: 按字符滑窗切分overlap 保证跨块的句子不被切断。 if overlap chunk_size: raise ValueError(overlap 必须小于 chunk_size) chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end].strip()) start end - overlap return [c for c in chunks if c] def embed(texts: list[str]) - np.ndarray: 批量向量化并做 L2 归一化归一化后点积等价于余弦相似度。 resp client.embeddings.create(modelEMBEDDING_MODEL, inputtexts) vectors np.array([item.embedding for item in resp.data], dtypenp.float32) norms np.linalg.norm(vectors, axis1, keepdimsTrue) return vectors / np.clip(norms, 1e-8, None) def build_index(doc_path: str) - int: text Path(doc_path).read_text(encodingutf-8) chunks split_text(text) vectors embed(chunks) STORE_PATH.parent.mkdir(parentsTrue, exist_okTrue) STORE_PATH.write_text( json.dumps( {model: EMBEDDING_MODEL, chunks: chunks, vectors: vectors.tolist()}, ensure_asciiFalse, ), encodingutf-8, ) return len(chunks) def search(query: str, top_k: int 3) - list[dict]: data json.loads(STORE_PATH.read_text(encodingutf-8)) if data[model] ! EMBEDDING_MODEL: raise RuntimeError(索引的向量模型与当前配置不一致请重建索引) matrix np.array(data[vectors], dtypenp.float32) q embed([query])[0] scores matrix q idx np.argsort(-scores)[:top_k] return [{text: data[chunks][i], score: float(scores[i])} for i in idx]这里有三个工程细节值得单独强调。第一overlap的存在是为了避免一句话被切断在两个块之间导致检索到一半的意思实际项目里这个参数对召回质量影响很明显。第二向量必须做归一化否则点积会被向量长度带偏相似度排序就不可靠了。第三也是最重要的索引里记录了生成时用的向量模型名换模型后必须重建索引——不同模型的向量空间完全不兼容混用会得到看似有结果但完全不相关的召回这是最难排查的坑之一。# 文件路径app/rag_qa.py from app.llm_client import chat_json from app.rag import search SYSTEM 你是企业内部文档助手。只能依据【参考资料】回答不允许使用资料之外的知识。 如果资料中没有答案answer 必须填写根据现有资料无法回答。 输出 json 对象格式为{answer: 回答内容, sources: [引用的资料编号]} def answer(question: str, top_k: int 3) - dict: hits search(question, top_ktop_k) context \n\n.join(f[{i 1}] {h[text]} for i, h in enumerate(hits)) user_prompt f【参考资料】\n{context}\n\n【用户问题】\n{question} result chat_json(SYSTEM, user_prompt) result[_hits] hits return result if __name__ __main__: from app.rag import build_index print(索引块数:, build_index(data/faq.txt)) print(answer(退款一般多久到账))运行python -m app.rag_qa预期能看到索引块数和一段带引用的回答。判断是否成功有两个标准一是sources里引用的编号对应的原文确实和问题相关二是问一个资料里完全没有的问题模型应该老实说无法回答。第二点比第一点更重要因为它验证了拒答边界有没有建立起来。7. 第三个闭环Agent 工具调用与循环控制第三个能力是让模型去“做事”而不是只“说话”。实现方式叫工具调用function calling你把可用的函数和参数结构描述给模型模型决定什么时候调用哪个函数、传什么参数你的程序执行完再把结果喂回去。注意执行动作的是你的代码模型只负责决策这个边界必须分清否则会写出很大的安全隐患。# 文件路径app/agent.py import json from app.llm_client import client, CHAT_MODEL TOOLS [ { type: function, function: { name: query_order_status, description: 根据订单号查询订单当前状态只有用户提供订单号时才调用, parameters: { type: object, properties: { order_id: { type: string, description: 订单号例如 A10023, } }, required: [order_id], }, }, } ] def query_order_status(order_id: str) - str: # 示例数据实际项目里替换为受权限控制的服务调用 fake_db {A10023: 已发货预计明天送达, B20011: 待付款} return fake_db.get(order_id, 未找到该订单) TOOL_MAP {query_order_status: query_order_status} def run_agent(user_input: str, max_steps: int 5) - str: messages [ {role: system, content: 你是订单助手。需要查询时调用工具不要凭空猜测订单状态。}, {role: user, content: user_input}, ] for step in range(max_steps): resp client.chat.completions.create( modelCHAT_MODEL, messagesmessages, toolsTOOLS, temperature0, ) msg resp.choices[0].message if not msg.tool_calls: return msg.content or messages.append( { role: assistant, content: msg.content or , tool_calls: [tc.model_dump() for tc in msg.tool_calls], } ) for tc in msg.tool_calls: name tc.function.name args json.loads(tc.function.arguments or {}) func TOOL_MAP.get(name) result func(**args) if func else f未知工具: {name} messages.append( { role: tool, tool_call_id: tc.id, content: str(result), } ) return 超过最大推理步数已终止 if __name__ __main__: print(run_agent(帮我查一下订单 A10023 现在什么状态))这段代码里最关键的其实是max_steps。模型的推理循环没有天然的终止保证可能反复调用同一个工具、反复生成调用参数没有步数上限就会不断烧 token 甚至卡死。生产环境里还要加上单次请求的超时和总 token 预算这两个是必须的护栏。另外工具描述里的“只有用户提供订单号时才调用”这类约束很重要。模型对函数描述的理解程度直接决定调用准确率描述写得越具体、越像给人看的说明书效果越好。参数校验也不能省模型可能编出一个不存在的订单号甚至传入超长字符串函数内部必须做类型和长度校验。8. 服务化、效果验证与常见问题排查前面三个闭环都是在本地跑的脚本最后一步是把它们变成能被其他系统调用的服务。这里用 FastAPI 做一个带流式输出的接口流式不是为了炫技而是因为大模型的首字延迟通常在几百毫秒到几秒之间非流式接口会让用户觉得卡住了。# 文件路径app/main.py import json from fastapi import FastAPI from fastapi.responses import StreamingResponse from pydantic import BaseModel from app.llm_client import client, CHAT_MODEL from app.rag_qa import answer app FastAPI(titleAI Demo) class AskRequest(BaseModel): question: str app.get(/health) def health() - dict: return {status: ok} app.post(/rag) def rag_endpoint(req: AskRequest) - dict: result answer(req.question) return {answer: result.get(answer), sources: result.get(sources, [])} app.post(/chat) def chat(req: AskRequest) - StreamingResponse: def event_stream(): stream client.chat.completions.create( modelCHAT_MODEL, messages[{role: user, content: req.question}], temperature0.3, streamTrue, ) for chunk in stream: # 部分兼容实现可能返回空 choices必须判空 if not chunk.choices: continue delta chunk.choices[0].delta.content if delta: yield fdata: {json.dumps({delta: delta}, ensure_asciiFalse)}\n\n yield data: [DONE]\n\n return StreamingResponse(event_stream(), media_typetext/event-stream)启动方式和服务验证uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload # 健康检查 curl http://127.0.0.1:8000/health # 结构化问答 curl -X POST http://127.0.0.1:8000/rag \ -H Content-Type: application/json \ -d {question:退款一般多久到账} # 流式输出-N 关闭 curl 缓冲 curl -N -X POST http://127.0.0.1:8000/chat \ -H Content-Type: application/json \ -d {question:用一句话解释什么是向量检索}判断成功的方式要分层看HTTP 层返回 200 只代表没崩业务层要看/rag返回的sources是否指向相关原文体验层要看/chat是不是逐段吐字而不是一次性返回。如果流式接口一次性返回全部内容多半是中间有反向代理开了响应缓冲或者漏了-N参数。问题现象可能原因排查方式解决方案启动报密钥为空.env未被加载或工作目录不对打印环境变量是否读到在项目根目录启动或显式指定 env 文件路径报上下文长度超限检索块太多或单块太长统计拼进 prompt 的字符数减少 top_k、缩短 chunk_size、做历史裁剪模型输出不是合法 json提示词未声明 json或温度过高打印原始返回内容system 中明确要求输出 jsontemperature 设为 0检索结果完全不相关换了向量模型但没重建索引对比索引中记录的模型名重建索引索引与模型版本绑定召回结果总差一点切块过大导致语义被稀释抽样看命中的 chunk 内容调小 chunk_size增加 overlap回答里出现资料外内容缺少拒答约束问一个资料里没有的问题测试提示词中强制“无答案则拒答”并保留引用编号频繁报限流错误并发过高或重试太激进看错误码和调用频率加并发上限重试使用指数退避加随机抖动流式输出卡住不动代理缓冲或未刷新直连服务测试关闭代理缓冲逐块 flush工具被反复调用不停缺少步数上限打印每轮消息设置 max_steps 和总 token 预算成本突然翻倍上下文变长或缓存失效统计每次请求的 token 用量加结果缓存压缩上下文按场景选小模型9. 工程最佳实践与三个月转型路线跑通 demo 和生产可用之间隔着一段不短的距离。这一段距离里最值钱的几件事恰恰是传统工程经验的用武之地。第一件事是把 prompt 当代码管理。提示词散落在各个文件里、改了就上线、出问题不知道改回了什么版本这是非常普遍的状态。更稳妥的做法是把 prompt 抽成独立文件或配置项带版本号和变更记录每次改动都跑一遍评测集。评测集不需要很大几十条覆盖核心场景和边界情况就够关键是每次改动都跑形成回归。# 文件路径eval/run_eval.py from app.rag import search CASES [ {question: 退款多久到账, expect: 工作日}, {question: 怎么修改收货地址, expect: 地址}, {question: 发票怎么开, expect: 发票}, ] def main() - None: hit 0 for case in CASES: hits search(case[question], top_k3) joined .join(h[text] for h in hits) ok case[expect] in joined hit int(ok) print((PASS if ok else FAIL), case[question], -, [round(h[score], 3) for h in hits]) print(f召回命中率: {hit}/{len(CASES)} {hit / len(CASES):.2%}) if __name__ __main__: main()这个脚本看起来简陋但它解决了一个真问题调完切块参数后你怎么知道是变好还是变坏。凭感觉调参是 AI 应用里最容易失控的地方有了这个基线每次改动都有数可依。第二件事是把可观测性补齐。每次请求至少记录请求 ID、模型名、输入 token、输出 token、耗时、是否命中缓存、是否触发重试、最终状态。这不是为了好看而是因为成本失控和延迟抖动在 AI 应用里出现得比传统服务更频繁没有这些数据你根本不知道钱花在哪。第三件事是把安全边界写死。密钥只走环境变量或密钥管理服务工具调用的每个函数内部都要做权限校验和参数校验不能因为“模型说可以”就放行涉及写操作、删除、支付的工具必须有人工确认或二次校验绝不能交给模型自主决定。这几点在演示阶段看不出问题上线后是会出事的。第四件事是控制成本。简单任务用便宜模型复杂任务才升级相同问题加结果缓存上下文只放必要信息不要图省事把整篇文档塞进去。这些做法听起来朴素但对账单的影响往往是数量级的。关于转型路线我给一个比较务实的三个月节奏供参考调整第 1 个月补齐概念和手感。跑通本文的三个闭环理解 token、上下文窗口、temperature、embedding 这些参数的实际影响。目标是能独立做出一个能用的内部小工具。第 2 个月补工程。加评测集、加日志、加缓存、加限流把 demo 改造成能被别人依赖的服务。同时开始看一两个主流框架的源码或文档理解它们如何组织检索和流程编排。第 3 个月做出一个有业务背景的完整项目。选一个你熟悉的业务领域把知识库、检索、工具调用、前端展示串起来并把评测数据和成本数据整理出来。面试时能讲清楚“为什么这么选、踩过什么坑、怎么验证效果”比罗列框架名有说服力得多。需要提醒的是这个领域的工具链变化确实快今天流行的框架明年可能就换了。所以真正值得投入的是那些不随框架变化的东西怎么设计上下文结构、怎么建立评测、怎么控制成本和延迟、怎么划清权限边界。这些能力换个框架依然成立。回到最开始那句话。“转到人工智能是不是最正确的决定”这个问题其实没有标准答案因为它取决于你能不能接受一个输出不确定、需要持续调优、评价标准靠数据说话的工作方式。但有一点可以确定如果你已经具备扎实的工程能力那么进入这个领域的最短路径不是重新读一遍数学而是把手上这套工程方法迁移过去先跑通一条最小链路再一点点把不确定性管住。第一步永远是让代码真正跑起来而不是再多看一篇科普。
阅读完成 · 觉得有帮助?
咨询建站