Skip to content

原文:https://www.mstx.cn/harness/411.html · 创建:2026-09-02 · 更新:2026-09-02 · 阅读:1322


从一段“魔法”代码说起 ​

在1.5节中,你跑通了第一个Agent。那段代码里有一个run_agent函数,它做了一件看似简单的事:反复调用LLM,直到得到一个最终回答。

如果你仔细看那段代码,会发现一个隐藏的结构:

python
for turn in range(max_iterations):
    response = call_llm(messages, tools)   # 思考
    if response.tool_calls:
        for tool_call in response.tool_calls:
            result = execute_tool(tool_call)  # 行动
            messages.append(result)           # 感知
    else:
        return response.content

这个结构——调用LLM、执行工具、把结果放回去、再调用LLM——就是Agent的核心循环。它看起来简单,但理解它的每个环节、以及这些环节之间的微妙关系,是构建可靠Harness的基础。

循环的三个阶段 ​

Agent的每一次迭代,都可以拆解为三个步骤:

用户输入:"帮我查一下北京天气,如果下雨就提醒我带伞"
    │
    ▼
┌─────────────────────────────────────────────┐
│  感知(Perceive)                            │
│  · 用户说了什么?                            │
│  · 工具刚返回了什么?                        │
│  · 之前的推理进行到哪一步了?                 │
└──────────────────┬──────────────────────────┘
                   │ 完整的对话历史 = 感知的全部依据
                   ▼
┌─────────────────────────────────────────────┐
│  思考(Think)                               │
│  · 我理解用户想要什么吗?                     │
│  · 我有哪些工具可以用?                       │
│  · 下一步该调用工具,还是给出回答?            │
│  · 如果调用工具,参数是什么?                 │
└──────────────────┬──────────────────────────┘
                   │ LLM的决策 = 文本 or 工具调用
                   ▼
┌─────────────────────────────────────────────┐
│  行动(Act)                                 │
│  · 执行LLM指定的工具                          │
│  · 将工具结果格式化为消息                      │
│  · 追加到对话历史中,进入下一轮                │
│  · 或者:LLM认为任务完成,输出最终回答         │
└─────────────────────────────────────────────┘

这个循环的本质是:每完成一次“行动”,就把行动的结果反馈给“感知”,触发下一轮“思考”。 这就是为什么Agent能够处理复杂任务——它不是一次性想出所有步骤,而是走一步,看一步,再想下一步。

为什么循环如此重要 ​

你可能会问:这有什么特别的?不过是一个while循环而已。因为对于Agent来说,这个循环就是它的“意识流”。

人类解决复杂问题时,也是这个模式:你拿到一个问题,先想想手头有什么工具(知识、人可以问、电脑可以查),然后做一步,看看结果,再想下一步。你不会在动手之前就把所有步骤规划得天衣无缝——你会根据中间结果调整策略。

Agent的循环也是这样。它不知道完成一个任务需要几步,也不知道每一步会遇到什么。它只能:

  • 基于当前信息判断下一步做什么
  • 执行那一步
  • 看到结果后重新判断

这就是为什么Agent应对开放性任务的能力远超传统程序——传统程序必须在编码时就穷举所有可能路径,而Agent可以在运行时发现路径。

一个完整的多轮示例 ​

让我们看一个稍微复杂的例子,感受循环是怎么“一步步推进”的。

用户目标:“帮我查一下我们团队下周有没有人过生日,如果有,帮我选一个合适的礼物。”

循环过程:

【第1轮】
感知:用户想查下周日历,然后根据结果决定是否买礼物。
     我有工具:query_calendar(查日历)、search_gifts(搜礼物)、add_to_cart(加购物车)
思考:第一步应该查日历。需要知道"下周"的具体日期范围。
     今天是2026-08-27(周四),下周是8月31日-9月6日。
行动:调用 query_calendar(team="我的团队", start="2026-08-31", end="2026-09-06")

【第2轮】
感知:工具返回——"张三:9月3日生日"。确实有人过生日。
思考:需要为张三选礼物。但我不知道张三喜欢什么。
     我可以搜通用礼物,或者先查有没有关于张三偏好的信息。
     我的工具中没有"查同事偏好",所以先搜通用礼物。
行动:调用 search_gifts(keyword="同事生日礼物", price_range="100-300元")

【第3轮】
感知:工具返回——5款推荐礼物(蓝牙耳机、机械键盘、保温杯、书、桌游)。
思考:这些选择差别很大。张三是什么样的人?
     我只有日历工具和购物工具,没有同事信息。
     我应该把这些选项呈现给用户,让用户选择,而不是自己决定。
行动:向用户展示选项,请求选择。

【第4轮】
感知:用户说"张三喜欢打游戏,选键盘吧"。
思考:用户提供了关键信息。张三喜欢游戏 → 机械键盘合适。
     我应该把机械键盘加入购物车。
行动:调用 add_to_cart(item="机械键盘", recipient="张三", occasion="生日")

【第5轮】
感知:工具返回——已成功加入购物车。
思考:任务完成。我可以总结一下做了什么。
行动:输出最终回答。

这个过程中有几点值得注意:

第一,Agent在每一轮都在做“信息差距分析”。 它不断问自己:“我知道什么?我需要知道什么?我有什么工具可以填补这个差距?”

第二,Agent知道自己的知识边界。 在第3轮,它没有假装知道张三喜欢什么,而是把决策权交还给用户。这是好的Agent行为——知道自己不知道什么。

