☰
AI Agent从零搭建指南:手写循环、工具设计与循环控制实战
2026/9/29 7:03:41 网站建设 项目流程

1. 为什么现在值得花时间搞懂 AI Agent

过去大半年,我身边不少做后端、做数据、甚至做产品的朋友都在问同一个问题:AI Agent 到底是个什么东西,跟我之前调个大模型 API 有啥本质区别?我一般不会先讲概念,而是直接反问一句:你有没有遇到过那种“一次性对话搞不定、需要来回好几步、中间还得查资料调工具”的活儿?如果有,那你就已经站在 Agent 的门口了。

先把话说透:AI Agent 不是某个具体产品,也不是某个模型,而是一套“让模型自己决定下一步做什么”的运行机制。普通的大模型调用,是你问一句它答一句,边界清清楚楚;而 Agent 的核心在于,它拿到一个目标之后,会自己拆解任务、自己选择工具、自己判断结果够不够好、不够好就再来一轮。这个“自己”不是玄学,背后是一套可拆解、可实现、可调试的工程结构。

我写这篇东西的出发点很实在:网上讲 Agent 的文章,要么停留在“未来已来”的宏大叙事,要么一上来就是一堆框架名词把人劝退。但真正想动手的人需要的,是从 0 到 1 搭一个能跑起来的最小 Agent,然后在这个骨架上一点点加东西。所以这篇内容适合三类人:完全没接触过 Agent 但想搞明白它到底怎么运转的初学者;已经会调模型 API、想进一步做点“能干活”的东西的开发者;以及想评估 Agent 到底能用在哪些业务场景里的产品和技术负责人。

我会按“先想清楚为什么这么设计,再动手实现,最后踩坑排查”的顺序来讲,中间会给出可以直接抄的代码结构、参数选择的计算逻辑,以及我自己在实操中踩过的坑。你不需要提前懂什么框架,只要会一点 Python、知道怎么调用模型接口,就能跟着走完。

2. 先把 AI Agent 的骨架想明白再动手

2.1 Agent 和普通大模型调用的本质区别

很多人第一次接触 Agent,会以为它是“更聪明的模型”。这个理解方向就偏了。模型本身没变,变的是模型外面套的那层循环和工具。我用一个生活化的类比来说明:普通调用大模型,就像你去餐厅点菜,你说“来个宫保鸡丁”,厨房做好端上来,结束。而 Agent 更像你雇了一个助理,你说“帮我安排一顿适合四个人的晚饭”,助理会自己想:四个人得几个菜、有没有人忌口、预算多少、要不要订位,然后一步步去执行,中间可能还要回来问你一句。

落到工程上,这个区别体现在三个地方。第一是目标驱动,你给的不是一条指令,而是一个目标,Agent 需要把目标拆成可执行的步骤。第二是工具调用,Agent 不能只靠“嘴”回答,它得能真的去查数据库、调接口、读文件、算数。第三是循环与反馈,做完一步要看结果,结果不对就调整,直到满足终止条件。

这里有个关键认知:Agent 的能力上限,很大程度上不取决于模型多强,而取决于你给它的工具和约束设计得好不好。我见过太多人一上来就换更大的模型,结果效果提升有限,反而是把工具描述写清楚、把终止条件收紧之后,整个 Agent 的稳定性上了一个台阶。

2.2 一个最小 Agent 需要哪几个核心部件

把花哨的东西全剥掉,一个能跑的最小 Agent 其实就四个部件,我用一张表把它们和职责列清楚:

部件职责缺了会怎样
目标与指令告诉 Agent 要达成什么、边界在哪Agent 漫无目的,容易跑偏
模型推理决定下一步做什么、调用哪个工具没有决策能力,退化成固定流程
工具集真正执行动作(查询、计算、写文件等)只能空谈,干不了实事
循环控制判断是否继续、何时终止要么死循环,要么一步就停

这四个部件里,最容易被低估的是循环控制。新手往往把注意力全放在“怎么让模型更聪明”上,结果 Agent 要么陷入无限循环烧钱,要么做了一步就草草收场。我的经验是,循环控制的设计优先级应该排在模型选型之前,因为它直接决定了你的 Agent 是“能用”还是“能用得起”。

