做 AI Agent 开发,很多人第一周做的事是到处收集提示词模板,第二周开始尝试让大模型调用外部工具,第三周才发现真正难的既不是提示词,也不是 API 调用,而是把“模型决策、工具执行、状态记忆、结果验证”这四个环节正确编排起来。市面上的智能体教程动辄几百集,但多数内容停留在概念科普和平台演示。如果已经具备 Python 基础,想真正动手写一个属于自己的智能体,最优路径不是从头刷完所有视频,而是先理解 Agent 的工作原理,再跑通一个最小可运行案例,最后再决定要不要引入 Dify、LangGraph、Hermes 这类框架。
这篇文章会沿着这条主线展开:先解释 AI Agent 的本质和 ReAct 推理循环,再给出 7 天学习路径和环境准备步骤,然后手写一个不依赖任何框架的最小 ReAct Agent,接着讨论工具调用机制,最后对比 Dify、Coze、LangGraph、Hermes 等方案的选型思路,并补充常见的报错排查路径、安全防护清单和生产落地建议。读完以后,你可以独立完成一个“大模型 + 工具 + 记忆 + 循环”的完整闭环,而不是只会拖拽节点或者套模板。
1. 智能体到底是什么:先把“模型能力”和“智能体能力”拆开
1.1 用一句话说清 Agent
大模型本身是一个“凭上下文预测下一个 Token”的推理引擎。它很强,但它不会主动去查数据库、不会调接口、不会记住上一次用户的偏好,也不会在调用外部服务失败后自动换一条路径。Agent(智能体)的本质,是在大模型外面包一层“目标执行闭环”:
收到任务 -> 规划 -> 调用工具 -> 观察结果 -> 再次决策 -> 输出答案或继续执行
换句话说,Agent 的核心不是模型,而是“模型 + 工具 + 循环控制”三者结合起来的执行框架。用户问“帮我订明天上午从北京到上海的高铁”,普通聊天机器人只能给出“建议去 12306 查询”这种回答;而 Agent 会尝试查询余票、比对车次、确认乘客信息、调用订票接口,最后把结果反馈给用户。模型负责判断每一个步骤该做什么,工具负责真正完成动作,循环负责判断任务是否结束。
很多人把 Agent 和大模型当成一回事,这是一个容易踩的误区。大模型是大脑,Agent 是“有手有脚有工具的大脑”。
1.2 一个智能体必须包含的四个组件
- 大脑:LLM 模型,负责任务拆解、决策、结果生成。目前常见的有 GPT、Claude、Qwen、DeepSeek、Llama 等,选型时要关注上下文长度、工具调用能力和价格。
- 工具:模型可以调用的外部能力,比如天气 API、计算器、数据库查询、搜索接口、内部业务系统、OCR、爬虫等。
- 记忆:短期记忆是当前会话里的上下文,长期记忆一般落库到向量数据库或关系型数据库,让 Agent 能跨会话记住用户信息和历史偏好。
- 编排逻辑:决定调用哪个模型、调用哪个工具、怎么拼接消息、遇到超时或错误怎么处理、最大循环多少轮、什么条件下必须终止。
四者缺一不可。如果只做“模型 + 提示词”,那不叫 Agent,只是一个更聪明的聊天框。只有把工具执行和循环控制加进来,模型才具备“行动能力”。
1.3 ReAct 推理循环是绕不开的模式
ReAct 是 Reasoning + Acting 的组合,意思是让模型一边推理一边行动。它是最常用的 Agent 工作模式,核心过程可以描述为:
- 用户输入一个问题。
- 模型根据自己的知识和当前可用工具,先思考“解决这个问题需要什么步骤”。
- 如果需要外部信息,模型输出一个“工具调用请求”,指定调用哪个工具、传什么参数。
- 程序真正执行这个工具,拿到返回结果。
- 把工具返回结果作为新消息回传给模型。
- 模型观察结果,判断任务是否完成。
- 如果没完成,继续循环;如果完成了,输出最终答案。
用现实中的助理来类比:领导让你准备一份竞品分析报告。你先查资料、再整理、发现数据不够、继续查、补充图表、最后输出报告。你不是一次性写完的,而是“搜索-观察-修正-再搜索”循环推进。ReAct 就是把这个过程程序化。
在这个模式下,模型不直接操作外部系统,它只会“描述意图”。真正执行工具的是你写的代码,这一点很关键。
2. 学习 AI Agent 的正确顺序:不要一上来就学框架
2.1 为什么不要直接学 LangGraph 或 Dify
很多初学者一上来就打开 LangGraph 文档或 Dify 拖拽界面,结果发现:节点概念懂,配置也能配,但一旦报错就完全不知道问题出在哪。因为框架把底层逻辑封装得太好,你看到的只是表层。
比如你在 Dify 里拖了一个“知识检索”节点,又拖了一个“LLM”节点,它们之间的消息传递是靠框架自动拼装的。一旦检索结果没有正确传给模型,你只能看到最终回答不符合预期,却说不清楚是工具描述写得不好、还是变量名写错了、还是模型没有触发工具调用。
正确的顺序是:先把裸循环跑通,再用框架去减少重复劳动。手写一遍最小 ReAct Agent 之后,你再看 LangGraph 的 StateGraph、Dify 的 Workflow,就会觉得这些概念非常直观,因为它们只是把你手写逻辑图形化、组件化了。
2.2 7 天学习路径表
用 7 天时间从零到能独立开发一个可用的 Agent,是可行的,前提是每天目标明确。下面这个路线可以作为参考:
| 天数 | 学习目标 | 核心内容 | 可交付成果 | 检验标准 |
|---|---|---|---|---|
| Day 1 | 理解 LLM 与 Agent 的边界 | Token、上下文、system prompt、工具调用 | 写一篇笔记说明 Agent 的四个组件 | 能用自己的话讲清楚“模型和工具的关系” |
| Day 2 | 掌握 OpenAI 兼容接口调用 | 请求结构、流式与非流式、错误处理 | 一个最简对话脚本 | 能打印出模型的完整返回 JSON |
| Day 3 | 实现最小 ReAct 循环 | 工具定义、tool_calls 解析、消息回传 | 一个包含 2 个工具的 Agent 脚本 | 支持多轮工具调用并最终输出答案 |
| Day 4 | 处理记忆与多轮会话 | 上下文拼接、历史消息管理 | 给 Agent 增加短期记忆 | 新问题能引用上一轮内容 |
| Day 5 | 了解主流框架 | Dify、Coze、LangGraph、Hermes | 对比框架差异 | 能说清什么时候该用框架 |
| Day 6 | 完成一个垂直场景 Agent | 比如销售咨询助手、天气助手 | 一个带工具链的完整 Agent | 对 10 条真实问题有稳定回答 |
| Day 7 | 部署和排查 | 日志、接口服务化、失败回退 | 用 FastAPI 包装 Agent | 能通过 HTTP 访问并记录日志 |
2.3 前置知识清单
开始前,建议确认自己具备这些基础:
- Python 基础语法:函数、类、异常处理、装饰器不要求精通,但至少写过一段完整脚本。
- HTTP 与 API 调用:理解 GET、POST,会使用 requests 或 httpx。
- JSON 基础:能读懂嵌套 JSON,并完成提取和赋值。
- 提示词基础:知道 system、user、assistant 三种角色的区别,理解 few-shot 的基本写法。
- 命令行基础:会创建虚拟环境、安装依赖、设置环境变量。
这些条件只要覆盖 60% 就可以开始。剩下的内容可以在写代码过程中边写边查。
3. 环境准备:本地模型、Python 虚拟环境和最小依赖
3.1 学习环境建议
学习阶段推荐在本地完成,省去云端费用,同时便于观察请求和响应的完整结构。环境要求如下:
| 依赖项 | 推荐版本或配置 | 说明 |
|---|---|---|
| 操作系统 | Windows 10/11、macOS、Linux 均可 | 尽量使用 64 位系统 |
| Python | 3.10 或 3.11 | 3.12 部分依赖可能尚未完全兼容,优先 3.11 |
| 模型服务 | OpenAI 兼容接口或 Ollama | 本地推荐 Ollama,云端推荐使用兼容接口 |
| 代码编辑器 | VS Code 或 PyCharm | 关键是能方便调试 Python |
| 包管理 | pip + venv | 不要直接安装在全局环境 |
如果本机显存不足,也可以直接调用云厂商公网 API。为了便于调试,建议先使用本地模型,比如通过 Ollama 运行参数较小的模型,跑通后再切换云端大模型。
3.2 创建虚拟环境并安装依赖
以 Windows 和 macOS 通用的方式为例:
mkdir ai-agent-demo cd ai-agent-demo python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活虚拟环境后,安装依赖:
pip install openai python-dotenv需要说明的是,这里的 openai 库不仅支持 OpenAI 官方接口,也支持其他提供“OpenAI 兼容接口”的模型服务。后续代码只依赖它,不引入其他重量级框架。
3.3 模型配置方式
把模型配置放到环境变量或 .env 文件中,不要写死在代码里。在项目根目录创建 .env 文件:
API_KEY=sk-xxxx BASE_URL=http://127.0.0.1:11434/v1 MODEL_NAME=qwen2.5:7b使用 Python 读取:
import os from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("API_KEY") BASE_URL = os.getenv("BASE_URL") MODEL_NAME = os.getenv("MODEL_NAME")如果是本地 Ollama,Ollama 的 OpenAI 兼容地址默认是http://127.0.0.1:11434/v1,API Key 可以随便填一个非空字符串。如果是云端服务,请按服务商提供的地址和密钥填写。
3.4 环境检查清单
在写业务代码前,先确认环境是否可用。这是很容易被跳过的步骤,但能省下大量排查时间:
python --version能正常输出且版本不低于 3.10。ollama list能看到已下载模型;如果没有,先执行ollama pull qwen2.5:7b下载。- 用一行脚本请求模型接口,确认能拿到非空的回答。
.env文件已经创建,并且python-dotenv安装成功。
4. 动手实现一个最小 ReAct Agent:不借助任何框架
4.1 定义两个基础工具
为了让示例足够简单又可运行,这里定义两个工具:一个是获取当前日期时间,一个是计算数学表达式。
import datetime import json def get_datetime(): """返回当前日期和时间""" now = datetime.datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") def calculator(expression: str): """计算简单数学表达式,例如 '1 + 2'""" try: result = eval(expression, {"__builtins__": {}}, {}) return str(result) except Exception as e: return f"计算出错: {e}"注意:这里的calculator使用eval,仅用于学习演示。生产环境绝对不要这样写,否则会引入严重的安全风险。后面会专门说明正确的工具设计方式。
4.2 构造工具说明列表
模型需要知道“有哪些工具”“每个工具是干什么的”“参数长什么样”,因此要维护一个 JSON 结构的工具说明列表:
tools = [ { "type": "function", "function": { "name": "get_datetime", "description": "获取当前日期和时间", "parameters": { "type": "object", "properties": {}, "required": [] } } }, { "type": "function", "function": { "name": "calculator", "description": "计算数学表达式,例如 '17 * 9'", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "要计算的数学表达式" } }, "required": ["expression"] } } } ]4.3 写核心循环
核心循环可以分为三步:调用模型、判断是否有工具调用、执行工具并回传结果。这里给出完整脚本,注意先阅读注释理解流程。
from openai import OpenAI from dotenv import load_dotenv import os import json load_dotenv() client = OpenAI( api_key=os.getenv("API_KEY"), base_url=os.getenv("BASE_URL"), ) # tools 列表见上方,略 def run_agent(user_query: str, max_steps: int = 8): messages = [ { "role": "system", "content": "你是一个智能体。当需要调用工具时,必须返回工具调用请求。" "工具调用结束后,你会收到工具返回结果。" "如果不需要调用工具,直接回答用户。" }, {"role": "user", "content": user_query} ] for step in range(max_steps): print(f"\n--- Step {step + 1} ---") response = client.chat.completions.create( model=os.getenv("MODEL_NAME"), messages=messages, tools=tools, temperature=0, ) message = response.choices[0].message # 如果没有工具调用,说明模型已经给出最终答案,直接结束 if not message.tool_calls: print("最终回答:", message.content) return message.content # 将 assistant 消息加入 messages,模型工具调用的上下文必须原样回传 messages.append(message) # 执行每一个工具调用 for tool_call in message.tool_calls: function_name = tool_call.function.name arguments = tool_call.function.arguments args = json.loads(arguments) if arguments else {} print(f"调用工具: {function_name}, 参数: {args}") if function_name == "get_datetime": result = get_datetime() elif function_name == "calculator": result = calculator(**args) else: result = f"未知工具: {function_name}" # 工具返回结果必须使用 role=tool 的消息 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result, }) # 超出最大步数时给出提示,避免死循环 print("达到最大步数,强制退出") return None if __name__ == "__main__": print(run_agent("现在几点了?17 乘以 9 等于多少?"))这就是一个最小可运行的 ReAct Agent。整个逻辑不依赖任何 Agent 框架,只依赖模型的工具调用能力和你自己写的循环控制。
4.4 这个最小实现最值得研究的几个细节
第一,为什么模型会返回tool_calls?因为请求里传了验证过的工具列表,模型经过训练后,会在觉得需要外部信息时输出结构化的工具调用请求,而不是普通文本。
第二,为什么要把 assistant 消息原样回传?因为模型需要知道“自己上次说了什么、请求调用哪个工具”,这部分上下文不能丢失。只回传一行“你调用了工具”是不够的,必须保留完整的tool_call结构和 ID。
第三,为什么要循环而不是一次调用完成?因为一个任务可能需要多个工具配合。比如先查当前时间,再根据时间计算一个表达式,整个过程可能需要两三轮才能完成。
第四,为什么要设置最大步数?因为模型可能反复调用同一个工具、输出循环或陷入死循环。max_steps是最后一道保险,学习阶段设置成 6 到 10 即可。
5. 工具调用与提示词编排:Agent 能落地的关键
5.1 函数调用(Function Calling)的请求与响应结构
上面代码里,tools参数是关键。它告诉模型“你可以调用这些函数”,但模型本身不会执行任何函数。真正执行函数的是你的代码,执行完再把结果通过role=tool的消息回传。这个过程通常称为函数调用或工具调用。
一次典型交互分为三段:
第一段,用户请求:“现在几点了?17 乘以 9 等于多少?”
第二段,模型返回一个 tool_calls 结构,包含函数名和 JSON 参数。
{ "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_datetime", "arguments": "{}" } }, { "id": "call_abc456", "type": "function", "function": { "name": "calculator", "arguments": "{\"expression\": \"17 * 9\"}" } } ] }第三段,你的代码执行函数,返回结果。
{ "role": "tool", "tool_call_id": "call_abc123", "content": "2026-03-12 14:30:00" }随后再次调用模型,模型会综合这些信息生成最终回答。
理解这个三段式结构,是排查 Agent 问题的核心。很多模型回答异常,本质上是 messages 中的角色、tool_call_id 或 content 拼接不正确。
5.2 对大模型的约束应该放在 System Prompt 还是函数定义中
System Prompt 适合放全局行为约束,比如“不要编造工具返回值”“如果工具报错,请直接说明错误”“回答要简洁”。而工具描述应该写到函数定义里,因为模型会根据description判断何时调用该工具。
推荐的写法:
system: 你是智能体。你需要根据用户问题判断是否调用工具。所有工具返回结果都以事实为准。 如果工具调用失败,不要猜测,直接把错误信息告诉用户。 function description: 计算数学表达式,例如 '17 * 9'。参数 expression 是一个字符串形式的表达式。不要写“你一定要用这个工具”“绝不能直接回答”,模型并不严格理解感叹号和绝对化表达,清晰的描述反而更有效。
5.3 工具设计规范
工具设计质量直接决定 Agent 能否稳定工作。常见规范包括:
- 工具粒度适中:一个工具只做一件事。比如“查询天气”和“发送天气通知”不要合并成一个工具。
- 输入一定要校验:模型传参可能出错,比如把字符串传给数字参数。工具内部要防御性编码。
- 返回值要结构化:优先返回 JSON 或固定格式字符串,方便模型理解。
- 错误要显式返回:工具执行失败时,返回的结果里必须包含错误原因,而不要抛异常中断整个程序。
- 敏感操作要二次确认:扣款、发消息、删除类操作,必须有人工确认节点。
- 尽可能幂等:比如“关闭工单”重复调用同一个工单 ID 时,不应该产生新的副作用。
5.4 为什么不要让模型输出 JSON 再手动解析
有些教程会让你写:“如果模型认为需要查天气,请输出 {tool: 'weather', city: '北京'},然后我的代码用 json.loads 解析。”这种方式学习可以,但生产不建议。
原因是模型直接输出自然语言 JSON 时格式不稳定,可能出现换行转义、字段缺失、中文标点等问题。函数调用机制由模型厂商在训练阶段做了对齐,输出稳定性更高,并且自带 tool_call_id,方便消息回传。
如果使用的是不支持函数调用的模型,才需要退回到“提示词约束 + 正则/json 解析”的折中方案。
6. 从手写循环到框架:Dify、Coze、LangGraph、Hermes 怎么选
6.1 什么时候该用框架,什么时候不需要
手写 ReAct 的最大好处是原理透明,任何环节出错都能定位。但缺点是随着业务复杂,代码会越来越长:要处理记忆、多用户会话、并发、日志、权限、人审、监控。此时引入框架能显著减少重复工作。
判断标准可以这样看:
| 场景 | 是否推荐用框架 | 推荐方案 |
|---|---|---|
| 学习原理、毕业设计、原型验证 | 不推荐 | 手写 Python |
| 可视化工作流、快速搭建内部工具 | 推荐 | Dify |
| 无代码/低代码搭建业务 Bot | 推荐 | Coze 这类平台 |
| 复杂图状态、多分支编排、需要持久化和回放 | 推荐 | LangGraph |
| 想基于开源项目二次开发,快速获得管理端 | 推荐 | 开源 Agent 项目,例如 Hermes 类项目 |
6.2 主流方案对比
| 维度 | 手写 Python | Dify | Coze | LangGraph |
|---|---|---|---|---|
| 上手难度 | 中等 | 低 | 很低 | 较高 |
| 可视化编排 | 无 | 有 | 有 | 弱 |
| 可控性 | 最高 | 中 | 低 | 高 |
| 知识库支持 | 需自行实现 | 内置 | 内置 | 需自行集成 |
| 记忆 | 需自行设计 | 内置 | 内置 | 需自行处理 |
| 生产级能力 | 全部自己搭 | 中到高 | 中 | 高 |
| 适合人群 | 想深入理解原理 | 快速交付 | 业务人员/原型 | 需要复杂状态编排 |
注意:Coze 在不同地区提供的产品能力可能不一样,实际使用时以官方文档为准。
6.3 Hermes 这类开源项目在 Windows 上部署要注意什么
网络上经常能看到“在 Windows 上部署 Hermes 智能体”的提问。这里不针对某一个项目做具体安装教学,而是给出通用判断思路:开源智能体项目到了生产环境,本质上是一个 Web 服务或 API 服务,部署时关注的是依赖、数据库、端口、环境变量四个问题。
在 Windows 上部署这类项目时,推荐顺序如下:
- 优先使用 WSL2 + Docker Desktop 运行,而不是直接裸跑 Python 脚本。原因是开源项目依赖较多,常见的问题比如某个底层库不支持 Windows、端口被占用、环境变量加载路径不同,在 Docker 容器里都能减少环境差异。
- 查看项目文档中的 Docker 部署方式,是否有
docker-compose.yml。如果有,修改.env文件中的数据库连接、端口映射和密钥。 - 如果项目明确原生支持 Windows,再考虑直接使用 Python 虚拟环境安装。
- 不要把
.env文件提交到 Git 仓库,里面通常包含 API Key、数据库密码等敏感信息。 - 学习阶段使用项目内置的 SQLite 即可,不要一开始就切换到 PostgreSQL,除非官方要求。
如果遇到启动失败,先看日志。常见的失败原因是数据库未启动、依赖版本不匹配、环境变量缺失,真正复杂的逻辑问题反而少见。
6.4 自建 Agent 最小的生产组件
无论是否使用框架,进入生产环境前至少要有这些组件:
- 对外 API 服务:把 Agent 包装成 HTTP 接口,例如用 FastAPI。
- 请求去重和幂等:同一个用户重试请求时,不应重复扣费或重复发消息。
- 结构化日志:记录请求 ID、模型名、工具名、耗时、异常、退出原因。
- 权限控制:谁可以调用哪些工具,必须有一个白名单或角色体系。
- 敏感操作人审:删除类、支付类、发送类操作需要人工确认。
- 失败回退:模型超时、工具异常时,用户得到的是明确提示,而不是空白页面。
7. 运行验证、日志分析和常见报错
7.1 用三组 Case 验证 Agent 是否真的可用
写完成一个 Agent 后,不能只看“能跑通”就结束,应该系统性验证。推荐从三组 case 开始。
Case 1:多步工具调用。提问“现在几点了?17 乘以 9 等于多少”。预期现象是:模型先调用get_datetime,再调用calculator,最后综合结果回答。如果模型一次调用就能同时执行两个工具,取决于模型能力和函数定义,都属于正常表现。
Case 2:无关闲聊。提问“你好,你是谁”。预期现象是:模型不调用任何工具,直接生成回答。如果模型仍然强行调用工具,说明工具描述写得过宽,或者 system prompt 把它逼得太紧。
Case 3:工具异常。提问“计算 1 除以 0”。预期现象是:工具内部返回“计算出错”,模型根据观察结果告诉用户“除数为零,无法计算”。如果模型忽略工具返回结果,硬编一个错误答案,说明消息回传或观察能力有问题。
三组 case 分别覆盖“正常工具调用”“无需工具调用”“工具异常处理”,是 Agent 最基础的稳定性测试。
7.2 排查链路:从现象倒推原因
出现 Agent 行为异常时,按下面顺序排查:
- 模型是否触发了工具调用?如果没有,优先检查
tools参数是否传入、工具描述是否与用户问题匹配、temperature是否过高。 - 工具是否执行成功?自己手动运行一次工具函数,确认入参解析是否正确、函数内部有没有异常被吞掉。
- 消息回传是否正确?确认
tool_call_id是否匹配,assistant 消息是否原样保留,role是否为tool。 - 循环是否正常退出?确认是否达到
max_steps,日志里是否反复调用同一个工具。 - 最终回答是否准确?检查模型是否把工具观察结果纳入判断,还是凭空猜测。
7.3 常见错误现象表
| 现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 模型不调用工具,直接给出猜测答案 | tools 参数未传、描述不够清晰、模型版本不支持函数调用 | 打印请求参数,确认 tools 存在;查看模型文档 | 选用支持函数调用的模型,优化工具描述 |
| 工具执行报错,但模型还在编答案 | 异常被吞掉、工具结果未回传 | 检查 tool message 是否真的 append 到 messages | 工具函数内部捕获异常并返回错误描述 |
| 同一工具被反复调用,陷入循环 | 工具返回结果没有让模型确认“成功”,或结果格式不清 | 查看日志里每轮 observation 内容 | 增加 max_steps,让工具返回结果更明确 |
| 模型报错,提示 tool_call_id 不匹配 | assistant 消息没有原样回传 | 检查代码是否重新构造了 assistant 消息 | 直接 append 原始 message 对象 |
| 中文 JSON 参数解析失败 | 模型返回了中文标点或格式不规范 | 打印原始 function.arguments | 使用支持函数调用的模型;若不支持,加强提示词约束 |
7.4 结构化日志建议
生产环境建议按 JSON 输出结构化日志,方便接入日志平台:
{ "request_id": "req_001", "user_id": "u_001", "model": "qwen2.5:7b", "input_tokens": 128, "output_tokens": 64, "tool": "calculator", "tool_duration_ms": 12, "status": "success", "finish_reason": "stop" }日志至少要覆盖:什么时候用户发起了请求、模型输出什么决策、工具执行多久、结果是成功还是失败、最终以什么原因退出。缺少日志的 Agent 项目,进入生产后排查问题会非常痛苦。
8. 生产落地要做到的防护和优化
8.1 工具权限与人审机制
工具权限是整个 Agent 系统安全性的核心。模型能调用的能力越多,风险越大。常用策略是分级:
黄色工具:只读类操作,如查天气、查数据库视图、搜索文档,允许模型自主调用。红色工具:有副作用的操作,如发送邮件、修改订单状态、删除数据,必须经过用户确认或人工审批。黑色工具:涉及资金、账号、隐私的操作,一般不允许模型直接调用,只能通过专用网关处理。
实现时可以在工具描述里注明“此操作需要用户确认”,也可以在代码层拦截:模型返回工具调用请求后,先进入确认节点,用户点确认再执行。
8.2 记忆管理的两种思路
短期记忆就是当前 messages 列表。随着对话变长,token 会越来越多,需要做裁剪:保留 system 消息、最近 N 轮对话,把更早的内容压缩成摘要。
长期记忆一般依赖外部存储。把用户的历史意图、偏好、重要事实提取为结构化记录,存到关系型数据库或向量库。下次对话时先检索相关记忆,再注入 system prompt。
生产项目不要把所有历史全部塞给模型,成本高且噪音大。先聚合提炼,再按需注入。
8.3 成本与延迟控制
- 设置最大工具调用次数,避免模型陷入循环造成大量消耗。
- 对工具结果做缓存,比如天气、股票、汇率这类数据可以缓存 5 到 15 分钟。
- 使用路由策略:简单问题走便宜小模型,复杂任务才调用大模型。
- 对长文本结果做截断,工具返回内容超过阈值时只保留关键部分。
- 记录每个请求的 token 消耗,设置每日账单告警。
8.4 发布前检查清单
| 检查项 | 确认内容 |
|---|---|
| 工具安全 | 是否有 eval 式危险函数;是否校验输入 |
| 敏感操作 | 删除、支付、发送类操作是否有人审 |
| 最大步数 | 是否设置 max_steps,防止死循环 |
| 消息结构 | assistant、tool 消息是否按规范回传 |
| 日志 | 是否记录 request_id、工具名、耗时、异常 |
| 配置外置 | API Key、模型名是否放到 .env,不硬编码 |
| 异常兜底 | 模型超时、工具异常时用户是否能看到明确提示 |
| 成本控制 | 是否有 token 消耗监控和每日告警 |
9. 本文最重要的一条实践建议
AI Agent 开发的关键不在“会用哪个框架”,而在于能不能把“模型决策、工具执行、结果验证、循环终止”这条链路控制好。如果你只能记住一件事,那就是:先把最小的 ReAct Agent 跑通,再往里面加工具、加记忆、加框架。Dify、Coze、LangGraph、Hermes 都只是工具,理解原理后学起来很快;不理解原理,换再多框架也解决不了 Agent 回答不稳定、工具调用失败、循环失控这些核心问题。
下一步可以这样做:把本文第 4 节的脚本完整运行一遍,然后替换成两个自己的工具。比如一个查汇率的工具、一个查本机文件列表的工具,然后尝试让 Agent 完成“查询汇率后计算 100 美元能换多少人民币”这类复合任务。跑通之后,再考虑引入 FastAPI 包装成 HTTP 服务,逐步补齐日志、权限和人审,就是一条从学习到生产落地的完整路径。