ByteNoteByteNote
AI 工作流专栏 15:Agent 基础与 ReAct 循环
字

字节笔记本

2026年10月6日 · 约 55 分钟读完

AI 工作流专栏 15:Agent 基础与 ReAct 循环

API中转
¥120

本文是《从零成为 AI 工作流工程师》系列专栏的 Agent 篇。很多人用大模型的姿势还停留在"被动问答":你问一句,它答一句,顶多帮你查个资料、调一个函数,它从不主动,从不反思,从不回头。但现实里的活儿没这么简单,你让 AI"帮我查下明天上海天气然后推荐穿搭",它得自己想:先查天气,再根据温度决定推短袖还是外套,最后组织成一句话。这个"自己拆步骤、自己挑工具、自己根据中间结果调整"的过程,就是 Agent(智能体)。本篇用一个核心类比,"派一个实习生去办事",把 Agent 的本质讲透,然后不依赖任何框架,用纯 Python 加 openai SDK 手写一个能跑的最小 Agent,带计算器、查时间、查天气三个工具。读完你会彻底明白:Agent 不是什么玄学,它的内核就是一个"LLM 自己决定要不要调工具"的 while 循环,也是你从"调 API 的人"升级为"设计智能体的人"的第一步。

本篇你将学到:

  1. Agent 到底是什么:用"派实习生买咖啡"类比,搞清它和单次 LLM 调用的本质区别
  2. Agent 的四大核心组件:大脑(LLM)、记忆(Memory)、工具(Tools)、规划(Planning)
  3. ReAct 范式(核心):Thought、Action、Observation 循环,用"查天气加推荐穿搭"的例子一步步演示
  4. 手写最小 Agent(重点,不用任何框架):纯 Python 加 openai SDK 实现 ReAct 循环,能调用 3 个工具
  5. Agent 的规划策略:任务分解(Task Decomposition)、Plan-and-Execute、ReWOO
  6. Agent 的记忆机制:短期记忆、长期记忆(向量库)、记忆总结(防上下文爆炸)
  7. Agent 与 Function Calling、Workflow 三者的根本区别
  8. Agent 的五大坑:死循环、烧钱、错误累积、不可控,以及为什么需要 HITL 和 LangGraph

Part A:Agent 是什么

一、什么是 Agent:从"派一个实习生去办事"说起

1.1 生活类比:实习生买咖啡

想象两种安排实习生干活的方式:

方式 A(像指挥传统 LLM 一样指挥):你站在实习生旁边,一步步发指令:"你现在走到电梯口""按下 1 楼""出电梯右转""看见星巴克走进去""对店员说一杯美式""扫码付款""拿好杯子回来"。实习生全程照做,但你必须步步指挥,少说一句他就卡住。

方式 B(像派一个 Agent 一样指挥):你只说一句:"帮我去楼下买杯美式回来,冰的还是热的你自己看着办,钱从我抽屉里拿。"然后你转身去开会。实习生自己规划路线、自己用手机支付、自己判断天气热不热决定冰热、路上发现星巴克排队太长就换一家瑞幸,最后把咖啡端到你桌上。

第二种方式里,实习生就是 Agent:你给的是目标,不是步骤;他用自己的判断力拆解任务、用工具(手机支付、导航)完成动作、根据反馈(排队太长)调整策略。

1.2 专业定义

把上面的类比翻译成技术语言:

Agent(智能体):一个以 LLM 为"大脑"、能自主感知(Perceive)、决策(Reason)、行动(Act)的系统。它接收一个目标(goal),自己决定调哪些工具(tools)、循环多步、根据每一步的观察结果(observation)调整下一步,直到任务完成。

关键词是自主。传统 LLM 调用是你写好 prompt、它吐一次答案就结束;Agent 是你给目标,它在一个循环里反复"思考、行动、观察",直到它自己判断"任务完成了"才停下。

1.3 和单次 LLM 调用的本质区别

维度单次 LLM 调用(传统方式)Agent(本篇的方式)
输入一句 prompt一个目标(goal)
输出一次性生成的文本多步行动后的最终结果
工具使用不用,或被你硬编码调用一次LLM 自己决定调不调、调几次、调哪个
循环没有循环,一问一答while 循环,反复推理直到完成
错误处理出错就出错,不会自我纠正能观察到"失败了",自己换个思路重试
可控性高(你写死每一步)低(LLM 自主决策,概率性)
成本可预测(一次调用)不可预测(可能循环几十次)

一句话总结:单次 LLM 调用是"打手电",你指哪它照哪;Agent 是"派个人去办事",你说目标,它自己找路。

1.4 小结

