一、引言:一条被严重低估的分界线

过去两年,绝大多数团队做"AI 落地"的第一步都是接一个 Chatbot:把知识库塞进向量库,套一层 RAG,对外宣称"智能助手"。

请添加图片描述

上线三个月后,业务方的反馈高度一致——它能聊,但干不了活。

问题不在模型不够聪明,而在范式本身。Chatbot 的输出空间是 token,它的任务是在给定上下文下生成"最像人话"的下一段文本;而 Agent 的输出空间是动作序列,它要回答的是"下一步该做什么,才能离目标更近"。

具体来说,聊天机器人有四个结构性缺陷:

  • 信息孤岛:模型只知道训练时和上下文里的事,对你的实时数据、内部系统一无所知;
  • 无状态:所谓"多轮对话",本质是把历史消息反复拼回 prompt,没有真正的记忆取舍;
  • 无法闭环:它只能给出"建议",比如"你可以试试用 grep 过滤 5xx",而不是结果本身;
  • 不可验证:中间推理全在模型黑箱里,错了你也不知道错在哪一步。

Agent 的分界线只有一句话:它能否自主决定"下一步做什么",并把这个决定变成对外部世界的真实操作,再根据操作结果修正下一步。

这篇文章不讲概念炒作,只讲清三件事:Agent 的循环长什么样、任务规划和工具调用在工程上如何落地、以及真实项目里最容易踩的七个坑。

二、核心原理:四根支柱 + 一个循环

工程上可以给出一个足够精确的定义:

Agent = LLM(控制器) + Planning(规划) + Memory(记忆) + Tools(工具) + Loop(循环)

2.1 控制循环:ReAct 才是骨架

ReAct(Reason + Act)是绝大多数 Agent 的原型:Thought → Action → Observation → Thought → … → Final Answer。

关键在于,这个循环的终止条件由模型自己判断(不再调用工具即为结束),而不是由代码写死轮数。这是 Agent 与"固定工作流"最本质的区别:工作流的控制流在工程师手里,Agent 的控制流在模型手里。代价是可控性下降,收益是泛化能力上升。

2.2 任务规划:隐式 vs 显式

  • 隐式规划(ReAct):每一步现场想下一步。灵活,适合探索性任务;但步数一多容易"迷路",上下文被中间结果污染。
  • 显式规划(Plan-and-Execute):先让模型输出一份 DAG 或有序步骤列表,再由执行器逐步跑。稳定、可审计、可并行;但一旦某步失败,整份计划可能作废。

生产环境的常见做法是混合式:先粗粒度规划出 3~5 个大步骤,每个步骤内部再用 ReAct 循环执行;某一步失败时触发"重新规划"而不是整体重来。

2.3 工具调用:模型只负责"选",不负责"执行"

Function Calling 的本质常被误解。它并不是模型真的调用了什么,而是:把工具的 JSON Schema 注入上下文,让模型输出一段结构化的"调用意图"。真正的执行权始终在你的代码手里。

这条边界极其重要——它是安全、审计、限流的唯一抓手。模型可以建议 rm -rf /,但要不要执行,是你的执行器说了算。

2.4 记忆与反思

记忆分两层:短期是工作记忆(当前任务的中间状态),长期是外部存储(向量库、数据库、文件系统)。上下文不等于记忆——上下文是无差别堆砌,记忆是有取舍的检索。

反思(Reflection)则是让 Agent 具备纠错能力:执行完一步后,用一个独立的验证器(可以是另一个 LLM,也可以是确定性代码)判断结果是否达成子目标,不达标就重试或换路。

三、代码实战:从零写一个可用的 Agent 循环

3.1 主循环:60 行说清 ReAct

import json
from openai import OpenAI

client = OpenAI()

SYSTEM = """你是一个运维分析 Agent,可以调用工具获取真实数据。
规则:
1. 禁止凭空猜测文件内容,必须先调用工具读取。
2. 每一步只调用一个最必要的工具,避免并行猜测。
3. 证据充分后,直接输出最终结论,不要再调用工具。
"""

