Solon v4.0.4

harness - 内置拦截器的修改及添加

</> markdown
2026年7月30日 下午4:45:10

solon-ai-harness 在引擎启动时预置了三个内置拦截器,分别负责三种不同的 Agent 运行保障机制:

  • compressionInterceptor(上下文压缩拦截器):负责工作记忆区的无损/近无损压缩
  • hitlInterceptor(人工介入拦截器):负责敏感工具调用的安全审计与人工审批
  • stopLoopInterceptor(防死循环拦截器):负责检测并纠正 Agent 陷入重复模式

1、内置拦截器的默认行为

HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .build();

默认情况下,引擎会自动创建并配置上述三个拦截器:

上下文压缩拦截器:触发条件为消息超过 40 条或 Token 超过模型上下文窗口的 75%。保留的消息窗口上限为 40 条。使用组合压缩策略(CompositeCompressionStrategy),先后执行:

  • KeyInfoExtractionStrategy:提取干货(去水),保留关键信息
  • HierarchicalCompressionStrategy:滚动更新摘要,维护分层压缩的历史摘要

防死循环拦截器:默认 (3, 8),即在 8 轮窗口内如果连续 3 次出现完全相同的工具调用特征,则注入自纠错提示。

HITL 拦截器:默认绑定 bash 工具的 BashToolStrategy(内含 6 条内置安全规则 + 权限引擎委托),并配置了 onApproved 审批自动记忆回调。

2、修改现有的内置拦截器

通过 Builder 阶段直接替换拦截器实例:

HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .compressionInterceptor(new ContextCompressionInterceptor())  // 替换压缩拦截器
  .hitlInterceptor(new HITLInterceptor())                       // 替换 HITL 拦截器
  .stopLoopInterceptor(new StopLoopInterceptor(5, 10))          // 替换防死循环拦截器
  .build();

注意:Builder 方法 compressionInterceptor()hitlInterceptor()stopLoopInterceptor() 是替换而非叠加。如需保留默认行为仅调整参数,可通过引擎运行时的 API 调整(见第 5 节)。

3、上下文压缩拦截器(ContextCompressionInterceptor)

拦截器的生命周期方法:重写 onReasonStart 进行压缩判断,并在模型返回上下文超长错误时通过 onReasonRetry 执行强制压缩。

3.1 构造参数说明

// 最简构造(maxMessages=15, maxContextRatio=0.75, 使用默认组合压缩策略)
ContextCompressionInterceptor interceptor = new ContextCompressionInterceptor();

// 指定最大消息数与最简压缩策略
ContextCompressionInterceptor interceptor = new ContextCompressionInterceptor(
  40                                     // maxMessages: 保留窗口最大消息数
);

// 指定完整参数
ContextCompressionInterceptor interceptor = new ContextCompressionInterceptor(
  40                                     // maxMessages: 保留窗口最大消息数(下限 10)
);
interceptor.setMaxContextLengthRatio(0.75);    // 模型上下文窗口使用比例(默认 0.75)
interceptor.setMaxRetries(3);                  // 压缩策略重试次数
interceptor.setMessageTriggerFactor(1.5);      // 触发滞后系数(>=1.0,越大越不易触发)
interceptor.setMinReservedMessages(5);          // 保留窗口最小消息数(显式覆盖,>=3)
interceptor.setPerMessageCap(8000);            // 单条消息 Token 硬上限(0=自动推导)
interceptor.setDefaultContextLength(128_000L); // 模型上下文窗口默认值

3.2 压缩策略(CompressionStrategy)配置

// 自定义组合压缩策略
CompressionStrategy strategy = new CompositeCompressionStrategy()
  .addStrategy(new KeyInfoExtractionStrategy())
  .addStrategy(new HierarchicalCompressionStrategy());

ContextCompressionInterceptor interceptor = new ContextCompressionInterceptor(
  40,
  0.75,
  3,
  strategy);

HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .compressionInterceptor(interceptor)
  .build();

也可通过引擎运行时动态调整压缩参数:

engine.setCompressionThreshold(50, 0.80);  // 调整阈值(maxMessages=50, ratio=0.80)

3.3 压缩保护机制