Agent 的本质就两点:① 目标驱动(你给目标不给步骤);② 自主循环(LLM 在循环里自己决定调工具还是直接答)。带着这两点直觉,我们看它的内部构造。

二、Agent 的四大核心组件

2.1 生活类比:一个完整的人需要什么

还是那个实习生。要让一个实习生能独立办事,他得具备四样东西:

  • 脑子:会思考、会判断、会做决定,这是 Agent 的 LLM(大脑)。
  • 记忆:记得你刚才交代了什么、记得公司规定、记得上次类似任务的教训,这是 Memory(记忆)。
  • 工具:手机支付、地图导航、查日历,这是 Tools(工具)。脑子里想得再好,没有手也办不成事。
  • 规划:能把"买咖啡"这个大目标拆成"下楼、找店、付款、返回"这些小步骤,并在卡壳时换方案,这是 Planning(规划)。

少了任何一个,Agent 就瘸腿:没脑子等于不会决策(变回脚本);没记忆等于鱼的记忆(每一步都从头开始);没工具等于纸上谈兵(只会说不会做);没规划等于无头苍蝇(乱调工具)。

Agent 的四大核心组件:大脑 LLM、记忆 Memory、工具 Tools、规划 Planning,与 while 循环伪代码

2.2 专业定义

组件作用典型实现
大脑(LLM)推理、决策、生成自然语言OpenAI / DeepSeek / Claude 等
记忆(Memory)存对话历史、存长期知识短期是消息列表;长期是向量数据库
工具(Tools)让 Agent 能"动手"做事Function Calling 函数、API、MCP
规划(Planning)把大任务拆成小步骤、反思纠错ReAct、Plan-and-Execute、ReWOO

注意:大脑是核心,其它三个是它的"外挂"。你可以这样理解:LLM 本来只会"说",加上工具它会"做",加上记忆它能"记得上下文",加上规划它能"想清楚再动手"。

2.3 这四者怎么协作

用户给一个目标,大脑看一眼目标和当前记忆,规划出第一步,选一个工具去调,工具返回结果并写进记忆,大脑再看记忆、规划下一步,如此循环,直到大脑说"我搞定了,输出最终答案"。

这个循环,就是下一节要讲的 ReAct。Agent 的所有花活儿,本质都是把这个循环做得更稳、更省、更可控。

Part B:ReAct 范式,Agent 的"思考方式"

三、ReAct:让 LLM 像人一样"边想边做"

3.1 生活类比:你查天气推荐穿搭时脑子里在转什么

朋友问你:"明天我要去上海出差,穿啥?"

你不会脱口而出答案,你脑子里会有一连串自言自语:

text
(思考)我得先知道明天上海天气。
(行动)打开天气 App,查"上海明天"。
(观察)App 显示:小雨,最高 18℃,最低 12℃。
(思考)18 度偏凉,又下雨。得推带外套的,还要防雨。
(思考)出差的话得正式点,不能穿运动裤。
(行动)在脑子里挑搭配:风衣 + 长袖衬衫 + 长裤 + 雨伞。
(最终回答)"明天上海 12-18℃ 有小雨,建议风衣配长袖衬衫,
          带把伞。出差的话这套也够正式。"

注意这里有个关键模式:思考(Thought)、行动(Action)、观察(Observation)反复交替,最后才给出最终答案(Final Answer)。

这就是 ReAct,即 Reason 加 Act。它不是什么新技术,它就是把人类解决问题的思考过程,显式地教给 LLM。

3.2 专业定义

ReAct 范式:2022 年 Google 论文提出的一种 Agent 推理框架。它让 LLM 在每一步交替产出三种内容:

  • Thought(思考):我现在想干什么、为什么。
  • Action(行动):调用某个工具(或决定不调)。
  • Observation(观察):工具返回的结果。

这个 Thought-Action-Observation 三元组循环若干轮,直到 LLM 产出 Final Answer(最终答案),循环结束。

Agent 的 ReAct 循环:Thought、Action、Observation 循环与 Final Answer 出口,下方是 mini_agent.py 的真实运行输出

3.3 一个完整的 ReAct 推理示例

下面这个例子模拟 Agent 处理"明天去上海出差穿啥"时,内部那一连串 Thought / Action / Observation 长什么样。这是 ReAct 论文风格的原汁原味演示(先不写真实代码,建立直觉):

text
用户问题:明天我要去上海出差,穿啥合适?

第 1 轮
Thought:  用户要去上海出差,需要根据天气推荐穿搭。
          我现在不知道上海明天的天气,得先查。