def run_agent(user_input: str, tools: dict, max_steps: int = 12):
    messages = [
        {"role": "system", "content": SYSTEM},
        {"role": "user", "content": user_input},
    ]
    schemas = [t["schema"] for t in tools.values()]

    for step in range(max_steps):
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=schemas,
            tool_choice="auto",
            temperature=0,          # Agent 场景几乎总是 0
        )
        msg = resp.choices[0].message
        messages.append(msg)

        # 模型不再请求工具 => 它认为任务已完成
        if not msg.tool_calls:
            return msg.content

        for call in msg.tool_calls:
            name = call.function.name
            try:
                raw = json.loads(call.function.arguments or "{}")
                args = tools[name]["_model"](**raw).model_dump()  # 参数校验
                result = tools[name]["fn"](**args)                # 真正的执行
            except Exception as e:
                # 关键:异常也要作为 Observation 回灌,模型才能自我修复
                result = f"ERROR: {type(e).__name__}: {e}"

            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": str(result)[:4000],   # 观察结果必须截断
            })

    return "达到最大步数仍未完成,请人工介入。"

这段代码里有三个容易被忽略但决定成败的细节:

  1. 工具返回值和异常都必须回灌到消息列表。很多新手在工具报错时直接抛异常终止,等于剥夺了模型自我修正的机会。把错误当成 Observation 喂回去,模型往往能自己换个参数重试。
  2. [:4000] 的截断不是可选项,而是护栏。真实工具(尤其是日志、网页、数据库查询)返回几万 token 是常态,如果不截断,下一次请求会直接撞上下文窗口上限,或者把关键证据挤到模型注意力边缘。更好的做法不是简单截断,而是“首尾保留 + 中间省略”,或者先用一个小模型做摘要。
  3. temperature=0 几乎总是正确选择。Agent 要的是稳定、可复现的动作序列,不是创意写作。温度调高会让同一任务在不同轮次走出完全不同的路径,给调试和评测带来灾难。

3.2 工具层:Schema、校验与执行必须分离

上面的主循环能跑,但它默认 tools 字典里已经包含了合法的 schema、Pydantic 模型和执行函数。真正工程化时,工具层要严格分成三件事:

  • Schema:给模型看的“菜单”,描述工具名、用途、参数类型;
  • 校验模型:给执行器用的“安检”,确保模型传来的参数类型正确、范围合法;
  • 执行函数:真正碰外部世界的代码,必须做权限、超时、限流和审计。

一个最小但完整的工具定义如下:

from pydantic import BaseModel, Field
import re

class ReadLogArgs(BaseModel):
    path: str = Field(..., description="日志文件绝对路径,必须在 /var/log/app 下")
    pattern: str = Field(..., description="grep 正则,例如 '5\\d\\d'")
    tail: int = Field(200, ge=1, le=5000, description="最多返回行数")

def read_log(path: str, pattern: str, tail: int = 200) -> str:
    # 真实实现必须做路径白名单,防止目录穿越
    if not path.startswith("/var/log/app/"):
        raise PermissionError("只允许读取 /var/log/app 下的日志")
    with open(path, "r", encoding="utf-8", errors="ignore") as f:
        lines = f.readlines()
    matched = [ln for ln in lines if re.search(pattern, ln)]
    return "".join(matched[-tail:])

tools = {
    "read_log": {
        "schema": {
            "type": "function",
            "function": {
                "name": "read_log",
                "description": "按正则过滤日志文件,返回最后 N 行匹配结果。用于排查错误码、异常栈。",
                "parameters": ReadLogArgs.model_json_schema(),
            },
        },
        "_model": ReadLogArgs,
        "fn": read_log,
    }
}

