☰
AI工程从零到一:环境搭建、知识库问答与Agent落地实践
2026/10/4 17:21:28 网站建设 项目流程

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.11

AI工程目前的生态,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_key=os.getenv("OPENAI_API_KEY")) # 1. 文档读取与切片 from langchain_core.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader = TextLoader("knowledge_base/faq.txt", encoding="utf-8") documents = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 每个切片的最大字符数 chunk_overlap=80, # 相邻切片之间保留的重叠字符数 separators=["\n\n", "\n", "。", "!", "?", ".", " "] ) chunks = splitter.split_documents(documents) print(f"文本已切分为 {len(chunks)} 个片段")

切片参数为什么要这么设置?chunk_size=500是我基于中文FAQ场景的经验值——太短了(比如100),语义信息不完整,检索出来的片段往往答不到点子上;太长了(比如2000),Embedding向量会被大量无关信息稀释,而且Prompt里能塞的片段数量有限。chunk_overlap=80的意义是避免一句话在切片处被拦腰截断,让关键上下文保持完整。切片不是越精细越好,而是要让每个片段尽量成为一个语义自洽的单元。

向量化和入库的部分,我习惯把每个片段的主键用文档名加序号的方式拼接,方便后续追溯:

# 2. 向量化与入库 collection = PersistentClient(path="./chroma_db").get_or_create_collection( name="knowledge_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:i+BATCH_SIZE] batch_ids = doc_ids[i:i+BATCH_SIZE] vectors = client.embeddings.create(model="text-embedding-3-small", input=batch_texts) embeds = [item.embedding for item in vectors.data] collection.add(ids=batch_ids, documents=batch_texts, embeddings=embeds) 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( model="text-embedding-3-small", input=[question] ).data[0].embedding # 相似度检索 results = collection.query( query_embeddings=[q_vec], n_results=top_k, include=["documents", "distances"] ) related_chunks = results["documents"][0] distances = results["distances"][0] context_text = "\n".join([f"[{i+1}] {c}" for i, c in enumerate(related_chunks)]) prompt = f"""你是一个客服知识助手。请严格基于下方提供的知识片段回答用户问题。 知识片段: {context_text} 用户问题:{question} 回答要求: 1. 如果知识片段中没有相关信息,明确回答“知识库中未找到相关内容”,不要编造。 2. 回答简洁、准确,必要时按点列出。 3. 在回答末尾标注参考片段的序号,格式如[1][2]。 """ resp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个严谨的客服知识助手,只依据知识库内容作答。"}, {"role": "user", "content": prompt} ], temperature=0.2, # 问答场景温度要低,减少随机性 max_tokens=800 ) return resp.choices[0].message.content, distances

temperature=0.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工程从零开始,最大的门槛不是数学、不是模型原理,而是把“不确定的模型能力”嵌入到“确定的工程体系”里的那份耐心和系统思维。多动手做,多设计评估,多记录踩坑,这些积累的价值,远超过追赶一个新模型发布的速度。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询