☰
从零构建AI Agent:模型调用、工具调用与会话管理实战学习路线
2026/10/10 11:02:14 网站建设 项目流程

1. 先搞清楚:从零构建 Agent 到底在学什么

很多人一上来就问“LangChain 和 Dify 哪个好”“CrewAI 能不能直接上生产”,这就像还没学会走路就纠结跑鞋买哪个配色。我从 2023 年开始折腾 Agent,踩过的第一个大坑就是:把框架当成了学习对象,而不是工具。框架三个月换一茬,但底层那套东西——模型怎么调、工具怎么接、上下文怎么管、会话怎么续——五年都不会变。

所以这篇东西,我想按“一个合格从业者从零到能跑通一个可用 Agent”的真实顺序来拆。不是按官方文档的目录顺序,而是按你实际动手时会遇到的依赖关系来排。你如果正在搜“agent 开发学习路线”“agent 从入门到精通”“ai agent 搭建”,那这篇应该能帮你省掉至少两个月的瞎转悠。

先说结论性的学习顺序,后面每一块我都会展开讲为什么这么排:

  1. 模型调用——先能稳定地跟模型说上话,理解 token、温度、流式、错误码
  2. 提示词与结构化输出——让模型按你要的格式吐东西,这是工具调用的前提
  3. 工具调用——Agent 的手和脚,Function Calling 是核心机制
  4. 会话与记忆——单轮变多轮,Agent 才有“连续性”
  5. 上下文压缩——上下文窗口是硬约束,不会压缩的 Agent 跑不长
  6. 编排与框架——到这一步再选 LangChain、Dify、CrewAI 才有判断力
  7. 可观测与安全——上线前必须补的课,trace、权限、注入防护

这个顺序的核心逻辑是:每一层都依赖下一层的稳定。你工具调用调不通,大概率是结构化输出没做好;结构化输出不稳定,大概率是模型调用参数没吃透。倒着学,处处是坑。

提示:不要跳过第 1、2 步直接上框架。我见过太多人用 Dify 拖了个工作流,结果模型返回个 JSON 解析失败就完全不知道从哪查起。

2. 第一层:模型调用,别小看这步

2.1 为什么模型调用是地基

Agent 的本质是“让模型在循环里做决策”。所以你得先能可靠地调用模型。注意是可靠,不是能跑通 demo。可靠意味着:知道 token 怎么算、知道超时怎么处理、知道 400 和 429 分别代表什么、知道流式和非流式的取舍。

热搜里有个词叫“ai agent token 是什么意思”,这问题看着基础,但真有人答不上来。token 是模型处理文本的最小单位,中文大概 1 个字 1 到 2 个 token,英文大概 4 个字符 1 个 token。为什么要在意?因为上下文窗口是按 token 算的,计费也是按 token 算的。你一个 Agent 跑一轮,系统提示词 + 历史对话 + 工具返回结果全都要塞进上下文,token 消耗是指数级增长的。

我实测过一个简单的客服 Agent,单轮对话平均消耗 2000 token,跑到第 10 轮的时候,光历史就占了 15000 token。如果不做压缩,第 20 轮直接爆窗口。

2.2 模型调用的关键参数与踩坑

调用模型时,这几个参数你必须门儿清:

参数作用常见坑
temperature控制随机性Agent 场景建议 0~0.3,太高会导致工具调用参数乱飘
max_tokens限制输出长度设太小会截断 JSON,导致解析失败
top_p核采样和 temperature 二选一调,别同时大改
stream流式输出工具调用场景慎用,部分模型流式下 function call 会碎
timeout超时默认值往往太短,Agent 多步推理容易超时

热搜里那个“workbuddy 调用 deepseek-coder-v2:16b 错误,请切换模型或重试。400”就是典型的模型调用层问题。400 通常意味着请求本身有问题——可能是模型名写错、可能是参数不合法、可能是上下文超了。遇到这种,第一步看 trace id,第二步把请求体打出来逐字段核对,别急着换模型。

