很多人问我“AI工程到底怎么入门”我自己也是从一个个零散脚本起步踩了不少坑才慢慢摸出一套能复用的工程化路径。今天这篇就围绕“ai-engineering-from-scratch”这个项目主题聊聊从零搭一个AI工程的全部关键环节需求拆解、技术选型、Prompt设计、Agent编排、工作流落地以及排障思路。适合想系统做AI应用、而不只是调用接口的开发者参考。我会尽量用做过项目的口吻把那些文档里不会写清楚的细节也一起讲透。先说说我对“AI工程”的理解。它和传统软件开发最大的不同在于模型行为存在不确定性。写一个普通函数逻辑是确定的但写一个LLM应用同样的输入在不同参数下可能给出不同结果。所以AI工程的核心不是写代码而是建立一套体系让不确定性可控、结果可评估、迭代可追踪。1. 整体设计与思路拆解1.1 项目目标与应用场景“ai-engineering-from-scratch”从字面看就是一个从零开始的AI工程项目。它要解决的问题很典型业务方提了一个需求比如“做一个能回答公司内部文档问题的机器人”然后你拿到了项目经理给出的一句话描述接下来怎么办大多数新手会直接打开Jupyter Notebook调用一下OpenAI的API跑通了就以为完成。但真到了生产环境你会发现要处理一连串问题模型偶尔回答错误怎么兜底上下文太长了怎么截断不同来源的数据格式怎么统一Agent调用外部工具失败了怎么重试这些问题往往是工程大头。我建议把AI工程项目拆成四层来看数据层数据获取、清洗、分块、向量化。这是地基数据质量决定了效果上限。模型层选择合适的基础模型设计Prompt、微调策略、推理参数调优。能力层把模型变成能调用工具、能查数据库、能执行动作的Agent。应用层通过工作流把多个能力串起来形成一条完整业务链路。这个项目的典型应用场景包括企业知识库问答、自动化报表生成、客服工单分类、智能审核辅助等。核心价值在于把LLM的能力和业务系统连接起来而不是让模型“裸奔”在对话里。1.2 为什么采用“工程化”而不是“脚本化”做过项目的人都有体会脚本是给自己用的工程是给团队和用户用的。脚本可以容忍“每次手动改一下参数”工程必须让流程自动化、错误可追踪、结果可复现。举个例子早期我自己做AI问答脚本效果不错但后来同事要复现结果发现依赖库版本冲突数据路径写死Prompt散落在各个单元格里。这种项目只能叫“实验”不能叫“工程”。工程化意味着你要思考版本控制Prompt、模型参数、数据快照都要纳入版本管理否则你无法回答“为什么昨天还好好的今天就不行了”。可观测性记录每次推理的输入输出、Token用量、耗时、错误原因出了问题能回放。模块化把数据加载、向量化、检索、生成、验证拆成独立模块方便替换和复用。评估体系建立一个测试集每次改动后用同一批问题跑一遍用指标判断效果是否下降。所以“工程化”解决的问题是显而易见的让AI项目从“能跑”变成“稳定跑”。我在实际项目中用了近一半时间在搭这部分基础设施而真正写业务逻辑的时间反而不多。这不是浪费因为后期迭代的效率提升是数倍的。2. 核心技术点与工具选型2.1 大模型选择与调用方式选模型是第一道坎很多人在这一步就犹豫很久。我的建议是先列需求再选模型别被参数大小和榜单带跑。几个需要考虑的方向效果要求需要推理能力强还是只要通用对话需要支持长文档吗成本约束每百万Token的价格是多少日均调用量预估多少部署环境数据能不能出内网如果不行就得用私有化部署的开源模型。生态支持是否支持函数调用、结构化输出、JSON模式等。下面是我常用的对比维度可以参考维度云端API开源私有化部署上线速度快开箱即用慢需要GPU与环境配置数据安全依赖服务商合规完全自控单次成本按Token付费灵活硬件投入高边际成本低可定制性受限可微调、可改造稳定性受服务商影响取决于自身运维能力我在项目中通常这样决策原型验证阶段用云API因为迭代快进入稳定期后如果调用量很大或有数据合规要求再切换到开源模型做私有化部署。开源的Qwen、DeepSeek、ChatGLM系列都比较成熟硬件允许的话效果接近商业API。调用方式上也有些讲究。直接用openai库没问题但要留意超时重试、错误码处理、限流策略。比如遇到429限流和500服务端错误处理方式完全不同。建议封装一个统一调用模块把这些逻辑收敛起来。2.2 Prompt Engineering让模型听指挥很多人觉得Prompt只是“用自然语言描述任务”其实深入之后才发现它是一套可迭代、可测试的技术。我总结下来的核心思路是把Prompt当代码管理而不是当一句提示语随手写。结构化Prompt通常包含这些区块System Prompt定义角色、目标、行为边界。用户指令明确输入格式、输出格式要求。Few-shot示例给2-5个输入输出对让模型模仿。约束条件禁止输出无效内容、遇到未知问题如何处理。举个例子我在做一个信息抽取任务时最初Prompt是“抽取这段话里的公司名称”效果很差。后来改成你是信息抽取引擎。从用户文本中提取公司名称。 要求 1. 只输出JSON数组不要带任何解释。 2. 不存在的字段返回空数组。 3. 如果文本包含期货、基金等金融信息也要识别。 示例 输入腾讯控股的股价今日上涨带动恒生指数走强。 输出[腾讯控股]之后效果明显提升。原因在于模型对“格式示例”的敏感度远大于对“规则描述”的敏感度。还要注意温度、top_p等推理参数。做抽取、分类、代码生成这类确定性任务温度建议调到0或0.1做创意写作、头脑风暴温度可以调到0.6以上。2.3 AI Agent让模型具备行动力单靠对话模型的用处有限。真正的工程突破口在于Agent——让模型不仅能“说”还能“做”。Agent的核心机制是“推理-行动-观察”循环也常称为ReAct。模型根据用户目标拆解出行动计划调用工具得到结果后再继续推理直到完成任务。实现一个最小Agent通常包含这些模块工具集合定义外部能力如搜索引擎、SQL查询、计算器、爬虫。规划器LLM负责将目标拆解成步骤。执行器根据规划的步骤调用工具处理返回结果。记忆模块为了在长任务中记住上下文。我测试过最简单的方式是用JSON定义工具描述让模型自动选择调用。例如工具描述{ name: calculate, description: 计算数学表达式的值, parameters: { type: object, properties: { expression: { type: string, description: 数学表达式例如 2 3 * 4 } } } }然后通过函数调用把模型输出映射到Python函数。这种方式对大多数做数据查询、报表生成的场景够用了。但Agent远没有那么神。它会陷入死循环会误解工具结果还会因为一步出错导致后续全面崩溃。所以我强烈建议在Agent外层加控制逻辑比如最大迭代次数、工具超时、人工审批节点、结果校验节点。工程上要“有限度地授权”不是全自动。2.4 多AI协作与工作流编排单个Agent能力有上限所以现在更流行的是“多角色协作”和“工作流编排”。这不是噱头而是合乎逻辑的演进。一个复杂的任务让一个Agent从头干到尾容易丢上下文、结果不稳定如果拆成多个子任务每个子任务配一个专门Agent反而各司其职。我常设计这些角色解析Agent负责理解用户意图提取任务参数。检索Agent负责从知识库或数据源找材料。生成Agent负责撰写最终答案或报告。审核Agent负责检查生成内容是否符合规则、有没有事实错误。这些Agent可以交给LangGraph、Dify这类工作流框架也可以自己写一个简单的协调器。我更推荐先自己写一次理解数据流再引入框架否则出了问题很难排查。工作流设计时有个关键点节点之间的数据传递。很多新手把每个Agent的输入输出都设计成自然语言字符串结果后面节点解析困难。我的做法是定义统一的消息协议比如包含role、payload、metadata的JSON结构让每个节点只提取需要的字段。这样既清晰又方便加日志和监控。3. 实操过程与核心环节实现3.1 环境准备与依赖配置这一节适合从零开始照着做。我的基础环境是Python 3.10用venv或者conda管理虚拟环境。强烈不建议直接把依赖装到全局环境因为AI项目依赖版本冲突太常见了。创建项目目录mkdir ai-engineering cd ai-engineering python -m venv venv source venv/bin/activate基础依赖可以这样装pip install openai langchain langchain-openai chromadb pydantic python-dotenv很多服务需要环境变量我习惯把所有Key和配置放进.env文件然后通过python-dotenv加载。注意不要把.env提交到Git仓库。一个常见坑是langchain版本升级频繁接口经常变。我一度很烦。后来转变思路框架只用来做胶水核心逻辑自己写这样框架换掉也不至于伤筋动骨。3.2 搭建一个RAG问答系统完整案例RAG检索增强生成是AI工程最常见的入门项目。它的原理很简单从知识库中检索与问题相关的片段把片段拼进Prompt再交给LLM生成答案。这样做能减少幻觉也能让模型基于最新数据回答。完整实现分五步加载文档分块Chunking向量化Embedding存储与检索Vector DB生成回答LLM我用一个简化版代码说明目标函数是ask_question(question)import os from dotenv import load_dotenv load_dotenv() from openai import OpenAI import chromadb from chromadb.utils import embedding_functions # 初始化客户端 client OpenAI() chroma_client chromadb.Client() # Step 12: 加载文档并分块假设已有文本列表 documents [ 文档内容一..., 文档内容二..., ] # 分块逻辑每块 800 字符重叠 100 字符 def chunk_text(text, chunk_size800, overlap100): chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) start end - overlap return chunks all_chunks [] for doc in documents: all_chunks.extend(chunk_text(doc)) # Step 3: 计算向量并存储到 Chroma collection chroma_client.get_or_create_collection( namedocs, embedding_functionembedding_functions.OpenAIEmbeddingFunction( api_keyos.environ[OPENAI_API_KEY], model_nametext-embedding-3-small ) ) ids [fchunk_{i} for i in range(len(all_chunks))] collection.add(idsids, documentsall_chunks) # Step 45: 检索 生成 def ask_question(question, top_k4): # 检索相关片段 results collection.query(query_texts[question], n_resultstop_k) contexts results[documents][0] prompt f基于以下参考资料回答问题。 如果参考资料中没有答案就说我不知道不要编造。 参考资料 {chr(10).join(contexts)} 问题{question} 回答 response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是严谨的知识助手。}, {role: user, content: prompt} ], temperature0.1 ) return response.choices[0].message.content这段代码虽然能跑但离生产还有距离。我实际会在这些地方加强添加重试机制应对调用失败记录检索的片段和来源方便验证和审计Prompt里加上引用标记要求模型标注依据来自哪一段对分块策略做实验因为分块大小直接影响检索质量。分块大小是我踩过最多坑的地方。太小单个片段语义不完整太大检索不精准且浪费Token。常见做法是按语义段落分块再结合标题层级。金融财报、法律合同这类结构清晰的文档最好先用解析器提取标题再以标题为边界分块。3.3 工作流编排从单次问答到自动化流水线单次问答跑通只是开始。实际项目里通常要有自动化流程每天晚上定时抓取新数据做清洗更新向量库再执行一批报表任务最终把结果推送到钉钉或邮件。我推荐先画一张数据流图数据源 - 处理器 - 向量库 - 任务队列 - Agent执行 - 结果审核 - 下发。这个流程可以用LangGraph表达但我最初是用简单的Python脚本加定时器实现的效果也很稳定。下面是一个简化版的定时工作流概念示例使用APSchedulerfrom apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime def update_knowledge_base(): # 拉取新文档处理分块更新向量库 pass def generate_daily_report(): # 从数据表取数调用Agent生成摘要发送邮件 pass scheduler BlockingScheduler() scheduler.add_job(update_knowledge_base, cron, hour2, minute0) scheduler.add_job(generate_daily_report, cron, hour8, minute30) scheduler.start()工作流里我体会最深的一点是要对每个节点设置“失败兜底”。比如向量库更新失败时不应该终止整个任务而应该记录错误并用前一天的索引继续服务。否则你会在大早上被报警电话叫醒。另外在工作流中加入“人审”节点也很重要。比如自动生成的内容在发出去之前送到一个Web UI上让业务人员确认。这个人工环节看似降低效率实际上避免了很多不可控风险尤其面向外部客户时值回票价。4. 常见问题与排查技巧实录4.1 模型输出不稳定如何调参现象同样的Prompt回答有时好有时差。原因通常是采样参数没控好。解决方向很明确调试确定性任务时把temperature调到0甚至0。注意有些模型0和0.0001不是一回事最好设0。设置seed参数如果API支持让多次运行结果尽量一致。使用JSON模式或函数调用把输出限制在固定结构内避免模型自由发挥。我自己遇到最诡异的一次是模型在特定Prompt下突然输出混乱检查后发现是上一个对话的上下文污染。把messages列表里的历史会话全部清空后恢复正常。所以排查顺序很重要先看输入有没有异常再看参数最后再怀疑模型本身。4.2 Prompt不生效如何调试加了很详细的Prompt但模型仍然无视。这种情况我见过太多次。常见原因Prompt里指令太多模型忽略了后面的部分。解决把最重要的约束放到开头和末尾。使用了负面表述比如“不要返回解释”模型反而容易关注到“解释”这个词。解决改为正面指令“只返回JSON结果”。示例太少模型不理解具体格式。解决增加Few-shot尤其是负例。分块数据质量差检索到的内容本身跑题。Prompt再怎么写也没用。排查Prompt问题我的工具是“打印整条Prompt”。很多框架只让你传参数但真正发给模型的Prompt是拼起来的。日志里记录完整请求信息能省一半排查时间。注意不要相信“Prompt可以一劳永逸”。业务数据会变、用户提问模式会变Prompt必须随评估指标持续迭代。4.3 成本高、响应慢如何优化AI工程上线后最常见的两大投诉是“太贵”和“太慢”。我建议从下面这些方向入手模型路由简单问题用小模型或便宜模型复杂问题才用大模型。可以用一个分类器做路由。Prompt缓存如果大量请求使用同样的系统Prompt和知识片段使用服务商提供的缓存功能能显著降低成本。检索压缩从知识库检索出来的片段先做一次相关性过滤只挑最相关的2-3段减少生成阶段的Token。流式输出面向用户时使用流式虽然总Token一样但首包延迟体验好很多。并发控制不要盲目并发很多API都有速率限制。精心设计的并发策略反而吞吐更高。记得给每一次调用打点记录Token和耗时。之后优化就有依据而不是拍脑袋。在我做过的项目里有一回生成报告很慢排查发现是因为在循环里反复调用同一个长Prompt但中间结果没有复用。把公共计算提前出来之后耗时从十几秒降到三秒。这说明很多“模型慢”其实是“工程代码写得不优雅”。4.4 排查技巧速查表下面是我压箱底的排查思路整理成表格方便快速对照症状可能原因快速动作回答内容正确但格式不对温度过高/缺少示例温度调0增加格式示例回答内容完全跑题Prompt指令冲突简化Prompt移除负面表述相同问题答案每次不同上下文污染/推理参数波动清空上下文固定seed引用内容不真实RAG检索相关度低检查分块策略和TopKAgent调用工具失败工具描述不清晰改写工具description增加成功案例响应突然变慢服务商限流/并发过高降并发查错误码启用重试这个表我自己打印贴在工位上。大多数问题不需要重构系统先按表格做一次快速排查大概率能解决。5. 实践经验与扩展建议聊到这里该说的技术点都覆盖了。最后照例分享一点我个人的感悟。AI工程从零到一最容易犯的错就是“一上来就追求最牛模型”。其实项目跑通阶段哪怕是普通模型只要工程结构清晰后期替换成本也很低。反过来模型选得再强如果数据混乱、Prompt不可控、没有监控一样会翻车。另外一个很深的体会是AI工程的本质是“人机协作”不是“全自动魔法”。你要给模型设计好边界给用户预留确认入口给系统设计兜底逻辑。真正的稳定性来自工程控制而不是模型本身的“智能”。这个主题还可以向几个方向延展如果你想做多模态把图片、PDF等非结构化数据纳入RAG如果你想做更细粒度的控制可以去研究微调和RLHF如果你关注Agent可靠性可以研究LLM的可观测性和评估基准。这些都等于是从“从零开始”走向“从一到十”的过程。我给想入坑的人一个可执行的起步建议用1-2周时间搭一个满足“一个Agent 一个工作流 一套日志监控”的最小系统不要选太复杂的业务。跑完一轮你自然知道下一步该学什么。毕竟AI工程和写脚本最大的区别在于它是一个需要持续迭代的体系。只有把它当成工程来对待才能真正稳定地创造价值。