智能体 · 循环

ReAct 循环

思考 → 行动 → 观察,直到结束

模型
Qwen3-1.7B 推理关闭
工具
lookup, calculate, finish
停止串
“\nObservation”
这次运行
4 次调用 658 个词元

全部步骤

  1. 提示词搭好循环

    系统提示词教给模型一种格式:先是 Thought,再从一列用普通文本写出的工具中选一个 Action,然后是 Observation。一个完整示例把格式演示一遍。问题还没读,就已经有 362 个词元了。

    提示词 362 个词元 · 3 个行动 · 1 个完整示例

  2. 先思考,再行动

    Qwen3-1.7B 写出一个 Thought,然后是一个 Action。给行动起名是一次普通的下一词元选择:在 “Action 1:” 之后,它把 100% 的概率放在 “lookup” 上。

    Thought k: … → Action k: tool[argument]

  3. 停下、运行工具、追加

    模型从不运行任何东西。它外面的程序盯着文本,在 “Observation” 即将出现的地方让它停下,用正则表达式读出行动,运行工具并追加真实结果。然后把上下文交还给模型。

    停下 → 解析 → 运行 → 追加 → 继续

  4. 一轮又一轮

    真实运行:两次查询,一次计算,然后 finish[20]。每个观察都来自程序,而不是模型。模型只决定查什么,以及拿结果做什么。

    4 次模型调用 · 3 个工具结果 · 答案 20

  5. 上下文在增长

    每一步都把模型的文本和工具的结果加进上下文,下次调用时再读一遍。如果在调用之间保留 KV 缓存,每个词元只处理一次(658)。如果每次都重新发送,这些调用要处理 2200 个。

    保留缓存 658 个词元 · 重新发送 2200 个

  6. 没有停止串

    没有停止串,模型会继续写下去,自己写出一个 Observation,一个格式正确的猜测。所以循环必须在 “Observation” 处截断文本,插入真实结果。

    停止串让结果保持真实

代码

while True:
    out = llm.generate(context, stop=['\nObservation'])     # a Thought and an Action, then stop
    context += out
    name, arg = re.search(r'Action \d+: (\w+)\[(.*)\]', out).groups()
    if name == 'finish': return arg
    result = TOOLS[name](arg)                                # the program runs the tool, not the model
    context += f'\nObservation {k}: {result}\nThought {k + 1}:'

延伸阅读