压缩拦截器内置了多层保护机制:

  • 初心链保护:标记为 META_FIRST 的消息(system prompt、用户原始问题)永不压缩
  • Tool-use 原子对保护Assistant(with tool_calls)ToolMessage 的调用-结果配对不会被拆散
  • 语义连贯补齐:截断点前的 Assistant thought 消息会一并纳入保留区
  • 单条消息守卫:单条超过 perMessageCap 的消息会被头尾截断,防止巨量消息撑爆上下文
  • 压缩效果告警:压缩后消息数 >= 原始 90% 时输出 WARN 日志

4、HITL 拦截器(人工介入)

HITL(Human-in-the-Loop)拦截器通过 ReAct 协议的生命周期钩子实现流程管控。

4.1 生命周期机制

方法说明
onAgentStart恢复挂起会话时自动判断是否回到 Action
onReasonStart推理开始前检查,无操作
onActionStart批级预检:扫全量 tool call,未决策的整批挂起
onToolCallStart单工具级防已决策二次应用
onToolCallEnd清理决策键、注入人工备注
onActionEnd会话未挂起时清理批挂起状态

4.2 使用方式

开关控制:HITLInterceptor 本身和 hitlEnabled 开关需同时满足才能生效:

// Builder 阶段配置
HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .hitlEnabled(true)              // 全局启用 HITL 开关
  .build();

hitlEnabled=true 为全局开关,拦截器仅为具体策略逻辑,两者缺一不可。

4.3 注册工具策略

通过 Builder 指定自定义 HITL 拦截器(此时会覆盖默认的 BashToolStrategy):

HITLInterceptor hitl = new HITLInterceptor();

// 使用内置策略
hitl.onTool("bash", new BashToolStrategy());            // bash 命令审计
hitl.onTool("write", new WriteToolStrategy("write"));   // 文件写入审计
hitl.onTool("edit", new WriteToolStrategy("edit"));     // 文件编辑审计
hitl.onTool("webfetch", new WebToolStrategy("webfetch")); // 网络访问审计

// 快速注册为敏感工具(使用默认敏感策略)
hitl.onSensitiveTool("write", "edit", "rm");

// 设置审批自动记忆回调(审批过的相同命令/路径自动放行)
hitl.onApproved((toolName, args) -> {
    engine.addPermissionRule(PermissionRule.allow(toolName));
});

HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .hitlInterceptor(hitl)
  .hitlEnabled(true)
  .build();

4.4 运行时动态开关

// 运行时启用/禁用 HITL
engine.setHitlEnabled(true);  // 启用,会调用 hitlInterceptor.setEnabled(true)
engine.setHitlEnabled(false); // 禁用

engine.setHitlEnabled() 会同时更新 HarnessOptions.hitlEnabledHITLInterceptor.setEnabled(),保持双开关一致。

4.5 内置 HITL 策略

策略类适用工具内置规则
BashToolStrategybash空命令放行、注入防御、系统特权黑名单、路径回溯防御、敏感文件防御、只读命令自动放行、不完整命令拒绝
WriteToolStrategywrite/edit路径回溯防御、敏感系统文件防御
WebToolStrategywebfetch/websearch高风险域名黑名单
HITLSensitiveStrategy任意通用敏感操作拦截

详细参考:《harness - HitlStrategy 安全审计策略》《harness - PermissionRule 权限规则引擎》

4.6 流式事件

HITL 拦截器在流式输出时会推送专门的事件:

  • HITLPendingEvent — 首次拦截挂起,携带 HITLTask 列表(callId、toolName、args、comment),用于前端展示审批卡片
  • HITLDecidedEvent — 决策生效,携带 HITLDecision(approved/skipped/rejected、modifiedArgs、comment),用于关闭/更新审批卡片

5、防死循环拦截器(StopLoopInterceptor)

通过 onThought 生命周期方法检测 Agent 的重复行为模式。使用规范化指纹(归一化的工具调用名组合/Action 行/内容前缀)标识每次迭代意图。

// 自定义参数:5 次相同特征 / 10 轮窗口
StopLoopInterceptor stopLoop = new StopLoopInterceptor(5, 10);

HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .stopLoopInterceptor(stopLoop)
  .build();

