如何实现一个智能体的自主循环

“自主循环”不是模型自己神奇地循环起来的,而是由外部代码实现事件循环(controller/driver)来“让模型自主”。模型在每一轮只做两件事:决定下一步要做什么(计划/选工具/给参数),或给出最终答案。代码负责执行工具、收集环境反馈、把结果喂回模型,并控制停止条件与安全。

一个智能体的自主循环:

  • 控制器(event loop/driver)
    • 负责循环调用 LLM、解析其结构化输出、执行工具、记录日志、判断停止条件。
  • 工具注册与执行器
    • 定义可用工具(函数签名/参数模式),执行真实动作(检索、代码运行、文件读写、API 调用等),并返回“可读的观察结果”。
  • 状态与记忆
    • 保存任务目标、历史对话、工具调用日志、重要中间结论的摘要(避免上下文爆炸)。
  • 策略与提示词
    • 规范模型的角色、目标、可用工具及其用法、约束(预算、风险、风格)、输出格式(如 JSON schema)。
  • 评估与安全
    • 解析/校验模型输出(JSON schema 校验)、超时/重试、越权拦截、配额与成本控制、沙箱执行。
  • 停止条件
    • 最大步数、时间/成本预算、置信度达标、用户中断、无进展检测等。

循环是如何一步一步运转的

  1. 初始化
    • 收到用户目标与约束;加载可用工具清单与参数模式;设置 max_steps/预算。
  2. 感知与计划(LLM)
    • 控制器把“历史 + 目标 + 工具说明 + 上一步观察”发给 LLM,请其输出 structured action(如:call_tool 或 final_answer)。
  3. 执行动作(代码)
    • 若是工具调用:按名称与参数执行真实工具,拿到观察结果(或错误)。
  4. 记录与反馈
    • 把工具结果追加到对话/日志中(可同时做摘要),再喂回 LLM。
  5. 评估与停止
    • 命中停止条件或 LLM 给出 final_answer 则结束;否则回到第 2 步。

最小化伪代码

说明:实际项目建议使用严格的 JSON/函数调用协议与模式校验。

tools = {"search": search_fn, "run_code": run_code_fn, "write_file": write_file_fn}
state = {"steps": 0, "log": []}

while state["steps"] < MAX_STEPS and budget_ok():
    prompt = build_prompt(user_goal, tools_schema=describe(tools), history=state["log"])
    resp = llm(prompt)  # 要求输出:{"type": "final" | "tool", "name": "...", "args": {...}, "reasoning": "..."}
    if resp["type"] == "final":
        return resp["content"], state["log"]
    elif resp["type"] == "tool":
        if resp["name"] not in tools: 
            obs = "TOOL_ERROR: unknown tool"
        else:
            try:
                obs = tools[resp["name"]](**resp["args"])
            except Exception as e:
                obs = f"TOOL_ERROR: {e}"
        state["log"].append({"action": resp, "observation": obs})
        state["steps"] += 1
    else:
        # 让模型自我修复输出格式
        state["log"].append({"observation": "FORMAT_ERROR: please return valid JSON per schema"})

自主循环解读

整体作用

  • 这是一个“自主循环执行器”:模型每次决定要不要结束(final)或调用工具(tool),系统负责执行工具、记录日志,然后继续下一轮,直到达到步数上限或预算耗尽。
  • 它把“思考(LLM)”和“行动(工具)”串成一个可控的回路。

关键变量与结构

  • tools: 定义了可调用的工具集合(搜索、执行代码、写文件)。
  • state: 维护当前状态,包含已走的步数 steps 和历史日志 log
  • while 循环:只要步数没超上限、预算也允许,就持续运行。

每轮循环做了什么

  • build_prompt(...):构造模型输入,包含用户目标、工具说明、历史记录。
  • resp = llm(prompt):模型必须返回结构化结果(JSON),告诉系统是结束还是用工具。
    • 预期格式包含 type(final/tool)、nameargsreasoning

响应分支逻辑

  • type == "final":模型认为任务完成,直接返回结果和执行日志。
  • type == "tool":模型要用工具
    • 若工具名不存在:记录 TOOL_ERROR: unknown tool
    • 否则尝试执行工具,捕获异常并写入 TOOL_ERROR: ...
    • 把“动作 + 观察结果”记录到 state["log"],并把步数加一
  • 其它情况(格式不合法):写入 FORMAT_ERROR,提醒模型按 JSON 规范输出

为什么这样设计

  • 这是一种“安全可控的自主循环”:模型只能通过工具做事,系统有明确的上限、预算 and 错误处理。
  • 日志是关键:下一轮会把历史带入 prompt,使模型能根据结果继续推理和修复。

如果你希望,我也可以把这段逻辑用流程图或更简化的伪代码再拆一层。

是否必须用框架

不必须。一个简单的 agent loop 用几十行代码即可。框架能加速开发并提供可视化/监控,但也会引入抽象层。建议先直连 API 做出最小可用循环,再评估是否引入框架。

ReAct 模式

那段伪代码就是典型的“ReAct 式”框架在工程上的落地版本:模型在“思考/决定”后选择 finaltool,系统执行工具并把观察结果写回历史,再进入下一轮。这正是 ReAct 的核心:Reasoning + Acting + Observation 的循环闭环。

简单对应关系:

  • Reasoningllm(prompt) 产出决策
  • Actingresp["type"] == "tool" 调用工具
  • Observationobs 写入 state["log"]
  • 循环控制:MAX_STEPSbudget_ok()

所以是“ReAct 思路 + 工程化执行器”的结合。