news 2026/10/1 14:18:00

从零搭建AI工程:提示词、Agent与RAG实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建AI工程:提示词、Agent与RAG实战指南

如果你在 GitHub 上搜过ai-engineering-from-scratch这个名字,应该能猜到它不是一个“调几个 API 跑个 demo”的玩具项目。它是一个从零到一、把大模型应用从想法推到生产环境的完整方法论,核心覆盖提示词工程(Prompt Engineering)、AI Agent 编排、RAG 检索增强、模型部署与成本控制这些关键环节。这个项目要解决的问题非常现实:你会用大模型的聊天窗口,也调得通 API,但一旦要做一个真正的产品就开始翻车——输出不稳定、逻辑跑飞、上下文失控、成本爆表。它适合正在做 LLM 应用落地的工程师、想从传统开发转 AI 方向的转型者,以及团队里要为业务搭建 AI 能力的负责人。哪怕你只写过几个脚本级的 Demo,走一遍这个项目的思路,也能帮你把那些“能跑”的东西变成“能上线”的东西。

1. 这个项目的完整设计思路:为什么从零开始、怎么分层

1.1 先从热词说起:harness engineering 和 AI 工程实践

最近圈子里流行一个词叫harness engineering,我更喜欢把它翻译成“驾驭工程”。大模型本质上是一个能力很强但脾气不稳定的引擎,它知道很多,但会幻觉、会跑题、会拒绝执行指令。所谓 AI 工程,不是把模型接进系统就完事,而是通过提示词约束、工具编排、记忆管理、评测反馈这些手段,把一匹野马驯成可靠的坐骑。ai-engineering-from-scratch这个项目,本质上就是 harness engineering 的一套从零到一的完整落地案例。

这也是它取名“from scratch”的原因——不依赖任何封装好的黑盒平台,把每一层机制都亲手搭一遍。当你手写出一个最小的 Agent 循环之后,再看 LangChain、Dify 这类框架,完全是降维视角:你知道每个抽象背后在发生什么,出了问题能直接定位到具体环节。社区里像 DeepSeek 近期公开的 AI 智能体训练新方法,也在把 Agent 从“提示词拼凑”推向“系统工程”。这个节点上,掌握 AI 工程的地基,比单纯追新框架重要得多。

1.2 从零开始的正确姿势:四层架构

我把这个项目的内容拆成了四层,每一层解决一类问题:

层级核心问题关键组件
提示词层怎么让模型听懂需求Prompt 模板、采样参数控制
能力层怎么让模型会干活Agent 循环、工具调用、RAG
部署层怎么让服务稳定可用API 封装、并发队列、监控
评测迭代层怎么让效果持续变好评测集、回归测试、评分卡

这四层对应一个很直观的类比:造车。大模型相当于发动机,提示词是方向盘,Agent 编排是变速箱,部署是发动机舱和底盘,评测则是仪表盘。你不会只装一个发动机就把车开上路,AI 工程也一样——光有模型能力,没有工程化的约束和反馈,产品是不可能稳定的。

一个常见的误区是先写完功能再补评测。这个项目的思路是反过来的:评测集在设计系统架构的第一天就建好,哪怕只有二十条典型的业务问题,每条配上期望输出。之后每改一次提示词、换一次模型版本,就拿这套评测集跑一遍回归,效果变好变坏一目了然。

1.3 技术选型背后的取舍

技术选型上,项目里有几条非常务实的决策,我逐条说下背后的逻辑。

语言选 Python 而不是 Node。当前 LLM 生态的第一语言就是 Python,OpenAI SDK、LangChain、Chroma、FastAPI 这些核心工具链都是 Python 优先。Node 也能做,但很多 Agent 框架和向量库的坑你要自己踩。除非团队已有很强的 Node 基础,否则没必要在语言上给自己加难度。

LangChain 要有节制地用。说实话,LangChain 早期版本的 API 变动非常大,今天能跑的代码可能三个月后就废弃了。我的做法是:学习阶段全部手搓,不用框架;生产阶段只抽取它的TextSplitter这类稳定组件,核心的 Agent 循环、工具调用逻辑自己写。这个项目的价值也正在这里——它让你有能力脱离框架写核心逻辑,而不是被框架绑架。

