拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零搭建AI工程:从模型接入到Agent编排的完整实践指南

从零搭建AI工程:从模型接入到Agent编排的完整实践指南

1. 项目概述:当你说“从零开始做AI工程”的时候,到底在说什么

“ai-engineering-from-scratch”这个标题,我第一眼看到的时候其实挺感慨的。市面上讲“从零开始学AI”的文章多到泛滥,但绝大多数要么是教你怎么装个库跑个demo,要么是直接甩给你一套调API的封装教程。真正能把“工程”两个字落到实处的,少之又少。

我个人的理解是,这个项目标题背后藏着一个很明确的诉求:不满足于当一个“调包侠”,而是想从底层逻辑出发,把AI应用从设计、开发、测试到上线迭代这条完整的链路自己捋一遍。它面向的是一群想真正理解AI系统如何运转的人——可能是刚入行的算法工程师,可能是想往AI方向转的后端开发者,也可能是已经在用LangChain或各类Agent框架做业务应用、但总觉得底层逻辑不够清晰的技术负责人。

这篇文章我不会跟你扯什么复杂的数学推导,也不会把某个开源库的源码逐行贴出来做书虫式讲解。我会以一个真实做过几个AI项目的从业者视角,把这个标题背后涉及的工程链路、设计决策、踩坑实录和排查思路完整地讲一遍。整个项目围绕一条主线展开:如何不依赖现成的“全家桶”式AI框架,而是像搭积木一样,从环境准备、模型接入、工具设计、Agent编排、评测体系到部署监控,亲手把一套完整的AI应用系统搭建出来。

从零开始不等于重复造轮子,更不等于非要从线性代数开始补数学。我见过太多人把“from scratch”理解成“什么都自己写”,结果死在了半路。真正的含义应该是:理解每一层组件存在的意义,知道系统里每个环节为什么这样设计,遇到问题的时候能够定位到具体层次去修复。这就是AI工程和AI脚本之间最大的分水岭。

2. 全链路架构拆解:一套AI系统到底有哪些“层”

2.1 基础设施层:先想清楚你的算力和模型跑在哪里

动手之前,第一件事其实是决定模型和算力策略。这不是选个贵的就行的简单事,它直接决定你后续所有架构设计。我当时做第一个项目的时候,贪图方便直接全部调线上API,结果到了评测环节发现数据要反复跑、token开销像流水一样淌,一天烧掉几百块那是常有的事。后来学乖了:先明确哪些场景必须上最强模型,哪些场景用小模型或者缓存策略就能兜住。

基础设施层主要包含三个决策点:

  • 计算资源:你用本地GPU、云GPU实例,还是直接调用托管API?这三种方式的成本结构完全不同,而且会反向影响你的架构设计。比如本地GPU适合批量推理和模型微调,但可用性低,不能扛线上流量;云实例弹性好但单价不菲;API最省心但受限于网络和单次上下文的长度。
  • 模型接入层:无论用OpenAI兼容接口、开源模型本地部署,还是国产模型平台,都需要一个统一抽象层。我习惯把所有模型调用封装成一个LLMClient接口,上层业务完全不管底层是GPT、Claude、Qwen还是本地部署的Llama。这一步看起来多余,但当你想换模型、做A/B测试、或者在多路模型之间做路由分发的时候,它省下的时间能以十倍计算。
  • 配置与环境隔离:API密钥、模型名称、温度参数、最大token数,这些全部放进环境变量或配置文件,严禁硬编码到代码里。我见过不止一个团队把密钥提交到Git仓库的事故,那才是最致命的“from scratch”翻车现场。
# 一个典型的模型接入抽象层,用Python实现大概长这样 from abc import ABC, abstractmethod import os import httpx class BaseLLMClient(ABC): @abstractmethod def chat(self, messages, temperature=0.7, max_tokens=1024): """输入消息列表,返回模型回复字符串""" ... class OpenAICompatClient(BaseLLMClient): """兼容OpenAI接口格式的客户端,适配绝大多数在线模型""" def __init__(self, model_name, base_url=None): self.model = model_name self.api_key = os.getenv("LLM_API_KEY") self.base_url = base_url or os.getenv("LLM_BASE_URL", "https://api.openai.com/v1") self.client = httpx.Client(headers={ "Authorization": f"Bearer {self.api_key}" }) def chat(self, messages, temperature=0.7, max_tokens=1024): resp = self.client.post(f"{self.base_url}/chat/completions", json={ "model": self.model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, }) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"]