2.3 为什么我建议从“手写循环”开始而不是直接上框架

现在市面上 Agent 框架不少,功能也很全,但我强烈建议第一次搭 Agent 的人先手写一遍最朴素的循环,哪怕只有几十行。原因很简单:框架帮你封装了循环、工具调用、状态管理,你用它跑通了,但你不清楚里面发生了什么。一旦出问题,你连从哪查都不知道。

手写一遍的好处是,你会亲眼看到“模型返回了一个工具调用请求,我解析它,执行工具,把结果塞回对话历史,再问模型”这个完整链路。这个过程走通一次,你对 Agent 的理解就从“听说”变成了“我知道它每一步在干嘛”。之后再上框架,你就能判断框架到底帮你省了什么、又在哪些地方限制了你。

我自己的路径就是这样:先用最原始的方式写了一个只会做加法和查天气的 Agent,跑通之后才去看框架文档,那时候看什么都觉得顺,因为每个概念我都能对应到自己手写的那段代码上。

3. 核心细节拆解:工具、提示词与循环控制

3.1 工具设计:Agent 的手和脚怎么定义

工具是 Agent 真正干活的地方,也是整个系统里最需要花心思的部分。一个工具本质上就是一个函数,加上一段给模型看的描述。模型根据描述来判断“这个任务该不该用这个工具”。所以工具描述写得好不好,直接决定模型会不会正确使用它。

我踩过的第一个坑就是工具描述写得太随意。当时我写了一个查询订单状态的工具,描述就一句“查询订单”。结果模型经常在该用的时候不用,在不该用的时候乱用。后来我把描述改成“根据订单号查询订单的当前状态,包括已支付、已发货、已签收;输入必须是订单号字符串”,命中率立刻上来了。这个细节看起来小,但它是 Agent 稳定性的地基。

工具设计有几个实操原则。第一,一个工具只做一件事,不要把“查询并修改”塞进一个工具,模型会分不清什么时候该调。第二,参数类型要明确,是字符串还是数字、必填还是可选,都要在描述里说清楚,否则模型传参经常出错。第三,返回值要结构化,最好返回 JSON 或者明确的键值对,方便模型理解结果。第四,工具要有失败处理,网络超时、参数错误这些情况要返回清晰的错误信息,让模型知道“这一步失败了,可以换个方式再试”。

下面是一个工具定义的示例结构,用 Python 字典来描述,方便你直接套:

tools = [ { "name": "get_order_status", "description": "根据订单号查询订单当前状态,返回状态字段。输入必须是订单号字符串。", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,例如 A123456" } }, "required": ["order_id"] } } ]

这段结构里,description是给模型看的,parameters是约束模型传参的。你会发现我特意在描述里加了“输入必须是订单号字符串”,这就是在给模型划边界。边界划得越清楚,Agent 越不容易乱来。

3.2 提示词工程:怎么让模型乖乖按流程走

Agent 的提示词和普通对话的提示词不一样。普通对话你只要说清楚问题就行,Agent 的提示词还得告诉模型“你有哪些工具、什么时候用、用完怎么判断”。这其实是一份行为规范,而不是一句提问。

我一般会把系统提示词分成四块来写。第一块是角色和目标,比如“你是一个订单处理助手,目标是帮用户查清订单状态并给出下一步建议”。第二块是可用工具清单,把每个工具的名字和用途列出来。第三块是工作流程,比如“先确认用户提供了订单号,没有就先问;拿到订单号后调用查询工具;根据返回状态给出建议”。第四块是约束,比如“不要编造订单状态,查不到就如实说”。

这里有个很实用的技巧:把“什么时候停止”写进提示词。很多人只告诉模型怎么开始,不告诉它怎么结束,结果模型要么一直循环,要么提前收尾。我会明确写“当你已经拿到订单状态并给出建议后,任务结束,直接输出最终答复”。这一句话能省掉大量调试时间。

还有一个细节是少样本示例。如果某个流程模型总是走不对,我会在提示词里塞一两个“输入-应该怎么做”的例子。比如给一个“用户说‘帮我看看订单’”的例子,展示正确的第一步是追问订单号。示例不用多,一两个就能显著改善行为。

3.3 循环控制:终止条件与最大轮次的取舍

循环控制是 Agent 的刹车和油门。没有它,Agent 要么停不下来,要么一步就熄火。我一般会设置两个终止条件,满足任意一个就停:一个是模型明确表示任务完成,另一个是达到最大轮次上限。

最大轮次怎么定?这得看任务复杂度。我的经验值是:简单查询类任务 3 到 5 轮足够,多步骤任务 8 到 12 轮,再复杂就得考虑拆成多个子 Agent 了。这个数字不是拍脑袋,而是根据“平均每步消耗的 token 数 × 轮次 × 单价”算出来的成本上限。举个例子,假设每轮平均消耗 2000 token,单价按常见水平算,10 轮就是 2 万 token。你心里得有个数,不然一个失控的循环能把预算烧穿。

除了轮次上限,我还会加一个重复检测。如果模型连续两轮调用了同一个工具、传了同样的参数,那大概率是卡住了,这时候应该强制终止并返回“未能完成”。这个机制能挡住相当一部分死循环。

max_turns = 10 history = [] for turn in range(max_turns): response = call_model(history, tools, system_prompt) if response.is_final: break if response.tool_call: result = execute_tool(response.tool_call) history.append(response.tool_call) history.append(result) else: # 达到最大轮次仍未完成 return "任务未能在限定轮次内完成"

这段伪代码里,is_final是模型给出的完成信号,tool_call是工具调用请求。注意for...else的用法,循环正常走完(没 break)就说明超轮次了。这个结构简单但很实用。

提示:最大轮次不要设得太大,宁可让 Agent 失败返回,也不要让它无限循环。失败了你还能看到日志去优化,烧钱烧到停不下来才是真的麻烦。

4. 从零搭一个能跑的最小 Agent

4.1 环境准备与依赖选择

动手之前先把环境理清楚。我建议用 Python 3.10 以上,因为类型提示和异步支持都比较完善。依赖方面,最小集合其实只需要一个能调模型接口的库,加上一个处理 JSON 的标准库就够了。如果你用的是某家云服务的模型接口,装它对应的 SDK 即可。

我不建议一上来就装一堆框架。先把依赖压到最低,跑通之后再按需引入。这样你能清楚知道每个依赖是干嘛的,出问题也好定位。虚拟环境一定要建,我见过太多人把全局环境搞乱,最后连哪个包冲突都查不出来。

python -m venv agent_env source agent_env/bin/activate # Windows 用 agent_env\Scripts\activate pip install requests

就这两步,环境就好了。requests用来发 HTTP 请求调模型接口,如果你用的 SDK 更方便,换成 SDK 也行。关键是别让环境成为你动手的阻碍。

4.2 定义工具函数并注册

接下来把工具写出来。我以一个“计算器”和一个“查天气”的假工具为例,因为这两个足够简单,能让你专注在 Agent 流程本身,而不是被业务逻辑分心。

import json def calculator(expression: str) -> str: try: result = eval(expression, {"__builtins__": {}}, {}) return json.dumps({"result": result}) except Exception as e: return json.dumps({"error": str(e)}) def get_weather(city: str) -> str: # 这里用假数据演示,实际应调用真实接口 fake_data = {"北京": "晴,25度", "上海": "多云,28度"} return json.dumps({"city": city, "weather": fake_data.get(city, "未知")}) TOOL_MAP = { "calculator": calculator, "get_weather": get_weather }

注意calculator里我用了eval,但把__builtins__清空了,这是为了防止执行危险代码。工具函数一定要考虑安全性,尤其是涉及执行、文件操作、网络请求的工具,输入必须做校验。这个细节很多人会忽略,等到出问题就晚了。

工具写完之后,要把它们的描述整理成模型能看懂的格式,也就是前面 3.1 节里那个tools列表。描述和函数要一一对应,名字不能写错,否则模型调用了你却没有对应实现,整个流程就断了。

4.3 主循环实现与状态管理

主循环是整个 Agent 的心脏。它的逻辑其实很朴素:把对话历史发给模型,看模型是要调用工具还是给最终答复,如果要调工具就执行、把结果追加到历史,然后继续下一轮。

def run_agent(user_input, max_turns=8): history = [{"role": "user", "content": user_input}] for turn in range(max_turns): response = call_model(history, tools, SYSTEM_PROMPT) if response.get("final_answer"): return response["final_answer"] tool_call = response.get("tool_call") if tool_call: tool_name = tool_call["name"] tool_args = tool_call["arguments"] if tool_name in TOOL_MAP: result = TOOL_MAP[tool_name](**tool_args) else: result = json.dumps({"error": f"未知工具 {tool_name}"}) history.append({"role": "assistant", "content": json.dumps(tool_call)}) history.append({"role": "tool", "content": result}) return "任务未能在限定轮次内完成"

这段代码里,history就是状态。Agent 的“记忆”全靠这个列表,每一轮的模型输出和工具结果都往里塞。这里有个容易出错的地方:不同模型接口对消息格式的要求不一样,有的要求工具结果用特定角色标记,有的要求放在特定字段里。你得对照你用的接口文档来调整,别照搬。

状态管理还有一个进阶话题是上下文长度控制。轮次多了之后,history 会越来越长,可能超出模型的上下文窗口。我的做法是保留最近若干轮,加上一个任务摘要。摘要可以让模型自己生成,比如“到目前为止,已经查到订单状态是已发货”。这样既省 token,又不丢关键信息。

4.4 跑通第一个任务并观察日志

代码写完,先别急着上复杂任务。用一个最简单的输入测试,比如“帮我算一下 23 乘以 47”。理想情况下,你会看到 Agent 调用calculator工具,拿到结果,然后给出最终答复。

跑的时候一定要打印完整日志,把每一轮的模型输出、工具调用、工具结果都打出来。我第一次跑通的时候,盯着日志看了半天,才真正理解“模型决定调用工具”是怎么回事。日志是你调试 Agent 的唯一眼睛,千万别省。

如果第一次没跑通,大概率是这几个原因:工具描述和函数名对不上、消息格式不符合接口要求、模型没有正确理解“什么时候该停”。对照日志逐个排查,基本都能解决。

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

5.1 Agent 陷入死循环怎么办

死循环是新手遇到最多的坑。表现是 Agent 反复调用同一个工具,或者在两三个工具之间来回横跳,就是不给最终答复。原因通常有三个:一是终止条件没写清楚,模型不知道什么时候算完成;二是工具返回的结果模型看不懂,它以为没成功就重试;三是任务本身超出了 Agent 的能力范围,它在硬撑。

排查顺序我一般是这样的。先看提示词里有没有明确的终止条件,没有就补上。再看工具返回值是不是结构化的、清晰的,如果返回一大段自然语言,模型容易误判。最后看任务是不是太复杂,如果是,就拆成多个子任务,或者干脆换人工处理。

我还会加一个重复调用检测作为兜底:记录最近几轮的“工具名+参数”,如果完全重复,就强制终止。这个机制救过我好几次,尤其是在工具接口不稳定、返回超时的时候。

5.2 工具调用参数总是传错怎么修

参数传错的表现是工具执行报错,或者结果明显不对。根因往往在工具描述不够精确。比如一个工具需要“日期”,你只写“日期”,模型可能传“明天”这种自然语言,而不是“2026-01-01”这种格式。

修法是在参数描述里给出格式示例。把"description": "日期"改成"description": "日期,格式为 YYYY-MM-DD,例如 2026-01-01"。别小看这个改动,它能挡掉大部分格式错误。另外,参数类型也要写死,是 string 就别让它传 number。

如果改了描述还是错,可以在工具函数里加一层参数校验和容错。比如日期格式不对就尝试解析常见格式,实在解析不了就返回明确的错误提示,让模型知道该怎么改。这种“工具自己兜底”的思路,比单纯指望模型传对参数要可靠。

5.3 成本失控与响应变慢的优化思路

Agent 比普通对话贵,这是事实,因为它一轮轮地调模型。成本失控通常来自两个地方:轮次太多,或者每轮塞进去的上下文太长。优化也是从这两处下手。

轮次方面,把能合并的步骤合并,把终止条件收紧。上下文方面,及时清理 history,只保留关键信息。我一般会做一个“历史压缩”,超过一定轮次就把早期对话总结成一句话。实测下来,这个操作能省掉相当一部分 token,而且对结果影响很小。

响应变慢往往是工具调用本身慢,比如查数据库、调外部接口。这时候可以考虑给工具加超时,超时就返回错误让模型决定下一步,而不是一直等。Agent 的稳定性,很大程度上取决于每个环节都有超时和兜底。

下面这张表是我整理的常见问题速查,方便你对照排查:

现象可能原因排查方向
反复调用同一工具终止条件不清、结果看不懂补终止条件、结构化返回值
参数格式错误工具描述不精确加格式示例、加参数校验
成本异常高轮次多、上下文长收紧轮次、压缩历史
响应特别慢工具接口慢、无超时加超时、异步化
直接给最终答复不调工具工具描述不吸引、提示词没引导优化描述、加少样本示例

注意:排查 Agent 问题时,永远先看日志,再看提示词,最后才怀疑模型。绝大多数问题都出在前两者,换模型往往是最后手段,而且经常解决不了根本问题。

6. 从最小 Agent 到实用 Agent 的扩展方向

6.1 多工具协作与任务拆解

最小 Agent 跑通之后,下一步就是让它能处理更复杂的任务。核心思路是任务拆解:把一个大目标拆成若干子任务,每个子任务可能对应不同的工具组合。比如“帮我安排一次出差”,可以拆成查航班、查酒店、算预算、生成行程单几个子任务。

实现上,你可以让模型先输出一个任务计划,然后按计划逐步执行。也可以引入“规划 Agent”和“执行 Agent”的分工,前者负责拆解,后者负责执行。这种多 Agent 协作的模式在复杂场景下很有用,但也会带来新的复杂度,建议在单 Agent 稳定之后再尝试。

我个人的经验是,不要为了多 Agent 而多 Agent。很多任务用单 Agent 加清晰的提示词就能搞定,硬拆成多个反而增加调试难度。判断标准很简单:如果单 Agent 的提示词已经长到难以维护,或者不同子任务需要完全不同的工具集,那才考虑拆分。

6.2 记忆与知识库的接入

Agent 的“记忆”分短期和长期。短期记忆就是对话历史,前面已经讲过。长期记忆则是把重要信息存下来,下次还能用。最简单的做法是把关键结论写进一个文件或数据库,需要时再读出来。

知识库接入是另一个常见需求。当 Agent 需要回答领域问题时,光靠模型自身知识不够,得去查资料。这时候可以接一个检索工具,让 Agent 先检索、再基于检索结果回答。这个模式就是常说的检索增强,能显著提升回答的准确性。

接入知识库时要注意检索结果的质量。如果检索出来的内容不相关,模型会被带偏。所以检索工具的描述要写清楚“什么时候用”,返回结果也要做筛选和排序,别一股脑全塞给模型。

6.3 评估与迭代:怎么判断 Agent 变好了

Agent 做出来只是开始,怎么判断它好不好、有没有变好,是个更实际的问题。我的做法是建一个测试集,把常见的输入和期望的输出列出来,每次改动之后跑一遍,看通过率。

测试集不用很大,十几二十条就够,但要覆盖典型场景和边界情况。比如正常查询、参数缺失、工具报错、超范围请求,这些都要有。跑测试的时候记录每条的耗时和 token 消耗,这样你不仅知道对不对,还知道贵不贵。

迭代的时候一次只改一个地方,改完跑测试对比。如果同时改提示词又改工具,出了问题你都不知道是哪个引起的。这个习惯看起来笨,但能帮你省下大量返工时间。Agent 的优化是个细活,急不得。

最后分享一个我自己的体会:Agent 这东西,看十篇文章不如自己动手跑通一个。哪怕它只会做加法和查天气,你跑通之后对它的理解,会比看再多资料都扎实。真正的门槛不在概念,而在那些只有动手才会遇到的细节里。

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

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

立即咨询