模型服务走双轨:OpenAI 风格 API 兼容层 + 开源本地模型。代码里统一用 OpenAI SDK 的调用方式,然后通过环境变量切换base_url。开发期用云端 API 快速验证效果,生产期如果数据敏感或调用量大,就切换到本地部署的 Qwen、Llama 这类开源模型。上层业务代码一行不用改,这是 OpenAI 风格 API 生态最大的红利。

向量库从轻的开始。项目初期用 Chroma 就够了,装起来简单、支持本地持久化,几十万条文本完全跑得动。等数据量真的到了百万级以上,再迁移到 Milvus 这类专业向量数据库。一上来就上重型组件,运维成本会把开发效率拖垮。

2. 核心细节解析:提示词、Agent、RAG 的底层逻辑

2.1 提示词工程:把“说人话”变成一种可复用的能力

提示词工程是 AI 工程里最基础、也是投入产出比最高的一层。很多人的提示词是随手写的自然段落,模型听得懂,但效果很不稳定。这个项目推荐的写法是结构化提示词模板,把角色、任务、上下文、约束、输出格式分开:

# 角色 你是一名资深的【领域专家】,擅长【具体能力】。 # 任务 你需要根据【输入】,完成【目标】。 # 输入 {输入} # 约束 1. 【必须遵守的规则】 2. 【需要避免的行为】 # 输出格式 请严格按照以下结构输出: {JSON 结构示例}

为什么要结构化?因为大模型的注意力机制对有清晰边界的指令更敏感。自然段落的描述容易产生歧义,模型要靠猜,而结构化模板把“我要什么”和“我不要什么”的边界划清楚了,模型不用猜,输出自然稳定。

采样参数也是提示词工程的一部分,很多人忽略了。我的经验是:

  • temperature控制随机性。事实抽取、分类、代码生成这类任务设0,让输出尽可能确定;文案创作、头脑风暴设0.8~1.2,让输出有创造力。
  • max_tokens一定要设上限。不设的话,模型可能一直写下去,既费钱又不可控。
  • temperature 和 top_p 二选一调整,不要同时改。这两个参数作用重叠,同时改容易让输出变得不可预测。

提示词本身也要做版本管理。我的习惯是把 prompt 目录放进 Git,每次修改记录 diff,配套对应的测试用例。模型升级后如果输出漂移,可以快速回滚到之前的提示词版本,而不是干着急。

还有一个经验:少写“不要”,多写“要”。模型对否定指令的遵循并不稳定,与其写“不要输出多余解释”,不如写“只输出 JSON,不要包含其他内容”。你给出明确的输出格式,比反复强调禁忌有效得多。

2.2 AI Agent 的编排机制:循环、工具调用、记忆

Agent 与普通 API 调用的本质区别在于:模型不是回答一个问题就结束,而是可以多轮推理、调用工具、根据工具返回的结果继续思考,直到完成任务。这个能力来自一个非常朴素的循环:

def run_agent(task, tools, max_steps=10): messages = [{"role": "user", "content": task}] for step in range(max_steps): resp = client.chat.completions.create( model="qwen2.5:7b", messages=messages, tools=tools ) msg = resp.choices[0].message # 模型没有调用工具,说明任务完成了,直接返回 if not msg.tool_calls: return msg.content # 模型决定调用工具,把这条消息加入对话历史 messages.append(msg) # 逐个执行工具,并把结果回传给模型 for call in msg.tool_calls: result = execute_tool(call.function.name, json.loads(call.function.arguments)) messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False) }) return "达到最大步数,任务未完成"

这就是 Agent 的“前额叶”:每轮根据工具返回的信息做下一步决策。它是怎么做到调用工具的?靠的是 Function Calling 机制——模型不直接执行任何代码,它只是输出一个结构化的 JSON,比如{"name": "get_weather", "arguments": {"city": "北京"}},真正执行的是你的程序。这就像你把工具箱摆在模型面前,它负责判断该用哪个工具,你负责帮它按按钮。