注意:上面的代码只是一个骨架示例,真实生产环境还需要加超时重试、异常分类处理、token用量统计和流式输出支持。千万别把这个直接拷到生产用,但理解这个抽象层的思路是后面所有工程化的前提。

2.2 数据与上下文工程层:模型不是数据库,别什么都往里塞

这是我认为整个项目中含金量最高的认知。很多第一次上手做AI工程的人,把模型当成一个超大号的数据库,什么问题都往上下文里一丢,指望它自动输出正确答案。短期demo可以,长期工程一定是灾难。

上下文工程要解决的核心问题是:用户的请求进来后,系统如何高效地组织、检索、裁剪信息,让模型在有限的上下文窗口内获得最相关的信息。这不是简单的“把文档塞进Prompt”,而是需要一套完整的数据管线。

我个人在项目里把这一层拆成了四个子模块:

  1. 知识库接入:文档解析、清洗、切分。切分策略直接决定检索质量,我踩过最大的坑是机械地按固定字符数切分,结果把完整的业务逻辑拆得七零八落,检索出来的片段语义不连贯。后来改成“按标题层级切分 + 语义相似度合并”,效果立刻不一样了。
  2. 向量化与检索:Embedding模型选型、向量库的选择、检索策略。这里面有太多细节,比如是单向量检索还是多路召回,要不要做重排(rerank),每种方案在不同场景下的准确率差异很大。
  3. 记忆管理:多轮对话的记忆不是简单的消息堆叠,而是要把历史会话中的关键信息抽取出来、结构化缓存。我自己常用的是“摘要 + 原始消息”双轨制:短期的原始消息保留最近几轮,再叠加一个不断更新的长短期摘要,既保证上下文不膨胀,又能留住跨会话的关键信息。
  4. 上下文裁剪策略:当检索结果太多,或者对话历史太长时,要做动态裁剪和排序。这里需要关注的不仅是token数量,还有信息在上下文中的位置——模型对中间部分的注意力往往弱于首尾,所以关键信息要尽量放在Prompt的开头或结尾附近。

2.3 Agent编排与执行层:从单轮问答到多步任务

如果你只是想做一个“在线聊天机器人”,那前两层就已经够了。但既然“AI工程”要解决的是真实业务问题,那一定会碰到多步骤、需要调用外部工具、并根据中间结果动态调整路径的任务。这就是Agent要干的事。

Agent编排层是项目里最复杂也是最有意思的部分。从结构上看,它至少包含:

  • 任务规划器(Planner):把用户的目标拆解成一个可执行的步骤列表。
  • 工具注册中心(Tool Registry):维护系统能调用的所有外部能力清单,比如搜索引擎、数据库查询、代码执行器、HTTP API等。
  • 执行与反馈循环(Execution Loop):调用模型 → 解析决策 → 执行工具 → 观察结果 → 再调用模型的这一整圈循环。

这个“循环”恰恰是当前AI Agent领域最热门也是最核心的概念。有些工程实践里把它叫 harness engineering,有些叫 loop engineering,本质都在讲一件事:不要让大模型一步登天直接输出答案,而是让它在一个受控的循环中不断思考、行动、观察,逐步逼近正确结果。

我之前在一个项目里手写过一个小型Agent循环,核心逻辑大概像下面这样:

def run_agent(task, max_steps=10): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": task} ] for step in range(max_steps): response = llm.chat(messages, temperature=0.3) decision = parse_decision(response) # 解析当前结果,判断是结束还是需要调工具 if decision["type"] == "final_answer": return decision["content"] # 执行工具调用,把结果追加回消息上下文 tool_result = execute_tool(decision["tool_name"], decision["arguments"]) messages.append({"role": "assistant", "content": response}) messages.append({"role": "tool", "content": tool_result}) raise TimeoutError("Agent loop exceeded max steps")

这个循环看起来简单,但里面有三个设计要点:

一是如何解析模型的输出。是让模型输出严格的JSON、用函数调用的原生格式,还是通过在文本中标记特殊符号来切分?我经验是最稳妥的方案是:优先选用模型自带的function calling能力,因为这是模型在训练阶段就对齐过的格式,解析失败率最低。如果用的是本地开源模型不具备这个能力,那就退而求其次用约束式解码或者先让模型输出一小段思考过程再结构化输出。

二是循环什么时候停。必须有最大步数限制和最小置信度阈值,否则遇到一个模棱两可的工具调用结果,Agent就会陷入“反复重试→结果还不一样的”死循环。这个问题在真实业务中非常常见,我至少见过三四个团队在这个问题上栽跟头。

三是状态跟踪与回溯。当Agent已经执行了三步,发现路径不对,需不需要回退?很多简单实现的做法是只往下走不回退,这在复杂业务场景中会累积错误。更健壮的设计是引入一个显式的“状态栈”,每执行一步就把中间状态压栈,一旦发现异常就可以逐层回溯重选路径。

2.4 评测与质量保障层:没有度量就没有工程

这是“从零开始做AI工程”里最容易忽略但最关键的环节。传统软件开发有单元测试、回归测试、覆盖率这些手段,到了AI时代,很多人却回到了“跑一下看看效果好不好”的原始状态。这在工程上是完全不可接受的。

我自己的经验是,AI系统的评测要分三个梯度:

  • 第一梯度:确定性断言。比如工具调用格式是否正确、某些硬性关键字是否出现、返回的JSON是否能解析。这些可以用传统测试框架直接做,属于不需要模型参与的规则评测。
  • 第二梯度:传统指标。比如分类任务的准确率、召回率、F1,检索任务的命中率、MRR。前提是你得准备一个有标准答案的测试集,这就要回到数据标注的功夫上。
  • 第三梯度:模型辅助评测。也就是用LLM-as-a-judge的方式,拿一个强模型当裁判,对生成内容做多维度的打分——比如相关性、忠实度、有害性等。这个方案灵活度高,但有个隐患:裁判模型本身也有偏好偏差。同一份回答,用一个裁判模型换一个提示词,打分可能差出10分。

实操心得:我在项目里落地评测体系时,是分三步走的。第一步先搭一个离线评测集,把过去半年业务中用户反馈好和反馈差的对话各抽几百条,做成自带标签的测试夹具;第二步把评测脚本接进CI/CD流水线,每次模型或提示词变更都必须跑完全量测试集才能合并;第三步才是在线监控,对生产流量做抽样子采样,配合人工标注修正评估模型的漂移。这套体系搭起来可能要花一周左右的时间,但后面省下的返工时间绝对远超这个投入。

3. 实操路线:从0.5到1.0的五个递进阶段

3.1 阶段零:先跑通“最小可行闭环”

很多人一上来就想直接写Agent框架,这是最大的误区。我的建议是先把最基础的链路跑通:用户输入 → 调用一次模型 → 展示响应。这个环节看似简单,但有两个容易被忽略的点。

第一个是模型参数对交互体验的影响。温度设太高,用户觉得回答飘;设太低,创造性任务又不够灵活。最大token数设少了,长文本生成直接被截断,用户看到的是一句半截话。这些参数必须在一开始就建立配置化的管理方式,而不是每改一次参数就动一次代码。

第二个是错误处理。模型API不是每次都能成功的,超时、限流、上下文超长、内容被内容审核拦截,这些异常场景都必须有对应的用户友好的兜底逻辑。我在第一个版本里没考虑这些,结果上线当天就因为高频请求触发了限流,每个用户都看到一大串英文报错。这种事故本来完全可以避免。

3.2 阶段一:给系统接入“外部工具”

最小闭环跑通后,下一步就是让模型具备调用工具的能力。我强烈建议从搜索工具做起,因为它能立刻让你的系统从“一个空有知识的聊天机器人”进化成“一个能获取实时信息的助手”,而且搜索工具的结果好坏比较容易量化评估。

工具接入的核心是函数描述的设计。每个工具至少要写清楚:函数名称(建议动词开头的snake_case)、参数列表(每个参数的名称、类型、是否必填、含义描述)、返回值结构。这些描述会被编码进模型调用的上下文中,描述写得越清晰精准,模型做出正确工具调用的概率就越高。