注意这里的分工:schema 是给模型“看”的,_model 是给执行器“验”的,fn 才是真正“做”的。模型可能传错类型、漏字段、编造枚举值,所以 ReadLogArgs(**raw).model_dump() 这一步不能省。校验之后,还要在 fn 内部做权限检查、超时控制、结果脱敏。模型只负责选工具,执行器负责一切安全边界。

3.3 规划器:把“想”和“做”拆开

ReAct 适合探索,但面对“排查线上故障”这种多阶段任务,最好先让规划器给出粗粒度步骤。下面是一个 Plan-and-Execute 的规划器示例:

PLANNER = """你是任务规划器。根据用户目标,输出 JSON 数组,每个元素包含 step 和 tool_hint。
要求:
1. 只输出 3~5 个大步骤;
2. 每步必须是可验证的;
3. 不要写具体命令参数,只写目标;
4. 最后一步必须是“汇总结论并给出建议”。
"""

def plan(goal: str) -> list[dict]:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": PLANNER},
            {"role": "user", "content": goal},
        ],
        response_format={"type": "json_object"},
        temperature=0,
    )
    data = json.loads(resp.choices[0].message.content)
    return data["steps"]

执行器拿到步骤列表后,逐步调用 run_agent,每一步只关注当前子目标。某一步失败时,把失败原因、已完成步骤和剩余步骤一起交给规划器重新规划。这样做的好处是:计划可审计、可人工干预、可并行;坏处是规划器本身也可能犯错,所以计划不能太细,否则一步错步步错。

3.4 记忆与反思:让 Agent 不重复犯错

短期记忆可以用一个简单的状态对象维护:

state = {
    "goal": "定位订单服务 5xx 突增原因",
    "completed": [],
    "evidence": [],
    "failed_attempts": [],
}

每次工具调用后,把关键 Observation 追加到 evidence,把失败动作和错误写入 failed_attempts。下一轮 prompt 只注入最近 N 条证据和所有失败记录,而不是把所有历史都塞进去。长期记忆则可以把“任务类型 → 成功计划 → 关键证据”存入向量库,下次遇到相似任务时先检索历史成功路径。

反思可以用一个轻量验证器实现:

def reflect(step_goal: str, observation: str) -> dict:
    prompt = f"""子目标:{step_goal}
观察结果:{observation}
请判断是否达成子目标。输出 JSON:
{{"done": true/false, "reason": "...", "next_hint": "..."}}"""
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"},
        temperature=0,
    )
    return json.loads(resp.choices[0].message.content)

反思不必每一步都做,否则成本和延迟会翻倍。建议只在以下情况触发:工具报错、返回为空、结果与预期严重不符、或者即将进入高风险操作。

3.5 实战案例:线上 5xx 排障 Agent

假设用户输入:“今天 14:00 后订单服务 5xx 突增,帮我定位原因。”一个混合式 Agent 的轨迹可能如下:

  1. 规划器输出步骤:① 查监控确认时间窗口和错误率;② 拉取对应实例日志;③ 过滤异常栈和超时;④ 关联最近发布记录;⑤ 汇总结论。
  2. 执行第①步:Agent 调用 query_metrics,Observation 返回“14:05 起 5xx 从 0.2% 升到 8.7%,集中在 pod-7 和 pod-8”。
  3. 执行第②步:Agent 调用 read_log,参数 path=/var/log/app/order.log、pattern=5\d\d|Exception、tail=500,Observation 返回大量 ConnectionPoolTimeoutException。
  4. 执行第③步:Agent 发现异常集中在数据库连接池,进一步调用 read_config 确认连接池上限为 20,而近期流量上涨 3 倍。
  5. 执行第④步:Agent 调用 list_recent_deploys,发现 13:50 有一次配置变更,把连接池从 50 改成了 20。
  6. 输出结论:根因是配置变更导致连接池过小,建议回滚配置并临时扩容。

这个案例里,Agent 的价值不是“聊天”,而是自主完成了“查监控 → 读日志 → 关联发布 → 给结论”的闭环。每一步都是真实动作,每一步的 Observation 都影响下一步决策。