Agent 的记忆和人类工作记忆一样分层次:

  • 滑动窗口:只保留最近 N 轮对话,适用于简单任务。这是最省钱的方式。
  • 总结记忆:每几轮生成一段摘要,把早期讨论压缩成精华,再配合滑动窗口。适合长对话场景。
  • 向量记忆:把长期知识写入向量库,按需检索相关内容注入上下文。适合知识密集型的任务。

实际设计 Agent 时,三种记忆经常组合使用。工具调用负责“动手”,记忆负责“不忘事”,循环负责“盯目标”——三者缺一不可。

2.3 RAG:让模型学会“查资料”再回答

RAG(Retrieval-Augmented Generation)解决的是大模型知识陈旧、不懂私域数据的问题。原理一句话:先检索,再生成。用户提问后,先从知识库中检索最相关的文档片段,拼进提示词,让模型基于这些资料回答。整个链路拆开是四步:切分、向量化、检索、生成。

切分是最容易被低估的一步。固定按 512 字符切,会把一句话从中间切断,检索时匹配到的片段语义不完整,生成时模型就瞎编。更好的方案是递归切分:优先按段落切,段落太长再按句子切,句子太长才按词切,同时保留 64 字符的重叠区,防止跨块语义丢失。我用的一组实测参数是chunk_size=512, chunk_overlap=64,分隔符按“段落-句子-标点-空格”的优先级设置,效果比固定切分稳定得多。

向量化是把文本变成高维向量。Embedding 模型的选择对中文场景特别关键,英文表现好的模型中文不一定好。我的建议是中文业务优先试bge-large-zh,预算充足可以用 OpenAI 的text-embedding-3-small,两者都有稳定的社区实践。

检索的基础是向量相似度,但这里有个进阶操作很多人都不知道:召回的 Top 结果不一定质量最好,要加 Rerank(重排)。先向量召回前 100 条,再用专门的 Rerank 模型精排,取出前 5 条送入生成。这个过程能显著提升检索准确率,代价是增加一点延迟。对精度要求高的客服问答、法律文档问答,这步省不得。

生成阶段则要把检索结果和原始问题一起拼进提示词,并且明确告诉模型:“只依据以下资料回答,资料中没有的内容直接说不知道”。否则模型还是会忍不住调动自己的参数记忆,产生幻觉。

2.4 多AI协作:从单模型到一条工作流

单模型的能力有上限,这就引出多 AI 协作的价值——用一个工作流把多个模型串起来,每个环节选最擅长那个。这个思路在内容生产领域已经跑通了,比如 AI 短剧的生产流程可以设计成:

编剧 Agent 生成脚本 → 分镜 Agent 拆解成镜头 → 画面 Agent 根据分镜生成图片 → 配音 Agent 为台词生成语音 → 质检 Agent 检查画面与台词的匹配度。

单模型干不了整个流程,但每个环节换成最合适的模型后,整体质量会明显上一个台阶。多 AI 协作有三种基本模式:

  • 串联:上一个 Agent 的输出是下一个 Agent 的输入,适合流水线式的任务。
  • 编排:主 Agent 负责拆解任务、调度多个子 Agent,适合复杂任务。
  • 竞争:多个模型对同一任务分别输出,再投票或打分选最优,适合高风险决策。

多 AI 协作真正麻烦的不是模型调用,而是交接格式。每个 Agent 的输出必须定义明确的 Schema,比如统一的 JSON 结构,否则下一个环节接不住。这就是为什么这个项目强调“工程化”,而不是“多接几个模型接口”——本质上是把每个环节的输入输出标准化,让不同模型之间能顺畅协作。

3. 实操过程:从零搭建一个 AI 工程项目的完整流程

3.1 第一步:环境准备与模型服务选型

工欲善其事,必先利其器。环境搭建这一步虽然简单,但选错了基础,后面全是坑。

conda create -n ai-eng python=3.11 -y conda activate ai-eng pip install openai langchain chromadb fastapi uvicorn python-dotenv requests

