1. 整体设计思路拆解
如果你搜过“AI工程”这个词,大概率会看到一堆乱七八糟的结果:有讲机器学习的,有讲提示词的,有讲大模型API调用的,还有卖课的。标题里这个“from scratch”很有意思,它不是让你从零学线性代数,也不是让你从零训练一个GPT,而是让你从零建立一套能落地、能交付、能迭代的AI应用开发能力。
我用半年时间把这个过程完整走了一遍,期间踩了无数坑,也总结出了一套相对清晰的路径。这篇文章就是把我走过的路线、踩过的坑、以及最终沉淀下来的方法论完整梳理一遍。适合正在转型的开发者、刚入行的AI工程师,以及那些已经被各种AI工具搞得焦虑不已的传统软件工程师。
先说结论:AI工程和传统软件工程最大的区别,不是语言和框架变了,而是问题的性质变了。传统开发面对的是确定性系统——你写一个函数,输入固定,输出就固定。AI工程面对的是概率系统——同一个提示词,同一个模型,每次输出都可能不同。这个根本差异决定了你的整个技术栈、架构设计、测试策略、部署方式,全部要跟着变。
很多从传统开发转过来的人第一个误区,就是拿写业务代码的思路去写AI应用。比如把提示词硬编码在代码里,比如不设计任何降级方案,比如把模型返回的JSON直接信任。这些习惯不改,做出来的东西就只能停留在demo水平,上不了生产。
那“从零开始”到底是从哪里开始?我的答案是:从搞清楚AI应用的技术栈分层开始。
我画过一张很粗的分层图,分享出来大家参考:
- 第一层是基础设施层:GPU、推理服务、模型部署框架(比如vLLM、TGI、ollama这些)
- 第二层是模型层:基础大模型、微调后的模型、量化后的模型
- 第三层是编排层:LangChain、LlamaIndex这类框架,或者你自己写的编排代码
- 第四层是应用层:业务逻辑、提示词管理、用户交互
- 第五层是评估层:数据集、评测指标、回归测试
大多数教程一上来就教你用LangChain搭个聊天机器人,这其实是从第三层开始的。基础不牢,后面每走一步都会漏风。
我在实际学习过程中,把大部分时间花在了三层上:一是模型本身的调用方式和能力边界,二是编排层的工作流设计,三是评估层的构建。这三件事搞通了,大部分AI应用的需求你都能接得住。
另一个重要思路转折,是我从“追新技术”转向“吃透稳定技术”。2024年到2025年这波AI发展,几乎每周都有新框架、新模型、新术语冒出来。如果跟着浪花走,你会发现自己永远在学入门教程,永远没有沉淀。我自己做了个决定:只深入掌握一套对我最核心的技术栈,其余技术保持了解即可。
我的这套核心栈是这样的:Python做胶水语言,FastAPI做服务层,OpenAI兼容协议做模型接入层,自研一套轻量Agent框架做编排,再用Prometheus+Grafana做监控,用pytest做回归测试。这套栈不是最新的,但每一层都是稳定的,出了任何问题,社区都有大量解决方案。
所以这篇文章的标题虽然是“from scratch”,但不是从数学公式开始,而是从工程实践开始。接下来我按真实的学习路径,把这个工程体系从头到尾拆一遍。
2. 核心细节解析与实操要点
2.1 提示词工程:AI时代的基本功
很多人觉得提示词工程就是“把需求写清楚让AI干活”,这个理解太浅了。真正的提示词工程,是在大模型的能力边界内,把不确定性降到最低。
我做过的第一个生产级提示词,是一个客服工单分类器。刚开始我写的提示词是这样的:
请将以下工单分类为:故障报修、咨询、投诉、建议。这个提示词看着没问题,实际一跑就露馅了。它有三个致命问题:第一,没给模型定义输出格式,有时候它输出“故障报修”,有时候输出“这是一条故障报修类型的工单”;第二,没有给示例,遇到边界case(比如“你们的服务态度让我想投诉,另外顺便问一下发票怎么开”)就乱分;第三,没有处理无法分类的情况,模型会硬给它塞一个标签。
后来我把这个提示词迭代到了第7版,结构变成了这样:
- 角色定义:你是一个客服工单分类助手
- 任务目标:对用户提交的工单进行意图分类
- 分类标准:明确列出每个类别的定义和边界条件
- 输出格式:严格限定为JSON格式,包含category和confidence两个字段
- 少样本示例:每个类别提供2-3个真实案例
- 边界处理:无法分类时输出unknown
改完之后准确率从76%提升到了92%。这个提升不是模型变聪明了,而是提示词把信息熵降低了。
我总结了一套提示词模板的写法,适用于大部分结构化任务:
第一步,定义角色和目标:让模型知道“你是谁、你要做什么、给谁做”。这一步看似简单,但很多人会漏掉目标对象。比如“写一封给客户的道歉邮件”和“写一封给技术负责人的故障说明邮件”,同样是写邮件,措辞风格完全不同。
第二步,提供上下文和约束条件:把业务背景、限制条件、必须遵守的规则都写清楚。比如“本次对话中不能编造数据”、“回复控制在200字以内”、“不能使用专业术语”等。约束条件越多,输出越可控。
第三步,明确输出格式:如果希望机器解析,一定要给JSON Schema或示例输出。如果是给人看,要给结构和排版要求。这一步是最容易被忽略的,也是后期解析报错的最大来源。
第四步,提供示例:少样本提示(few-shot)是目前提升准确率最有效的手段。不需要多,每个类型2-3个即可,但要覆盖典型场景和边界情况。
第五步,定义失败行为:告诉模型“如果无法完成,输出什么”。很多团队不做这一步,导致生产环境出现了大量无法解析的错误输出。
提示词工程的核心本质,其实是在跟概率模型做沟通。你写清楚一层,不确定性就减少一分。你写模糊一句,它就在某个你不知道的维度上随机发挥。所以我每次写提示词都会自问三个问题:角色清楚吗?任务边界清楚吗?输出格式可解析吗?三个都回答“是”,这条提示词基本就合格了。
2.2 工作流设计:别急着上Agent
2025年最热的词绝对是Agent,但我作为一路踩坑过来的人,要给各位泼一盆冷水:80%的AI应用场景不需要Agent,一个设计良好的工作流就够了。
工作流和Agent的区别,我打个比方。工作流就像流水线:每个工位干的活是固定的,工位之间的流转顺序也是固定的。Agent更像一个自由职业者:你给它一个目标,它自己决定怎么做、分成几步、用什么工具。
流水线的优势是稳定、可控、好排查问题;Agent的优势是灵活、能处理意外情况,但代价是不可解释、不可预测、调试困难。
我自己接手过的项目里,凡是工作流能解决的,绝不上Agent。比如内容审核系统,它就是一个典型的工作流:先做敏感词过滤,再做模型分类,然后人工抽检。每一步的输入输出都是确定的,模型只是其中一个环节,而且还有降级方案——模型挂了就只走敏感词过滤。
我把工作流设计总结成几个步骤:
第一,把任务拆成串行或并行的步骤。每个步骤必须是单一的、明确的、可验证的。比如“写一篇文章”可以拆成:定选题、列提纲、写初稿、审核修改、定稿。每一步的产物是明确的。
第二,定义好步骤间的数据契约。上一步的输出是下一步的输入,这个格式必须提前定死。我在实际项目中定义了一个统一的“步骤上下文”结构,所有步骤读入和产出的都包含:原始输入、中间结果、置信度、状态标记。
第三,给每个步骤设计降级策略。理想情况下每个环节都成功,但现实是模型会超时、会返回垃圾内容。降级策略可以是重试、用规则替代、或者直接标记为人工处理。没有降级策略的工作流,上线就是给自己找麻烦。
第四,全程加日志和追踪。每个步骤的输入输出都要留痕。一旦下游发现问题,能快速定位到是哪个环节出的问题。我用的是自研的步骤追踪器,本质就是一个带上下文的日志系统。
什么情况下才需要上Agent?我的判断标准是:任务步骤不固定、需要动态决策、工具选择不可预判。比如“帮我调研一下新能源车市场的竞争格局,产出一份报告”,这种任务需要模型自主决定查哪些资料、读哪些网页、最后怎么组织,这时候Agent才有意义。
但哪怕上Agent,我也建议你把它的能力边界圈起来:限定它能用的工具、限定它的行动步数上限、给它配置好护栏提示词。我见过太多Agent失控的例子,最后变成死循环、胡言乱语、甚至乱调工具。Agent不是魔法,它依然是一个概率模型,只是在更大的空间里游走而已。
2.3 模型选型:本地部署和API调用的权衡
每个做AI应用的人都会面临这个问题:用云端API还是本地部署模型?我的答案是:取决于你的数据敏感度、流量规模、成本预算,以及延时要求。
刚开始我做demo的时候,无脑用云端API,因为快、便宜、效果还好。但一旦涉及到企业内部数据,问题就来了。有一次客户要求在纯内网环境跑一个文档问答系统,云端API直接出局,只能本地部署。当时我选的是Qwen系列的中尺寸模型,量化后部署在一张消费级显卡上,效果虽然比不上云端旗舰模型,但够用。
这些年我把模型选型的逻辑梳理成了一张决策表,核心看四个维度:
| 维度 | 优先API | 优先本地部署 |
|---|---|---|
| 数据敏感度 | 非敏感、通用数据 | 业务数据、隐私相关 |
| 调用量 | 低频、弹性流量 | 高频、稳定流量 |
| 延迟要求 | 宽松 | 严格(毫秒级响应) |
| 成本结构 | 按量付费可接受 | 一次性投入、长期成本更低 |
本地部署的坑比很多人想象中多。首先是显存不够的问题,7B模型FP16推理大约需要14GB显存,加上KV cache和运行时开销,实际需要16GB以上。量化到4bit可以降到6GB左右,但量化后的效果下降能不能接受,需要实际测试。其次是并发问题,本地部署的推理服务并发能力远低于云API,你需要压测、调参、加排队机制。
我目前最常用的本地推理框架是vLLM和ollama。vLLM适合生产环境,吞吐量高,支持OpenAI兼容接口;ollama适合开发调试,傻瓜式安装,一条命令就能跑起来。简单场景用ollama,生产服务用vLLM,这个组合帮我搞定过很多项目。
还有一个常见误区:本地部署的目的不是为了跟云端比效果,而是为了保住数据的私域性和控制成本结构。如果你没有这两个诉求,坦白说,直接用云API是更理性的选择。别为了“本地部署”而本地部署,那是技术自嗨。
3. 实操过程与核心环节实现
3.1 从零搭建一个完整的AI问答系统
前面讲的都是方法论,这节我来完整走一遍实操流程。目标很明确:从零搭建一个基于本地知识库的问答系统,本地上跑模型,能上传文档,能自动切分,能做检索增强,最后输出一个可以直接用的服务。
先说整体架构,它是典型的三段式:
- 接入层:FastAPI提供HTTP接口,接收用户问题
- 处理层:先做知识库索引,再通过向量检索召回相关内容
- 生成层:把问题+召回内容打包成提示词,丢给大模型生成回答
这里面有个不可跳过的前置步骤:先把模型跑通,再写业务代码。很多新手一上来就装LangChain、装向量数据库,结果最后发现模型起不来,前面全白做了。
第一步,本地跑起一个大模型
我用的是ollama,原因无他:安装简单、跨平台、模型管理方便。在Windows和Linux上都能跑。
# 安装ollama(Linux/macOS) curl -fsSL https://ollama.com/install.sh | sh # 拉取一个中尺寸模型 ollama pull qwen2.5:7b # 启动模型并测试 ollama run qwen2.5:7b "你好,介绍一下你自己"第一次跑模型的时候,我遇到的最大问题是内存不足。7B模型默认加载进内存,我的开发机32GB内存,跑起来占了将近9GB。后来加了OLLAMA_MAX_LOADED_MODELS配置,限制同时加载的模型数量,情况好了很多。
第二步,校准模型服务接口
ollama从某个版本开始,自带OpenAI兼容接口,这意味着我可以直接用OpenAI的SDK去连它。这一步非常关键,因为后续不管换成哪家云API,代码都不用大改。
from openai import OpenAI # 直接指向本地 ollama 服务 client = OpenAI( base_url="http://localhost:11434/v1", api_key="ollama" # 本地不需要鉴权,随便填 ) resp = client.chat.completions.create( model="qwen2.5:7b", messages=[ {"role": "system", "content": "你是一个知识库问答助手。"}, {"role": "user", "content": "什么是RAG?"} ] ) print(resp.choices[0].message.content)这一步做通的标志是:你能够通过统一的代码接口切换不同的模型提供方。我为后续做了一层抽象,统一成get_model_client()工厂方法,配置项里写local就走ollama,写dashscope就走阿里云API,写openai就走OpenAI。一套代码,多端切换。
第三步,实现知识库索引
知识库的核心问题是如何把文档切成合适的块、然后向量化存储。切分看起来很简单,实际很讲究。切太粗,一块包含多个主题,检索精度下降;切太细,一句话一个块,上下文信息不足。
我目前的经验值是:普通文本按500-800字切分,带重叠窗口50-100字。这是一个平衡了效率和效果的区间。切分后每块需要带上来源引用,这样以后可以追溯答案到具体文档位置。
向量化我用的是sentence-transformers库,本地跑一个embedding模型。这步需要注意版本问题,不同embedding模型的向量维度不同,后面你切换向量库或者模型时候会遇到维度不匹配的坑,所以要把embedding模型和向量的维度固化下来。
from sentence_transformers import SentenceTransformer # 加载本地 embedding 模型 model = SentenceTransformer("BAAI/bge-large-zh-v1.5") # 对切块文本做向量化 chunk = "人工智能是计算机科学的一个分支。" vector = model.encode(chunk).tolist() # 打印向量维度,记住它,后续建表要用 print(len(vector)) # bge-large-zh-v1.5 输出 1024 维这里的embedding模型选择有个反直觉的细节:不是参数越大越好。全局向量化用的是bge-large-zh-v1.5,1024维,在中文场景下效果不错。一开始我贪图省事用了multilingual-e5-small,只有384维,跑出来的检索结果明显不如bge系列。维度越高不代表检索越准,但模型本身的训练语料和领域适配性更加关键。
第四步,向量存储与检索
我刚开始用的是chromadb,因为它轻量、不需要单独起服务、方便本地开发。后面迁移到生产环境才换成了milvus或者pgvector。
import chromadb client = chromadb.Client() collection = client.get_or_create_collection( name="knowledge_base", metadata={"hnsw:space": "cosine"} ) # 添加文档向量 collection.add( ids=["doc-001", "doc-002"], embeddings=[vec1, vec2], documents=["第一篇文档内容", "第二篇文档内容"], metadatas=[{"source": "2024-annual-report.pdf"}, {"source": "product-manual.pdf"}] ) # 检索相似内容 results = collection.query( query_embeddings=[query_vector], n_results=5 )这里要重点说明检索的参数选择。距离度量我选了余弦相似度,而不是欧氏距离。原因是:向量的绝对位置没有意义,方向才有意义。embedding模型输出的向量往往模长不一致,余弦相似度能更好地刻画语义相近程度。另外n_results的选择也有讲究:太少召回不够,太多噪音多。我用5作为默认值,后续根据测试结果调整。
检索环节还有一个大多数人忽略的关键点:先做粗召回再做精排序。粗召回靠向量相似度拉出20条,精排序用一个重排序模型(reranker)从这20条里挑最相关的5条。这比直接向量检索5条效果要好得多。我自己用的是bge-reranker-base,中英文都支持,实测下来准确率提升明显。
第五步,组装RAG提示词并生成回答
这是整个系统最关键的一步:把检索到的内容组织成提示词上下文。
def build_prompt(question, contexts): system_prompt = "你是一个严谨的AI助手。请基于给定的参考资料回答用户问题。" context_block = "\n\n".join( f"[资料{i+1}] {ctx['document']}\n来源: {ctx['source']}" for i, ctx in enumerate(contexts) ) user_content = ( f"参考资料如下:\n{context_block}\n\n" f"请基于以上资料回答问题:{question}\n" f"如果资料中没有相关内容,请明确回答'资料中未找到相关信息'。" ) messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content} ] return messages # 生成回答 messages = build_prompt("我们产品支持哪些退货方式?", retrieved_contexts) resp = client.chat.completions.create( model="qwen2.5:7b", messages=messages, temperature=0.3, max_tokens=1024 )这段代码里有两个值得品味的细节。第一,temperature=0.3,不是默认的1.0。因为做知识问答,我们希望模型偏向保守、忠实于参考资料,而不是自由发挥。温度越低,输出越确定。这个行为在测试中对比过,温度从1.0降到0.3,编造率明显下降。
第二,我在提示词里加了一句“如果资料中没有相关内容,请明确回答”。这个看似不起眼的约束,实际上把模型幻觉的概率降低了一大截。很多RAG系统回答不准确,不是因为检索差,而是因为模型在找不到答案的时候硬答。你给它一条退路,它会更谨慎。
3.2 服务化部署与性能调优
系统跑通之后,下一步就是把它变成正式服务。我用FastAPI做了一层包装,核心代码量不大,但涉及几个关键问题:并发控制、超时处理、流式输出。
并发控制:本地模型的并发能力有限,盲目开高并发只会让每个请求都变慢。我在服务层做了一个简单的信号量控制,限制同时处理的请求数:
import asyncio from fastapi import FastAPI app = FastAPI() semaphore = asyncio.Semaphore(4) # 最多4个并发请求 @app.post("/chat") async def chat(request: ChatRequest): async with semaphore: messages = build_prompt(request.question, contexts) resp = client.chat.completions.create(...) return resp这个Semaphore(4)是我压测出来的经验值。并发太高,显卡上的排队延迟会吃掉所有增益。4在当前硬件条件下是个折中。
超时处理:模型推理是慢操作,必须设置超时。FastAPI里用asyncio.wait_for包住整个调用,超时后返回一个降级响应比如“系统繁忙,请稍后重试”。实测LLM的响应时间波动很大,有时2秒,有时30秒,所以超时得设得宽容一点,我通常设为60秒。
流式输出:这是体验层面的决定性优化。同样一个长回答,一次性返回要等十几秒,流式输出首字1秒内就能看到,体感差异巨大。FastAPI配合StreamingResponse实现流式输出非常简单,前端用fetch或者EventSource接流就行。
from fastapi.responses import StreamingResponse @app.post("/chat/stream") async def chat_stream(request: ChatRequest): async def generate(): stream = client.chat.completions.create( model="qwen2.5:7b", messages=..., stream=True ) for chunk in stream: delta = chunk.choices[0].delta.content if delta: yield delta return StreamingResponse(generate(), media_type="text/plain")这一段的工程价值非常大。很多团队的RAG系统demo效果好,上线体验差,就差在流式输出这一环——用户面对一个转圈十几秒的页面,第一反应就是“系统太慢了”。流式输出至少把等待焦虑化解了。
3.3 评估与测试:别让模型裸奔上线
说实话,大多数个人开发者和中小团队做AI应用,最缺乏的就是评估环节。大家把系统跑通了、demo演示美美的,就直接上生产。然后real用户一提问,效果原形毕露。
我做AI工程最深的体会是:没有评估体系,就没有迭代基础。你连现在这个版本好坏都不知道,怎么优化?怎么回归?怎么对齐业务指标?
我搭了一套非常轻量的评估框架,跑了半年,收益巨大。
第一层,构造测试集。从真实历史对话里挑100条有代表性的问题,覆盖各种意图类型和难度级别。然后人工标注标准答案。这100条就成为回归基线。
第二层,跑自动化评测。每次改提示词、换模型、调参数,都跑一遍这100条,算一个准确率指标。如果新改的提示词导致准确率下降,说明是负优化,回滚。
# 我通常用pytest组织评估用例 pytest tests/test_rag_eval.py -v --tb=short第三层,评估维度拆开看。单独看回答准确率不够,我还会拆出三个维度单独看:检索结果相关性、幻觉程度、完整度。这三个维度问题根源完全不同:检索相关性问题出在embedding和切分策略,幻觉出在提示词和温度参数,完整度出在上下文长度和模型能力。拆开看才能精准定位。
评测环节还有一个心得:让AI当裁判不是不靠谱,但要给裁判定规则。我会用另一个模型对回答打分,打分标准是明确的、结构化的。比如“对比标准答案,判断模型回答是否包含核心要点,1-5分”。用模型裁判做初筛,人工只复核低分样本,这个组合的效率极高。
4. 常见问题与排查技巧实录
4.1 模型输出总是“答非所问”怎么办
这个问题的原因通常是:提示词里的角色和任务描述不够清晰,或者检索到的上下文质量太差。
排查步骤我建议按顺序走:
第一步,检查提示词本身。把messages完整打印出来,放回模型单独对话里试一下。如果单独试也答非所问,说明是提示词的问题,不是检索的问题。最常见的情况是角色定义和任务目标写得模棱两可,比如“你是助手”这种毫无区分度的角色。
第二步,检查检索结果。把检索返回的前5条内容打出来看,是不是真的和问题相关。很多情况下问题是检索到的内容压根不相关,模型只能硬答。这时候去调embedding模型、切分策略、或者召回数量。
第三步,检查上下文拼接。有些时候检索结果相关,但拼进提示词后格式混乱,模型无法理解“哪段是资料,哪段是问题”。我用[资料1] [资料2]这种显式标注后,效果改善非常明显。格式本身就是一种信息,模型很吃这一套。
第四步,降低温度参数。如果上面三步都排查过了还不满意,试试把temperature降到0.1甚至0。有些问题领域就是不需要创造力,稳定比出彩更重要。
4.2 向量检索不准,召回结果乱七八糟
这个问题的排查方向同样是多角度的。至少有三个变量会影响召回效果。
Embedding模型:中文场景下,bge-large-zh-v1.5和bge-m3都是不错的选择。如果发现英文效果好、中文效果差,大概率是embedding模型的中文语料覆盖不足。换成专门的中文模型试一下。
切分粒度:切得太细,单块信息量不够,检索时匹配不到;切得太粗,一块混了多个主题,检索出来噪音太大。我做过一组对照实验,发现500-800字的块在大多数场景下效果最好。但也分场景:如果文档是FAQ形式,按“一问一答”为单位切分效果会好得多;如果是法律合同这种长段落文本,可能需要更长的块单位。
检索策略:直接向量检索不是唯一的召回方式。某些场景下,关键词检索(BM25)和向量检索的混合召回效果反而更好。这不算高深的东西,就是把两种检索的结果合并去重,然后一起进重排模型。我在处理“包含专业术语的查询”时,混合召回的效果明显优于纯向量检索,因为向量检索对精确术语的匹配能力不强。
如果试完这三个方向还是不行,那就要检查一下你的测试集本身是不是有问题。有的问题本身语义模糊、或者需要多跳推理,这些case即使模型再强也难搞对。把“困难case”和“正常case”分开统计准确率,能帮助你更清楚地定位到底是系统问题还是问题本身太难。
4.3 本地推理速度太慢,响应时间不可接受
本地部署最让人头疼的就是性能问题。我拥有一台消费级显卡的机器,跑7B量化模型,第一次上线时单次请求耗时15秒以上,完全不可用。优化后降到了3秒左右,勉强能接受。
核心优化手段有四招:
第一招,模型量化。从FP16量化到INT8或者INT4,推理速度能提升一倍以上,显存占用也大幅下降。代价是效果略微下降,但多数场景下可接受。我用过GGUF的Q4_K_M量化格式,效果和速度比较均衡。
第二招,换推理框架。ollama适合开发和低并发场景,但生产环境我换成了vLLM。vLLM的Continuous Batching技术能显著提升吞吐量,特别是在并发场景下。实测同样的模型,vLLM的吞吐量是ollama的3-5倍。
第三招,控制上下文长度。这个点很容易忽略。LLM的推理耗时和输入token数强相关,你把几千字的上下文全部丢进去,每次推理都会变慢。如果的业务不需要那么长的上下文,就在API调用时用max_tokens限制输出,同时控制输入长度。我在RAG系统里会默认只塞回5条相关资料,总token控制在1500以内。
第四招,加缓存层。语义缓存很多人没听说过,原理是把高频问题(去重后)的答案缓存起来。先用新的问题去和缓存中的问题做一次向量相似度检索,匹配度超过阈值就直接返回缓存答案。这一招在客服、售前这类高频重复问答场景下,能把有效负载降低60%以上。但要小心里面的风险:相似问题和完全相同的问题不是一回事,缓存误命中会导致答非所问。
4.4 一些问题排查速查表
我把常用的问题排查整理成一张速查表,方便大家直接对照:
| 表现 | 第一排查点 | 第二排查点 | 第三排查点 |
|---|---|---|---|
| 模型答非所问 | 角色任务描述不清晰 | 上下文拼接格式 | temperature过高 |
| 检索结果不相关 | embedding模型选型 | 切分粒度过大过小 | 召回策略单一 |
| 响应时间过长 | 推理框架未优化 | 上下文token过多 | 并发争抢显存 |
| 输出JSON解析失败 | 输出格式未严格约束 | 模型知识不足 | 截断触发导致JSON断裂 |
| 事实编造严重 | 无“拒绝回答”兜底 | 检索上下文缺失 | 温度过高 |
| 模型输出含敏感词 | 缺少内容过滤器 | 提示词未定义边界 | 需要一个审核模块兜底 |
4.5 避坑清单:那些看文档学不到的教训
最后分享几条我个人付出真金白银换来的避坑经验。
第一条,AI应用的测试集一定要从真实数据里挖,不要自己编。自己编的测试集永远觉得自己系统没问题,真实用户的提问方式千奇百怪,示意图里根本没有覆盖。我后来做了个数据回流机制,把线上评分低的case定期拉回测试集,效果提升非常快。
第二条,提示词要版本化管理。我把提示词写进配置文件,而不是硬编码在代码里。每个改动记录用途和效果,方便回滚。这么做的直接原因是吃过一次亏:改了一个提示词,全线上效果暴跌,但因为没留版本,回去找都找不到上一版是什么。
第三条,设计降级方案是必须的。模型服务是不可靠的:网络抖动、显存溢出、API限流,各种情况都会发生。我的所有AI服务都有三层降级:先用规则匹配,规则不行用小模型,小模型不行返回人工兜底话术。降级方案不是锦上添花,是生存刚需。
第四条,别盲目相信Agent的自主能力。我见过很多团队用Agent处理需要高准确率的业务,结果输出稳定性差得离谱。Agent适合做“探索型任务”,不适合做“精度型任务”。如果你要做精度型任务,请用确定性工作流,把模型的使用范围严格限定在某个环节里。
第五条,本地部署和云API最好双轨跑。我的生产环境一直保留两套模型接入能力:本地主机和云API。平时用本地跑,重要时刻或者本地负载高了切云端。这个切换只需要改配置,不需要改代码。双轨部署的额外成本不高,但关键时刻能救命——比如GPU服务器宕机,你可以半小时内把流量切到云端,保住业务连续性。
5. 后续还能怎么往前拓展
当你把这套AI工程体系跑通了,会发现它还有很大的扩展空间。我提出几个方向,都是我走过或者正在走的路:
第一个方向,从单模型走向多模型协同。现在的场景大多是单一模型完成全部任务,当任务足够复杂时,可以尝试不同模型分工:一个模型做理解,一个模型做检索重排,一个模型做生成。每个模型各司其职,整体可靠性会提升。这种架构需要额外设计模型间的通信协议和数据格式。
第二个方向,把系统数据闭环跑起来。目前大多数团队的AI系统是“使用-结束”,没有数据回流的闭环。如果能把用户的每一次提问、每一次反馈、每一次评分都记录下来,沉淀成一个持续迭代的数据飞轮,系统的价值会指数级增长。这个方向不涉及前沿AI技术,更多是数据工程和产品设计能力。
第三个方向,从单机走到分布式推理。当单卡显存装不下模型,或者单机并发扛不住流量时,就需要考虑多机分布式的推理部署。比如用vLLM配合Ray把推理扩展到多台机器,或者用推理网关做路由。这套体系做出来后,AI应用才算真正具备了“工程化”的完整拼图。
每次回顾这段从零起步的折腾过程,我最大的感受是:AI工程不是说会调API就完了,也不一定要学会训练模型。真正核心的能力,是把这些概率系统变成可控、可测、可持续迭代的软件产品。这条路没有捷径,只能按工程的逻辑一步一步走,把每个环节都打磨扎实。希望这篇记录能给正在这条路上的你一些参考。