Action:   get_weather(city="上海", date="明天")
Observation: {"city": "上海", "temp_low": 12, "temp_high": 18,
              "condition": "小雨", "wind": "东北风 4 级"}

第 2 轮
Thought:  上海明天 12-18℃,有雨有风,偏凉。出差需要商务得体,
          又要防雨防风。我推荐:风衣 + 长袖衬衫 + 长裤 + 雨伞。
          我应该再确认一下明天是星期几,判断是否工作日着装更正式。
Action:   get_current_weekday()
Observation: "2026-07-04 是星期六"

第 3 轮
Thought:  星期六出差可能是周末活动,着装可以稍微休闲一点,
          但风衣防雨仍然必要。综合给出最终建议。
Final Answer: 明天上海 12-18℃,小雨,东北风 4 级。
              建议穿:风衣 + 长袖衬衫 + 卡其长裤,务必带伞。
              周末出差着装可稍微休闲,但风衣防雨防风不可省。

看到没有?Agent 自己决定了查两次工具(先天气,再星期几),并把两次观察综合起来给出答案。这就是 ReAct 的魔力:你只给了一句话目标,它在内部走了 3 轮思考。

3.4 ReAct 为什么有效

有人会问:LLM 一次性也能查天气啊,干嘛要"思考"?

答案:显式的 Thought 让 LLM 的推理过程"慢下来",准确率大幅提升。这和 Prompt Engineering 里"链式思考(Chain of Thought)"是同一个道理:让模型把思考写出来,比让它直接答更准。ReAct 在此基础上又加了"行动"环节,让模型能一边想一边动手查实际信息,避免了"拍脑袋瞎编"(幻觉)。

3.5 小结

ReAct 就是 Thought、Action、Observation 的循环,加一个叫 Final Answer 的出口。它不是玄学,是一套"让 LLM 显式思考、显式调工具"的约定。下一节我们就把这个约定,用 100 行纯 Python 跑起来。

Part C:手写最小 Agent(不用任何框架)

四、用 openai SDK 手搓一个能跑的 ReAct Agent

这是本篇的精华。市面上 Agent 框架(LangChain、LangGraph、AutoGen、CrewAI)一大堆,但框架会藏住本质。我们今天不依赖任何 Agent 框架,只用 openai 这一个库加几个 mock 工具函数,把一个 ReAct Agent 从零写出来。你跑完会发现:Agent 的核心代码不到 60 行。

4.1 思路:把 ReAct 翻译成代码

ReAct 的"Thought-Action-Observation 循环"翻译成代码就是一个 while True:

text
while True:
    1. 把"对话历史 + 工具列表"发给 LLM
    2. 如果 LLM 返回了 tool_calls(它想调工具):
        - 执行工具,把结果塞回对话历史
        - 继续循环(回到第 1 步)
    3. 如果 LLM 直接返回了文本答案(没有 tool_calls):
        - 它说"我搞定了",输出答案,跳出循环

就这么简单。LLM 自己决定"调不调工具、调几个",你只负责"执行它点名要的工具,把结果喂回去"。这就是 Agent 和 Function Calling 的区别:Function Calling 是你发起一次调用,Agent 是 LLM 在循环里自己发起多次调用。

关键认知:openai SDK 的 tools 参数(Function Calling)天然就是为 ReAct 设计的。LLM 看到 tools,会自己判断"现在该不该调工具"。你只要在外面套个 while 循环,反复喂结果,它就变成了 Agent。

4.2 完整代码(可直接复制运行)

先装依赖:pip install openai。然后准备一个 .env 文件放 OPENAI_API_KEY(或 DEEPSEEK_API_KEY,本代码同时兼容)。

python
# mini_agent.py:不用任何框架,纯手写一个 ReAct Agent
# 依赖:pip install openai
# 运行:python mini_agent.py
import os
import json
from datetime import datetime
from openai import OpenAI

# ===== 1. 初始化客户端(兼容 OpenAI / DeepSeek)=====
# OpenAI 和 DeepSeek 的 SDK 接口完全一致,只差 base_url 和 key
if os.getenv("DEEPSEEK_API_KEY"):
    client = OpenAI(
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        base_url="https://api.deepseek.com",
    )
    MODEL = "deepseek-chat"
else:
    client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    MODEL = "gpt-4o-mini"

# ===== 2. 定义工具(Agent 的"手")=====
# 每个工具就是一个普通 Python 函数,外加一个 JSON Schema 描述
def calculate(expression: str) -> str:
    """安全的计算器:只允许数字和 + - * / ( ) 空格"""
    import re
    if not re.fullmatch(r"[\d\s+\-*/().]+", expression):
        return "错误:包含非法字符"
    try:
        # 注意:eval 有风险,这里已用正则白名单过滤,仅作演示
        result = eval(expression, {"__builtins__": {}}, {})
        return f"{expression} = {result}"
    except Exception as e:
        return f"计算失败:{e}"