四、真实项目里最容易踩的七个坑

坑 1:把 Agent 当工作流用。 如果任务步骤完全确定,比如“每天 9 点拉数据生成报表”,直接写 cron + 脚本更稳。Agent 只适合步骤无法预先穷举、需要根据中间结果动态决策的场景。

坑 2:工具粒度太粗或太细。 工具太粗,模型不会用;工具太细,选择空间爆炸。建议一个工具只做一件事,描述里写清“何时用、何时不用、返回什么”。例如 read_log 比 execute_shell 安全得多,也比 read_file + grep + tail 三个工具更不容易选错。

坑 3:没有超时、重试和熔断。 外部 API 挂了,Agent 可能卡死或无限重试。每个工具调用必须有超时;同一工具连续失败 3 次应中断当前步骤;整体循环必须有最大步数。

坑 4:把整个上下文当记忆。 token 爆炸会稀释注意力,还会让成本失控。要分层:工作记忆只保留当前任务状态;中期记忆做摘要;长期记忆走向量检索。

坑 5:缺少可观测性。 只记录最终答案,不记录 Thought、Action、Observation,出了问题无法复盘。必须落盘 trace,包括每步耗时、token 消耗、工具参数、返回摘要、异常堆栈。

坑 6:权限过大。 给 Agent 数据库写权限、shell 权限、生产环境删除权限,迟早出事。必须最小权限,危险操作走人工确认,所有工具调用留审计日志。

坑 7:没有评测集。 靠感觉调 prompt,今天好了明天坏。要建 20~50 个真实任务,定义成功标准,每次改动跑回归。核心指标包括:任务成功率、平均步数、工具错误率、人工介入率、单任务成本。

五、FAQ:关于 Agent 的常见疑问

Q1:Agent 一定要用 Function Calling 吗?
不一定。早期 ReAct 用文本解析 Action: tool_name 也能跑,但结构化输出更稳、更省 token、更容易校验。生产环境优先 Function Calling 或 JSON Schema。

Q2:循环多少步合适?
一般 8~15 步。超过 15 步通常说明任务该拆,或者工具粒度有问题。最大步数不是目标,而是安全阀。

Q3:用哪个模型?
规划用强模型,执行用便宜模型,反思用中等模型。不要所有环节都用最贵的模型,成本会失控。

Q4:Agent 和 RAG 是什么关系?
RAG 是 Agent 可调用的工具之一。Agent 可以决定“什么时候检索、检索什么、要不要换关键词再检索”,而 RAG 只是被动地把检索结果拼进上下文。

Q5:如何防止死循环?
步数上限、重复动作检测、状态哈希。如果连续两步的 Action 和参数完全相同,直接中断或强制换路。

Q6:多 Agent 有必要吗?
大多数场景单 Agent + 工具足够。多 Agent 会增加通信成本、状态同步成本和调试难度,除非任务天然可并行且角色边界清晰。

Q7:怎么评测 Agent?
不要只看最终答案。要记录完整轨迹,按任务成功率、步数、工具错误率、人工介入率、成本五个维度评估。最好有沙箱环境,允许重复跑。

六、总结

Agent 不是“更聪明的聊天机器人”,而是把 LLM 放进一个“感知—决策—行动—反馈”的闭环。聊天机器人的终点是生成一段文本,Agent 的终点是改变外部世界的状态。分界线只有一句话:它能否自主决定下一步做什么,并把这个决定变成真实操作,再根据操作结果修正下一步。

工程落地的关键,是四根支柱加一个循环:LLM 做控制器,Planning 决定路径,Memory 保留取舍,Tools 连接现实,Loop 持续修正。

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

更多硬核网安与AI工具包,请扫码获取完整源码!
但比这些更重要的是护栏——权限、超时、审计、评测、人工介入。不要追求“全自主”,要追求“可观测、可干预、可回滚”。能做到这三点,Agent 才真正从 Demo 走向生产。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