主题
原文: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在架构层面的第二个关键设计空间。