def get_current_time() -> str:
    """返回当前的日期和时间"""
    now = datetime.now()
    return now.strftime("%Y-%m-%d %H:%M:%S 星期") + \
           ["一", "二", "三", "四", "五", "六", "日"][now.weekday()]

def get_weather(city: str, date: str = "今天") -> str:
    """模拟天气查询(真实项目里换成调用天气 API)"""
    # 这里用 mock 数据演示,真实场景换成和风天气/心知天气等 API
    mock = {
        ("上海", "今天"): "晴,22-30℃,东南风 3 级",
        ("上海", "明天"): "小雨,18-24℃,东北风 4 级",
        ("北京", "今天"): "多云,15-28℃,北风 2 级",
        ("北京", "明天"): "晴,16-29℃,微风",
    }
    return mock.get((city, date), f"暂无 {city}{date} 的天气数据(mock)")

# 工具名到函数的映射表(执行时按名字查找)
TOOL_REGISTRY = {
    "calculate": calculate,
    "get_current_time": get_current_time,
    "get_weather": get_weather,
}

# 工具的 JSON Schema 描述(告诉 LLM 有哪些工具、怎么用)
# 即 Function Calling 的 tools 参数
TOOLS_SCHEMA = [
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "数学计算器。输入一个数学表达式(如 '12*8+5'),返回计算结果。",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "数学表达式,如 '12 * 8 + 5'"
                    }
                },
                "required": ["expression"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "获取当前的日期、时间和星期几。",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询某城市某天的天气(温度、天气状况、风力)。",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名,如 '上海'"},
                    "date": {"type": "string", "description": "日期,'今天' 或 '明天'"}
                },
                "required": ["city"]
            }
        }
    }
]

# ===== 3. Agent 主循环(ReAct 的代码化身)=====
SYSTEM_PROMPT = """你是一个乐于助人的 AI 助手,可以通过调用工具来完成任务。
你的工作方式是 ReAct:先思考(Thought)需要什么信息,再调用合适的工具(Action),
观察工具返回的结果(Observation),然后继续思考,直到能给出最终答案。
你可以连续调用多次工具。如果不需要工具,直接回答即可。"""

def run_agent(user_query: str, max_steps: int = 10) -> str:
    """运行 Agent。max_steps 防止它陷入死循环(重要!)"""
    # 对话历史 = Agent 的"短期记忆"
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_query},
    ]

    for step in range(1, max_steps + 1):
        print(f"\n=== Agent 第 {step} 步 ===")

        # ① 把对话历史 + 工具列表 发给 LLM
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            tools=TOOLS_SCHEMA,        # 把工具告诉 LLM
            temperature=0,             # Agent 场景通常要稳定,温度设 0
        )
        msg = response.choices[0].message

        # ② 把模型的回复加进记忆(无论它是要调工具还是直接答)
        messages.append(msg.model_dump(exclude_none=True))

        # ③ 判断:模型想调工具,还是已经答完了?
        if not msg.tool_calls:
            # 没有工具调用,模型给出了最终答案,退出循环
            print(f"最终答案:{msg.content}")
            return msg.content

        # ④ 模型点名要调工具,逐个执行
        for tool_call in msg.tool_calls:
            name = tool_call.function.name
            args = json.loads(tool_call.function.arguments)
            print(f"调用工具 {name}({args})")

            # 执行真正的函数(这就是 Action)
            func = TOOL_REGISTRY.get(name)
            if func is None:
                observation = f"错误:未知工具 {name}"
            else:
                observation = str(func(**args))
            print(f"观察结果:{observation}")

            # 把工具结果塞回对话历史(这就是 Observation)
            # 格式必须是 role="tool" + tool_call_id 对应
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": observation,
            })
        # 一轮结束,回到循环顶部,让 LLM 看着新结果继续思考

    print("警告:达到最大步数,强制停止(可能陷入死循环)")
    return "Agent 未能在限定步数内完成任务"

# ===== 4. 试跑 =====
if __name__ == "__main__":
    # 试一个需要多步的任务
    result = run_agent(
        "我现在要出门去上海出差,帮我算一下(1280 + 560)* 0.85 是多少钱,"
        "顺便告诉我现在几点、明天上海天气如何,最后给我一个出差小提示。"
    )
    print("\n" + "=" * 50)
    print("Agent 最终输出:")
    print(result)

4.3 运行起来会看到什么