还有个热词“为什么 chatgpt 调用 deepseek-v4-flash 模型可以进行代码调整,无法实现其他任务执行”,这其实涉及模型能力边界的问题。不同模型在训练时侧重的能力不同,有的擅长代码,有的擅长对话,有的工具调用训练得充分。你选模型不能只看价格和速度,要看它在你的任务上表现如何。我的做法是:同一个任务,拿 3 个候选模型各跑 20 条测试用例,看成功率、延迟、成本三个维度,再决定用哪个。

2.3 实操:一个健壮的模型调用封装

不管你用什么语言,模型调用这层建议自己封装一层,别到处裸调。核心要处理:

  • 重试机制:429 和 5xx 要退避重试,400 不要重试
  • 超时控制:单次调用设 30~60 秒,整体 Agent 循环设总超时
  • 日志记录:请求体、响应体、耗时、token 消耗全记下来
  • 错误分类:网络错误、限流、参数错误、模型错误分开处理
import time import logging def call_model_with_retry(client, messages, max_retries=3, base_delay=1.0): for attempt in range(max_retries): try: resp = client.chat.completions.create( model="your-model", messages=messages, temperature=0.2, timeout=45 ) return resp except RateLimitError: delay = base_delay * (2 ** attempt) logging.warning(f"限流,{delay}s 后重试") time.sleep(delay) except BadRequestError as e: logging.error(f"请求错误,不重试: {e}") raise except Exception as e: logging.error(f"未知错误: {e}") if attempt == max_retries - 1: raise time.sleep(base_delay)

这段代码看着简单,但重试策略是区分新手和老手的分水岭。新手所有错误都重试,结果 400 重试三次浪费 3 秒还失败;老手只对可恢复错误重试,并且用指数退避避免打爆对方。

3. 第二层:提示词与结构化输出,工具调用的前提

3.1 为什么结构化输出这么关键

Agent 要调用工具,就得让模型输出一个机器能解析的格式,通常是 JSON。但模型天然输出的是自然语言,你要通过提示词把它“逼”成 JSON。这一步做不好,后面工具调用全是玄学。

热搜里“agent 中的分类模型”其实也是这个范畴——很多 Agent 第一步是分类,判断用户意图走哪条分支。分类的输出必须是确定的标签,不能是“我觉得可能是查询类”这种模糊表述。

结构化输出的常见方案有三种:

  1. 纯提示词约束:在 system prompt 里写“只输出 JSON,不要任何其他文字”
  2. JSON mode:部分模型支持 response_format 参数强制 JSON
  3. Function Calling:用工具定义的方式让模型输出结构化参数

我的经验是:能用 Function Calling 就别用纯提示词。因为 Function Calling 是模型层面训练过的,稳定性高一个数量级。纯提示词约束在简单场景能用,一旦嵌套复杂就容易崩。

