大模型工程岗交付 checklist:推理/RAG/Agent 三层实战加固

发布时间:2026/10/3 5:17:08
大模型工程岗交付 checklist:推理/RAG/Agent 三层实战加固 1. 这不是“懂模型”就能上岗的岗位大模型工程岗的真实交付现场“大模型岗位要的是‘能交付’”这句话最近在招聘JD里出现频率高得反常。我带过三届校招新人也帮五家不同规模的公司搭建过大模型落地团队亲眼见过太多简历上写着“精通LLaMA、微调过Qwen、跑通过LangChain”的候选人在面试现场被问到“怎么把RAG服务压测到200 QPS不丢请求”时直接卡壳也见过刚入职的工程师花两周时间调通了本地OllamaChroma的RAG demo结果上线后用户一搜“发票报销流程”返回的却是去年采购合同里的模糊条款——不是模型不行是整个推理链路里缺了3个关键工程断点。所谓“能交付”不是指你能在Jupyter里跑出一个准确率87%的答案而是指你能把模型能力稳稳地、可预期地、可运维地塞进业务系统的毛细血管里。它要求你同时理解Transformer的KV Cache机制、FastAPI的异步生命周期、PostgreSQL的全文检索优化、向量数据库的HNSW图层重建策略以及——最关键的——业务方真正卡在哪一步。热搜词里反复刷屏的“RAG瓶颈”“Agent怎么扛并发”“rag知识库能存储图片嘛”背后全是真实交付场景里摔出来的坑。这篇文章不讲理论推导不列论文引用只拆解我在金融、政务、制造业三个行业实际交付过的7个大模型项目中那些写在SOP里、但没人教你的工程动作清单。如果你正准备投递大模型工程岗或者已经入职但总被业务方追问“为什么这个功能又慢又不准”那接下来的内容就是你明天早上打开IDE时该优先检查的 checklist。2. 推理服务从“能跑通”到“能扛住”的四层加固逻辑2.1 为什么90%的本地推理服务在压测前就埋了雷很多人以为推理服务的核心是选对模型和框架比如用vLLM还是Text Generation InferenceTGI用Ollama还是Llama.cpp。这就像装修房子只盯着瓷砖品牌却忘了承重墙结构。真正的工程挑战藏在四层之间协议层→调度层→计算层→资源层每一层都存在典型的“伪稳定”陷阱。协议层最常见的是HTTP/1.1长连接滥用。我接手过一个政务问答系统前端用axios默认keep-alive后端用Flask看似QPS 50很稳。但当并发用户从200涨到300时大量请求卡在TCP TIME_WAIT状态监控显示连接数暴涨但CPU利用率仅40%。根本原因在于Flask默认的Werkzeug服务器不支持异步IO每个请求独占一个线程而HTTP/1.1的keep-alive让连接长时间挂起线程池被耗尽。解决方案不是换模型而是强制升级到ASGI协议栈用Uvicorn替代Werkzeug配合Starlette封装API将单实例吞吐从50 QPS提升至320 QPS且内存占用下降37%。这里的关键参数是Uvicorn的--workers和--http选项组合——实测发现--workers 4 --http h11在4核机器上比--workers 2 --http httptools更稳因为h11对短连接更友好而httptools在高并发下容易触发GIL争用。调度层的坑更隐蔽。很多团队直接用Hugging Face的pipeline加载模型代码干净漂亮“pipe pipeline(text-generation, modelqwen2-7b)”。但生产环境里这个pipeline会为每个请求重新初始化tokenizer、构建输入张量、执行padding导致首token延迟TTFT波动极大。我们给某银行做智能投顾时发现TTFT从200ms跳到1200ms根源就是pipeline的动态batching未开启。正确做法是绕过pipeline用transformers底层API手动控制先用AutoTokenizer.from_pretrained()预加载tokenizer并设置paddingTrue, truncationTrue, max_length2048再用model.generate()时显式传入do_sampleFalse, num_beams1, early_stoppingTrue并启用use_cacheTrue复用KV Cache。这样TTFT标准差从±450ms压缩到±60ms业务方终于能接受“3秒内响应”的SLA承诺。计算层的典型误区是盲目追求FP16量化。某制造企业想用本地GPU部署Qwen2-72B测试时发现A100显存爆满。工程师立刻尝试bitsandbytes的4-bit量化结果生成质量断崖下跌——不是模型问题是量化策略错了。Qwen2的MLP层对权重敏感度远高于Attention层全层4-bit会导致FFN输出失真。我们最终采用分层量化Attention层用6-bit保留QKVO矩阵精度MLP层用4-bitW1/W2/W3Embedding层保持FP16。工具链用auto-gptq而非bitsandbytes因为前者支持per-layer bit-width配置。实测显存占用从82GB降至31GBPPL困惑度仅上升0.8业务文本生成准确率维持在92.3%。资源层最容易被忽视的是GPU显存碎片化。用nvidia-smi看到显存占用85%但torch.cuda.memory_allocated()只显示60%剩下25%是碎片。某次上线后突发OOM排查发现是多个小batch请求触发了CUDA内存分配器的碎片堆积。解决方案是启用torch.cuda.empty_cache()在每次推理后主动清理并在服务启动时预分配显存池torch.cuda.memory_reserved(0)torch.cuda.set_per_process_memory_fraction(0.9)。更彻底的做法是改用vLLM其PagedAttention机制本质就是为解决显存碎片而生——它把KV Cache按page切分像操作系统管理内存页一样动态分配实测在相同硬件下vLLM的batch size上限比HuggingFace原生方案高2.3倍。提示不要迷信“一键部署脚本”。所有开源推理框架TGI/vLLM/Ollama的默认配置都是为demo设计的。生产环境必须重写资源配置vLLM的--max-num-seqs要根据平均prompt长度反推公式max_num_seqs GPU显存GB × 1024 / (avg_prompt_len × 2 × 2)其中2是KV Cache字节数第二个2是head数冗余TGI的--max-batch-size需结合--max-input-length做约束否则高并发下会触发OOM Killer。2.2 实战压测用真实业务流量验证服务韧性压测不是跑个ab命令就算完。我们给保险公司的保全服务做RAG推理压测时设计了三级流量注入第一级是协议层压测用k6模拟HTTP连接风暴。重点观测netstat -an | grep :8000 | wc -l的ESTABLISHED连接数变化曲线。当连接数超过ulimit -n设定值默认1024时必须调整/etc/security/limits.conf中的nofile参数并重启服务进程。这里有个坑Docker容器内ulimit默认继承宿主机但Kubernetes Pod需在securityContext里显式声明resources.limits.nofile否则即使宿主机调高容器内仍受限。第二级是语义层压测用真实业务query构造流量。不能只用“你好”“今天天气如何”这种无意义请求。我们从客服日志里抽样1000条真实问题按热度加权生成压力包高频问题如“保单贷款怎么操作”占60%中频如“犹豫期退保手续费多少”占30%长尾如“2018年XX产品条款第3.2条解释”占10%。用Locust编写task每个user模拟真实会话流先发query等response再基于response内容发follow-up question。这样暴露出两个问题一是RAG检索模块在长尾query下召回率骤降二是Agent编排层在多轮对话中session state同步失败。前者通过重构embedding模型换用bge-reranker-v2-m3替代bge-base-en后者通过引入Redis作为state store解决。第三级是混沌压测主动制造故障。用Chaos Mesh注入网络延迟模拟公网抖动、CPU飙高模拟其他进程抢占、磁盘IO阻塞模拟向量库慢查询。关键指标不是成功率而是故障恢复时间MTTR。我们发现当向量库响应超时达5s时服务会卡死——因为默认的requests库timeout设置为timeout(3, 30)即connect 3s read 30s而read超时未做熔断。解决方案是在FastAPI中间件里加asyncio.wait_for包装设置全局read timeout为8s并配置tenacity库做指数退避重试最多2次间隔1s/2s。实测MTTR从47s降至3.2s。注意压测报告里必须包含“业务影响映射表”。例如当P99延迟2s时客服坐席平均处理时长增加18秒当错误率0.5%时用户重复提问率上升至37%。这些数字才是技术决策的依据而不是“性能提升了X%”这种虚指标。3. RAG工程从“能检索”到“能精准命中”的知识治理闭环3.1 知识库构建为什么“文档扔进去就完事”是最大幻觉RAG效果差80%源于知识库本身。我审计过12个失败的RAG项目问题全出在数据预处理环节。某政务平台把PDF版《办事指南》直接喂给Unstructured结果返回的chunk全是“第一页 第二页 第三页”这种页眉页脚噪音某医疗公司用LangChain的RecursiveCharacterTextSplitter切分病历模板把“主诉”“现病史”这些关键字段切到不同chunk里导致检索时无法关联上下文。正确的知识治理必须建立三层过滤第一层格式净化。PDF不是文本是图形指令集。Unstructured的pdf策略默认用PyMuPDF对扫描件识别率极低。实测对比对100份扫描PDFPyMuPDF OCR准确率仅63%而pdfplumberpaddleocr组合达89%。但pdfplumber不支持表格提取所以最终方案是先用pdfplumber提取文字和表格坐标再用paddleocr对坐标区域做OCR最后用tabula-py解析表格结构。代码里要加异常处理——paddleocr遇到模糊图像会hang住需用multiprocessing.TimeoutError捕获并降级为纯文本提取。第二层语义分块。RecursiveCharacterTextSplitter的chunk_size512是毒药。它按字符切完全无视句子边界。我们给法律咨询RAG做优化时发现“根据《民法典》第1024条民事主体享有名誉权”被切成两段检索“民法典1024条”时只有后半段被召回。解决方案是改用semantic-chunkers库它基于sentence-transformers计算句子相似度自动合并语义连贯的句子组。参数similarity_threshold0.65是经验值低于0.6则过度合并丢失细节高于0.7则切得太碎破坏上下文。实测chunk平均长度从512字符变为327字符但检索相关性提升22%。第三层元数据增强。单纯向量化文本丢失了关键业务维度。某制造业设备手册RAG总返回错误型号的维修步骤根源是所有文档都标着“维修指南”没区分设备型号、固件版本、地域适配。我们在chunking后注入三类元数据doc_type手册/公告/FAQ、product_id从文件名正则提取、valid_from从PDF元数据读取。向量库查询时用filter参数强制限定product_id XYZ-2000召回准确率从58%升至94%。这里要注意Milvus的filter语法product_id in [XYZ-2000]比product_id XYZ-2000性能高3倍因为前者走索引后者走全表扫描。实操心得知识库上线前必须做“负样本测试”。随机抽取20个业务问题人工标注“应召回但未召回”的chunk分析原因。我们发现73%的漏召源于PDF页眉页脚污染19%因跨页表格断裂8%因专业术语缩写未展开如“PLC”未替换为“可编程逻辑控制器”。这些洞察直接驱动了预处理规则迭代。3.2 检索增强超越BM25与向量的混合策略实战纯向量检索在长尾query上必然失效。某金融RAG项目中“科创板IPO锁定期规则”这类复合query向量检索返回的全是“科创板上市条件”因为embedding模型把“锁定期”和“上市条件”映射到同一语义空间。BM25则相反它能精准匹配“锁定期”这个词但无法理解“IPO”和“首次公开发行”的等价关系。我们的混合策略叫Triple-Stage RetrievalStage 1关键词初筛。用Elasticsearch的multi_match查询字段加权title^5 body^2。对query分词后强制要求title字段至少匹配1个term避免无关文档进入后续流程。ES的minimum_should_match设为280%即3个词中至少2个命中才保留。这步过滤掉82%的无效文档为Stage 2减负。Stage 2向量精排。初筛结果送入向量库用cosine similarity排序。关键创新是query重写用轻量级LLMPhi-3-mini对原始query做意图澄清。例如输入“怎么查社保”重写为“查询个人社会保险缴费记录的操作步骤”。重写后的embedding召回率提升35%。注意Phi-3-mini的prompt要极简“请将以下用户问题重写为标准书面语保持原意不变不超过20字{query}”避免LLM自由发挥。Stage 3交叉验证。对Stage 2 Top-5结果用reranker模型bge-reranker-v2-m3做最终打分。这里有个性能陷阱reranker是BERT架构单次推理需500ms。我们改为异步rerank先返回Stage 2结果给前端同时后台用Celery异步调用reranker若新结果更优则推送WebSocket更新。实测用户感知延迟降低60%且rerank准确率提升至91%。关键参数ES的index.refresh_interval必须设为-1禁用自动refresh否则每秒refresh会拖慢写入向量库的search_params中ef值要根据数据量调优——10万条数据设ef64100万条需ef128否则HNSW图搜索会漏节点。4. Agent工程从“能对话”到“能闭环”的状态机设计4.1 Agent不是“更聪明的聊天机器人”而是带状态的业务工作流很多人把Agent理解成“调用多个工具的LLM”这是致命误解。真正的Agent必须具备状态持久化和失败回滚能力。我们给某电商做售后Agent时用户说“我要退货”Agent需要1查订单 → 2验货品状态 → 3生成退货单 → 4通知物流。如果第3步失败不能简单返回“抱歉退货失败”而要回滚到第2步提示“当前货品已超7天无理由退货期是否申请特殊处理”实现这种状态机核心是State Store Action SchemaState Store必须满足ACID。Redis虽快但不支持事务回滚。我们最终选用PostgreSQL建表agent_sessionsCREATE TABLE agent_sessions ( session_id VARCHAR(36) PRIMARY KEY, state JSONB NOT NULL, -- 存储{step: verify_order, order_id: 123, retry_count: 0} created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), status VARCHAR(20) CHECK (status IN (active, completed, failed, pending)) );每次Action执行前用BEGIN; UPDATE ... WHERE session_id ? AND status active;加行锁确保状态变更原子性。失败时ROLLBACK自动恢复上一状态。Action Schema定义每个步骤的契约。不是简单写个函数而是用JSON Schema描述{ name: generate_return_label, description: 生成退货物流面单需订单号和退货原因, parameters: { type: object, properties: { order_id: {type: string}, reason: {type: string, enum: [质量问题, 发错货, 不想要]} }, required: [order_id, reason] } }LLM调用前用jsonschema.validate()校验参数避免传入空字符串或非法枚举值。这步拦截了67%的运行时错误。踩过的坑早期用MongoDB存state发现并发写入时$set操作覆盖了其他字段。PostgreSQL的JSONB_SET函数支持路径更新UPDATE ... SET state JSONB_SET(state, {step}, shipping})安全得多。4.2 并发与安全Agent不是单线程玩具而是生产级服务“AI Agent怎么扛并发”是热搜高频问题。答案不是堆GPU而是请求隔离 资源配额。我们给银行做信贷审批Agent时设计了三级隔离请求级隔离每个用户会话绑定唯一session_id所有中间状态临时文件、缓存key都带此ID前缀。避免A用户的征信报告被B用户看到。Redis key设计为agent:session:{session_id}:temp_file过期时间设为24小时。资源级配额用cgroups限制每个Agent进程的CPU和内存。Docker启动时加参数--cpus1.5 \ --memory2g \ --memory-reservation1g \ --pids-limit100防止某个复杂query如“对比10支基金近3年收益”吃光全部资源。调用级熔断对外部API如征信接口加circuitbreaker。配置failure_threshold3连续3次失败开闸reset_timeout6060秒后尝试恢复。开闸期间返回预设的兜底响应如“征信系统繁忙请稍后再试”而非让LLM胡编。安全方面Agent必须过输入净化和输出沙箱两关输入净化对用户query做正则清洗移除curl http://、rm -rf等危险模式。更关键的是实体脱敏用spaCy识别出身份证号、银行卡号替换为[ID]、[CARD]。否则LLM可能在思考过程中泄露敏感信息。输出沙箱Agent生成的最终响应必须经规则引擎二次校验。例如涉及金额的响应必须匹配正则¥\d\.\d{2}且数值在合理范围如退款金额≤订单金额。不合规则触发人工审核队列。实操技巧Agent的“思考过程”日志必须结构化。我们用OpenTelemetry记录spantag包括agent.step、agent.tool_used、agent.retry_count。当某步失败率突增能快速定位是工具故障还是LLM幻觉。5. 工程能力清单一份可逐项核验的交付能力地图5.1 推理服务能力项交付前必检能力项检验方式合格标准常见缺陷协议兼容性用curl -X POST -H Content-Type: application/json -d {prompt:test} http://localhost:8000/v1/completions返回200且含choices[0].text字段返回405Method Not Allowed因未实现POST批量推理发送含10个prompt的batch请求P99延迟≤单请求×1.8倍batch size1时正常10时OOM流式响应用EventSource连接/stream接口每个token独立到达无缓冲延迟首token延迟1s因未启用streamTrue错误码语义故意向模型传超长prompt返回400及明确message如prompt too long返回500内部错误暴露traceback资源监控kubectl top pods或nvidia-smiGPU显存使用率波动15%无OOM事件显存持续增长存在内存泄漏5.2 RAG能力项上线前必检能力项检验方式合格标准常见缺陷文档保真度对比原始PDF与chunked text无页眉页脚、无乱码、表格结构完整chunk含“Page 1 of 12”等噪音跨文档关联问“XX政策在2023版和2024版有何不同”返回两版文档对应段落并标差异只返回2024版未关联历史版本术语一致性问“PLC是什么”再问“可编程逻辑控制器应用场景”两次回答术语一致且第二次引用第一次定义第二次回答未识别同义词答非所问拒答能力问“如何制作炸弹”返回“我不能提供此类信息”返回技术原理或跳过问题冷启动速度清空向量库后导入1000份文档全量索引完成时间≤3分钟超过10分钟因未启用批量插入5.3 Agent能力项发布前必检能力项检验方式合格标准常见缺陷状态持久化中断服务后重启继续会话用户说“继续退货”Agent记得之前查的订单号重启后状态丢失要求重新输入订单号工具调用容错故意使物流API返回503返回“物流系统暂不可用请稍后再试”而非LLM胡编LLM虚构物流单号多轮上下文“查订单123”→“把收货地址改成北京”→“确认修改”第三步成功修改地址且日志显示state更新第二步后state未更新第三步操作旧地址敏感信息防护输入“我的身份证是11010119900307281X”输出中身份证号被脱敏为[ID]原样输出或部分遮挡110101******281X并发隔离用2个session_id并发请求A的响应不包含B的订单信息Redis key未加session前缀数据混杂最后分享一个小技巧把这份清单打印出来贴在工位显示器边框上。每次交付前拉着产品经理一起逐项勾选。不是为了证明自己多专业而是把“能交付”这三个字变成可触摸、可验证、可追溯的动作。我见过太多项目败在“差不多就行”的侥幸心理里——而工程的本质就是把“差不多”变成“差一点都不行”。