跑这段代码,控制台会打印出 Agent 内部的整个思考过程,类似这样:

text
=== Agent 第 1 步 ===
调用工具 calculate({'expression': '(1280 + 560) * 0.85'})
观察结果:(1280 + 560) * 0.85 = 1564.0
调用工具 get_current_time({})
观察结果:2026-07-04 14:23:11 星期六
调用工具 get_weather({'city': '上海', 'date': '明天'})
观察结果:小雨,18-24℃,东北风 4 级

=== Agent 第 2 步 ===
最终答案:(1280+560)*0.85 = 1564 元;现在是 2026-07-04 周六 14:23;
明天上海小雨 18-24℃,记得带伞和外套。出差小提示:周六出行人少,
高铁/打车都方便,但带伞防雨。

注意三个细节,每一个都是 Agent 的"自主性"体现:

  1. 它在第 1 步就同时调了 3 个工具(calculate、get_current_time、get_weather),这是 Function Calling 的"并行调用"能力,LLM 自己判断这三个工具互不依赖,可以一起调。
  2. 它在第 2 步选择不再调工具,而是综合三个观察结果直接给答案,这就是"Final Answer"出口。
  3. 整个过程你只写了一句 prompt,剩下全是 LLM 自己决定的。这就是 Agent。

4.4 这段代码为什么是"最小 Agent"

ReAct 概念代码里对应
Thought(思考)LLM 在 response.choices[0].message 里的推理
Action(行动)func(**args) 执行工具那一行
Observation(观察)messages.append({"role": "tool", ...}) 喂回去
循环外层的 for step in range(...)
Final Answerif not msg.tool_calls: return msg.content
记忆messages 列表
工具TOOLS_SCHEMA + TOOL_REGISTRY
大脑client.chat.completions.create

你会发现,ReAct 那张抽象的循环图,每一格都对应代码里一两行。这就是为什么反复强调"Agent 不是玄学":它就是把人类解决问题的过程,用代码精确地复刻出来。

4.5 三个一定要知道的细节

① 为什么 temperature=0? Agent 是干活的,不是写诗。我们要它稳定、可复现,每次给同样的输入走同样的步骤,所以温度设 0(或很低)。创作型任务才需要高温度。

② 为什么要有 max_steps? 这是 Agent 最重要的安全阀。LLM 有时会"卡住":反复调同一个工具,或者陷入"我需要更多信息、调工具、还是不够、再调"的死循环。没有 max_steps,你的账单会爆掉。任何严肃的 Agent 都必须有最大步数 / 最大 token / 最大时长三道闸。

③ 为什么 tools 里要写详细的 description? LLM 是根据 description 来判断"现在该不该调这个工具"的。描述写得越清楚,它选工具越准。写工具描述其实是 Agent 开发里最被低估的"手艺活",很多 Agent bug 的根因就是工具描述太模糊,LLM 选错了工具。

4.6 小结

你已经从零写出了一个能跑的 Agent。它没有任何框架依赖,60 行核心代码,却已经具备"自主规划 + 自主选工具 + 多步推理"三大能力。 市面上所有 Agent 框架(LangChain 的 Agent、LangGraph、AutoGen)本质上都是在这 60 行外面加工程化包装:记忆管理、错误重试、并发、可观测性。理解了这 60 行,你就理解了所有 Agent 框架的内核。

Part D:Agent 的进阶机制

五、Agent 的规划策略:ReAct 不是唯一选择

ReAct 是"边想边做",但有时候任务太大,光靠边想边做会乱。下面介绍三种常见的规划策略,它们不是替代 ReAct,而是 ReAct 的"前置工序"或"变种"。

5.1 生活类比:做饭的三种策略

  • ReAct(边想边做):打开冰箱看到啥,决定做啥,做到一半发现少了盐,下楼买盐,回来继续。灵活但容易乱。
  • 任务分解(先拆再干):做一桌菜前,先列清单"四个菜一个汤",每个菜再拆"洗、切、炒、装盘"。有条理。
  • Plan-and-Execute(先全计划好再执行):先把整桌菜的所有步骤写成菜谱贴墙上,再严格按菜谱一步步做。最稳但最不灵活。

5.2 任务分解(Task Decomposition)

思路:先让 LLM 把大任务拆成一串小任务,再逐个解决。适合"写一份市场调研报告""做一个网页"这类复合任务。

python
# 伪代码:任务分解的思路
plan = llm("把这个任务拆成 3-7 个子任务:", user_goal)
# plan = ["1. 搜集竞品信息", "2. 分析价格区间", "3. 总结建议"]
for subtask in plan:
    result = mini_agent(subtask)  # 每个子任务交给一个小 Agent
    context.append(result)