Python 选 3.11 而不是最新的 3.12/3.13,原因是 LLM 生态的工具链对 3.11 的适配最成熟,遇到底层依赖冲突的概率最小。模型服务我建议做双轨配置,用一个.env文件管理:

OPENAI_API_KEY=sk-xxx # 如果走本地模型,就改成下面的配置 # OPENAI_BASE_URL=http://localhost:11434/v1

这里的核心技巧是:本地用 Ollama 跑开源模型(Qwen、Llama 系列),它会暴露一个 OpenAI 风格兼容的接口。于是你的代码里始终用同一个 OpenAI SDK 客户端,通过切换base_url就能在云端 API 和本地模型之间无缝切换。开发期用云端 API 快速验证效果,生产期切到本地模型省成本或满足数据不出域的要求,上层逻辑完全不用动。

3.2 第二步:用 OpenAI 风格 API 实现一个带工具调用的 Agent

这一步是整个项目的高潮:实现一个 AI 测试开发助手。它要能运行测试、查日志、总结失败原因。先定义工具:

TOOLS = [ { "type": "function", "function": { "name": "run_pytest", "description": "在指定目录下运行 pytest 测试用例", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "测试目录或文件路径"} }, "required": ["path"] } } }, { "type": "function", "function": { "name": "search_log", "description": "在日志文件中搜索关键词,返回匹配的行", "parameters": { "type": "object", "properties": { "keyword": {"type": "string", "description": "搜索关键词"}, "file": {"type": "string", "description": "日志文件路径"} }, "required": ["keyword", "file"] } } } ]

然后是工具执行函数:

import subprocess def execute_tool(name, arguments): if name == "run_pytest": result = subprocess.run( ["pytest", arguments["path"], "-q"], capture_output=True, text=True, timeout=120 ) return { "returncode": result.returncode, "stdout": result.stdout[-2000:], "stderr": result.stderr[-2000:] } if name == "search_log": import re with open(arguments["file"], "r", encoding="utf-8", errors="ignore") as f: lines = f.readlines() matched = [line.strip() for line in lines if arguments["keyword"].lower() in line.lower()] return {"matches": matched[-50:]} return {"error": f"未知工具: {name}"}

核心循环就是上一章那段伪代码的完整实现。我给这个 Agent 下发一个实际任务:“运行tests/test_api.py,如果有失败用例,在logs/app.log里查找 ERROR 相关日志,总结失败原因。”它会先调run_pytest得到测试结果,发现失败后,再调search_log查日志,最后综合工具返回的信息给出分析结论。过程中每一步的决策都可以打印出来,方便排查。

这里我想特别说一个体验:这段 Agent 骨架代码,我并不是全部手敲的。我把需求描述给 CodeBuddy 这类 AI 编程助手,它会先生成一版脚手架,我再做代码审查,补充工具函数和异常处理。这里有个重要的心得:AI 编程助手写出来的代码,你至少要能读懂每一步在干什么,否则出了问题根本无从下手。这也呼应了项目名里的“from scratch”——你要先手搓一遍核心循环,才能真正用好这些编程助手。

3.3 第三步:RAG 检索管道的落地

接下来给这个项目加上知识库能力,让 Agent 能基于内部文档回答问题。先做知识入库:

from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma # 1. 切分 splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) docs = splitter.split_text(raw_text) # 2. 向量化 + 入库 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_texts(docs, embeddings, persist_directory="./db")

查询时:

# 3. 检索 retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) contexts = retriever.invoke(question) # 4. 生成 prompt = f"""请只依据以下资料回答问题。 资料中没有的信息,请直接回复“资料中未提及”。 资料: {chr(10).join(contexts)} 问题:{question} """

这一步最容易犯的错误是“跑通了就算完”。切分参数、Embedding 模型、相似度阈值这些都要跟你的领域文本匹配。比如技术文档密集的文本,chunk_size可以放到 768,代码和注释混合的文档则要调小。代码跑通只是开始,检索质量要拿真实问答对去测,至少准备 30 条覆盖不同场景的 QA 对,手动检查每条检索结果的相关性。