第三,循环的长度是不可预知的。 这个任务用了5轮。如果中间出了错(比如日历工具返回格式异常),可能更多。Harness必须为这种不可预知性做好设计。

循环中的关键设计决策 ​

理解了循环的基本结构后,让我们深入几个关键的设计决策。这些决策直接影响Harness的质量。

决策1:LLM如何表达“思考”的结果?

LLM的“思考”输出有两种可能:

  • 最终回答:一段直接面向用户的文本
  • 工具调用请求:一个结构化的指令,指定要调用哪个工具和什么参数
python
// LLM的思考输出 = 工具调用请求
{
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "query_calendar",
        "arguments": "{\"team\":\"我的团队\",\"start\":\"2026-08-31\",\"end\":\"2026-09-06\"}"
      }
    }
  ]
}

这个设计看似简单,但有一个微妙的点:LLM可能会同时请求多个工具调用。比如它认为“查天气”和“查日历”这两个操作彼此独立,可以在同一个响应中一起请求。Harness需要决定:并行执行还是串行执行?

并行执行更快,但风险是:如果第二个工具的参数依赖于第一个工具的结果,并行就会出错。简单Harness可以串行执行,进阶Harness可以并行执行并在失败时降级为串行。

决策2:如何管理对话历史?

对话历史是Agent“感知”的全部依据。它的管理方式直接影响Agent的表现和成本。最朴素的方式,把所有消息(用户消息、LLM响应、工具结果)全部追加到一个列表里,每次调用LLM时全部发送。但是问题来了:上下文窗口有上限。当对话超过一定长度时,你需要截断。截断哪些?最旧的消息?工具结果?系统提示词?没有标准答案,只有权衡。

此外,对话历史中既有面向LLM的消息(让它理解上下文),也有面向用户的消息(最终回答)。这两者并不总是重合。Harness需要维护好这个区别。

第5章会专门讨论记忆管理。这里你只需要知道:对话历史的管理策略,是循环设计的核心难点之一。

决策3:循环什么时候终止?

循环不能无限进行。终止条件需要明确:

  • 正常终止:LLM给出了最终回答(没有tool_calls)
  • 达到最大迭代次数:循环了N轮还在调用工具,强制停止
  • 超时:总耗时超过限制
  • 用户中断:用户发送了停止信号
  • 工具执行失败且无法恢复:某个关键工具连续失败

每种终止方式都需要被记录和报告。一个Agent在“最大迭代次数”处终止,和“正常终止”是不同的情况——前者可能意味着任务没有完成,后者意味着完成了。

在1.5节的代码中,最大迭代次数是5。如果Agent在5轮内没有完成任务,它会返回一个警告消息。这是一个粗糙但必要的安全阀。

决策4:循环中的错误如何处理?

工具可能失败。API可能超时。LLM可能返回格式异常的响应。

这些问题发生在循环的任何一步,Harness必须决定:

  • 重试:同一个工具调用,重新执行一次
  • 告知LLM并让它决定:把错误信息作为工具结果返回,让LLM重新思考
  • 终止循环:错误无法恢复,停止执行

把错误信息返回给LLM是一个有趣的设计。LLM看到“工具执行失败:网络超时”后,可能会选择换一个工具、改变策略、或者告知用户它做不到。这种让LLM参与错误恢复的做法,是Agent优于传统程序的关键特性之一。

循环的“隐形成本” ​

最后,让我们谈谈循环的成本。

每一次循环 = 一次LLM调用 + 可能的工具执行 + 对话历史的增长。

假设一个Agent平均需要5轮完成一个任务,每轮调用gpt-4o的费用是$0.01,那么一个任务的成本是$0.05。如果每天有10,000个任务,每天的成本是$500。这还不包括工具执行的资源消耗。

更重要的是延迟。每一轮循环需要等待LLM的响应(1-5秒),5轮就是5-25秒。用户能接受的等待时间是有限的。减少循环次数,是Agent工程中一个持续的优化目标。

第9章会讨论如何通过可观测性来追踪和优化这些成本。这里你只需要意识到:循环的次数 = 成本 + 延迟。每一次不必要的循环,都是在烧钱和考验用户耐心。

从循环到Harness ​

理解了循环的基本结构,你就理解了Harness的“心脏”。Harness的所有其他模块,都是为这个循环服务的:

  • 工具系统(第4章):让循环中的“行动”更安全、更规范
  • 记忆系统(第5章):让循环中的“感知”更精准、更持久
  • 推理与规划(第6章):让循环中的“思考”更可靠、更结构化
  • 评估体系(第7章):度量这个循环的整体表现
  • 可观测性(第9章):记录这个循环的每一步
  • 安全层(第10章):确保这个循环不会越界

在下一节中,我们将把这个循环放进Harness的整体架构中,看看Runtime、Tool Registry、Memory、Evaluator这些模块是如何围绕循环组织的。

本节小结 ​

Agent的核心循环是感知→思考→行动的反复迭代。它的力量来自于在运行时发现路径,而非预定义全部步骤。它的成本来自每一次循环的LLM调用和工具执行。它的复杂性来自对话历史管理、终止条件、错误处理等设计决策。

这个循环不是一个“实现细节”——它是Agent的本质。理解了它,你就理解了为什么Agent有时候表现惊艳,有时候又显得笨拙:它不是在“执行一个程序”,而是在“边走边想”。