final_report = llm("根据以下信息写报告:", context)

5.3 Plan-and-Execute(先计划后执行)

思路:第一步让一个"规划 Agent"产出完整的分步计划,第二步让一个"执行 Agent"严格按计划走,每完成一步回头更新计划。优点是可控、省 token(执行 Agent 不用每次都重新规划),缺点是不够灵活(计划错了难回头)。

经典实现参考 LangGraph 的 Plan-and-Execute 教程。

5.4 ReWOO(Reasoning WithOut Observation)

思路:把"规划"和"调用工具"解耦:规划阶段一次性生成所有工具调用(用占位符表示依赖),执行阶段批量调工具,最后把结果填回占位符。好处是大幅减少 LLM 调用次数(省钱),适合工具调用密集、依赖关系明确的任务。

举例:规划阶段产出 #E1 = search("上海天气")、#E2 = search("北京天气")、#E3 = compare(#E1, #E2),然后一次性执行 E1、E2(可并发),再执行 E3。

5.5 三种策略对比

策略灵活度成本适合场景
ReAct(边想边做)高高(每步都调 LLM)探索性任务、信息不全
任务分解中中复合任务、写报告
Plan-and-Execute低低流程明确的任务
ReWOO中最低工具调用密集、依赖明确

实战建议:从 ReAct 起步(最简单、最灵活),任务变复杂了再上任务分解或 Plan-and-Execute,对成本敏感且依赖明确时考虑 ReWOO。

5.6 小结

规划策略是 Agent 的"大脑皮层"。ReAct 是默认选择,任务分解 / Plan-and-Execute / ReWOO 是针对不同场景的优化。没有最好的策略,只有最适合任务的策略,这也是 AI 工作流工程师的核心判断力。

六、Agent 的记忆机制:让 Agent 不变成"鱼的记忆"

6.1 生活类比:金鱼和老人

金鱼的记忆只有 7 秒,每次遇到新情况,它都得从头认识世界。这就是没有记忆的 Agent:对话一长,它就忘了前面说过什么,开始前后矛盾、重复提问。

老人的智慧来自几十年积累的经验,遇到类似的事,能想起上次怎么处理的。这就是有长期记忆的 Agent:能记住你的偏好、上次的对话、项目的来龙去脉。

6.2 三种记忆

① 短期记忆(Short-term Memory)= 对话历史

就是上文 mini_agent.py 里的 messages 列表。每轮对话、每次工具调用、每次观察,都 append 进去。它是 Agent 的"工作台",所有当前推理都基于它。

问题:context window(上下文窗口)是有限的。GPT-4o 是 128K token,看似很大,但一个长任务 Agent 调几十次工具、读几十个网页,很快就会塞爆。塞爆后要么报错,要么模型"忘记"前面的内容。

② 记忆总结(Summary)= 防爆阀门

当对话历史太长,我们用一个 LLM 调用,把前面的几十轮总结成几句话,再用这几句话替换掉冗长的历史。就像开会时写"会议纪要":你不需要记住每个人说的每句话,记住结论就行。

python
# 伪代码:记忆总结
if len(messages) > MAX_MESSAGES:
    # 把前面的历史让 LLM 总结
    summary = llm("请把以下对话总结成要点:", messages[:50])
    messages = [system_prompt, {"role": "summary", "content": summary}] + messages[50:]

③ 长期记忆(Long-term Memory)= 向量数据库

把重要的信息(用户偏好、历史决策、知识库)向量化存进向量库,本质就是 RAG 那一套。Agent 每次开始任务时,先把当前问题向量化,去向量库召回 Top-K 相关记忆,塞进上下文。

这其实就是 RAG 的思路:RAG 是 Agent 长期记忆的实现方式。

6.3 一个综合记忆架构

生产级 Agent 通常三层都用:

text
用户提问
   ↓
[长期记忆] 向量库召回相关历史经验 → 注入上下文
   ↓
[短期记忆] 当前对话历史 + 召回的记忆 → 一起发给 LLM
   ↓
...Agent 循环...
   ↓
[记忆总结] 历史超长时,自动压缩成要点,腾出 context 空间
   ↓
[长期记忆] 任务结束后,把这次的结论再存回向量库

6.4 小结

记忆是 Agent 的"经验积累系统"。短期记忆管当前任务,长期记忆管跨任务的经验,记忆总结防止上下文爆炸。没有长期记忆的 Agent,每次都从零开始;有了长期记忆,它越用越懂你。

七、Agent vs Function Calling vs Workflow(重要澄清)

这是新手最容易混淆的三兄弟,这里彻底讲清楚。

