ARTICLE DETAIL

资讯详情

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

AI工程从零到生产:提示词、RAG、Agent与可观测性实战指南

AI工程从零到生产:提示词、RAG、Agent与可观测性实战指南 最近一直在琢磨一件事很多同学说自己在“做大模型应用”但聊到工程细节得到的回答往往是“调用一下API把提示词写好就完事了”。真的只有这么简单吗我起了一个项目叫ai-engineering-from-scratch目标很直白——从零开始捋一遍AI应用从提示词到生产环境的全过程把那些文档里不会写、短视频也讲不清楚的坑一个个填平。这篇文章适合两类人一类是刚有Python基础、想系统入行AI工程的人另一类是已经在调Prompt、接模型API但总觉得架构散乱、上线心里没底的开发者。文章不会只在概念层面打转我会把整个项目拆成可执行的路径提示词工程、RAG检索、Agent工具调用、可观测性、部署迭代、常见故障排查每一步都给出我实际用过的代码片段和踩坑记录。1. 先搞清楚AI工程和传统软件工程差在哪1.1 确定性系统变成了概率系统传统软件工程里输入确定了输出就确定。你写一个add(a, b)传1, 2进去永远返回3谁来做都一样。但大模型不是这样你同样问了“帮我写个函数”连续问三次可能得到三种思路其中一次还会有幻觉。这是AI工程最核心的认知转变我们不是在写确定逻辑而是在设计一个概率系统的边界。这个边界靠什么约束不是靠多写几个if else而是靠提示词模板、检索过滤、输出约束、评测集、日志追踪这些工程手段把模型的能力围起来让它在一个足够窄的河道里流动。明白了这一点你就不会再用“它答得对不对”这种单一标准去评判系统而是会问答错时系统能不能感知能否降级成本是否可控1.2 为什么很多AI项目死在了原型阶段我见过太多团队花两周做出来一个炫酷的Demo然后用两个月试图把它变成稳定的产品最后又推倒重来。问题通常出在同一个地方Demo只验证了“模型能不能做这件事”而生产环境考验的是“这个系统每天被调用一千次时还能不能可靠地做这件事”。两者差距巨大。比如一个知识库问答Demo里你只喂三五篇文档试了试没有注意切块大小、召回条数、上下文截断、token成本。上线后用户随便丢进来一份两万字的技术文档模型要么答不到点上要么被无关内容干扰要么单次请求烧掉几十万token。这些问题的解决方案早在做Demo时就应该浮现出来。所以我把这个项目的学习路径定成了先能用再会调最后会造。先用现成的API把闭环跑通再用RAG、缓存、结构化输出把可靠性夯实最后才去考虑模型微调、私有化部署、自建评测体系这些上层工程。2. 提示词工程是AI工程的起点也是地基2.1 一个稳定的提示词模板需要哪些部分我见过不少提示词只有一个句子“帮我写一份周报。”模型确实能写但你可能要来回改三版才得到想要的结果。想要可复用、可评测、可上线提示词至少需要四个部分角色与目标、输入内容、输出格式约束、边界与底线。角色与目标告诉模型“你是谁、要做什么”输入内容提供足够上下文输出格式约束避免它给你一堆散文边界与底线用来处理“不知道就承认不知道”的场景。下面是我在项目里用的一个基础模板骨架你可以直接抄走prompt_template 你是一个严谨的技术文档助理。请根据给定的上下文回答问题。 【上下文】 {context} 【用户问题】 {question} 【要求】 1. 如果上下文中没有明确答案只回答“根据现有资料无法回答”不要编造。 2. 回答使用Markdown格式保持简洁。 3. 回答最后给出参考来源编号如果没有则省略。 这里没有堆太多“你要聪明”“你要专业”之类的空话。经验是不太可能靠微调大模型提升发挥靠的是白纸黑字的结构化约束。2.2 从自由文本走向结构化输出真正上线后你通常不想让模型输出一段自然语言后人工去“读”而是希望它直接吐出结构化数据送到下游系统。这时一定要利用API支持的结构化输出能力而不是仅仅在提示词里写“请输出JSON”。以OpenAI为例可以用response_format{type: json_object}或更严格的json_schema以Claude为例可以配合工具调用强制约束字段。我的建议是能走结构化输出就走结构化输出这比任何后处理解析都稳。顺带分享一个关键参数不要把temperature随便设置成0.7后就不管。输出格式约束稳定的场景比如分类、信息抽取temperature设到0.1甚至0都不过分需要头脑风暴的场景才拉高。很多“格式不稳定”的抱怨其实原因不是模型差而是参数对不上场景。2.3 提示词迭代要用版本管理提示词本身是一份代码。我在项目里给每个提示词都建了版本号改了模板就更新版本并记录当时的评测分数。原因很简单模型API升级后同样的提示词可能表现波动线上效果变差时你要能回滚到某个已知良好版本否则一切只能靠猜。实操上我习惯用JSON配置来管理提示词模板{ prompt_id: tech-qa-v3, model: gpt-4o-mini, temperature: 0.1, template: ..., notes: 增加来源编号要求修复幻觉问题 }3. 动手实现一个RAG知识库问答3.1 为什么先做RAG而不是微调对绝大多数业务场景第一选择从来不是微调模型而是RAG检索增强生成。原因很现实业务数据每天都在变微调一次模型成本高、周期长而且自定义参数一多就会覆盖通用知识。RAG的思路则是把数据存在外部回答时先检索相关片段再把片段塞进上下文让模型作答。它做的是“开卷考试”而不是逼模型把整本教材背下来。这个选择的“为什么”还体现在成本上微调后的模型参数动辄几十GB每次更新都要重跑训练RAG只需要更新文档库和索引改动成本低得多。所以我的项目里第一个完整功能就是RAG问答而不是微调Demo。3.2 完整链路代码摄入、切块、向量化、检索、生成第一步摄入文档。简单场景下直接读文本文件复杂场景需要解析PDF、Word、HTML。第二步把长文本切成小块。第三步用Embedding模型把每块转成向量并存入向量库。第四步用户提问时把问题也转成向量在库里做相似度检索。第五步把命中的文本拼接进提示词交给大模型回答。这里我用FAISS作为本地向量库示例因为它只要pip install faiss-cpu就能跑适合学习和原型验证from openai import OpenAI import faiss import numpy as np client OpenAI() def get_embedding(text: str) - list: resp client.embeddings.create( modeltext-embedding-3-small, inputtext ) return resp.data[0].embedding # 1. 文本切块简化版 def split_text(text: str, chunk_size500, overlap50) - list: chunks [] start 0 while start len(text): end start chunk_size chunk text[start:end] chunks.append(chunk) start end - overlap return chunks # 2. 构建向量索引 chunks split_text(open(manual.txt, encodingutf-8).read()) vectors np.array([get_embedding(c) for c in chunks]).astype(float32) index faiss.IndexFlatL2(vectors.shape[1]) index.add(vectors) # 3. 检索 def search(query: str, top_k: int 3): q_vec np.array([get_embedding(query)]).astype(float32) scores, idxs index.search(q_vec, top_k) return [chunks[i] for i in idxs[0]] # 4. 生成 def answer(question: str): ctx \n.join(search(question)) prompt f根据以下资料回答问题\n{ctx}\n\n问题{question} resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.1 ) return resp.choices[0].message.content这段代码可以直接跑但它是教学版本。真实项目里我会再加一层缓存、一层版本管理和一个中间服务这些后面会讲。3.3 三个最影响效果的参数Chunk Size、TopK、Embedding模型不要小看切块这个环节。切块太大检索出来的片段讲了一堆无关内容模型容易被带偏切块太小语义不完整检索召不回关键信息。我测试下来的经验是一般技术文档用300到600字符起步按产品波动调整切段之间保留50字左右的重叠避免上下文被硬生生截断。给个具体的观察有一版我把chunk调到1200字符结果回答虽然流畅但经常混进无关操作说明调到400字符后准确性立刻上去但偶尔丢失前文指代信息。这个trade-off必须亲自跑自己的数据。TopK决定每次给模型几段资料。太少了答不全太多了成本高且干扰大。我用3到5起步再看答案质量调。至于Embedding模型我优先考虑与主模型同厂商的版本这样在兼容性和价格上更好评估对中文场景建议用测试集对比一下看检索召回的准确率再定别只盯着排行榜。4. Agent不是万能的但工程化之后很能打4.1 ReAct循环的本质是“边想边做”很多初学者把Agent理解成“一个很聪明的机器人”这是错的。工程视角下Agent就是一个循环拿到任务让模型判断下一步要调用哪个工具执行工具把结果返回给模型再判断下一步直到任务完成或到达上限。所谓ReAct就是“Reasoning Acting”的循环。想手动实现一个最小版并不难核心就是我在下面展示的循环逻辑。需要特别注意的是这个循环每一步都在消耗token和延迟所以必须设置循环上限比如最多执行5步否则模型可能在一个失败的工具调用上反复打转。tools { get_weather: lambda city: f{city}天气晴25度, calculator: lambda expr: eval(expr) # 仅为示例生产环境不要直接eval } def run_agent(task: str, max_steps: int 5): messages [{role: system, content: 你是一个能调用工具的助手。}, {role: user, content: task}] for _ in range(max_steps): resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, tools[...] # 定义工具描述 ) msg resp.choices[0].message if msg.tool_calls: messages.append(msg) for tc in msg.tool_calls: result tools[tc.function.name](tc.function.arguments) messages.append({ role: tool, tool_call_id: tc.id, content: str(result) }) else: return msg.content return 达到最大步数任务未完成更推荐的做法是直接用LangChain的ToolNode或OpenAI函数调用机制但理解这个手写循环能帮你定位很多诡异问题比如“为什么Agent没调用工具”“为什么工具结果丢了”。4.2 工具调用最容易翻车的三个细节第一工具描述要写得像API文档而不是一句话简介。给模型“它能干什么、参数是什么、什么时候不该调用”的明确信息。我见过太多工具调用失败案例最后都死于参数没写清楚。第二工具返回结果必须结构化最好每次带状态码和错误信息。如果你的工具直接返回一段“计算失败”模型很难判断是重试还是换方案。第三权限边界要提前设死。别让Agent拥有能删数据库的权限——至少在生产环境要加一层人工审批或只读策略。4.3 别追求“通用Agent”先做“窄场景Agent”我的建议是不要一开始就做一个能解决任何问题的“超级助手”。先圈定一个具体场景比如“查询订单状态、处理退货申请”或“根据简历筛选候选人并排期面试”。这背后的逻辑是场景越窄工具集越小模型做决策的搜索空间就越小系统越可控。窄场景跑稳了再逐步叠加新工具、新意图你会发现工程难度会低一个量级。5. 上线前必须补的课可观测性与评测集5.1 只盯“答得对不对”会错过真正的问题很多团队上线AI功能后每天只看用户反馈是否好评全然不管调用失败率、延迟和token成本。这是AI工程的一个大坑。大模型服务有概率性、有外部依赖没有监控就像开一辆没有仪表盘的车。我在项目里给每一条请求都加了结构化日志记录关键信息时间、模型、提示词版本、输入Token数量、输出Token数量、延迟、是否命中缓存、使用的工具调用链、最终回答。这些日志至少有三种用途一是排障时能还原现场二是算清单用户成本三是回放真实Prompt构建评测集。如果你只有一个群聊截图和一句“它回答很怪”排查会非常痛苦。5.2 搭建一个小而实用的回归评测集评测集是AI工程里最容易偷懒、又最不该偷懒的部分。不用一开始就做得很复杂几十条典型问题就够了。我习惯分三类黄金用例答案是确定的必须答对、边界用例空输入、超长输入、敏感问题以及对抗用例专门挑知识库没有的内容测试它会不会胡编。每次改提示词、换模型、调检索参数后都跑一遍评测集记录准确率、成本和延迟变化。这一步能让“我觉得效果变好了”变成“准确率从82%涨到88%”。建议把评测集放进Git仓库管理并写脚本一键执行这样改动会越来越放心。5.3 成本与延迟两个容易被忽视的指标在大模型应用里成本不只是钱还意味着缓存策略和响应速度。我在项目里用了两层缓存一层是请求级缓存完全相同的用户提问直接返回历史答案另一层是Embedding缓存避免同一段文档反复向量化。还可以通过异步任务把耗时的长文本总结放到后台队列前端立刻返回“处理中”而不是让用户干等。另外我建议定期跑一份成本报表按功能模块、按用户维度拆开看。如果没有这层数据你很难向业务方解释“为什么这周账单涨了一倍”也很难发现某段提示词里塞了太多冗余上下文。6. 从原型到生产部署架构与迭代纪律6.1 单体脚本先跑通再拆服务我见过一种极端第一个Demo就直接上了微服务、K8s和消息队列结果排查问题时链路长到无处下手。AI工程完全可以反着来先用单体脚本跑通全流程再根据性能瓶颈拆分。我在这个项目里一开始就是一个app.py把RAG、LLM调用、日志全部放在一起模型跑得通才把独立模块抽成函数、再抽成服务。拆服务时我优先拆三块检索服务、生成服务、可观测性服务。检索服务负责切分和向量查询生成服务负责提示词拼接和模型调用可观测性服务负责日志、链路追踪和评测。这样改任何一段逻辑时不至于牵一发而动全身。6.2 API直连、微调、开源部署怎么选这是所有AI工程都会面对的核心选型。我的判断标准是这样的方案适用场景主要成本我的经验商用API直连大部分MVP和中小业务token费用、网络延迟最快验证效果性价比最高开源模型私有化数据敏感、调用量极大、定制需求强GPU机器、运维成本适合稳定业务不是起步选择微调需要特定风格、特定领域术语RAG无法满足训练数据准备、算力不建议一上来就微调对本土业务来说API直连最大的问题是数据出域但很多场景可以配合协议和脱敏手段缓解。等用户量起来后再评估是否投资GPU推理。顺序不要反。6.3 版本管理与灰度发布一切可回滚我们前面说提示词要版本化模型配置也要版本化。我的做法是把模型名、temperature、top_p、提示词模板、检索参数这些都写进配置中心线上运行前保存一份快照。每次改动都走“评测集跑分→小流量灰度→观察指标→全量”这套流程。灰度时我重点盯三类指标错误率、平均延迟、评测集中的黄金用例准确率。宁可灰度多跑两天也不要一把梭。这套纪律救过我很多次尤其是模型厂商升级底层版本时经常有“你以为没变其实变了”的坑。7. 常见故障与排查技巧实录7.1 输出解析失败、幻觉和截断这类问题在生产环境出现频率最高。截断问题通常发生在输出Token上限设得太小或者回答内容过长幻觉问题多半是上下文不足或边界约束没写清楚解析失败多半是模型返回了Markdown包裹的JSON而你的解析器只认裸JSON。排查顺序先看日志里完整的模型返回内容确认是截断、格式错误还是语义错误再检查提示词约束和参数设置。我给项目里的解析函数加了容错逻辑先尝试json.loads失败后用正则抽取JSON块再失败就把响应原样返回给前端人工查看。这样即便出错也不会直接崩掉整个流程。7.2 检索噪声导致回答质量下降明明文档里写得很清楚模型却回答得似是而非大概率不是模型问题而是检索到的片段太杂。我处理这类问题有三板斧第一降低TopK减少无关干扰第二提高切块质量比如按语义段落而不是固定字符硬切第三在提示词里明确告诉模型“只依据资料不要使用其他知识”。还有一个容易忽略的点需要检查向量库里的内容是否过期。知识库一旦更新旧向量必须同步删除、重建索引否则模型会持续引用过时信息。7.3 延迟抖动和成本失控延迟波动最大的来源通常是模型服务本身其次是检索阶段速度。如果是调用外部API建议对所有模型调用设超时和重试重试时要带退避策略否则高峰期一旦抖动所有请求会同时重试把系统打成雪崩。成本失控则基本都和上下文过大、缓存命中率低有关。我每周都会把日志里的token用量按接口分组找出哪些接口token消耗异常再针对性压缩提示词或增加缓存。我把常用问题整理成了排查表方便你直接对照现象可能原因排查手段格式解析失败输出用Markdown包裹JSON加正则抽取多级容错回答过时向量库未更新重建索引检查摄入流程回答太发散TopK过大 / 提示词缺边界降低TopK补边界要求单次成本异常高上下文塞了过多文档压缩切块、限制检索数量响应变慢模型服务抖动加超时、退避重试、缓存最后分享一点个人体会这个项目做下来我最深的感触是AI工程说到底是“怎么和不完美的系统共处”的手艺。它不是一个能一次性做完的版本而是一套持续迭代的机制——每次改动要有记录每个决策要有数据支撑每条异常要能追溯。做到第三周时我就把“评测集跑分”当成和“代码能编译”一样不可或缺的步骤这让我在后来的每次调整里都少了很多提心吊胆。如果你也想从零开始走一遍建议不要只做看客。花一个周末把本文里的RAG和Agent代码跑通再给它加上日志和评测集你会比背十篇架构文章都更理解“AI工程”这四个字的分量。等项目跑起来之后回头看你会感谢那个愿意从“一把梭”开始、但一步步把坑填满的自己。
返回列表