我当时做过一个实验:把搜索工具的参数描述从一句“query: string”改成“query: 一个简洁的搜索关键词,通常是疑问句中去除停用词后的核心实体”,模型调用工具的成功率直接提升了十几个百分点。这个细节很值得跟大家分享,函数描述本身就是一种廉价而高效的Prompt优化。

3.3 阶段二:加上记忆与上下文管理

工具调用的下一步是让Agent在多次交互中“记住”之前的对话内容。这一步的工程复杂度是跳跃性上升的。

我在项目里亲手实现了一套轻量级记忆模块,核心设计如下:

  • 短期记忆:保留最近N轮(N一般取5-8)的原始消息,用于保证对话的连贯性。
  • 长期记忆:每一轮对话结束时,异步调用模型把本轮对话压缩成结构化摘要,存入本地向量库或JSON文件。
  • 记忆检索:当新对话进来时,先从长期记忆中检索与当前问题相关的旧摘要,注入上下文。

这里有一个很关键的参数平衡问题:短期记忆的N轮数和长期记忆的摘要长度,直接决定单次请求的token消耗和响应延迟。N太大则上下文爆掉,N太小则对话连贯性受损。我是用线上日志统计用户的真实平均对话轮数来确定N的,而不是拍脑袋定的。

3.4 阶段三:引入评测与回归机制

当Agent能力扩充到一定程度,就进入了最难受的阶段——改了A场景的表现,B场景却退化了。我自己的项目里经常出现的情况是:为了提升代码生成的准确率改了一下工具调用的逻辑,结果语义问答的准确性掉了5个百分点。没有评测体系的话,这种回归问题会累积到你完全不敢动代码的程度。

我的落地方式是搭了一套“场景回归集”:把过去业务中积累的所有用户场景分类,每个场景手工准备20-30个测试用例,并为每个用例标注明确的通过标准。然后再做“跨场景同测”脚本,每次改动后自动跑一遍全部用例,输出一张“场景×用例的通过率矩阵”。任何一次修改都必须保证所有场景的通过率不下滑,才能进入下一步。这套东西前期准备烦琐,但它几乎是最快能让你从“随缘开发”切换到“工程化迭代”的杠杆。

3.5 阶段四:多Agent协作与复杂任务编排

单Agent的系统能力上限,通常卡在上下文长度和模型单次推理的深度上。要处理更复杂的任务,就得做多Agent协作。这一步项目本质上是把一套系统拆分成多个有明确分工的“角色”,再由一个编排层统一调度。

多Agent架构没有银弹,我用过且验证可行的有三种模式:

  • 主管-下属模式:一个主管Agent负责任务拆解和结果验收,多个下属Agent各自负责一个子任务。适合子任务之间耦合度低、可以并行的场景。
  • 流水线模式:每个Agent是一个独立阶段,前一个Agent的输出是后一个Agent的输入。适合任务链条清晰、每阶段目标明确的场景。
  • 辩论与投票模式:多个不同角色Agent针对同一问题独立给出分析和建议,再由一个裁判Agent或规则函数选择最优答案。适合高风险决策场景。

多Agent协作最大的坑在于状态同步与通信开销。我做过一个项目里同时启用三个Agent,每个Agent都要读共享上下文,结果token消耗直接翻了四倍,响应时间从2秒飙到了15秒。后来加了共享内存模块和按需检索,把“全量共享上下文”改成“按需拉取上下文切片”,才把性能拉回可用的范围。

4. 工具选型解析:框架用哪家,还是自己写

4.1 主流框架的定位与局限

现在市面上的AI框架大致可以分为两类:通用编排框架和特定用途脚手架。通用编排框架以LangChain、LlamaIndex为代表,特点是组件丰富、抽象层次高、上手快。特定用途脚手架则更像是给某个具体业务场景量身定做的模板,比如一些AutoGen、CrewAI这类主打多Agent协作的工具。

我个人的使用感受是:框架在项目初期能帮你节省大量时间,但到了后期往往成为约束。尤其是当你要做细粒度的控制——比如自定义工具调用失败的恢复策略、定制上下文裁剪的逻辑、精细调整个别Agent的Prompt——框架的封装就会成为一层“厚厚的墙”,你要么花大量精力去读懂它的源码和扩展机制,要么就得绕开它自己另起炉灶。

