Skip to content

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


从循环到架构 ​

在2.1节中,我们剖析了Agent的核心循环:感知→思考→行动。这个循环本身只需要一个while循环和几次API调用就能实现。但当你把这个循环放进真实的生产环境中时,一系列问题会迫使你添加更多的结构:

  • 工具越来越多,如何统一管理和安全调用?
  • 对话越来越长,如何在有限的上下文窗口内保持Agent的记忆?
  • 循环跑了5轮,如何判断它做得好不好?
  • 出错了,如何知道在哪一步出了什么问题?

这些问题的答案,构成了Harness的架构。本节将呈现一个典型的Harness架构全景,然后逐一介绍每个核心模块的职责和它们之间的协作关系。

架构全景 ​

┌─────────────────────────────────────────────────────────────────────┐
│                           Harness                                   │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                      Agent Runtime(运行时)                  │   │
│  │  ┌────────────────────────────────────────────────────────┐  │   │
│  │  │              核心循环:感知→思考→行动                    │  │   │
│  │  │                                                        │  │   │
│  │  │   while not should_terminate():                        │  │   │
│  │  │       context = memory.get_context()       ←──┐        │  │   │
│  │  │       decision = llm.think(context)           │        │  │   │
│  │  │       if decision.is_tool_call():             │        │  │   │
│  │  │           result = registry.execute(          │        │  │   │
│  │  │               decision.tool_name,             │        │  │   │
│  │  │               decision.arguments              │        │  │   │
│  │  │           )                                   │        │  │   │
│  │  │           memory.add_tool_result(result) ─────┘        │  │   │
│  │  │       else:                                            │  │   │
│  │  │           return decision.content                      │  │   │
│  │  └────────────────────────────────────────────────────────┘  │   │
│  └──────────┬──────────────────┬─────────────────┬──────────────┘   │
│             │                  │                 │                  │
│  ┌──────────▼─────┐  ┌─────────▼──────┐  ┌───────▼───────┐          │
│  │  Tool Registry │  │  Memory System │  │   Evaluator   │          │
│  │  (工具注册表) │  │  (记忆系统)   │  │  (评估器)    │          │
│  │                │  │                │  │               │          │
│  │  · 工具定义    │  │  · 短期记忆    │  │  · 评估数据集   │          │
│  │  · Schema管理  │  │  · 长期记忆    │  │  · 评分标准     │          │
│  │  · 执行沙箱    │  │  · 会话状态    │  │  · 回归测试     │          │
│  │  · 权限控制    │  │  · 上下文窗口  │  │  · 对比实验     │          │
│  └────────────────┘  └────────────────┘  └───────────────┘          │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │              Observability Layer(可观测层)                  │   │
│  │         Trace记录 · 日志 · 指标 · 告警 · 可视化                │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │              Security Layer(安全层)                         │   │
│  │        输入过滤 · 输出审查 · 权限边界 · 审计日志                │   │
│  └──────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

这个架构图看起来复杂,但它的核心逻辑很清晰:Runtime是引擎,其他模块是引擎的支撑系统。 就像一辆车——引擎(Runtime)负责驱动,燃油系统(Memory)提供能量,转向系统(Tool Registry)控制方向,仪表盘(Evaluator)告诉你开得怎么样,后视镜(Observability)让你看到身后发生了什么,安全带(Security)在你撞车时保护你。

让我们逐一深入每个模块。

Agent Runtime:引擎 ​

职责 ​

Runtime是Harness的执行核心。它拥有循环的控制权:什么时候调用LLM、什么时候执行工具、什么时候终止。所有其他模块都是被Runtime调用的服务。

核心能力 ​

第一:循环控制。 ​

Runtime决定循环的边界条件:最大迭代次数、超时限制、终止信号处理。它还需要处理循环中的异常——LLM返回了格式错误的内容怎么办?工具抛出了未捕获的异常怎么办?