3.4 第四步:把服务部署上线

最后用 FastAPI 把整个能力封装成一个 HTTP 服务:

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ChatBody(BaseModel): prompt: str history: list = [] @app.post("/chat") async def chat(body: ChatBody): result = run_agent_with_rag(body.prompt, body.history) return {"reply": result}

启动命令:

uvicorn main:app --host 0.0.0.0 --port 8000

生产部署有几个细节必须处理,否则本地能跑、线上必挂:

第一,同步 IO 要改异步或上队列。模型推理动辄几秒,如果用同步方式,线程会被全部占满,新请求全部排队。FastAPI 的异步接口配合任务队列,能有效缓解这个问题。

第二,并发控制。自部署模型在单张 GPU 上同时服务的请求通常只有 1-4 个,多了会互相拖慢。云端 API 则要看上游的限速(RPM/TPM),需要在代码里做限流和重试。

第三,成本要算清楚。以某主流 API 为例,给一个直观的估算:

项目数值
单次问答输入 token 数约 500
单次问答输出 token 数约 300
输入价格约 $0.15 / 百万 token
输出价格约 $0.60 / 百万 token
单次问答成本约 $0.000255
100 万次调用成本约 $255

如果换成自部署开源模型,成本结构会变化:GPU 机器是固定支出,单次调用的增量成本接近零。两者的平衡点是调用量——调用量大、数据敏感,自部署划算;调用量小、需求多变,API 划算。

4. 常见问题与排查技巧实录

4.1 Agent 说话不算数、陷入死循环

这是我见过最多的问题。症状是日志里同一个工具调用重复几十次,Agent 一遍遍地查天气、查数据库,就是不给出最终结论。

原因通常是两个:一是没有设置最大步数,模型有无限次循环的机会;二是模型的 prompt 里没有明确“什么时候该停止”。我的解法是三层兜底:max_steps=10硬上限、工具调用总次数预算超了强制中断、在系统提示词里写明“如果结论已明确,直接输出最终答案,不要重复调用工具”。踩过坑之后你会发现,没有中断机制的 Agent 不是一个 Agent,只是一个会自嗨的聊天机器人。

4.2 输出格式不稳定、JSON 写崩

让模型输出结构化 JSON,它偶尔会在 JSON 里塞进注释、多一个逗号,或者干脆输出一大段废话。这个问题不能靠“让它乖一点”来解决,得靠工程手段:

  • 使用 API 提供的 JSON Mode,在请求参数里加response_format={"type": "json_object"},模型就会被强制约束在 JSON 输出框架内。
  • 在提示词里给一个完整的 JSON 示例,模型会照着示例的格式输出。
  • 代码里做解析兜底:先json.loads解析,解析失败就带上错误信息重试一次,最多重试两次。稳定性和解析成功率会明显提升。

4.3 RAG 检索不到东西、答非所问

用户问了一个知识库里明明有的问题,模型却说不知道。排查流程很重要:先看检索环节,再看生成环节,不要一上来就怀疑模型。

我的排查顺序是这样:先打印召回的 Top 5 文档,看它们和问题的相关性;如果检索结果相关,问题在生成环节;如果检索结果不相关,问题在切分或 Embedding 环节。

常见原因和对应解法:

  • chunk 太小,导致片段语义不完整。调大chunk_size,或者改用父子块策略——父块存完整上下文,子块用于检索。
  • query 和文档用词差异太大,向量匹配不上。加混合检索,把 BM25 关键词检索的结果和向量检索结果合并。
  • 检索 Top 结果不够精。加 Rerank 步骤,先召回 100 条再精排取前 5。

4.4 成本失控、一个工作日烧掉几百块

成本失控的原因大多是三个:把全文文档一股脑塞进上下文、Agent 循环没有步数上限、每次都从头计算没有缓存。我的方案是组合拳:

  • 上下文裁剪:对话历史只保留最近 10 轮,更早的内容做成摘要。
  • 语义缓存:对用户的提问做向量化,相似的提问直接返回缓存结果,不调模型。实测在客服场景下能砍掉 30%-50% 的模型调用。
  • 小模型兜底:意图识别、分类、抽取这类简单任务用 mini 级小模型,只有复杂推理才用大模型。