这里我给一个务实的建议:如果你对Agent的底层运行机制还不完全清楚,先用成熟框架把业务跑通,把注意力放在业务逻辑和评测上;等业务稳定了,再选择性地把框架中与你业务耦合最深的那个环节替换成自研实现。不要一开始就为了“技术洁癖”而自己从零写调度器,那样性价比实在太低了。

4.2 自研小框架的取舍标准

什么情况下值得自研?我的判断标准是三条:

  1. 现有框架无法满足你对性能的极致要求,比如单轮响应延迟必须低于某阈值,而框架的延迟开销占了不可忽略的比例;
  2. 你需要对执行过程做非常细致的日志埋点和控制——比如把每一步工具调用的输入输出全部记录下来用于评测或审计,而框架的原始日志不能满足;
  3. 你需要在多个Agent之间做自定义的复杂协同逻辑——比如条件分支跳转、任务优先级抢占、多Agent结果合并策略等。

我后来给自己的项目写了一个极简的Agent核心,也不复杂,就三个类:ToolRegistry(工具注册)、ContextManager(上下文管理)、AgentLoop(循环调度)。加起来不到三百行代码。但它让我获得的最大收益是:任何一步出问题我都能快速定位到是哪一层逻辑的锅——这在调框架的黑盒时几乎不可能做到。

4.3 模型选型:不要只看排行榜

模型选型是整个项目中性价比最高也最容易踩坑的决策点。很多人习惯用一两个公开榜单的指标来选模型,但这在真实业务中往往不太实用。榜单指标和你业务场景上的实际表现之间的相关性,常常低得惊人。

我的做法是搭建一个业务专属的模型评测基线:准备几十条与你业务高度相关的测试问题,然后让候选模型各跑一遍,按你定义的通过标准打分。一次评测可能花几个小时,但比盲目相信榜单要靠谱得多。

具体到选型的策略上,我分三个档次:

  • 主力模型:承担最复杂的推理和生成任务,选最强的模型,但只在必要的时候启用。
  • 经济模型:处理简单任务,比如意图识别、实体抽取、格式转换。这类任务用最强模型纯属浪费,经济模型至少能节省一半以上的token开支。
  • 本地模型:处理隐私敏感数据,或者作为离线批量处理的兜底。本地模型的质量目前跟在线模型有差距,但进步很快,值得持续跟踪。

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

5.1 模型输出不可控:对话变成了“脱缰的野马”

这是所有做AI工程的人都会碰到的问题。表现为:模型偶尔不按预设格式输出、突然在回答里“扮演”其他角色、或者对同一个问题给出自相矛盾的答案。

排查思路分两步:首先检查系统提示词是否足够明确。很多人在System Prompt里只写了“你是助手”四个字,然后就指望模型自己理解一切。这是不现实的。系统的约束需要是显式的、可校验的。我见过一个写得比较极端的System Prompt,把输出格式、禁止行为、兜底回复示例、评分标准全部写进去了,然后模型的失稳概率明显下降。

其次要检查温度参数和采样参数。温度过高是脱轨的第一号元凶。有些模型支持top_p、frequency_penalty等参数,它们对输出的可控性影响也很大。调试的时候建议先调低温度,再看是否需要改提示词。不要一上来就大改提示词,那样你根本分不清问题出在哪个环节。

5.2 Agent工具调用失败:循环卡住或者反复重试

工具调用环节最常见的故障是:模型生成了一个格式不正确的工具调用指令,或者调用的参数与实际工具不匹配。

我的经验是把工具调用失败的处理逻辑设计成分层恢复机制:

  • 第一层:语法修复。如果模型输出的JSON格式不完整,尝试用正则或系统自身容错算法补全。
  • 第二层:参数映射。模型给出的参数名或枚举值与工具定义不完全一致时,做一个语义模糊匹配的转换。
  • 第三层:错误反馈。把工具返回的完整错误信息送回模型,让模型基于错误信息自己调整调用策略。