python
class AgentRuntime:
    def run(self, user_input: str) -> AgentResult:
        memory = self.memory.create_session(user_input)

        for turn in range(self.max_turns):
            # 检查超时
            if self._is_timeout():
                return AgentResult(status="timeout", ...)

            # 思考:调用LLM
            decision = self.llm.think(memory.get_context())

            # 记录轨迹
            self.tracer.log_llm_call(decision)

            # 判断:是回答还是工具调用
            if decision.is_final_answer():
                return AgentResult(status="success", content=decision.content)

            # 行动:执行工具
            result = self.registry.execute(
                name=decision.tool_name,
                arguments=decision.arguments,
                context=memory.get_context()  # 用于权限判断
            )

            # 感知:将结果加入记忆
            memory.add_tool_result(result)

        return AgentResult(status="max_turns_exceeded", ...)

第二:流程编排。 ​

在更复杂的场景中,Runtime不仅控制单一循环,还可能编排多个循环——比如一个规划Agent先制定计划,然后几个执行Agent分别完成子任务,最后汇总Agent整合结果。这种多层循环的编排,是Runtime的高级职责。

第三:状态管理。 ​

Runtime需要维护Agent执行的状态:当前处于哪一步、哪些工具已调用过、哪些信息已确认。这个状态不同于Memory——它是执行层的状态,而Memory是信息层的状态。

一个关键的设计原则 ​

Runtime应该保持尽可能薄。它的职责是编排,而不是实现。工具的执行逻辑在Tool Registry中,记忆的管理策略在Memory System中,评估的逻辑在Evaluator中。Runtime只是把这些组件按正确的顺序串起来。

这个原则很重要,因为薄Runtime = 高可测试性。你可以独立测试工具系统、记忆系统、评估器,而不需要启动整个Agent循环。当Agent出问题时,你能快速定位是哪个模块的问题。

Tool Registry:工具注册表 ​

职责 ​

Tool Registry是Agent的能力目录。它管理着Agent可以使用的所有工具:定义、Schema、执行逻辑、权限边界。

核心能力 ​

第一:工具定义与Schema管理。 ​

每个工具都需要一个明确的定义,告诉LLM这个工具是做什么的、需要什么参数。这个定义的质量直接影响LLM能否正确地选择和使用工具。

python
@tool(
    name="query_calendar",
    description="查询指定时间范围内的日历事件",
    parameters={
        "type": "object",
        "properties": {
            "start": {"type": "string", "description": "开始日期,格式YYYY-MM-DD"},
            "end": {"type": "string", "description": "结束日期,格式YYYY-MM-DD"},
            "team": {"type": "string", "description": "团队名称,默认为当前团队"}
        },
        "required": ["start", "end"]
    }
)
def query_calendar(start: str, end: str, team: str = "我的团队") -> dict:
    ...

一个好的工具描述应该像好的API文档:清晰、精确、有示例。很多Agent的失败不是因为模型不够聪明,而是因为工具描述写得太差——模型不知道这个工具能做什么、参数该怎么填。

第二:工具执行与沙箱。 ​

工具的执行不应该在Agent的进程里直接跑。想象一个工具是execute_sql,如果它直接在Agent进程中执行,一条错误的SQL可能拖垮整个系统。Tool Registry需要提供隔离的执行环境:超时控制、资源限制、异常捕获。

第三:权限控制。 ​

不是所有工具在任何情况下都可以被调用。send_email可能只允许在用户确认后调用。delete_record可能只允许特定角色的用户触发。Tool Registry需要管理这些权限规则,并在执行前进行校验。

第四:工具发现与选择。 ​

当工具数量超过几十个时,把所有工具的Schema都塞进LLM的上下文是不现实的(会消耗大量token)。Tool Registry需要提供工具发现能力——根据当前对话内容,筛选出最相关的几个工具供LLM选择。

Memory System:记忆系统 ​

职责 ​

Memory System管理Agent的信息:对话历史、用户偏好、任务状态、以及从工具结果中提取的知识。

记忆的三个层次 ​

短期记忆(Working Memory) :当前会话的对话历史。这是LLM每次调用的直接输入。它的管理核心是上下文窗口的分配——系统提示词占多少?对话历史占多少?工具结果占多少?当窗口不够时,优先丢弃什么?

