1. 为什么“从零开始”这么有吸引力
做AI工程这件事,我第一次被问最多的问题就是:“现在各种框架和低代码平台都这么成熟了,直接拿来用不就行了,为什么要自己从零搭一遍?”我能理解这种想法,毕竟这个领域的工具链一年比一年丰富,拖拽式编排、一键微调平台层出不穷,好像谁都能在几个小时内“做出”一个AI应用。但真干过几轮实际项目就会发现,用别人封装好的东西,和真正理解自己系统里每一层发生了什么,完全是两码事。从零开始做AI工程,不是要排斥现成工具,而是要亲手把那些“黑盒”拆开,搞懂里面是怎么运转的,明白自己做决策时到底在选什么。
什么叫“从零开始”?放到AI工程这个语境下,就是你要独立设计一套完整方案:从需求拆解、数据处理、模型接入、提示词编排,到结果校验、异常兜底、性能观测和部署迭代,每一个环节都由你亲自定义和实现。市面上确实有大量工具能包办其中一环,但当你尝试从空白目录开始,一点点把自己的工程搭起来,你会被迫回答很多平时忽略的问题,比如“这个模型返回的结果真的可以用吗”“用户重复提交时我怎么处理”“模型挂了怎么办”。这些问题的答案,恰恰才是工程能力的真正分水岭。
这篇文章适合谁?我的核心读者群大概有两类。一类是刚接触AI应用开发的新人,有点编程基础但没系统走完一个完整项目,想搞明白到底哪些是真正绕不开的;另一类是已经在用现成平台或封装库做功能的从业者,希望补上底层理解,不再只是“调接口、看效果”,以后遇到问题能自己定位和修复。我在这篇分享里会尽量用实际案例和代码来讲,不是说教,就是把你带入一个真实的项目现场,跟着我从零走一遍。
顺带说一句,如果你期望的是一个“十分钟搞定一切”的速成教程,那这篇文章可能不适合你。我会反复强调设计、验证和兜底,这些看起来慢、看起来“不需要”,但到了线上环境,它们往往比某个花哨的模型调用更值钱。从零开始的真正价值,不在于让你从头发明轮子,而在于让你知道自己手里拿的轮子到底是怎么造出来的。
2. 核心概念:AI工程不只是写提示词
2.1 提示工程与大模型基础
很多新人一上来就问“提示词怎么才能写好”,但我建议把“提示工程”放回它本来的位置:它只是整个AI工程链条里很靠前的一环,远未覆盖系统的复杂性。提示词决定了模型解读任务的方式,但决定不了数据质量、调用稳定性、反馈闭环和系统性能。如果把AI工程比作盖房子,提示词更像是一张户型图上的标注说明,画得再细,你仍然得先有地基、墙体和管线。
大模型的基础能力有几个关键点,你必须心里有数。第一是上下文窗口,它限制了你能放进一次请求的资料总量,工程上经常需要通过摘要、分片、检索来控制输入长度。第二是温度等采样参数,它们控制输出的随机程度,做客服机器人时就该调低,做创意文案时可以调高一些。第三是输出格式的约束能力,虽然模型能听懂“请输出JSON”,但没有强约束时它还是会偶尔夹带解释文字,这部分工程上要有缓冲和处理预案。对这些基础特性的理解(引自公开技术资料中的通用原理),决定了你后续在流程设计上能否合理决策。
再说回提示词本身,我有一套比较朴素的写法,可以概括成四个层次:角色定义、任务描述、约束条件、输出示例。角色定义让模型明确立场,比如“你是资深数据分析师”;任务描述说清楚要做什么;约束条件划出边界,比如“不要编造数据”“只基于给定材料回答”;输出示例则直接给出你期望的结构。实际做下来,第4层往往最管用,因为大模型对“你想要什么”的理解,很多时候是依赖“你能给什么样子”的例子。
2.2 提示修辞与工程边界的区分
真正做过一段时间你会意识到,提示词写得再精妙,也不是AI工程的全部。工程的核心,是把不可控的模型行为装进一个可控的系统框架里。比如模型输出的格式漂移,光靠提示词约束不靠谱,我一般在系统代码里加一层解析函数,把返回文本里的JSON段提取出来,解析失败就走重新生成的逻辑,再不行就直接返回人工兜底提示。这种思维在AI工程中特别重要,术语叫“防御性设计”。
另外,不少教程会鼓吹“用自然语言就能实现全自动开发”,真实落地时你会发现,越是在流程严谨的场景里,越不能把关键判断完全交给模型。我的经验是模型适合做单点能力,比如摘要、分类、抽取、改写,而全链路编排、权限控制、多轮状态管理这类事情,最终还是要落在严谨代码上。说白了,人负责搭骨架,模型负责填血肉,两者边界清晰,项目才不会“跑着跑着变成玄学”。
3. 从零到一:设计并搭建一个可运行的AI工程项目
3.1 项目目标与技术选型
我从零做过的第一个完整AI项目,是从空目录开始搭一个典型的Agent式文档问答系统,用户上传PDF资料,系统负责切片、索引、召回,最终回答问题时给出有据可查的答案。这个项目把AI工程里最核心的要素都覆盖了:语言模型调用、数据预处理、向量检索、结果校验、性能优化。我选这个例子来展开,因为它既有足够代表性,又不会复杂到让人跟不动。
技术栈我没有选很重的东西,核心就是Python + FastAPI做服务层,OpenAI兼容接口做模型底座,一套本地向量数据库处理文档索引,再加一个最基础的前端页面做交互演示。选它的理由很简单,这套组合在当下生态里最通用、资料最全、且能让你看清楚每个环节的边界。有人可能会问,为什么不直接用一个编排平台?我在3.1开头说了,平台会隐藏很多中间细节,当你想理解“为什么慢”“为什么不准”“为什么流量翻倍就挂”时,还是得自己掌控这些底层依赖。
3.2 服务端框架与依赖管理
我习惯先搭服务骨架,把一个最小的FastAPI应用跑起来,确认端口、路由、健康检查都通了,再往上加AI逻辑。下面这段就是最基础的一个版本:
from fastapi import FastAPI app = FastAPI(title="ai-engineering-from-scratch") @app.get("/health") def health_check(): return {"status": "ok"}依赖管理这块我要多说两句。Python项目的依赖,千万不要一股脑全装进全局环境,后面版本冲突能把人折磨死。我一般用pyproject.toml配合虚拟环境来固定版本,每个项目独立一套解释器和依赖树,换机器迁移的时候直接整包拉起来,省心很多。项目初期就定好Python版本,我推荐3.11以上,新特性和第三方库的适配度都更好。
3.3 文档解析与切片策略
文档问答里第一个容易翻车的环节是文档解析。PDF看着规整,但里面的表格、页眉页脚、双栏排版,解析出来经常是乱的。我试过很多库,最终用了一款开源的文档解析服务,能比较干净地抽出版本、章节和连续段落。处理逻辑是先把各类文件统一转成纯文本,再做清洗:去掉无意义的空行、修正断行问题、过滤掉页眉页码。
清洗之后的文本要切片,这一步直接决定了后续检索的准确率。切片策略没有万能解,我实践下来的原则是“按语义块切,而不是按固定长度硬切”。比如一个章节下的小标题和它下面的几段,最好分到同一个切片里;表格单独成片;超长段落可以二次切分,但相邻部分要保留overlap(重叠),避免检索时因为上下文截断而丢失关键信息。我常用的切分大小在300到800个词之间,具体参数要看你的文档领域和模型上下文能力,这个没有标准答案,建议多做几组试验,用检索质量去反推最优值。
3.4 向量化与检索链路
切片之后要做的就是把文本块转成向量,存入向量数据库。选型上我偏向本地优先的策略,数据不需要传到外部服务,适合开发和测试阶段的快速迭代;等到数据量上来了,再考虑把索引迁移到专业化的线上向量服务。向量化的模型选择也是一个工程决策,通用场景用常见的嵌入模型就够,垂直领域则可以收集一批领域数据做微调,但这个成本高,通常只有到了需要显著提升召回质量时才值得做。
检索链路组装起来大概是:收到用户问题,先对问题做一次改写或扩展,比如“X产品的退款政策”补成“X产品在什么条件下可以退款、退款的流程和时限”,然后拿改写后的内容做向量检索,取回top-k个相关切片,再拼进提示词上下文。很多初学者容易忽略“检索再排序”这一步,我建议在向量召回后多加一个重排环节,用模型对候选片段打分,选出和问题最匹配的一到三段,这样回答质量的提升非常明显,成本增加却很微小。
4. 核心细节解析与工程实操
4.1 提示词模板与结构化输出
进入AI部分后,第一个要设计的细节就是提示词模板。模板不是简单往字符串里塞变量,它需要严格区分“系统指令”“用户输入”“参考资料”三个区域。系统指令写模型的角色和长期约束,比如“你是一个帮助用户解答产品问题的客服助手,回答必须基于给定资料,资料中没有信息时明确说不知道”;用户输入区放当前问题;参考资料区放检索回来的上下文片段。
结构化输出方面,我把自己的做法直接分享出来:如果业务下游需要拿输出继续做逻辑判断,就明确要求模型只返回一段JSON,并在解析时用正则手段提取JSON块,而不是直接对整段输出做json.loads,因为模型偶尔会输出一些额外解释。解析失败就进入重试逻辑,重试两次还失败,就降级为普通文本模式返回给用户,保证服务不中断。我贴一段核心示意代码,你感受一下:
import json, re def extract_json_from_text(text: str) -> dict: """从模型输出中提取JSON对象,避免格式漂移导致解析失败""" pattern = r"\{.*\}" matches = re.findall(pattern, text, re.DOTALL) for match in matches: try: return json.loads(match) except json.JSONDecodeError: continue raise ValueError("no valid json found")这个函数不是银弹,但它在真实环境中帮我挡掉了大量解析问题,是我个人项目里的必备工具。
4.2 关键参数选择与成本控制
模型参数里,我日常最关注四个:模型名、temperature、max_tokens、top_p。温度字段默认给0.2,用于事实性问答;如果任务偏创意,我再酌情调到0.7到0.9;max_tokens要按任务预留,回答太短可能截断,回答太长又会拖慢响应。top_p我通常保持默认,不做太多改动,除非有特别强烈的风格要求。
成本控制这件事,做成期的工程尤其容易忽略。很多人一上来就调大模型,回答一次花几毛钱几千tokens,看着不多,量一上来就肉疼。我常用的省钱手段有几个:缓存高频问题的答案、用小模型做意图分类和文档粗筛、重复内容不重复向量化。这些手段都不复杂,但组合起来成本能降一个数量级,而且对用户体验几乎没有损伤。
5. 实操过程与核心环节实现
5.1 构建完整问答接口
我把整个问答流程串成一个服务接口,代码结构大致如下:
from fastapi import FastAPI, UploadFile, Form from pydantic import BaseModel class AskRequest(BaseModel): question: str history: list = [] @app.post("/ask") async def ask_question( question: str = Form(...), session_id: str = Form(...), ): # 1. 检索相关文档片段 fragments = retrieve_fragments(question) # 2. 组装提示词上下文 prompt = build_prompt_with_context(fragments, question) # 3. 调用模型并解析结果 answer = call_llm_with_fallback(prompt) # 4. 记录日志,用于后续质量评估 log_interaction(session_id, question, fragments, answer) return {"answer": answer}这个接口看起来简单,但每个步骤内部都藏着细节。检索函数为什么要先做问题改写?因为用户口语化问题直接拿去向量检索,经常搜不到好结果。组装环节为什么要明确划分上下文和问题?因为模型容易把不同的输入源混在一起,输出时就会串味。日志记录为什么不能省?因为判断一个AI系统的效果,靠的不是感觉,而是回头能从日志里看到什么组合召回了什么片段、产出了什么答案。
5.2 从开发到上线要补的工程课
本地跑通不算完,上线前我通常会补上三件事:接口限流、结果校验、监控告警。接口限流保护的是你的模型成本预算,防止脚本和异常流量把账号打爆;结果校验保护的是用户信任,模型输出可能胡乱或跑偏,要在返回前做一次规则判断;监控告警则是保障运维安心,服务调用失败率、平均延迟、token消耗量,这些指标用一套简单的日志统计就能看到趋势。我在项目里用一套轻量级的可观测组件,把每次调用的关键数据打点上报,再用一个看板展示,基本能做到秒级发现问题。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
我把自己从零搭建过程中遇到过的典型问题整理成了一张表,每一条背后都有实际踩坑背景,不是泛泛而谈:
| 问题现象 | 根因分析 | 解决方案 |
|---|---|---|
| 回答质量不稳定,时好时坏 | 检索召回片段不精准,上下文混乱 | 引入问题改写 + 增加检索后重排环节 |
| 模型输出JSON频繁解析失败 | 格式漂移,夹带解释性文字 | 用正则提取JSON块 + 重试降级策略 |
| 接口响应特别慢 | 同时请求多个模型调用,串行等待 | 把相互独立的调用改成并发 |
| 同样的文档换个PDF就检索不到内容 | 解析环节依赖版式,表格和扫描件处理不足 | 升级解析工具、加OCR兜底、清洗策略标准化 |
| 用户多轮对话中,模型忘了前文 | 上下文保留策略过于简单 | 引入摘要记忆机制,控制历史长度 |
| 线上出现短时间请求暴涨 | 缺少限流,成本失控 | 接入Rate Limiter,按用户维度和IP维度双限制 |
| 向量检索一直返回某个固定文档 | 同质化片段过多,检索被“胖文档”淹没 | 调整切片策略,限制单文档召回权重 |
6.2 独立AI工程师的避坑经验
最后集中说几条我觉得特别重要的经验,都不是从文档里能直接查到的。第一,不要急着上复杂架构,先做出一个能跑的端到端最小闭环,再去一点点加功能,这个顺序能让你少掉一半头发。第二,工程里的每一层都给自己留“逃生门”——模型解析挂了有兜底、检索结果为空有兜底、整个服务不可用也要有缓存方案,系统能不能撑住用户压力,靠的往往不是你主链路写得有多好,而是兜底设计得有多密。第三,尽量用可量化的指标去评价系统,而不是主观感觉“回答挺好的”,给自己建立一套日志和评估脚本,每次改动都能对比前后效果,这样迭代才有方向。
我还有一个小习惯,每次模型升级换代时,都先将一个固定的测试集跑一遍,观察输出风格和质量变化,然后才考虑是否切换。模型层的变更影响面其实很大,轻率升级可能让线上表现“莫名其妙变差”,有了固定测试集,你至少能快速定位是否是模型侧行为漂移导致的。
7. 这条路还能怎么往下走
写完这些,我特别想说一句心里话:从零起步做AI工程,最大的收获不是“我会调接口了”,而是你建立了一套属于自己的判断体系。以后再看到新框架、新模型,你不会只是跟风替换,而是会先问自己:它在哪一层解决什么问题?替换的代价是什么?对我的系统哪些指标会真正变好?
很多人觉得AI工程的技术门槛主要在模型,待久了你会发现真正的门槛在工程整合能力:把数据、模型、代码、用户体验焊成一个可靠的整体。这个能力无法靠“看教程”获得,只能靠亲手踩坑、亲自修错、反复打磨。希望这篇分享能给你一张还算清晰的地图,接下来的路,还得你自己一步步走。