检测到循环后的处理机制:

  1. 生成自纠错提示文案(含有效性评估、强制收敛、避免重复三条指导)
  2. 作为用户消息注入到工作记忆区(ChatMessage.ofUser()
  3. 清除计数器,避免注入消息被重复计数
  4. 不挂起会话、不强制终止,LLM 在下一轮推理时看到提示并自行纠正

6、添加自定义拦截器(通过 HarnessExtension)

通过 HarnessExtension 接口,可以在 Builder 构建阶段或引擎运行阶段对 Agent 的行为进行扩展。

6.1 HarnessExtension 接口定义

public interface HarnessExtension {
    /** 在 Agent 构建时回调,参数为当前构建的 agentName 与 agentBuilder */
    void configure(String agentName, ReActAgent.Builder agentBuilder);
}

6.2 Builder 构建阶段

HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
  .sessionProvider(sessionProvider)
  .extensionAdd(new HarnessExtension() {
      @Override
      public void configure(String agentName, ReActAgent.Builder agentBuilder) {
          // 添加自定义拦截器
          agentBuilder.defaultInterceptorAdd(new ReActInterceptor() {
              @Override
              public void onAgentStart(ReActTrace trace) {
                  // 自定义逻辑
                  System.out.println("Agent started: " + agentName);
              }
          });

          // 可以设置拦截器优先级(数字越小优先级越高)
          // agentBuilder.defaultInterceptorAdd(5, interceptor);
      }
  })
  .build();

注意defaultInterceptorAddcompressionInterceptor()hitlInterceptor() 等替换式方法不同,它是添加而非替换。替换式方法会替换掉引擎默认创建的拦截器实例。

6.3 运行时动态添加/移除

引擎运行后,通过以下 API 动态管理扩展(每次变更会触发主 Agent 重建):

// 运行时添加扩展
engine.addExtension(new HarnessExtension() {
    @Override
    public void configure(String agentName, ReActAgent.Builder agentBuilder) {
        agentBuilder.defaultInterceptorAdd(new MyCustomInterceptor());
    }
});

// 运行时移除扩展
engine.removeExtension(extension);

6.4 自定义拦截器注册到具体生命周期的方法

ReActAgent.Builder 提供了以下注册方法:

Builder 方法说明
defaultInterceptorAdd(ReActInterceptor)添加默认拦截器到所有生命周期
defaultInterceptorAdd(int priority, ReActInterceptor)指定优先级的拦截器(数值越小越靠前执行)
defaultTalentAdd(Talent)添加 Talent(技能包),包含多个工具

关于完整的 ReActInterceptor 生命周期方法,参见 《react - ReActInterceptor 拦截器》

7、通过 AgentDefinition 的 tools 配置启用 HITL

在 Agent 定义的工具体系中,hitl 是一个特殊的工具名,通过它来启用人工介入拦截器:

AgentDefinition definition = AgentDefinition.builder()
    .name("my-agent")
    .toolsAdd("read", "write", "bash", "hitl")  // 启用 HITL 拦截器
    .build();

hitl 出现在工具列表中,且 hitlEnabled=true 时,Agent 会自动将 HITLInterceptor 添加到 ReAct 拦截器链中。

8、完整示例:组合使用三种拦截器

// 1. 自定义压缩策略
CompressionStrategy strategy = new CompositeCompressionStrategy()
    .addStrategy(new KeyInfoExtractionStrategy())
    .addStrategy(new HierarchicalCompressionStrategy());

ContextCompressionInterceptor compressionInterceptor =
    new ContextCompressionInterceptor(40, 0.75, 3, strategy);

// 2. 自定义 HITL 策略
HITLInterceptor hitl = new HITLInterceptor()
    .onTool("bash", new BashToolStrategy())
    .onTool("write", new WriteToolStrategy("write"))
    .onSensitiveTool("rm", "del")
    .onApproved((toolName, args) -> {
        engine.addPermissionRule(PermissionRule.allow(toolName));
    });

// 3. 构建引擎
HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
    .sessionProvider(sessionProvider)
    .compressionInterceptor(compressionInterceptor)
    .hitlInterceptor(hitl)
    .hitlEnabled(true)
    .stopLoopInterceptor(new StopLoopInterceptor(5, 12))
    .toolsAdd("read", "write", "edit", "bash", "glob", "grep", "ls", "hitl")
    .build();

// 运行时动态调整
engine.setHitlEnabled(true);
engine.setCompressionThreshold(50, 0.80);