长期记忆(Long-term Memory) :跨会话的持久信息。用户上次说过的偏好、历史工单的处理方式、从过往任务中积累的经验。通常通过向量数据库或知识图谱实现。

情景记忆(Episodic Memory) :Agent过去执行的完整任务记录。这不仅包括对话内容,还包括执行轨迹、工具调用序列、最终结果、用户反馈。情景记忆是Agent从经验中学习的基础。

python
class MemorySystem:
    def __init__(self):
        self.working_memory = WorkingMemory()   # 当前会话的对话历史
        self.long_term_memory = VectorStore()   # 跨会话的持久知识
        self.episodic_memory = EpisodicStore()  # 历史任务的完整记录

    def get_context(self) -> list[Message]:
        """组装当前LLM调用所需的上下文"""
        context = []
        context.extend(self.working_memory.recent_messages(max_tokens=4000))
        context.extend(self.long_term_memory.retrieve_relevant(self.working_memory.current_query))
        return context

    def add_tool_result(self, result: ToolResult):
        """工具结果加入短期记忆"""
        self.working_memory.append(result.to_message())

        # 如果工具结果包含值得长期记忆的信息,提取并存入
        if result.contains_durable_knowledge():
            self.long_term_memory.store(result.extract_knowledge())

记忆管理的核心矛盾是:上下文窗口有限,但信息无限。 你无法把所有历史都塞进LLM,所以必须选择——什么该记住、什么该忘记、什么该压缩后记住。第5章会深入讨论这些策略。

Evaluator:评估器 ​

职责 ​

Evaluator是Harness的质检系统。它在Agent执行前定义“什么是好”,在执行后度量“实际做得多好”,并在每次改动后验证“有没有变差”。

核心能力 ​

第一:评估数据集管理。 ​

评估数据集是Agent的“考试题”。它包含一组输入和对应的期望行为。期望行为可以是:

  • 精确的工具调用序列
  • 一个关键属性的判断(“Agent是否在回答前调用了查询工具”)
  • 一个由LLM-judge打分的输出质量评分
  • 一个人工标注的参考回答
python
eval_dataset = [
    {
        "input": "帮我查一下订单#12345的状态",
        "expected_tool_calls": [("check_order", {"order_id": "12345"})],
        "forbidden_actions": ["send_email"],  # 这个任务不应该触发发邮件
        "reference_answer": "您的订单已发货"
    },
    {
        "input": "把这条消息发给张三",
        "expected_behavior": "ask_confirmation",  # 应该先请求确认
        "forbidden_actions": ["send_message_without_confirmation"]
    },
    ...
]

第二:评分与指标计算。 ​

Evaluator在Agent运行后计算指标:

  • 任务完成率:Agent成功完成任务的百分比
  • 工具调用准确率:工具选择和参数正确的百分比
  • 输出质量分:由LLM-judge或人工评审打分的平均值
  • 效率指标:平均循环次数、平均token消耗、平均耗时
  • 安全指标:触发安全规则的比例

第三:回归测试。 ​

当你修改Prompt、更换模型、更新工具后,Evaluator在固定的评估集上重新运行Agent,对比新旧版本的指标。如果新版本在评估集上表现更差,你有数据支撑回滚。

第四:在线评估。 ​

评估不只在开发阶段进行。生产环境中,Evaluator可以采样一部分真实会话进行自动评估,持续监控Agent的表现是否在退化。这需要与Observability层配合。

模块之间的协作 ​

理解了各模块的职责后,让我们看一次完整的执行中,它们是如何协作的:

用户输入:"帮我查一下张三下周有没有空开会"
    │
    ▼
