很多人问我,“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 1&2: 加载文档并分块(假设已有文本列表) documents = [ "文档内容一...", "文档内容二...", ] # 分块逻辑:每块 800 字符,重叠 100 字符 def chunk_text(text, chunk_size=800, overlap=100): 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( name="docs", embedding_function=embedding_functions.OpenAIEmbeddingFunction( api_key=os.environ["OPENAI_API_KEY"], model_name="text-embedding-3-small" ) ) ids = [f"chunk_{i}" for i in range(len(all_chunks))] collection.add(ids=ids, documents=all_chunks) # Step 4&5: 检索 + 生成 def ask_question(question, top_k=4): # 检索相关片段 results = collection.query(query_texts=[question], n_results=top_k) contexts = results["documents"][0] prompt = f"""基于以下参考资料回答问题。 如果参考资料中没有答案,就说'我不知道',不要编造。 参考资料: {chr(10).join(contexts)} 问题:{question} 回答:""" response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是严谨的知识助手。"}, {"role": "user", "content": prompt} ], temperature=0.1 ) return response.choices[0].message.content这段代码虽然能跑,但离生产还有距离。我实际会在这些地方加强:
- 添加重试机制,应对调用失败;
- 记录检索的片段和来源,方便验证和审计;
- Prompt里加上引用标记,要求模型标注依据来自哪一段;
- 对分块策略做实验,因为分块大小直接影响检索质量。
分块大小是我踩过最多坑的地方。太小,单个片段语义不完整;太大,检索不精准且浪费Token。常见做法是按语义段落分块,再结合标题层级。金融财报、法律合同这类结构清晰的文档,最好先用解析器提取标题,再以标题为边界分块。
3.3 工作流编排:从单次问答到自动化流水线
单次问答跑通只是开始。实际项目里通常要有自动化流程:每天晚上定时抓取新数据,做清洗,更新向量库,再执行一批报表任务,最终把结果推送到钉钉或邮件。
我推荐先画一张数据流图:数据源 -> 处理器 -> 向量库 -> 任务队列 -> Agent执行 -> 结果审核 -> 下发。这个流程可以用LangGraph表达,但我最初是用简单的Python脚本加定时器实现的,效果也很稳定。
下面是一个简化版的定时工作流概念示例,使用APScheduler:
from 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', hour=2, minute=0) scheduler.add_job(generate_daily_report, 'cron', hour=8, minute=30) 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检索相关度低 | 检查分块策略和TopK |
| Agent调用工具失败 | 工具描述不清晰 | 改写工具description,增加成功案例 |
| 响应突然变慢 | 服务商限流/并发过高 | 降并发,查错误码,启用重试 |
这个表我自己打印贴在工位上。大多数问题不需要重构系统,先按表格做一次快速排查,大概率能解决。
5. 实践经验与扩展建议
聊到这里,该说的技术点都覆盖了。最后照例分享一点我个人的感悟。
AI工程从零到一,最容易犯的错就是“一上来就追求最牛模型”。其实项目跑通阶段,哪怕是普通模型,只要工程结构清晰,后期替换成本也很低。反过来,模型选得再强,如果数据混乱、Prompt不可控、没有监控,一样会翻车。
另外一个很深的体会是:AI工程的本质是“人机协作”,不是“全自动魔法”。你要给模型设计好边界,给用户预留确认入口,给系统设计兜底逻辑。真正的稳定性来自工程控制,而不是模型本身的“智能”。
这个主题还可以向几个方向延展:如果你想做多模态,把图片、PDF等非结构化数据纳入RAG;如果你想做更细粒度的控制,可以去研究微调和RLHF;如果你关注Agent可靠性,可以研究LLM的可观测性和评估基准。这些都等于是从“从零开始”走向“从一到十”的过程。
我给想入坑的人一个可执行的起步建议:用1-2周时间,搭一个满足“一个Agent + 一个工作流 + 一套日志监控”的最小系统,不要选太复杂的业务。跑完一轮,你自然知道下一步该学什么。毕竟AI工程和写脚本最大的区别,在于它是一个需要持续迭代的体系。只有把它当成工程来对待,才能真正稳定地创造价值。