Agents(智能体)


**智能体(agent)**是指模型在一个循环中运行,不断挑选下一个要调用的工具,直到任务完成。本章接下来要讲的,就是这到底意味着什么、它在哪些地方能真正发挥价值,又在哪些地方会悄悄让你付出代价。
智能体循环
在工具调用中,模型调用一个函数,你把结果发回去,就完事了。但有些任务需要好几个步骤,而且你事先并不知道具体要走哪些步骤。"找到最便宜的机票并预订"可能意味着要先搜索、再比较、然后查日历、最后预订,而这个先后顺序得由模型在执行过程中自己摸索出来。
所以,不再是处理单次工具调用,而是把整个过程放进一个循环里。这个不断重复的过程就是**智能体循环(agent loop)**——同一轮流程反复执行,直到任务完成:
- 把对话内容和可用的工具列表发给模型。
- 模型要么调用一个工具,要么给出最终答案。
- 如果它调用了工具,就执行工具、把结果加入对话,然后回到第 1 步。
- 如果它给出了最终答案,就停止。
每一轮,模型都会看到到目前为止发生的一切,包括之前工具调用的结果,然后据此决定下一步该做什么。这就是与工具调用的全部区别所在:你要不断循环下去,直到模型自己认为任务已经完成。
真正的变化在于由谁来掌舵。在此之前的每一章里,都是你的代码在决定控制流:你决定什么时候调用模型、什么时候运行工具、接下来做什么。而智能体把这个决定权交给了模型。这个循环不断地问"接下来该做什么?",而模型每次给出的预测就是答案。你用可控性换来了灵活性,而这笔交易正是它既能带来更大能力、也会带来更大风险的根源。
从底层来看,其实没有发生什么新鲜事。本质上还是同一种预测机制,只是上下文在不断增长——工具调用那个来回往复的过程,反复上演罢了。模型并不会先制定一个计划再照着执行。每一轮它只是根据当下能看到的信息,预测出那个"最佳的下一步",然后循环再次运行。
当这一切顺利运转时,看起来就像是在朝着一个目标进行推理。而当它不顺利时,智能体就会在原地打转,或者一路走进死胡同,因为整个系统里没有任何一部分在真正掌管全局计划。
一个最小可用的智能体
下面是用代码写出来的这个循环。它复用了工具调用一章里的工具定义和实现,并把它们包进一个循环里,这个循环会一直运行,直到模型不再请求调用工具为止。
import json
def run_agent(user_text, tools, tool_impls, max_steps=6):
messages = [{"role": "user", "content": user_text}]
step = 0
while step < max_steps:
response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
message = response.choices[0].message
messages.append(message)
calls = message.tool_calls
if not calls:
return message.content # 没有要调用的工具:这就是最终答案
# 执行所有请求的工具,并把结果反馈回去
for call in calls:
args = json.loads(call.function.arguments)
result = tool_impls[call.function.name](args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
step += 1
return "已停止:智能体达到了步数上限。" # 安全网从头到尾读一下:调用模型,把它返回的消息推入对话,如果它请求了工具,就把所有工具都执行一遍并把结果附加进去,然后再循环一次。当模型返回的是文本而不是工具调用时,那就是最终答案,直接返回即可。
max_steps 这个值设定了一个**步数上限**,也就是防止循环失控的安全网,下一节要讲的正是这个话题。这里的写法对应的是某一家服务商的 SDK;不同服务商的字段名会有所不同,但循环本身在哪里都是一样的。
停止条件与护栏
一个由模型自己决定何时停止的循环,也可能会停不下来。它可能会不停地调用工具、反复重复自己,或是一路走进死胡同。因为每一步都是一次按 token 计费的模型调用,一个失控的智能体会很快烧掉大量的金钱和时间。
所以,护栏(guardrails)——也就是你给这个循环设下的各种限制——是设计中本该包含的一部分,不是最后再补上去的润色步骤:
- 设置步数上限。 就像
max_steps那样,限制循环次数。一旦达到上限,就停止并返回。 - 验证工具的输入参数。 参数是模型自己选出来的,所以在执行任何有实际后果的操作之前,一定要先检查这些参数。
- 限制工具的能力范围。 只给智能体它真正需要的最小权限。一个只能"读"的工具,比一个能"删"的工具更安全。
- 留意成本。 每个任务往往要经过多次模型调用,成本会很快叠加起来,所以要跟踪 token 用量并设定预算。
把这些串联起来的一条线索是:模型是在临场随机应变地走出这条路,所以你要提前设好它自己不会设的那些限制。假定智能体至少会犯一次错,然后让这次错误变得可以承受。
什么时候工作流比智能体更好
智能体很让人兴奋,这也正是为什么会有人在根本不需要的时候也想用它。这里有一条简单的原则:如果你已经知道具体步骤,就不要用智能体。把这些步骤直接写成普通代码,也就是一个**工作流(workflow)**,每一步都按固定顺序执行。
工作流更可靠、更便宜,而且调试起来也容易得多,因为路径是你自己掌控的,而不是指望模型能找到它。把智能体留给那些开放式的任务,也就是具体步骤要取决于过程中发现了什么的情况。
很多看起来"需要智能体"的功能,其实更适合用一次结构化输出调用,或者一次手动接好的RAG查询来实现。只有在你无法提前写出具体步骤的时候,才该考虑用智能体。
实际应用
上面那个 run_agent 函数就是全部内容。给它一个问题、一组工具,以及这些工具的实现,它就会一直循环,直到任务完成,或者达到步数上限。一个小任务就能展示出这个循环是怎么发挥作用的:
answer = run_agent(
"北京的天气怎么样?我应该带伞吗?",
tools, # 包含来自工具调用一章的 get_weather
tool_impls, # {"get_weather": ...}
)
# 智能体调用 get_weather,读到"多云",然后在最后一轮直接给出答案:
# "北京现在多云,气温 18 度,带伞是个不错的主意。"一个工具,一个循环,一个答案。同样的结构可以扩展到多个工具、多个步骤,而形态不会改变。一旦你能搭建出这种能力的东西,接下来的问题就是:你怎么知道它真的管用?这正是评估这一章要讲的内容。
run_agent 这个循环就是全部套路:给它一个问题、工具、以及这些工具的实现,它就会一直循环,直到模型不再请求工具,或者达到步数上限。一个工具一个循环,可以扩展到很多个而形态不变。接下来的问题是:你怎么知道它真的管用?这正是评估要讲的内容。 