3.2 提示词设计的几个反直觉经验

  • 少即是多:system prompt 不是越长越好。我见过有人写了 3000 字的提示词,结果模型注意力被稀释,关键指令反而没执行。核心指令放前面,细节放后面。
  • 给例子比讲道理管用:与其写“请准确提取实体”,不如给两个输入输出示例。few-shot 在结构化输出上效果拔群。
  • 负面指令要谨慎:“不要输出 markdown”这种,模型有时候反而会输出。更好的做法是正面描述你要什么。
  • 分隔符要清晰:用 ``` 或 ### 把不同部分隔开,模型解析起来更稳。

3.3 实操:一个稳定的 JSON 输出模板

你是一个信息提取助手。请从用户输入中提取以下字段,以 JSON 格式输出。 字段定义: - intent: 用户意图,只能是 [查询, 创建, 修改, 删除] 之一 - entity: 操作对象,字符串 - params: 参数字典,没有则为空对象 输出要求: 1. 只输出 JSON,不要任何解释文字 2. 不要用 markdown 代码块包裹 3. 字段缺失时用 null,不要编造 示例输入:帮我查一下北京明天的天气 示例输出:{"intent": "查询", "entity": "天气", "params": {"city": "北京", "date": "明天"}} 用户输入:{user_input}

这个模板的关键在于:字段定义明确、枚举值封闭、给了示例、明确了缺失处理。我拿这个模板测过 5 个模型,GPT 系列和 Claude 系列基本 100% 稳定,部分开源模型在复杂输入下会掉到 85% 左右,这时候就要加校验和重试。

注意:即使模型输出看起来是 JSON,也一定要用 try-catch 包住解析。我遇到过模型输出里带了个中文引号导致 json.loads 失败的,这种只能靠校验兜底。

4. 第三层:工具调用,Agent 真正的手脚

4.1 Function Calling 的工作机制

工具调用的本质是一个多轮协议:

  1. 你把工具定义(名称、描述、参数 schema)随请求发给模型
  2. 模型判断需要调用工具时,返回一个 tool_calls 结构,包含工具名和参数
  3. 你本地执行这个工具,拿到结果
  4. 你把结果作为 tool 角色的消息再发给模型
  5. 模型基于结果继续推理或再调工具

这个循环就是 Agent 的核心。理解了这个,你就理解了 80% 的 Agent 框架在干什么——它们无非是把这个循环封装得更优雅。

热搜里“agent tool agent skills”“agent harness: 驾驭 ai agent”说的都是这层。harness 这个词很形象,就是“马具”,你给模型套上工具这套马具,它才能干活。

4.2 工具设计的黄金法则

我踩过的坑总结成几条:

  • 工具粒度要适中:太细(比如 get_user_name、get_user_age 分开)会导致调用轮次爆炸;太粗(一个 do_everything)模型不知道怎么填参数。按业务动作划分最合适。
  • 描述比实现重要:模型只看描述来决定调不调、怎么调。描述里要写清楚:什么时候用、参数什么含义、返回什么。我见过工具实现完美但描述写“查询数据”四个字,结果模型从来不调。
  • 参数 schema 要严格:用 JSON Schema 定义,枚举值列全,必填项标清楚。模型对 schema 的遵循度比自然语言描述高。
  • 错误要可读:工具执行失败时,返回给模型的错误信息要能让模型理解并调整。返回“Error 500”模型一脸懵,返回“城市名不存在,请检查拼写”模型就知道换个参数重试。

4.3 实操:定义一个天气查询工具

{ "name": "get_weather", "description": "查询指定城市指定日期的天气。当用户询问天气相关问题时使用。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如北京、上海" }, "date": { "type": "string", "description": "日期,格式 YYYY-MM-DD,默认为今天" } }, "required": ["city"] } }

对应的执行逻辑:

def get_weather(city, date=None): try: result = weather_api.query(city, date) return {"status": "success", "data": result} except CityNotFound: return {"status": "error", "message": f"城市 {city} 不存在,请确认名称"} except Exception as e: return {"status": "error", "message": f"查询失败:{str(e)}"}

注意错误返回的结构——status 和 message 分开,模型能根据 status 判断要不要重试,根据 message 知道怎么改参数。这个设计细节,是我调了几十次工具调用后才悟出来的。

4.4 多工具编排的坑

当你有 5 个以上工具时,新问题来了:模型可能选错工具,或者该调 A 却调了 B。解决办法:

  • 工具描述里写清边界:“查询订单状态用这个,查询物流用那个”,别让描述重叠
  • 减少同时暴露的工具数:如果业务能分阶段,第一阶段只暴露 3 个工具,第二阶段再换一批
  • 加一层路由:先用一个分类模型判断意图,再决定给模型看哪些工具

热搜里“agent 编排 dify”说的就是这类问题。Dify 这类平台帮你把编排可视化了,但底层逻辑还是这些。

5. 第四层:会话与记忆,让 Agent 有连续性

5.1 会话恢复到底在恢复什么

热搜里“会话恢复”是个高频词。用户跟 Agent 聊到一半关掉页面,下次打开要接着聊,这就是会话恢复。听起来简单,做起来要考虑:

  • 存什么:完整消息历史?还是压缩后的摘要?还是关键实体?
  • 存哪:内存、Redis、数据库?各自的生命周期和成本不同
  • 怎么恢复:恢复后怎么让模型知道“我们之前聊过”

我的做法是分层存储:最近 5 轮完整存,5 轮以前的压缩成摘要存,关键实体(用户提到的订单号、城市名等)单独抽出来存。恢复时,摘要 + 关键实体 + 最近几轮一起塞回去。

5.2 记忆的三种类型

从业者一般把 Agent 记忆分三类:

类型内容存储方式生命周期
短期记忆当前对话历史内存/Redis会话级
长期记忆用户偏好、历史事实向量库/数据库持久
工作记忆当前任务的中间状态内存任务级

热搜里“agent 记忆”问的就是这套。很多人一上来就想做长期记忆,结果短期记忆都没管好。我的建议是:先把短期记忆做扎实,长期记忆等有明确需求再加。因为长期记忆涉及检索、去重、更新,复杂度高一个量级。

5.3 实操:会话存储的最小实现

class SessionStore: def __init__(self, redis_client): self.redis = redis_client self.ttl = 3600 * 24 # 24小时过期 def save(self, session_id, messages): # 只存最近20条,更早的压缩 recent = messages[-20:] self.redis.setex( f"session:{session_id}", self.ttl, json.dumps(recent, ensure_ascii=False) ) def load(self, session_id): data = self.redis.get(f"session:{session_id}") if not data: return [] return json.loads(data)

这个实现很粗糙,但能跑。关键点是设了 TTL,不然 Redis 会被撑爆。我见过有人不设过期,跑了两个月 Redis 内存告警。

提示:会话恢复时要注意消息顺序和角色完整性。我遇到过恢复后 messages 里 tool 消息丢了对应的 assistant tool_calls,导致模型报错。存储时要保证消息成对。

6. 第五层:上下文压缩,Agent 跑得长的关键

6.1 为什么必须压缩

上下文窗口是硬约束。GPT-4 系列 128k,Claude 系列 200k,看着很大,但 Agent 场景消耗极快:

  • system prompt:500~2000 token
  • 工具定义:每个工具 100~300 token,10 个工具就 2000+
  • 历史对话:每轮 500~2000 token
  • 工具返回:有的工具返回一大坨 JSON,几千 token 起步

一个跑 20 轮的 Agent,轻松超过 50k token。不压缩,要么爆窗口,要么成本爆炸。

6.2 压缩的几种策略

热搜里“上下文压缩”是核心词,我按效果从低到高排:

  1. 截断:直接丢最早的对话。简单粗暴,但会丢信息
  2. 摘要:用模型把早期对话总结成一段话。效果好,但要多调一次模型
  3. 关键信息抽取:只保留实体、决策、结论,丢掉寒暄和过程
  4. 向量检索:把历史存向量库,需要时检索相关片段。适合超长会话

我的实战组合是:最近 3 轮完整保留 + 更早的做摘要 + 关键实体单独维护。这样既保证近期上下文完整,又不会丢关键信息。

6.3 实操:摘要压缩的实现

def compress_history(messages, keep_recent=6): if len(messages) <= keep_recent: return messages old_messages = messages[:-keep_recent] recent_messages = messages[-keep_recent:] summary_prompt = f"""请将以下对话总结成一段简洁的摘要, 保留关键信息:用户意图、已确认的事实、待办事项。 不要遗漏任何数字和专有名词。 对话内容: {format_messages(old_messages)} """ summary = call_model(summary_prompt) compressed = [ {"role": "system", "content": f"以下是之前对话的摘要:{summary}"} ] + recent_messages return compressed

这个函数的关键参数是keep_recent。设太小,近期上下文不够;设太大,压缩效果不明显。我实测下来 6 条(3 轮)是个平衡点。当然这取决于你的单轮消息数,有的 Agent 一轮就 4 条消息(user、assistant、tool、assistant),那 keep_recent 要相应调整。

注意:摘要本身也会消耗 token,而且摘要可能丢信息。所以摘要提示词要明确要求保留数字和专有名词,这两类信息丢了最容易出问题。

7. 第六层:编排与框架,到这一步再选

7.1 主流框架的定位差异

热搜里“agent 框架如 langchain、dify、crewai 等,哪个好”是经典问题。我的回答是:看你的场景和团队。

框架定位适合场景学习成本
LangChain代码库,灵活需要深度定制高
Dify低代码平台快速验证、非技术团队低
CrewAI多 Agent 协作角色分工明确的场景中
AutoGen多 Agent 对话研究、复杂协作中高

我的建议是:先用裸代码把第 1 到 5 层跑通,再上框架。因为框架帮你封装了这些层,但你不知道它怎么封的,出问题就抓瞎。等你裸手写过一个能跑的 Agent,再看框架源码,会有“哦原来是这样”的感觉。

7.2 什么时候该上框架

判断标准很简单:

  • 你的 Agent 逻辑超过 3 个分支,手写 if-else 开始乱了 → 上编排
  • 你需要多 Agent 协作 → 上 CrewAI 或 AutoGen
  • 你要给非技术同事用 → 上 Dify
  • 你要做产品级可观测 → 上 LangSmith 或类似工具

热搜里“agent 框架与编排”“agent 架构”问的就是这层。但记住,框架是加速器不是地基。地基不牢,框架越厚塌得越狠。

7.3 多 Agent 协作的坑

如果你要上多 Agent,提前知道这几个坑:

  • 通信开销:Agent 之间传消息也是 token,3 个 Agent 互相聊,token 消耗是单 Agent 的 3 倍以上
  • 死循环:A 等 B,B 等 A,或者两个 Agent 互相客套停不下来。要设最大轮次
  • 责任不清:出错了不知道是哪个 Agent 的问题。要给每个 Agent 明确的职责边界和日志

我做过一个 3 Agent 的客服系统(接待、查询、回复),实测下来 token 成本是单 Agent 的 4 倍,延迟是 2.5 倍。所以多 Agent 不是越多越好,能单 Agent 解决就别拆。

8. 第七层:可观测与安全,上线前的必修课

8.1 可观测性:trace 是你的救命稻草

热搜里“trace id”出现好几次,说明大家都被排查问题折磨过。Agent 的可观测性比普通服务复杂,因为它是多轮循环,中间状态多。

必须记录的东西:

  • 每一轮的输入消息(完整)
  • 模型的原始输出(完整)
  • 工具调用的名称、参数、结果、耗时
  • 每一轮的 token 消耗
  • 整体耗时和总 token

有了这些,出问题才能定位。我见过最惨的情况是:Agent 偶尔返回错误答案,但没记日志,完全不知道哪轮出的问题,只能加日志重跑。

8.2 Agent 安全:不只是防注入

热搜里“agent 安全”是个大话题。我按优先级排:

  1. 提示词注入:用户输入里藏指令,让 Agent 干不该干的事。防护靠输入过滤 + 权限隔离
  2. 工具权限:Agent 能调的工具要有权限控制,删除类操作要二次确认
  3. 数据泄露:Agent 不要把敏感数据返回给不该看的用户
  4. 资源耗尽:限制最大轮次、最大 token、最大耗时,防止死循环烧钱

提示:工具权限这块,我的做法是给工具分级。查询类随便调,写入类要确认,删除类要人工审批。这个分级在工具定义里就标好,执行层强制检查。

8.3 一个真实的排查案例

有次线上 Agent 突然开始胡说八道,返回完全不相关的内容。查 trace 发现:某轮工具返回了一个超长的 JSON(几万 token),把上下文挤爆了,模型后面的推理全乱。解决办法是给工具返回加长度限制,超长的截断或摘要后再返回。

这个坑让我明白:工具返回也是上下文的一部分,必须像管理对话历史一样管理它。后来我在工具执行层加了个统一的返回处理器,超过 2000 token 的结果自动摘要。

9. 学习路线的常见误区与我的建议

9.1 几个典型误区

  • 误区一:先学框架再学原理。结果框架一升级就懵,因为不知道底层变了什么。
  • 误区二:追求最新模型。模型迭代快,但调用方式、工具协议这些是稳定的。把稳定的学扎实,换模型只是改个名字。
  • 误区三:忽视工程化。demo 能跑和线上能跑是两回事。重试、超时、日志、监控,一个都不能少。
  • 误区四:一上来就多 Agent。单 Agent 都没调明白,多 Agent 只会让问题指数级复杂。

9.2 我的建议路线

如果你现在从零开始,我建议这样安排时间:

  • 第 1 周:模型调用 + 结构化输出,写 20 个测试用例跑通
  • 第 2 周:工具调用,定义 3 个工具,跑通完整循环
  • 第 3 周:会话管理 + 上下文压缩,让 Agent 能跑 20 轮不崩
  • 第 4 周:选一个框架重写一遍,对比手写和框架的差异
  • 第 5 周起:加可观测、加安全、做压测,准备上线

这个节奏不算快,但每一步都扎实。我见过太多人一周就“学会”了 Agent,结果遇到问题完全不知道从哪查。

9.3 关于面试的准备

热搜里“agent 面试题”“agent 八股文”说明大家在准备面试。我的看法是:Agent 面试考的不是背概念,是排查问题的思路。常见问题:

  • 工具调用失败怎么排查?→ 看 trace,看模型输出,看参数 schema
  • 上下文超了怎么办?→ 压缩策略,分层存储
  • Agent 死循环怎么防?→ 最大轮次 + 超时 + 循环检测
  • 怎么评估 Agent 效果?→ 测试集 + 成功率 + 人工抽检

这些问题没有标准答案,考的是你有没有真跑过、真踩过坑。

10. 一些零散但重要的实操心得

最后分享几个我在实际项目里攒下的小经验,都是文档里不会写的:

关于模型选择:别迷信榜单。同一个模型在不同任务上表现差异巨大。我的做法是建一个 50 条的测试集,覆盖你的核心场景,每次换模型都跑一遍。这个测试集比任何榜单都准。

关于成本控制:Agent 的 token 消耗是普通对话的 5 到 10 倍。上线前一定要算清楚:单次会话平均多少 token,日活多少,乘起来就是日成本。我见过没算成本就上线的,第一个月账单出来直接傻眼。

关于延迟:Agent 多轮循环,延迟是累加的。单轮 2 秒,5 轮就 10 秒。用户体验上,超过 5 秒就要考虑流式输出或者异步处理。我的做法是首轮快速响应,后续轮次用流式,让用户感觉“它在想”。

关于测试:Agent 的测试比普通服务难,因为输出不确定。我的做法是断言关键字段而不是完整输出。比如断言“返回了 JSON”“intent 字段是查询”“调用了 get_weather 工具”,而不是断言完整字符串。

关于版本管理:提示词、工具定义、模型版本,这三个任何一个变了,Agent 行为都可能变。所以每次变更都要记录,并且跑回归测试。我吃过亏:改了一句提示词,结果某个边缘场景挂了,一周后才发现。

关于用户预期:Agent 不是万能的,要管理用户预期。我的做法是在 UI 上明确告诉用户“我能做什么”,并且在 Agent 不确定时主动说“我不太确定,要不要转人工”。这比硬答一个错误答案体验好得多。

这个领域变化快,但底层那套东西变化慢。把模型调用、工具调用、会话管理、上下文压缩这四样吃透,上面换什么框架、换什么模型,你都能快速上手。反过来,只追新框架不练基本功,风一吹就倒。

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

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

立即咨询