AI工程从零到一:环境搭建、知识库问答与Agent落地实践 1. AI工程到底在做什么先别急着写代码这些年“AI工程”这个词被提得越来越频繁身边不少朋友一上来就问我用Python调一下OpenAI的接口算不算AI工程我的回答通常是算但那只是整座冰山浮在水面上的一个小角。我理解的AI工程ai engineering from scratch是围绕AI能力构建完整、可维护、能持续演进的应用系统的全过程。它不只是写模型调用的代码而是包含了需求拆解、数据准备、Prompt设计、模型选型、推理链路搭建、效果评估、成本控制、监控告警和迭代机制这一整套环节。真正做AI工程的人一半时间是AI算法工程师另一半时间是后端工程师、数据工程师甚至还要兼职做一点产品经理的活。这个主题适合谁三种人最适合往下读一是刚入门AI开发、想系统建立工程化认知的初学者二是已经在写业务代码、准备把LLM能力集成进现有系统的后端工程师三是团队里被推着去负责AI项目落地、但还没有完整踩过一遍坑的技术负责人。这篇内容不会教你从头训练一个大模型那成本太高、周期太长也不是绝大多数团队真实需要的。我会把重点放在如何从零开始把一个AI点子变成稳定可靠、能上线、能迭代的实际工程。先说一个最容易被忽略的认知AI工程和传统软件工程最大的区别在于“不确定性”被引入了核心链路。传统后端接口的输入输出是可预期的但大模型的输出是概率性的同样的Prompt这次返回这个下次可能返回另一个。这带来了一系列连锁反应你的代码要考虑重试和容错你的测试不能只靠断言精确匹配你的监控要看的不只是接口延迟和错误率还要看响应质量和语义漂移。这些都不是从单纯的模型调用代码里能学到的。2. 从零搭建AI工程环境Python版本、虚拟环境和依赖管理的完整方案工欲善其事必先利其器。很多人把注意力全放在模型选型上结果环境一团乱项目跑不起来、依赖冲突、版本不兼容一排查就是半天。这里我基于自己反复折腾过的经历给出一套已经验证过很多遍的环境搭建方案。2.1 环境准备Python版本选3.10还是3.11AI工程目前的生态Python依然是最稳的主力语言。我的建议是直接在3.10或3.11中选择一个不要用3.8以下的老版本也暂时不要太激进地冲3.13。原因很实际PyTorch、Transformers、LangChain、Pydantic这类核心库对3.10/3.11的支持最成熟很多第三方库的预编译轮子在3.13上可能还没跟上遇到缺轮子需要本地编译的时候你会感受到真实的痛苦。注意不要直接在操作系统全局环境里装Python包也不要用系统的python命令跑项目。后面会讲到为什么。这里有一个很关键但经常被忽略的细节Python版本本身可能会成为整个工程后续的隐形瓶颈。比如你后面想用一些偏底层的高性能库或者想把自己写的模块打包发布版本太老会直接卡住你。我在实际项目里遇到过因为用了3.8导致一个关键库只能装老版本、老版本又有安全漏洞的情况最后花了半天把所有环境迁移到3.11。环境选择这件事宁可一开始选一个当下不新但足够稳的版本也不要贪新踩坑。2.2 虚拟环境与依赖管理推荐uv备选Poetry很多初学者会习惯用pip直接装包但pip在依赖解析和版本管理上太弱了一个项目装了几十个包之后就容易乱成一锅粥。我现在的标准操作是# 安装uv curl -LsSf https://astral.sh/uv/install.sh | sh # 用uv初始化项目并指定Python版本 uv python install 3.11 uv init my-ai-project cd my-ai-project # 创建虚拟环境并激活 uv venv --python 3.11 source .venv/bin/activate # 添加依赖 uv add openai langchain-core pydantic pandas为什么推荐uv而不是裸用pip因为速度差异巨大对依赖版本的解析也更严格能减少不少依赖地狱的麻烦。如果你所在团队已经统一用Poetry也没有问题Poetry的项目管理和打包机制同样成熟只是安装依赖的速度比uv慢一些。关键是选一个工具用它把依赖锁文件管理起来别人clone你的项目之后跑一条命令就能复现环境这才是工程化的基本盘。2.3 模型接入选型API优先还是本地部署环境搭好之后紧接着要面临模型选型。这里我根据自己的经验给出一个直白的判断标准团队人数少、没有运维精力、业务场景复杂多变优先用云端大模型API比如OpenAI、Anthropic或者国内厂家的API。省下的GPU维护成本和人月绝对比重模型训练贵得多。数据隐私要求极高、场景固定、调用频次高且可控考虑本地部署开源模型比如Qwen系列、Llama系列。但你要接受的现实是显卡采购、推理服务部署、并发优化、模型更新维护全部是摊销成本。预算敏感且并发不高可以先从API的小模型或者中等规模模型起步比如用便宜型号做初筛用贵型号做精排。我不厌其烦地强调一点模型选型不是越强越好而是在效果、延迟、成本三者之间找平衡点。我见过一个团队为了用最强模型做日志分类每个月API账单几千块但那个场景用小型模型微调或者精心设计的Prompt就能覆盖90%的需求。先算清楚账再谈技术选型。下面这个表是我常用的模型接入成本决策参考以文本类任务为例非精确价格仅示意量级因素云端API本地部署初期投入低按量付费高需采购GPU服务器运维成本接近零需专人维护推理服务和资源效果天花板高直接用前沿模型取决于开源模型版本和硬件规模数据隐私需脱敏和合规评估相对可控数据不出内网适合阶段快速验证、产品迭代期稳定期、大规模调用期3. 第一个可落地的AI工程项目做一个带Prompt管理的智能问答助手拿一个具体项目来演练是最快建立工程感觉的方式。我从零开始带大家做一个“客服知识库智能问答助手”。这个项目麻雀虽小五脏俱全有知识输入、有检索、有Prompt管理、有模型调用、有评估闭环正好把AI工程的核心链路串起来。3.1 项目需求与功能规划需求听起来很简单把公司的产品FAQ和帮助文档扔进去员工或用户在对话框里提问AI能基于内部知识库给出准确回答而不是让模型胡编乱造。但需求落到工程上至少要拆成这样输入处理支持上传txt、md、pdf格式的文档自动完成文本解析和清洗。知识库存储把文档切分成合理粒度的片段存入带向量索引的数据库比如用chroma或pgvector。检索增强用户提问时先从知识库中检索最相关的片段拼接进Prompt再交给大模型生成回答。答案生成与引用模型必须基于检索到的片段回答并在回复中标注知识来源。效果评估收集一批典型问答对每次改动Prompt或者检索策略后能离线回归。日志与会话管理记录每次问答的输入、检索结果、输出、耗时和Token消耗便于后续分析和优化。你看光是这个看似简单的需求就已经涉及了数据准备、向量检索、Prompt工程、模型链路、评估、可观测性这六个工程模块。这也正是AI工程和“写个脚本调模型”拉开距离的地方。3.2 核心代码实现从文档解析到检索增强生成我这里给出一个用Python实现的核心链路使用OpenAI的Embedding接口做向量化用chroma做向量存储用OpenAI的Chat Completions做生成。如果你用的是国产大模型API替换成对应SDK即可链路结构不变。import os from openai import OpenAI from chromadb import PersistentClient client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) # 1. 文档读取与切片 from langchain_core.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader TextLoader(knowledge_base/faq.txt, encodingutf-8) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个切片的最大字符数 chunk_overlap80, # 相邻切片之间保留的重叠字符数 separators[\n\n, \n, 。, , , ., ] ) chunks splitter.split_documents(documents) print(f文本已切分为 {len(chunks)} 个片段)切片参数为什么要这么设置chunk_size500是我基于中文FAQ场景的经验值——太短了比如100语义信息不完整检索出来的片段往往答不到点子上太长了比如2000Embedding向量会被大量无关信息稀释而且Prompt里能塞的片段数量有限。chunk_overlap80的意义是避免一句话在切片处被拦腰截断让关键上下文保持完整。切片不是越精细越好而是要让每个片段尽量成为一个语义自洽的单元。向量化和入库的部分我习惯把每个片段的主键用文档名加序号的方式拼接方便后续追溯# 2. 向量化与入库 collection PersistentClient(path./chroma_db).get_or_create_collection( nameknowledge_base, metadata{hnsw:space: cosine} ) doc_ids [f{doc.metadata.get(source, unknown)}_#{i} for i in range(len(chunks))] texts [chunk.page_content for chunk in chunks] # 分批生成embedding避免单次请求过大 BATCH_SIZE 64 for i in range(0, len(texts), BATCH_SIZE): batch_texts texts[i:iBATCH_SIZE] batch_ids doc_ids[i:iBATCH_SIZE] vectors client.embeddings.create(modeltext-embedding-3-small, inputbatch_texts) embeds [item.embedding for item in vectors.data] collection.add(idsbatch_ids, documentsbatch_texts, embeddingsembeds) print(知识库向量化完成已入库片段数, len(doc_ids))检索阶段的查询同样用同一个Embedding模型来向量化用户问题然后在库中找最接近的片段。这里有个工程细节查询时用的Embedding模型必须和入库时保持一致否则向量空间的语义对齐会被破坏检索质量直线下降。这是新手最容易踩的坑之一。生成阶段关键在于Prompt模板的设计。我给这个项目设计的模板长这样你是一个客服知识助手。请严格基于下方提供的知识片段回答用户问题。 知识片段 {context} 用户问题{question} 回答要求 1. 如果知识片段中没有相关信息明确回答“知识库中未找到相关内容”不要编造。 2. 回答简洁、准确必要时按点列出。 3. 在回答末尾标注参考片段的序号格式如[1][2]。# 3. 检索增强生成 def rag_answer(question: str, top_k: int 3): # 查询embedding q_vec client.embeddings.create( modeltext-embedding-3-small, input[question] ).data[0].embedding # 相似度检索 results collection.query( query_embeddings[q_vec], n_resultstop_k, include[documents, distances] ) related_chunks results[documents][0] distances results[distances][0] context_text \n.join([f[{i1}] {c} for i, c in enumerate(related_chunks)]) prompt f你是一个客服知识助手。请严格基于下方提供的知识片段回答用户问题。 知识片段 {context_text} 用户问题{question} 回答要求 1. 如果知识片段中没有相关信息明确回答“知识库中未找到相关内容”不要编造。 2. 回答简洁、准确必要时按点列出。 3. 在回答末尾标注参考片段的序号格式如[1][2]。 resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个严谨的客服知识助手只依据知识库内容作答。}, {role: user, content: prompt} ], temperature0.2, # 问答场景温度要低减少随机性 max_tokens800 ) return resp.choices[0].message.content, distancestemperature0.2这个参数是刻意压低的。知识库问答追求的是准确和稳定不是发散创造。我之前在一版配置里用默认的1.0结果同一个问题隔三分钟问两次答案措辞飘来飘去给客服人员造成了很大困扰。问答类场景温度设置在0到0.3之间比较合适创意写作类场景才适合把温度调高到0.7以上。3.3 评估机制没有评估闭环就别谈迭代代码能跑通的时候很多人会松一口气觉得项目完成了。实际上最难的部分才刚刚开始你需要一套评估机制不然完全不知道改动Prompt之后效果是变好了还是变差了。我建议在项目初期就准备20到50条覆盖典型场景的评测问题集并为每道题预先写好评测标准和期望答案类型。评估方式可以是人工抽查也可以让一个更强的模型当裁判给分还可以结合代码校验判断是否包含关键实体。例如这样一个简单的评测函数def check_answer_quality(answer: str, expected_keywords: list[str]) - bool: 检查回答是否包含期望的关键信息作为简化的自动化评估 return all(kw in answer for kw in expected_keywords) # 示例评测项 EVAL_CASES [ { question: 退换货的流程是什么, expected_keywords: [申请, 审核, 寄回], }, { question: 客服热线的工作时间, expected_keywords: [9:00, 21:00], }, ] def run_evaluation(): passed 0 for case in EVAL_CASES: answer, _ rag_answer(case[question]) ok check_answer_quality(answer, case[expected_keywords]) passed ok print(f[{PASS if ok else FAIL}] 问题{case[question]}) print(f评测通过率{passed}/{len(EVAL_CASES)})有了这套东西你才敢放心地改Prompt、换模型、调检索参数。每次改动之后跑一遍评测集通过率没有下降才敢合并到主分支。没有评估闭环的AI工程本质上是在裸奔。4. 从单次问答到Agent工程复杂度陡增的三个关键点如果你已经做完了上面的智能问答项目下一步通常就是跃跃欲试让AI不只是回答问题而是能自己拆解任务、调用工具、完成一系列操作。这就是Agent的方向。但Agent的工程量比单轮问答复杂得多我结合实践聊聊最关键的三个点。4.1 任务拆解与工具调用设计我在筹划Agent功能时最核心的设计原则是每个操作都要有明确边界和校验逻辑。一个Agent在认知层面可以想得很宏观但在工程执行层面必须落到具体的函数调用上。在设计工具调用时建议采用注册表模式让Agent知道有哪些工具可用、参数是什么、何时该调用。下面是我常用的工具定义脚本借助Pydantic来定义参数结构清晰且可校验from pydantic import BaseModel, Field class WeatherToolParams(BaseModel): city: str Field(description城市名例如北京) date: str Field(description日期格式YYYY-MM-DD默认今天) class WeatherTool: 示例工具查询天气。实际使用时可替换为真实API或内部服务。 name weather_query description 查询指定城市和日期的天气情况 params_schema WeatherToolParams def run(self, city: str, date: str) - str: # 这里对接实际天气服务 return f{city} {date} 天气晴朗气温 20-30 摄氏度。工具定义不仅为了给Agent提供接口也是在给自己提供可测试、可替换的边界。一个好的工具抽象应该做到“今天接的是天气API明天换成内部系统Agent层的代码完全不用改”。这个抽象层级如果不提前做后面每个Agent动作都会变成一堆纠缠不清的散装调用。4.2 上下文管理Agent最大的隐形杀手Agent通常需要多轮推理和多次工具调用上下文窗口会被迅速填满。我用一个实际例子来说明这个问题的严重性做一个“查天气并帮用户规划出行”的Agent第一轮User输入“北京周末适合去哪儿”Agent需要知道周末日期可能要调日历工具再调多个城市的天气工具最后还要汇总。几轮下来历史消息、工具返回结果、中间推理过程全都堆在上下文里。Token消耗呈指数级上升而且长上下文会导致模型注意力分散、回答质量下降。我目前的实践方案是用消息摘要机制把超过一定轮数的历史消息压缩成一个阶段性摘要。工具返回结果只保留关键字段不把完整JSON全部塞进上下文。严格区分“系统级不可变指令”和“会话级可变信息”系统指令每次放在Prompt最前面避免被后续消息稀释。很多时候Agent“变笨”不是模型能力的问题而是上下文被垃圾信息污染了。管理好上下文就是管理好Agent的注意力。4.3 可观测性与成本控制Agent每一次任务可能引发多次模型调用。如果没有采集统计月底账单来了都不知道钱花在哪。我的建议是项目一开始就接入可观测性体系至少记录这些维度每次请求的模型名称、输入Token数、输出Token数、耗时、成本估算。工具调用链路调用了哪些工具、顺序是什么、每个工具是否成功。最终结果是成功还是失败如果是失败卡在哪一步。当Agent出错时没有完整链路数据排查问题就像大海捞针。有一次线上Agent频繁报错我顺着链路的日志发现是其中一个工具返回了一个非预期格式的字段模型解析失败后整个流程直接崩掉。如果没有链路追踪这种问题可能要排查大半天。5. 常见问题与排查技巧实录AI工程的运行过程中有一批出现频率极高的问题。我把它们整理成一张速查表再挑三个典型多说几句。问题现象可能原因排查思路回答与知识库无关检索没召回有用片段Embedding模型不一致打印检索到的片段检查是否用了同一个Embedding模型同一问题答案飘忽不定temperature过高Prompt指令太弱把temperature降到0.3以下强化“严格依据知识片段”的指令回答编造知识库不存在的内容Prompt约束不足检索片段缺乏关键信息模型幻觉增加“不知道就明确说不知道”的指令提高top_k或优化切片质量Token消耗暴涨上下文过长每次请求重复拼入大段系统指令做历史消息摘要精简系统指令工具返回只留关键字段接口调用频繁超时并发控制缺失依赖的API响应慢增加重试机制和超时时间控制并发请求数或者增加本地缓存向量数据库体积过大重复入库切片粒度过小导致片段数过多入库前做哈希去重调整切片chunk_size第一个要展开说的是“检索失败但看起来像生成失败”。很多新手遇到回答不准第一反应是换更强的模型结果换了也没用。实际原因往往是知识库被切得太碎或者检索出来的片段和问题语义对不上。排查时先把related_chunks打出来看一眼你就会发现80%的问题出在检索端而不是生成端。第二个是重试机制的设计。大模型API偶尔会返回非200状态码尤其并发高的时候会触发限流。我的经验是连接超时设定在10秒以内读取超时可以放宽到60秒遇到限流或5xx错误用指数退避的方式重试最多3次每次等待时间分别为1秒、2秒、4秒。注意不是所有请求都值得重试——如果用户的问题本身不合规或触发了内容审核重试再多次也一样没必要浪费钱。第三个是关于测试策略的转变。传统软件工程里单元测试断言精确值在AI工程里输出是概率性的不能指望模型两次输出一模一样。所以我们的测试要从“断言内容精确等于”改为“断言关键属性成立”比如回答是否包含必要字段、是否引用了正确的知识库片段、格式是否符合JSON结构。测试策略不调整迟早会被脆弱测试搞疯。6. AI工程绕不开的六个“为什么”从技术细节到团队协作做了一段时间AI工程后我发现最大的障碍往往不在技术本身而在思维和协作层面。这里有六个我反复思考的问题也是我在实际项目中复盘最多的部分。6.1 为什么AI工程的“维护成本”远高于“开发成本”传统软件的需求相对固定AI应用的需求却在不断漂移。模型版本会升级Prompt策略要优化知识库要持续更新评估集要跟着业务变化修正。这些维护工作不像传统Bug修复那样可以明确定时定量。很多团队在立项时只评估了“开发要多久”却忽略了“上线之后谁来持续维护、多久迭代一次”导致项目上线即半瘫痪。我的经验是AI工程的维护成本至少要按开发成本的1.5到2倍来预估否则项目迟早会因为无人维护而枯萎。6.2 为什么“再等等”是最大的项目风险不少团队陷入一种等待心态等更好的模型出来等框架更成熟等别人趟完坑再动。但AI工程恰恰是一个需要在实践中积累手感的事情。模型迭代再快你对业务的理解、对Prompt的调试能力、对数据质量的把控才是项目成功的关键变量。尽早用小成本把闭环跑起来比空想半年再动手有效得多。6.3 为什么团队协作模式也在变传统软件团队里产品和开发之间用需求文档交接边界相对清晰。AI工程里需求往往是模糊的——“我想让AI帮我自动分析客户反馈”听起来清楚但到底分析哪些维度、输出什么格式、置信度多少可以自动化处理需要产品、开发和业务人员一起探索。所以AI工程的协作模式更像一支侦察小队需要快速试错而不是瀑布流式的层层交接。在这个领域探索本身就是在定义需求。6.4 为什么“这不是我的活”思维很危险在传统工程里系统之间的职责边界往往比较清晰你只管好自己的模块就好。在AI工程里全链路的任何一个环节出问题最终都表现为“AI表现不好”但没人能一口咬定是数据问题、Prompt问题还是模型问题。这意味着每个工程师都需要对整个链路有基本的感知。我见过太多人固守自己那一亩三分地结果排查问题时互相推诿效率极低。主动去了解上下游不是义务而是成本最低的自我保全。6.5 为什么“AI只能解决20%的问题”是常态很多业务方对AI抱有极高期待觉得大模型无所不能。实际落地的过程中你会发现把任务自动化真正跑通往往需要大量非AI的工程配套数据清洗、权限系统、审核机制、异常处理这些AI之外的苦活占掉了80%的工程量。这不是坏事反而说明你的系统在走向成熟。别被“AI很厉害”的光环迷惑把地基打牢才能让AI在它擅长的20%里发挥真正的价值。6.6 为什么“持续学习”是这一行的隐形成本AI领域的技术栈变化速度远超传统软件开发。几个月前还是热门方案今天可能已经出现了更好的替代品。我不建议普通开发者追每一个新框架但至少要维持一个习惯持续关注一线大厂和头部开源项目的发布说明了解主流技术选型的迭代方向。我自己每个月会集中花半天时间快速过一遍重要更新判断哪些值得引进哪些只是概念上的热闹。AI工程是一场持久战保持节奏比偶尔冲刺更重要。7. 从零到一落地AI工程的五个实操习惯文章最后我把这几年从零搭建AI工程的经验浓缩成五个习惯这是我认为“从入门到能打”的关键。第一养成“先定义评估再写功能”的习惯。哪怕只是在纸上写清楚“什么样的输出算合格”也能避免后续无数次的返工。评估定义了你才有改进的方向标。第二养成“每次改动只动一个变量”的习惯。新手经常同时调整Prompt、模型和检索参数结果效果变好了不知道是哪个变量的功劳变差了也不知道该回滚哪一个。一次只动一个变量才能沉淀下真正可复用的经验。第三养成“所有日志先落盘”的习惯。线上环境一旦出问题没有日志就等于没有发生过。模型调用记录、检索结果、错误信息、Token用量全部记录下来你才有事后分析的余地。第四养成“先做窄而深再做宽而广”的习惯。把一个小场景做到90分比做十个50分的场景更有价值。AI工程最忌讳什么都想覆盖最后什么都做不深。第五养成“定期复盘成本”的习惯。AI应用不像传统服务那样边际成本趋近于零每一次调用都在产生费用。定期分析Token成本看看哪些场景可以砍掉、哪些Prompt能精简、哪些功能可以换更便宜的小模型这是从一个能跑的项目走向一个能盈利的产品的必经之路。我见过太多项目死在“效果很好但成本不可控”上提前算好这笔账能帮你走得更远。我自己的体会是AI工程从零开始最大的门槛不是数学、不是模型原理而是把“不确定的模型能力”嵌入到“确定的工程体系”里的那份耐心和系统思维。多动手做多设计评估多记录踩坑这些积累的价值远超过追赶一个新模型发布的速度。