第三层往往是最有效的,因为大模型对“错误信息”非常敏感,能给到高质量的纠错反馈。但要注意必须把最大重试次数限制在2-3次,否则会让响应时间变得不可接受。

5.3 Token耗尽与上下文膨胀

上下文膨胀几乎是所有AI应用规模化的必经之痛。现象很直接:系统运行着运行着变慢了,成本越来越高,模型回答开始丢失早期对话中的关键信息。

排查时先用日志把每一轮的token消耗打出来,观察是哪些环节在吃token。大部分情况下你会发现,罪魁祸首是“历史消息重复发送”“检索结果冗余”“工具返回结果没压缩”这三件事。

我的对策是:

  • 历史消息分层:短期记忆保留原文,长期记忆压缩成摘要,每层设置上限。
  • 检索结果压缩:检索回来的文档先抽取与问题最相关的段落,再做冗余去除和重排,而不是把整篇文档塞进上下文。
  • 工具输出瘦身:工具调用结果如果是长文本,先做截断或摘要再放回上下文。这一步能极大延长可用的对话轮数。

5.4 评测集维护:标注过时与过拟合

评测体系搭起来之后,第二阶段最容易出现的问题是评测集与线上真实分布脱节。今天收集的测试用例,三个月后业务侧重点变了,测试集已经没有代表性了。这时就需要建立评测集的定期更新机制。

我的做法是每两周做一次“线上用例抽样in”的流程:从生产日志中随机抽一批新对话,人工标注后加入评测集,同时移除一部分已经失去代表性的旧用例。这是一个运营性质的工作,没有自动化捷径,但它是保证评测体系长期有效的必要成本。

另外要警惕“过拟合测评集”的问题。当一个系统的Prompt或Agent逻辑反复根据评测集调优到一定程度后,评测集上的分数会虚高,线上效果却可能原地踏步甚至下降。缓解手段是维护两个互相隔离的测试子集——一个叫“开发集”用于迭代调优,一个叫“留出集”只在发布前跑一遍。如果开发集分数一直在涨而留出集分数停滞,那就说明开始过拟合了,该停下来审查方向了。

5.5 响应延迟:多Agent系统的性能瓶颈

多Agent协同的延迟问题是把“demo能跑”变成“产品能用”的最大障碍。我观察到延迟主要消耗在三个地方:模型推理时间是天然的硬成本,多个Agent串行执行时时间叠加,还有多次模型调用之间的等待和调度开销。

解决思路从两个方向入手。一是并行化:把可以并行的子任务从串行改为并发执行,用Python的asyncio或者多线程来管理。这一步往往能直接砍掉一半以上的响应时间。另一种是减少模型调用次数:能一次推理完成的就不要拆成多次,能用规则判断的就不要调用模型。模型不是万能的,系统中尽可能把“确定性逻辑”用传统代码实现,让模型只负责真正需要语义理解的部分,这个原则能显著降低延迟和成本。

6. 从“能跑”到“好用”:我自己的一点点体会

项目做到最后,我最大的体感是:AI工程和传统软件工程在思维方式上有一个根本的差异——传统开发追求的是确定性,而AI工程必须拥抱概率性。你写的每一段代码都默认模型可能会有意外输出,你设计的每一个流程都要考虑模型调用失败时的替代路径。这种思维模式的转变,比学会某个具体框架难得多,也重要得多。

如果你正在做类似从零开始搭建AI系统的项目,我的最后一条建议是:不要追求一步到位,先做一个小而完整的东西,把链路跑通,再逐步填充能力。跟你分享一个我最早做过的蠢事——那时候我花了两周时间设计一个“完美的”多Agent架构,画了一堆架构图,结果真正的代码一行都没写,后来发现那个架构根本没法落地,推倒重来。一切架构设计都应该基于对真实运行数据的理解,而不是凭空想象。

这个项目后续还有很多扩展空间,比如把在线评测结果反馈为模型微调的训练数据、把单一Agent扩展到可插拔的工具生态、加入记忆压缩的自动策略优化等。但核心的工程底座——那些评测、循环、上下文管理、可观测性的设计——一旦打好,后续无论往上叠什么能力,都是顺水推舟的事。希望这篇文章里写下的这些实操经验,能帮你少走几步我已经踩过的弯路。

返回列表