Harness(挽具/框架)在软件工程中通常指用于测试、约束和驱动目标系统的工程基础设施。在智能体(AI Agent)领域,Harness 不是又一个 Agent 框架,而是连接"能运行的 Demo"与"可信赖的生产系统"之间的关键工程层。
1. 引言:从 Demo 到生产的鸿沟
构建一个能调用工具的 LLM Demo 只需要 50 行代码。但将其部署为高可用、可观测、可评估、可回滚 的生产系统,则需要一套完整的工程基础设施------这就是 Agent Harness 要解决的问题。
智能体与传统软件服务的根本差异在于非确定性:
| 维度 | 传统微服务 | AI 智能体 |
|---|---|---|
| 行为确定性 | 输入确定,输出确定 | 相同输入可能产生不同工具调用序列 |
| 状态空间 | 有限、显式 | 近乎无限,受 Prompt 和上下文影响 |
| 故障模式 | 异常抛出、超时 | 幻觉、工具误调用、循环调用、预算耗尽 |
| 测试方法 | 单元测试 + 集成测试 | 语义评估 + 对抗测试 + 人工审核 |
Agent Harness 的核心使命是:在保留 LLM 创造力的同时,为其套上工程化的约束、观测与治理体系。
2. Agent Harness 的定位与边界
2.1 不是什么
- 不是 Agent 框架:LangChain、LlamaIndex、AutoGen 负责"如何构建智能体",Harness 负责"如何运行、测试和治理智能体"。
- 不是模型训练平台:不负责微调或预训练,只负责推理阶段的工程化。
- 不是简单的 API 网关 :网关处理路由和限流,Harness 需要理解智能体的语义状态(当前步骤、工具调用意图、记忆上下文)。
2.2 核心职责
┌─────────────────────────────────────────┐
│ Agent Harness 职责边界 │
├─────────────────────────────────────────┤
│ Runtime │ 生命周期管理、并发、熔断、重试 │
│ Tooling │ 工具注册、Schema 治理、沙箱化 │
│ Memory │ 状态持久化、记忆压缩、上下文窗口 │
│ Observe │ 全链路追踪、成本监控、日志结构化 │
│ Evaluate │ 离线评估、在线 A/B、回归测试 │
│ Guardrails │ 输入防护、输出过滤、预算控制 │
└─────────────────────────────────────────┘
3. 核心架构设计
3.1 分层架构
text
┌─────────────────────────────────────────────┐
│ Application Layer (业务智能体) │
│ - 客服 Agent / 代码助手 / 数据分析 Agent │
├─────────────────────────────────────────────┤
│ Orchestration Layer (编排层) │
│ - 计划分解 (Planning) / 反思 (Reflection) │
│ - 多智能体协作协议 │
├─────────────────────────────────────────────┤
│ Harness Core (核心层) │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ Runtime │ Tool │ Memory │ Eval │ │
│ │ 运行时 │ Registry│ 记忆 │ 评估器 │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
│ ┌─────────┬─────────┬─────────────────┐ │
│ │ Trace │ Guard │ Config Mgmt │ │
│ │ 追踪 │ Rails │ 配置管理 │ │
│ └─────────┴─────────┴─────────────────┘ │
├─────────────────────────────────────────────┤
│ Foundation Layer (基础层) │
│ - LLM Provider (OpenAI / Claude / 私有模型) │
│ - Vector DB (记忆存储) │
│ - Message Queue (异步队列) │
└─────────────────────────────────────────────┘
3.2 设计原则
- 确定性外壳,非确定性内核:Harness 本身的调度逻辑必须是确定性的,只有 LLM 调用本身允许非确定性。
- Fail Fast, Recover Gracefully:工具调用失败立即捕获,而非让 LLM 无限重试。
- 观测即代码:每一次 LLM 调用、每一次工具执行、每一次状态转移都必须生成结构化 Telemetry。
- Prompt 即配置:Prompt 模板应纳入版本控制,支持灰度发布和快速回滚。
4. 关键组件详解
4.1 运行时编排(Runtime)
智能体运行时不是简单的 while 循环,而是一个状态机。
typescript
// 概念性状态机定义
type AgentState =
| { phase: 'THINKING'; context: Context }
| { phase: 'TOOL_CALL'; tool: string; args: unknown }
| { phase: 'OBSERVATION'; result: unknown }
| { phase: 'FINAL_ANSWER'; output: string }
| { phase: 'ERROR'; error: AgentError };
// Harness 负责状态转换的可靠性
class AgentRuntime {
async execute(session: Session, maxSteps: number = 10): Promise<AgentState> {
for (let step = 0; step < maxSteps; step++) {
const state = await this.transition(session);
if (state.phase === 'FINAL_ANSWER' || state.phase === 'ERROR') {
return state;
}
// 防止无限循环:检测重复工具调用模式
if (this.detectLoop(session.history)) {
return { phase: 'ERROR', error: new LoopDetectedError() };
}
}
return { phase: 'ERROR', error: new MaxStepsExceededError() };
}
}
关键技术点:
- 并发隔离:每个会话必须拥有独立的上下文,防止用户 A 的数据泄漏到用户 B 的会话中。
- 超时与熔断:单次 LLM 调用超时(如 30s),整体会话超时(如 5min)。连续失败触发熔断,降级到预设回答。
- 重试策略 :只对幂等的工具调用启用指数退避重试,非幂等操作(如扣款、发邮件)必须严格去重。
4.2 工具注册与治理(Tool Registry)
工具是智能体连接外部世界的"手脚",也是最大的风险面。
yaml
# tool-registry.yaml 示例
tools:
- name: send_email
version: "2.1.0"
schema:
type: object
properties:
to: { type: string, format: email }
subject: { type: string, maxLength: 200 }
body: { type: string, maxLength: 5000 }
required: [to, subject]
sandbox: true # 在隔离环境执行
idempotent: false # 非幂等,需严格防重
acl: ["customer_service"] # 仅客服 Agent 可用
rate_limit: 10/min
cost_budget: 0.05 USD/call # 单次调用成本上限
治理原则:
- Schema 强制校验:LLM 输出的工具调用参数必须经过 JSON Schema 严格校验,不通过则立即拒绝并反馈给 LLM。
- 沙箱化执行:工具代码运行在隔离进程或 WASM 沙箱中,禁止访问文件系统、网络(除白名单外)和环境变量。
- 权限最小化:每个工具绑定 ACL,Agent 身份鉴权通过后才可调用。
4.3 记忆管理(Memory)
LLM 的无状态特性要求 Harness 显式管理记忆。
| 记忆类型 | 存储介质 | 生命周期 | 技术要点 |
|---|---|---|---|
| 工作记忆 | 进程内存 / Redis | 单次会话 | 滑动窗口管理,防止超出上下文长度 |
| 短期记忆 | Redis / SQLite | 用户会话期间 | 最近 N 轮对话,支持快速检索 |
| 长期记忆 | Vector DB | 持久化 | Embedding 检索,定期摘要压缩 |
| 事实记忆 | 图数据库 | 持久化 | 实体关系抽取,防止记忆矛盾 |
记忆压缩策略 :
当会话过长时,Harness 应触发摘要压缩:
- 将早期对话送入 LLM 生成摘要;
- 将摘要 + 最近 3 轮原始对话作为新的上下文;
- 原始对话归档到 Vector DB,保留可检索性。
4.4 观测与追踪(Observability)
智能体的可观测性不能停留在日志级别,必须是语义化追踪。
json
// OpenTelemetry 风格的 Agent Trace Span
{
"trace_id": "abc123",
"span_id": "span_001",
"name": "agent.execution",
"attributes": {
"agent.id": "customer_support_v2",
"llm.model": "gpt-4",
"llm.tokens.input": 1240,
"llm.tokens.output": 380,
"llm.cost_usd": 0.042,
"tool.calls": ["query_kb", "create_ticket"],
"tool.results": [{"status": "success"}, {"status": "success"}],
"latency_ms": 3450,
"session.turns": 5
}
}
关键指标:
- 任务成功率 :会话是否达到
FINAL_ANSWER且用户未中断。 - 工具调用准确率:工具选择是否正确,参数是否有效。
- 幻觉率:通过事实核查模块或 LLM-as-Judge 评估。
- 成本效率:单次会话的平均 Token 消耗和美元成本。
4.5 评估体系(Evaluation Harness)
智能体无法像传统软件那样用断言测试,需要多维评估框架。
离线评估(CI/CD 阶段)
python
# 评估用例示例
test_cases = [
{
"input": "查询我上周在京东的订单",
"expected_tools": ["auth_check", "query_order"],
"forbidden_tools": ["delete_account"], # 不应调用的工具
"expected_entities": {"time_range": "last_week", "platform": "jd"},
"judge_criteria": "回答必须包含订单号、商品名称和物流状态"
}
]
# LLM-as-Judge:用更强的模型评估输出质量
def llm_judge(output: str, criteria: str) -> Score:
prompt = f"请根据以下标准评估回答质量...\n回答:{output}\n标准:{criteria}"
return strong_model.generate(prompt)
在线评估(生产环境)
- Shadow Mode:新版本的 Agent 并行运行,但不影响真实用户,只记录差异。
- A/B Test:分流不同版本的 Agent,对比任务成功率和用户满意度。
- 人工审核队列:对低置信度会话抽样,人工标注后回流到训练/优化流程。
4.6 安全防护(Guardrails)
智能体的安全威胁模型比传统应用更复杂:
| 威胁 | 防护措施 |
|---|---|
| Prompt Injection | 输入层:检测越狱指令;输出层:防止泄露系统 Prompt |
| 工具滥用 | 参数校验 + ACL + 人工确认高危险操作(如转账、删数据) |
| 数据泄漏 | PII 检测与脱敏,输出层过滤敏感信息 |
| 预算攻击 | Token 上限、单次会话成本上限、工具调用次数上限 |
| 无限循环 | 最大步数限制、重复调用检测、循环图检测 |
python
# Guardrails 管道示例
class GuardrailPipeline:
def process(self, context: Context) -> GuardResult:
# 1. 输入检查
if self.injection_detector.detect(context.user_input):
return Block(reason="SUSPICIOUS_INPUT")
# 2. 预算检查
if context.session_cost > self.budget_limit:
return Block(reason="BUDGET_EXCEEDED")
# 3. 输出检查
if self.pii_filter.contains_pii(context.llm_output):
context.llm_output = self.pii_filter.redact(context.llm_output)
return Pass()
5. 工程实践
5.1 测试策略金字塔
/\
/ \ E2E 评估 (端到端场景,LLM-as-Judge)
/----\
/ \ 集成测试 (工具调用链 + 记忆状态)
/--------\
/ \ 单元测试 (确定性逻辑:状态机、校验器)
/------------\
- 确定性部分必须 100% 覆盖:Schema 校验、状态转换、权限检查。
- 非确定性部分用统计测试:同一测试集运行 N 次,成功率 > 阈值。
5.2 Prompt 工程化
Prompt 是智能体的"源代码",必须纳入工程化管理:
text
prompts/
├── versions/
│ ├── customer_support/
│ │ ├── v1.0.0/
│ │ │ ├── system_prompt.md
│ │ │ └── tool_descriptions.json
│ │ └── v1.1.0/ # 当前灰度版本
│ └── sales_agent/
├── schemas/
│ └── prompt_variable_schema.json # 变量类型约束
└── tests/
└── prompt_regression/ # Prompt 变更后的回归测试
5.3 模型路由(Model Router)
Harness 应支持根据场景动态路由到不同模型:
yaml
routing_rules:
- condition: "task.complexity == 'high' && budget.remaining > 0.1"
model: "claude-3-5-sonnet"
priority: 1
- condition: "task.type == 'summarization'"
model: "gpt-3.5-turbo"
priority: 2
- condition: "fallback"
model: "local-llama-3-8b"
priority: 99
6. 常见陷阱与避坑指南
| 陷阱 | 现象 | 解决方案 |
|---|---|---|
| 过度智能化 | 让 LLM 决定一切,包括是否重试、是否继续 | 关键控制逻辑(循环检测、预算检查)必须硬编码在 Harness 中 |
| 工具幂等性忽视 | 网络超时后重试导致重复扣款/发邮件 | 所有工具调用必须带幂等键(Idempotency Key) |
| Prompt 裸奔 | 系统 Prompt 直接拼接用户输入,易被注入 | 使用结构化消息(OpenAI Message Format),分离 system/user/assistant |
| 观测盲区 | 只记录最终输出,看不到中间工具调用 | 全链路 Trace,每个 LLM 调用和工具调用都是独立 Span |
| 记忆泄漏 | 用户 A 的上下文混入用户 B 的会话 | 严格的 Session 隔离 + 上下文变量审计 |
| 评估虚假繁荣 | 用 LLM 评估自己的输出,分数虚高 | 评估模型必须独立于被测模型,引入人工校准集 |
7. 演进路线建议
阶段一:基础 Harness(0-3 个月)
- 搭建 AgentRuntime(状态机 + 超时/重试)
- 实现 ToolRegistry(Schema 校验 + 基础 ACL)
- 接入 OpenTelemetry 追踪
阶段二:治理强化(3-6 个月)
- 部署 Guardrails 管道(输入/输出过滤)
- 构建离线评估框架(LLM-as-Judge)
- 实现记忆压缩与长期记忆存储
阶段三:规模化(6-12 个月)
- 多智能体编排协议(主从 / 去中心化)
- Shadow Mode 与 A/B Test 平台
- 成本优化(模型路由、缓存策略)
8. 结语
Agent Harness 是智能体工程化的"隐形基础设施"。用户不会直接感知它的存在,但一旦缺失,智能体系统就会从"有趣的 Demo"迅速退化为"不可控的黑盒"。
优秀的 Harness 设计遵循一个核心哲学:对 LLM 保持敬畏,对工程保持严谨。让创造力发生在模型层面,让可靠性发生在 Harness 层面------这才是智能体走向生产的唯一路径。