┌────────────────────────────────────────────────┐
│ Runtime 启动循环                               │
│                                                │
│  ┌─────────────────────────────────────────┐   │
│  │ 1. Runtime请求Memory组装上下文          │   │
│  │    Memory返回:系统提示词 + 对话历史    │   │
│  │    + 从长期记忆检索到的"张三偏好下午"   │   │
│  └─────────────────┬───────────────────────┘   │
│                    │                           │
│  ┌─────────────────▼───────────────────────┐   │
│  │ 2. Runtime调用LLM                       │   │
│  │    LLM返回:tool_call(query_calendar)   │   │
│  │    Observability记录此次LLM调用         │   │
│  └─────────────────┬───────────────────────┘   │
│                    │                           │
│  ┌─────────────────▼───────────────────────┐   │
│  │ 3. Runtime请求Tool Registry执行工具     │   │
│  │    Registry校验权限:query_calendar     │   │
│  │    属于"读操作",无需额外确认           │   │
│  │    Registry在沙箱中执行,返回结果       │   │
│  │    Observability记录工具调用和结果      │   │
│  └─────────────────┬───────────────────────┘   │
│                    │                           │
│  ┌─────────────────▼───────────────────────┐   │
│  │ 4. Runtime将工具结果交给Memory          │   │
│  │    Memory加入短期记忆                   │   │
│  │    Memory判断结果中含"张三日历"信息,   │   │
│  │    可能值得存入长期记忆                 │   │
│  └─────────────────┬───────────────────────┘   │
│                    │                           │
│                    │ 循环继续...               │
│                    ▼                           │
│  ┌─────────────────────────────────────────┐   │
│  │ 最终:LLM给出回答                       │   │
│  │ Runtime将结果交给Evaluator              │   │
│  │ Evaluator在评估集上运行,记录指标       │   │
│  │ Observability生成完整的执行轨迹报告     │   │
│  └─────────────────────────────────────────┘   │
└────────────────────────────────────────────────┘

这个流程中,Runtime是调度者,其他模块是服务提供者。每个模块都可以被独立替换、独立测试、独立优化。这就是Harness架构的力量:模块化带来的不是复杂度,而是可控性。

从架构图到代码:一个最小实现 ​

为了让你对架构有更具体的感知,这里给出一个最小Harness的代码骨架。它包含了四个核心模块的雏形:

python
class Harness:
    def __init__(self, llm, tools, memory, evaluator=None):
        self.runtime = AgentRuntime(llm, tools, memory)
        self.tool_registry = ToolRegistry(tools)
        self.memory = memory
        self.evaluator = evaluator
        self.tracer = Tracer()  # 可观测层

    def run(self, user_input: str) -> AgentResult:
        # 启动一次Agent执行
        result = self.runtime.run(user_input)

        # 记录完整轨迹
        self.tracer.record(result)

        # 如果有评估器,执行评估
        if self.evaluator:
            score = self.evaluator.evaluate(result)
            result.score = score

        return result

这个骨架虽然简单,但它体现了核心的设计原则:Runtime负责执行,Registry负责工具,Memory负责信息,Evaluator负责度量,Tracer负责记录。 每一层的职责清晰,边界明确。

本书后续章节的架构映射 ​

理解了Harness的架构后,你就有了全书的地图:

模块对应章节
Agent Runtime(循环控制)第2.1节 + 第6章
Tool Registry(工具系统)第4章
Memory System(记忆系统)第5章
Evaluator(评估体系)第7-8章
Observability Layer(可观测性)第9章
Security Layer(安全层)第10章

在接下来的几章中,我们将从零开始实现每个模块。每实现一个模块,你就会更深地理解:Harness不是一套复杂的概念,而是一组解决具体问题的工程决策的累积。

本节小结 ​

Harness的典型架构由四个核心模块组成:

  • Agent Runtime:执行引擎,控制核心循环的运转
  • Tool Registry:能力目录,管理工具的定义、执行和安全
  • Memory System:信息中枢,管理短期、长期和情景记忆
  • Evaluator:质检系统,度量Agent的表现和变化

这些模块围绕Agent的核心循环组织,各自解决一类具体问题,彼此通过清晰的接口协作。理解这个架构的关键不是记住模块的名字,而是理解它们各自回答的问题:怎么执行?(Runtime)能做什么?(Registry)记得什么?(Memory)做得好不好?(Evaluator)

在下一节中,我们将讨论Agent的另一个重要维度:单Agent与多Agent的编排模式差异。这是Harness在架构层面的第二个关键设计空间。