7.1 三者的关系

三者是一条能力递进链:Function Calling 是一次工具调用,你决定调几次,模型决定调哪个;Agent 是 LLM 在循环里自主决定调几次工具;Workflow 是你预先画好的多步骤流程,每个节点里可能用到 LLM。可控性上 Workflow 最高、Agent 最低,灵活度上正好相反。

7.2 用一句话加一个场景区分

概念一句话定义典型场景谁决定步骤
Function CallingLLM 调用一次工具(你触发,模型选工具)"查上海天气"然后调一次天气 API你决定"调几次",模型决定"调哪个"
AgentLLM 在循环里自主调多次工具(LLM 触发)"帮我策划一次出差,包括订票查天气算预算"LLM 自己决定调几次、调哪个
Workflow你预先画好的多步骤流程,每步可能用 LLM"邮件、分类、摘要、入库"的固定流水线你(开发者)写死步骤

7.3 三个本质区别

① 谁掌握控制权?

  • FC:你在代码里调用,模型只负责"这次该不该用工具"。
  • Agent:模型在循环里自己决定"继续调工具还是收工",控制权部分交给 LLM。
  • Workflow:你完全掌握控制权,LLM 只是流程里的"打工人",每一步干啥你写死了。

② 确定性还是概率性?

  • Workflow 是确定性的:同样的输入,永远走同样的步骤(除非你在某个节点用了 LLM 才有概率性)。
  • Agent 是概率性的:同样的输入,它可能这次调 3 个工具,下次调 5 个,甚至走完全不同的路径。
  • FC 介于两者之间:单次调用是概率的,但整体流程是你控制的。

③ 什么时候用哪个?

  • 流程清晰、固定、要可审计,用 Workflow(如客服分流、报表生成)。
  • 任务开放、多变、需要灵活决策,用 Agent(如调研、规划、coding)。
  • 只是让 LLM 用一两个工具补能力(查个天气、算个数),Function Calling 就够了,别上 Agent(杀鸡用牛刀)。

7.4 小结(一段话记住)

Function Calling 是 Agent 的"原子能力",Agent 是 Function Calling 的"自主循环版",Workflow 是与 Agent 平行的"另一条路线"(确定性对概率性)。 真实项目里,它们经常组合:Workflow 编排大流程,某些节点内部跑一个 Agent,Agent 内部用 Function Calling。

八、Agent 的局限与坑:为什么需要"可控 Agent"

讲到这里你可能热血沸腾,觉得 Agent 无所不能。冷静一下。 Agent 是 LLM 应用里最难做稳的部分,坑多得能写一本书。下面这五个坑,每一个都会在生产环境咬你一口。

8.1 五大经典坑

坑 1:死循环(Infinite Loop)

LLM 反复调同一个工具,或者陷入"我需要更多信息、调工具、还是不够、再调"的死循环。前面我们的 max_steps 就是防这个的。生产级 Agent 还要加:相同参数重复调用检测、单次任务最大 token、单次任务最大时长。

坑 2:烧钱(成本不可控)

单次 LLM 调用几分钱,Agent 一个任务循环 20 步就是 20 次调用,再每次塞一长串对话历史,token 消耗指数级上升。一个复杂 Agent 任务跑掉几块钱甚至几十块钱很正常。对策:缓存工具结果、限制历史长度、用便宜模型做规划用强模型做关键决策。

坑 3:错误累积(Error Cascade)

Agent 第 1 步选错了工具 / 读错了网页,第 2 步基于错误结果继续推理,第 3 步错上加错,到最后输出一个"看起来很合理但完全跑偏"的答案。LLM 很少自己发现前面错了。对策:关键步骤加验证、加反思(Reflection)机制。

坑 4:不可控 / 不可预测

同样的输入,Agent 今天走 A 路径,明天走 B 路径。这在需要审计、合规、可复现的企业场景是致命的。老板问你"为什么 Agent 给客户发了这封邮件",你答不上来。对策:日志记录每一步、关键决策加人工审批。

坑 5:幻觉 + 工具误用

LLM 会"编造"工具的返回结果(明明工具返回空,它说"查到了"),或者调一个不存在的工具、传错误的参数。对策:工具返回要结构化、加 schema 校验、让 LLM 在工具失败时明确知道"失败了"。

8.2 这些坑催生了什么

正因为纯 ReAct Agent 太"野",业界发展出两个方向的解法:

① HITL(Human-in-the-Loop,人在回路)

关键步骤(发邮件、转账、删数据、发布上线)不让 Agent 自动执行,而是暂停下来等人工确认。Agent 负责干累活,人负责把关关键动作。这是企业级 Agent 的标配。