4.5 排查清单速查表

现象第一步排查常见解法
Agent 不调用工具检查 tools 参数是否传全在提示词中明确“你可以使用以下工具”
工具调用报错打印工具返回的原始 JSON给工具执行包上 try-except,返回结构化错误信息
回答和检索内容无关打印送入模型的最终 prompt在提示词中写死“仅根据资料回答”
延迟过高查看请求日志中每一步耗时把无依赖的模型调用改成并发执行
换了模型版本效果变差跑评测集回归对比回滚模型版本或提示词

跑完ai-engineering-from-scratch这个项目,我最大的体会是:AI 工程真正的门槛不是算法,而是工程心智。你得学会把大模型当成一个不可靠但很聪明的同事,关键路径上全都要有兜底策略:最大步数、超时、输出校验、数据回滚,一个都不能少。最后分享一个小技巧:从第一天开始就记录每一次调用的输入输出和 token 消耗,用表格也好、用日志平台也好。这些数据之后会在优化效果、解释成本、排查线上问题时帮你省下大量口水。我踩过最深的坑就是先写完功能再补日志,结果线上出问题时只能靠肉眼猜,那感觉太痛苦了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 14:17:59

聚合AI外贸GEO服务商哪家强?适用于小语种站点与Google/Bing双引擎优化

海外买家正在转向AI提问,外贸获客逻辑已经变了全球贸易数字化进程正在加速,海外采购商的决策入口正在发生根本性迁移。过去是搜关键词、打开网页、逐一比较,如今越来越多买家直接向ChatGPT、Gemini、Claude等AI提问:有哪些靠谱的中…

作者头像 李华
网站建设 2026/10/1 14:17:21

英辰朗迪AI获客每日AI精选(2026.09.30)

一、技术前沿第1条:Anthropic 发布 Claude Sonnet 5.5,速度提升 30%、成本下降 30%核心内容:Anthropic 于 9 月 29 日推出 Claude Sonnet 5.5,为 Claude 5.5 家族第二款模型。相比 Sonnet 5,API 响应速度提升超过 30%&…

作者头像 李华
网站建设 2026/10/1 14:17:17

ARP欺骗攻击防御全攻略:DHCP Snooping与DAI实战详解

先说一个比较扎心的现实:很多网络工程师对ARP的理解停留在“IP查MAC”这五个字上,但真到了排障现场,遇到全网掉线、网关MAC被篡改、内网抓包全是大量重复ARP广播的时候,才发现对ARP的认知远远不够。这篇是我个人实验笔记的第三期&…

作者头像 李华
网站建设 2026/10/1 14:17:01

单视频流三维实时重构在海关货物查验与异常行为智能识别中的应用方案

一、项目概述当前海关货物查验与现场作业监管普遍存在二维视频监管维度单一、货物堆位空间关系识别困难、人员异常行为漏判、查验过程不可量化、风险预警被动滞后、现场态势与监管数据脱节等行业痛点。传统海关查验监管主要依赖人工视频巡查、纸质记录、事后回看与经验判断&…

作者头像 李华
网站建设 2026/10/1 14:15:55

DEH系统六大核心硬件详解:从LVDT到EH油泵的原理与故障处理

1. DEH系统到底在控制什么:从汽轮机调节的本质说起DEH这三个字母,全称是Digital Electro-Hydraulic Control System,中文叫数字电液控制系统。很多刚接触电厂热工控制的朋友,第一次听到这个名字会觉得它离自己很远,其实…

作者头像 李华
网站建设 2026/10/1 14:14:46

Attention与优化器的同构演进:从数学结构到工程实践

1. 项目概述:当注意力机制和优化器开始“长成一个样子”你有没有在调模型时突然愣住过?——看着Attention层里那堆QKV矩阵乘、softmax归一化、加权求和的流程,再低头瞅一眼AdamW优化器里那一串带偏置校正的动量更新、二阶矩估计、权重衰减分离…

作者头像 李华