ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零搭建AI工程闭环:数据清洗、微调、Agent编排与部署全链路实践

从零搭建AI工程闭环:数据清洗、微调、Agent编排与部署全链路实践 做AI工程这件事我真正想说的是只会在网页上调对话和能独立从零搭一个AI应用中间隔着一整套看不见的体系。2024年年底我立了一个项目名字就叫“ai-engineering-from-scratch”——从零搞一套完整AI工程闭环数据清洗、模型微调、提示设计、Agent编排、效果评估、上线部署全都不外包。项目做下来最大的感受真正难的不是调用大模型而是怎么把模型放进一个可维护、可评估、可回退的工程系统里。这个项目适合两类人一类是已经开始用AI工具但想搞懂背后原理的开发者另一类是负责把AI落到业务里的技术负责人。如果你只想找个聊天网页这篇文章不是写给你们的如果你想亲手验证一套从数据到部署全链路怎么走通跟着往下看就对了。1. 项目为什么值得从零做起整体设计与选型逻辑1.1 “从零开始”不等于从零发明很多人一听“from scratch”就头大以为要自己写Transformer、自己训基座模型。不是的工程意义上的from scratch是基础设施自己搭、数据处理自己写、微调和评测自己跑、Agent编排自己设计但模型本体可以选开源。为什么要这么干市面上能用的AI平台太多了你甚至在聊天窗口里就能完成一个不错的内容生成流程。但平台是黑盒你不知道它上下文怎么处理、不知道为什么某些输入更慢、不知道结果被什么策略筛过。自己把整条链路搭起来之后每个环节的成本和瓶颈都摊在面前做优化时才有方向。就像学做饭买料理包加热五分钟也是一种结果但只有自己备菜、开火、调味你才知道“这盘菜咸了”是盐的问题还是生抽的问题也才能控制出品质量。你不需要从种水稻开始但你需要亲自掌握从备菜到出锅的关键环节。对团队来说这套能力带来的影响是决定性的别人只能被供应商牵着走你能自己定位问题、自己改策略、自己控制迭代节奏。1.2 技术选型不追新只求组合成熟我给自己定的选型原则是每一层都选社区验证过、文档齐全、出现问题时能搜得到答案的组件。模型层选了Qwen2.5-7B-Instruct。选它主要三个原因中文能力强、7B尺寸消费级显卡能跑、指令遵循能力在同尺寸里稳定。配套选了BGE-M3做embedding中文检索效果不错而且输出固定维度向量和很多向量库的兼容性都很好。编排框架选了LangGraph而不是纯手写状态机。一开始我也纠结过LangGraph重、学习曲线陡但项目里一旦要处理“多轮对话工具调用记忆管理”手写状态机很容易失控。LangGraph把节点、边、状态管理给抽象了调试和回溯都方便。我之前用它写过带工具的客服Agent体验还算稳。存储选了PostgreSQL加pgvector而不单独上独立向量数据库。原因很简单少一个组件就少一类运维问题。项目初期一个PG实例既能存业务表也能存向量检索的表。等到确实撑不住了再拆出来这种“渐进式架构”对从零开始的项目最友好。选型的核心不是每个工具都是社区里最热门的而是它们组合在一起时链路最短、排障路径最清晰。1.3 整条链路搭完才知道瓶颈在哪我先按五段拆解项目数据工程、模型调优微调提示、Agent编排、评估测试、部署运营。这个顺序有讲究数据决定上限模型决定基座编排决定形态评估决定能不能放心用部署决定能不能持续用。没有搭骨架之前我一直以为瓶颈在模型能力搭完之后才看清半个项目周期耗在数据清洗和评估集建设上。所以这篇博文的后四章全是我实际跑完后的记录不是纸上谈兵。每个环节我会先讲“为什么这么干”再给可以直接抄的参数和步骤遇到绕不过去的坑也会单独列在最后一章省的你再踩一遍。2. 环境搭建与数据工程最先卡住你的不是模型2.1 开发机配置和软件栈我的实际操作环境分为两层本地开发机加一台GPU服务器。开发机上只装Python 3.11和CUDA驱动用pip和conda分开管理环境。这里有个新手非常容易踩的坑不要一股脑把所有包装进同一个环境。我会拆成“训练环境”和“推理环境”两个环境装不同版本的torch、vllm、transformers否则依赖冲突能把一个下午全吃光。训练环境用NVIDIA的NGC容器镜像推理环境用vLLM自带的镜像各管各的互不污染。GPU服务器配置其实不用太豪华7B模型微调用两张RTX 3090或者一张A100都够。我实际用两张4090LoRA微调7B模型能吃下大概4万条数据峰值显存约36GB。这类项目里显存规划比计算速度更关键因为一旦OOM整轮训练直接崩。我的建议是提前把每次训练预期显存算出来模型权重占一半梯度、优化器状态、激活值各占一部分LoRA因为只训练小部分参数激活值仍是大头。把max_seq_len设短一点往往能救回半张卡。2.2 数据获取公开数据集的取舍艺术从零开始最容易被忽视的就是数据。很多人把模型一下载就急着微调结果训练完发现模型全学会了胡话。我建议数据获取的第一优先级不是“量大”而是“干净”。我用过几个公开的中文指令数据集质量参差不齐清洗时发现一个典型问题很多开源SFT数据集里掺杂大量重复样本、无意义口语和带倾向性的内容。你模型还没训好先学会了废话回头看就很难救。我最后采用的做法是筛掉明显低质的来源再用规则去重——用Jaccard相似度对instruction字段做去重相似度超过0.85的直接丢弃。别用“全部数据都有用”的思路。数据量和模型效果之间不是线性关系1万条高质量指令往往胜过50万条重复噪音。这条经验在项目里多次被验证我第一次用全量数据训练评估集得分反而不如第二版只用一半数据但清洗更狠的模型。2.3 清洗与格式化一个真实处理流程下面这段流程是我在项目里实际跑过的可以直接参考。数据统一转成JSONL每行是一条指令样本字段主要是instruction用户请求input可选的上下文输入绝大多数情况留空output期望模型给出的回答metadata来源、语言、标签后面评估时要靠它分层抽样清洗规则我列了四条第一过滤包含URL堆叠、邮箱、无意义乱码的样本第二过滤一句话都说不完整的“半截数据”第三对超长样本截断超过2048 token做头部保留加尾部保留中间摘要化第四对明显涉黄涉暴等不符合公序良俗的内容整批删除这类数据不光影响安全训练下去会让模型在正常对话里也“跑偏”。给一个简单的Python过滤示例这段代码的意图是说明清洗不是花哨的算法而是一组简单但有明确判断依据的规则。import json from pathlib import Path def clean_line(line: str) - dict | None: try: obj json.loads(line) except json.JSONDecodeError: return None if not obj.get(instruction) or not obj.get(output): return None if len(obj[instruction]) 4 or len(obj[output]) 8: return None if len(obj[output]) 1500: obj[output] obj[output][:1500] return obj def main(raw_path, out_path): p Path(raw_path) with p.open(r, encodingutf-8) as f_in, open( out_path, w, encodingutf-8 ) as f_out: for line in f_in: obj clean_line(line.rstrip(\n)) if obj: f_out.write(json.dumps(obj, ensure_asciiFalse) \n) if __name__ __main__: main(raw.jsonl, clean.jsonl)实际操作里我还会加一步“去重”用set()对完整JSON字符串做一次MD5哈希再用相似度去重做第二轮。双重过滤下来数据量通常会砍掉三到四成但这正是把水挤干训练出的模型反而更扎实。3. 模型微调与提示工程让模型听你话的两种姿势3.1 先别急着微调Prompt到底能解决多少问题我见过太多项目需求刚提出来就说“我们要微调模型”。其实90%的场景先用提示工程就能解决尤其是已有知识库的问答、格式转换、简单分类。微调的成本和风险都比提示高做得不好反而引入劣化。提示工程里最关键的是三件事角色设定、约束条件、few-shot示例。我举个实际例子把客服系统里的意图识别Prompt从“你是一个分类器”改成你是一个严谨的中文客服意图分类器。只输出下列选项之一query_order、apply_refund、check_logistics、other。如果用户输入包含订单号优先归类为query_order。示例1. 用户“帮我查一下订单123456到了没” 输出query_order。2. 用户“我想退款但是不知道怎么操作” 输出apply_refund。现在开始分类用户请问我的快递到哪了同样一个任务加了示例和约束后准确率肉眼可见提升。别小看这一步提示工程是投入产出比最高的环节改一次提示词可能比重训一版模型更快见效。但也要承认瓶颈当模型在训练里完全没见过某个行为模式或者知识需要持续更新提示就顶不住了。这两个场景分别对应微调和RAG。3.2 微调实操LoRA的参数设计真到了需要微调的时候我从不开全量微调全部用LoRA。原因不复杂LoRA只训练小部分新参数显存占得少不容易灾难性遗忘迭代起来快。7B模型的LoRA在单机双卡上一版训练基本一个晚自习的时间就能出结果非常适合走“训一版-评估-再调”的循环。我项目中一组稳定可复现的参数直接给出来参数名取值我的理解lora_rank16太低8学不住复杂指令太高64容易过拟合也没有必要lora_alpha32控制新参数影响幅度alpha是rank的两倍最稳妥lora_dropout0.05防止过拟合learning_rate2e-4LoRA常用范围1e-4到5e-42e-4稳妥num_epochs3数据量4万以内3轮足够超过3轮容易背题per_device_batch_size8配合梯度累积让总batch保持32附近max_seq_len2048覆盖绝大多数指令场景为什么要关注这些参数因为它们直接决定训练稳定性和最终效果。比如learning_rate设置过大loss上下乱跳训练曲线像心电图设置过小训练半天loss还在原地。我一般会把训练日志里loss变化实时盯一下前500步loss如果降不到初始的一半就说明lr或数据有问题尽早停掉改参数不要心疼已经跑完的时间。还有一点LoRA的target modules要覆盖q_proj和v_proj最好把k_proj、o_proj也加上只改两个projection会导致信息流瓶颈。3.3 微调常见翻车点loss下降但生成质量变差大概率是训练数据格式不统一或者训练集里“正确答案”本身质量低。你让模型学垃圾它学得再快也是垃圾。模型回答全是套话、车轱辘话过拟合信号。尤其当训练文本里有大量“请注意”“综上所述”这类模板话术模型会把它们当成风格偏好。数据清洗阶段就要把这些模板痕迹去掉。微调后通用能力下降这在LoRA上少一些但仍会发生。对策是混合一部分通用数据到训练集里别让业务数据占比超过80%。另外微调完第一件事不是直接上生产而是跑一遍通用能力评测集对比基础模型的分数有没有明显回落。4. Agent设计与多AI协作从单点能力到完整体系4.1 先设计一个真正能干活的Agent提示工程和微调解决的是“单个模型输出质量”但真实业务里模型往往需要调用工具、查数据库、访问外部服务。单次生成搞不定就得做Agent。我这次项目里设计了一个最简但能真正跑通的Agent订单客服助手。结构分成四块规划决定下一步要做什么、工具调用查询订单、查退货政策、检索知识库、记忆保存本轮对话关键信息、反思根据工具返回结果判断是否完成。规划部分就靠一次模型调用给定“可用工具列表当前用户问题”让模型输出一个JSON格式的“行动计划”。工具调用通过function calling机制实现模型输出“调用订单查询工具参数order_id”后端代码负责执行真实业务查询拿到结果后再放回上下文让模型做下一步判断。我踩过的一个关键坑刚开始把工具返回结果原封不动塞进上下文结果模型经常被几页长的日志干扰开始胡说八道。后来我把工具返回做摘要只回传业务字段和必要信息准确率立刻上来。工具返回值质量直接决定Agent最终回答质量这是一个很容易被忽视的工程细节。4.2 多AI协作从“一个模型”到“一组模型”现在的Agent往往不是一个大模型单独干而是多个模型按角色协作现在圈里的说法叫harness engineering意思是把多个模型“套上马具”协同工作。你做规划用指令遵循强的模型做精调用知识密集的专用模型做评分/批评用另一个更严格的模型每个模型只干自己擅长的事。我把客服Agent做成三角色协作planner负责拆解用户意图决定调用哪个工具executor负责处理工具返回的数据生成草稿回答critic负责检查草稿是否答非所问、是否包含重复信息不通过就打回executor重写这种结构的核心收益是职责单一每个环节都能单独评估、单独优化。代价是交互轮数增多、token成本上升而且错误会沿链条向后传播——planner错了后面再努力也白搭。所以工程上要在关键节点加“门禁”比如planner输出格式校验不通过直接要模型重试而不是让它带病往下走。我还会为每个角色设定独立的temperatureplanner用低温度保证稳定executor用中等温度保持自然critic用更高温度找出更多问题。4.3 工作流把碎片任务串成流水线多AI协作不能靠脑子记要落在可编排的工作流里。我用的做法是代码即工作流不引入可视化拖拽平台原因是从零项目要保留“能出问题就能查”的能力。常见三种工作流模式流水线pipeline一个模型输出传给下一个模型处理适合“意图识别→信息抽取→内容生成”路由router根据条件把请求分发给不同模型适合不同语言、不同复杂度的请求分流映射归约map-reduce把一个复杂任务拆成多个子任务并行处理再合并结果适合长文档总结以下是一段简化的LangGraph伪代码用来展示怎么把四步串成一个图from langgraph.graph import StateGraph def parse_user(state): ... def call_tool(state): ... def draft_answer(state): ... def review(state): ... graph StateGraph(AgentState) graph.add_node(parse_user, parse_user) graph.add_node(call_tool, call_tool) graph.add_node(draft_answer, draft_answer) graph.add_node(review, review) graph.add_edge(parse_user, call_tool) graph.add_edge(call_tool, draft_answer) graph.add_edge(draft_answer, review) graph.add_edge(review, call_tool) # critic 不通过时重来最后这条边是条件回边表示如果critic不通过回到工具调用重来。实际项目里要加“最大重试次数”做保护否则一个坏案例能把上下文窗口撑爆。我默认设成最多重试两次第三次直接返回兜底话术并记录异常这比让用户陷入死循环要好得多。5. 评估、测试与上线AI工程里最容易被低估的部分5.1 离线评估不能只盯着loss很多团队微调完看loss低了就上线这是灾难的开始。loss下降只代表模型“背”下了训练集不代表它能泛化到真实问题。我的做法是维护一个固定评估集规模不大但覆盖分层100条线上真实问题和50条构造的边界case。每次模型更新都跑一遍评估指标记录对比。评估指标不只看文本相似度还要看任务完成率。指标作用坑任务完成率用规则或模型判断用户问题是否被真正解决别只看回答长度检索召回率5RAG系统里判断知识库有没有把正确文档捞回来指标高不代表回答正确格式正确率模型输出JSON或表格时是否有效很基础但最容易翻车LLM-as-judge用一个强模型给回答打分打分模型也有偏置需要校准LLM-as-judge是现在必备手段它的基准是人工判断而非模型自身。用法是把用户问题、正确的参考回答、模型实际回答一起交给judge模型让它按“准确性、完整性、语气”三个维度打分。注意点judge模型本身也有偏好比如容易给更长的答案更高分所以评估时要加上对长度做归一化的要求或者坚持随机顺序对比不要总把模型输出放在固定位置。我实测过同一个模型放在A/B两个位置得分能差出10分以上不控制变量等于白测。5.2 回归测试与线上监控评估集只能离线兜底上线之后必须有线上的闭环。回归测试我把它接在CI里每次代码变更或prompt调整都会自动跑一遍小评估集分数不达标就阻止合并。批量跑大概100条eval几分钟出结果完全能接受。线上监控重点看三个信号P95延迟、GPU利用率、用户反馈。延迟上去了要看是不是上下文太长GPU利用率低说明并发设计没吃满用户反馈是最直接的评估来源所以我搭了一个反馈通道客服对话结束页加“这个回答是否有帮助”按钮把反馈数据回流到评估集里形成数据飞轮。这里有一个更进阶的玩法把线上日志按周采样人工标注一部分并merge进评估集让评估分布始终跟随真实业务变化。没有这一步离线评估分数再漂亮也和用户体感脱节。5.3 部署把模型包成一个演进服务部署推荐FastAPI包一层HTTP服务再配合vLLM做推理后端。架构文字描述客户端请求先进到FastAPI网关网关做鉴权、格式校验、记录trace然后把请求发往vLLM推理服务vLLM支持连续批处理能显著提高GPU利用率。RAG检索用PostgreSQL/pgvector提前用embedding模型把知识库向量化。部署时有一个关键参数maximum concurrent batch。如果设太小GPU空闲设太大P95延迟暴增。我的做法是压测100并发逐步加到300观察延迟和显存找到拐点再下调20%作为安全水位线。写代码的时候不要把大模型调用放在同步路径上否则一个慢请求会堵死整个线程池。我在FastAPI里用的是异步请求配合vLLM的streaming输出前端体验也更好。每次发布新模型不是直接全量切流而是先灰度10%对比评估指标和线上反馈确认没问题再逐步放量。一旦发现异常能一键回滚到上一个版本这个机制比模型本身更重要。6. 常见问题与排障实录6.1 十个高频问题速查问题原因与排查方向解决参考环境里import torch报错找不到CUDAconda和pip混装导致libcudart版本不一致统一用NGC镜像或conda环境检查nvidia-smi和torch.version.cuda训练时loss为NaN学习率过大或数据里有异常长文本降低lr到1e-4清洗超长样本LoRA训练后输出完全跑偏数据质量差或训练格式不统一抽20条训练集人工审查重做清洗RAG检索不到正确文档embedding模型和chunk size不匹配检查chunk切分长度长文档用滑动窗口加重叠prompt改了没效果缓存没清或测试集覆盖不全面排查缓存层测试集加边界caseAgent陷入工具调用死循环缺少最大迭代次数或工具返回没格式化加最大迭代数和输出校验推理服务OOM并发过高或max_model_len过大调低并发、限制max_model_len加流式返回在线feedback和离线评估结论不一致离线评估集偏离真实分布从线上日志抽样补充评估集多模型协作时token账单爆涨每个环节都塞了完整历史裁剪上下文窗口只传关键字段摘要模型回答出现安全风险数据或prompt缺少安全护栏加系统级安全过滤层高危输入直接拦截6.2 四个我踩过而且差点放弃的坑第一个坑盲目相信公开数据集的“干净”标签。我在一个叫“高质量对话数据集”的仓库里发现大量口语垃圾和重复模板模型训练完说话满嘴套话。从那次以后我要求自己每次切换数据集之前先做50条抽样人工审查眼睛看过的东西才敢进训练集。第二个坑离线评估集太乐观。我一开始评估集全是从训练数据里抽的题目模型“见过”答案评估分数漂亮得不像话。上线后被真实用户一轮轮问题打爆。后来把线上日志按周抽样合并进评估集才让评估分数和真实体验有了基本对应。第三个坑GPU OOM出现在我最想不到的环节——不是训练而是并发推理。训练时我精打细算推理时以为vLLM能兜底结果某次线上大并发直接OOM。查半天发现是max_model_len设太大输入输出都可能撑到8kGPU显存按最大长度预分配直接把显存吃光。把max_model_len调到2k后问题消失。第四个坑多语言提示词混用。我习惯在prompt里中英混杂比如“请用json格式输出return only JSON”但主模型对混合指令理解不统一经常把英文指令直接复述出来。后来我限定所有提示词统一用中文写模型行为稳定了很多。这四个坑的共同教训是AI工程的每一步都要有反馈闭环不管是数据的、评估的还是运行时的。没有闭环你只能靠玄学调参有了闭环问题都能定位到具体环节。我个人复盘这个from scratch项目最大的体会是AI工程不是把模型train出来就完事而是一整套关于“质量-成本-稳定性”的平衡术。你能把评估、测试、部署、回滚做扎实才算真正把AI接进了业务。这个项目我还在持续扩展下一步打算把评估集做成半自动化从线上反馈自动生成新case进评估集让飞轮转得更快。如果你也在从零搭自己的AI工程建议先别急着追求酷炫的模型把数据清洗和评估闭环搭建起来你会少走非常多的弯路。
返回列表