② 可控 Agent 框架(如 LangGraph)

把 Agent 从"一个自由循环"变成"一张有边界的状态图":你定义好 Agent 能走的节点、节点之间的转移条件、哪里必须暂停等人确认、哪里可以并发。LangGraph 本质上是 Workflow 和 Agent 的融合:保留 Agent 的自主决策,但套上 Workflow 的可控骨架。

8.3 小结

Agent 很强大,但自主性 = 不可控性 = 风险。生产级 Agent 的核心命题不是"让它更聪明",而是"在保持自主的同时,把它装进可控的笼子里"。这个笼子,就是 HITL 加可控框架(LangGraph)。

九、本章小结

  1. Agent 的本质是"目标驱动 + 自主循环":你给目标不给步骤,LLM 在循环里自己决定调不调工具、调几次,直到它判断任务完成。
  2. 四大组件缺一不可:大脑(LLM)是核心,记忆(Memory)、工具(Tools)、规划(Planning)是它的外挂。
  3. ReAct 是 Agent 的核心心智模型:Thought、Action、Observation 循环加一个 Final Answer 出口。它就是"把人类思考过程教给 LLM"。
  4. 手写最小 Agent 只要 60 行:openai SDK 的 tools 参数加一个 while 循环就构成 Agent。所有框架都是在这外面加工程化包装。理解了这 60 行就理解了所有 Agent 框架的内核。
  5. 规划策略有四选:ReAct(默认)、任务分解、Plan-and-Execute、ReWOO(最省钱)。没有最好,只有最适合。
  6. 记忆有三层:短期(对话历史)、长期(向量库)、总结(防爆阀门)。RAG 就是 Agent 长期记忆的实现方式。
  7. FC / Agent / Workflow 三兄弟:FC 是单次工具调用,Agent 是 FC 的自主循环版,Workflow 是确定性的预定义流程。三者常组合使用。
  8. Agent 五大坑:死循环、烧钱、错误累积、不可控、幻觉误用。生产化的解法是 HITL 加可控框架(LangGraph)。
  9. 一句终极认知:Agent 的核心代码很简单,难的是把它做稳、做省、做可控。 这正是 AI 工作流工程师的核心价值。

十、动手练习

练习 1(基础):把上文的 mini_agent.py 跑起来,问它"现在几点?123 乘以 456 等于多少?"观察它是怎么一次性并行调两个工具的。然后改一下 get_weather 的 mock 数据,加上你所在城市,问它"明天我该穿什么",看看它的 ReAct 推理过程。把你看到的 Thought-Action-Observation 三轮打印贴在笔记里。

练习 2(进阶):给 Agent 加一个新工具 search_wiki(keyword),用 requests 调 Wikipedia 的 API(https://en.wikipedia.org/api/rest_v1/page/summary/{keyword})返回摘要。然后问它"鲁迅是谁?他生于哪一年?"观察它怎么自己决定调这个新工具。体会:加工具不用改 Agent 主循环,只改 TOOLS_SCHEMA 和 TOOL_REGISTRY,这就是工具扩展的优雅之处。

练习 3(挑战):给 Agent 加"记忆总结"机制:当对话历史超过 20 条消息时,调用一次 LLM 把前面的历史总结成 200 字以内的要点,替换掉旧历史。然后让 Agent 处理一个长任务(比如"帮我调研三家国产新能源车的价格和续航并对比"),观察总结前后 token 消耗的变化。你已经亲手实现了 Agent 的防爆阀门。

练习 4(思考题):找一个你自己工作里的真实场景,判断它该用 FC、Agent 还是 Workflow,并写出理由。例如:"每天早上自动把昨天的客户邮件分类汇总发给我",这是 Workflow 还是 Agent?为什么?用第 7 节的"谁掌握控制权"标准检验一遍你的判断。

十一、延伸阅读


把 Agent 装进"笼子",是本文之后的进阶方向。 本文带你从原理到代码走通了第一遍:Agent 的本质是一个"LLM 自主调工具"的 while 循环,60 行核心代码就够。但你也看到了 Agent 的五大坑:死循环、烧钱、错误累积、不可控。这些坑,单靠 ReAct 解决不了。 LangGraph 这类可控 Agent 框架正是为此而生:它把 Agent 从"自由循环"变成"有边界的状态图",用节点(Node)和边(Edge)画出 Agent 流程图,在关键节点强制暂停等人审批(HITL),并支持多个 Agent 协作。如果说本篇教的是 Agent 的"心法",可控框架教的就是把它装进"笼子"的"招式",值得作为你的下一步。

相关文章

分享: