---
title: "team - TeamInterceptor 拦截器"
---


在多智能体协作（Multi-Agent System）中，流程的透明度与可控性至关重要。TeamInterceptor 提供了对 TeamAgent 全生命周期的观察与干预能力。它不仅是一个日志记录点，也是实现合规审计、动态权限控制、成本熔断及内容安全的扩展点。


### 1、内置拦截器


| 拦截器 | 名称 | 描述 |
| -------- | -------- | -------- |
| LoopingTeamInterceptor     | 协作死循环拦截器     | 通过回溯 TeamTrace 识别并阻断成员间的无效重复。支持「单点停滞」（同一 Agent 连续产出高度相似内容）与「序列嵌套」（A-B-A-B 型踢皮球）两类检测，命中后在 `shouldSupervisorContinue` 返回 false 熔断。     |

`LoopingTeamInterceptor` 的判定参数（最小检测长度、相似度阈值、回溯窗口、允许重复次数）目前为类内私有字段，使用默认值即可：相似度阈值 0.95、回溯窗口 10 步、不允许重复。它基于归一化编辑距离（Levenshtein）计算相似度，且只审计 Agent 产出记录，不统计系统开销步骤。


### 2、注册与启停

拦截器可在构建期通过 Builder 注册，支持指定顺序（index 越小越先执行）：

```java
TeamAgent agent = TeamAgent.of(chatModel)
        .defaultInterceptorAdd(new LoopingTeamInterceptor())
        .defaultInterceptorAdd(new CostLimitInterceptor(), 100)
        .build();
```

也可在调用期通过 `options` 动态追加：

```java
agent.prompt("...").options(o -> o.interceptorAdd(new MyAuditInterceptor())).call();
```

自定义拦截器建议继承 `AbsTeamInterceptor`（4.0.0+），它实现了 `isEnabled()` / `setEnabled()`，可在运行期动态开关：

```java
public class CostLimitInterceptor extends AbsTeamInterceptor {
    @Override
    public boolean shouldSupervisorContinue(TeamTrace trace) {
        //协作回合超过 10 轮则熔断
        return trace.getTurnCount() < 10;
    }
}
```

若直接实现 `TeamInterceptor` 接口，`isEnabled()` 默认返回 `true`，`setEnabled()` 为空实现（即无法关闭）。内置的 `LoopingTeamInterceptor` 即属这种情况。

> 关于 enabled 的生效范围：Team 生命周期各时机点（onTeamStart、onTeamEnd、shouldSupervisorContinue、onModelStart、onModelEnd、onSupervisorDecision、shouldAgentContinue、onAgentEnd）在调用前都会检查 `isEnabled()`。但 FlowInterceptor 的节点钩子是在 Flow 启动前一次性快照注册的，运行中途改 enabled 不会影响本轮的 `onNodeStart` / `onNodeEnd`。


### 3、核心治理维度

TeamInterceptor 通过三个互补的切面维度构建治理体系。下表按一次协作的真实执行顺序排列：


| 层级 | 拦截方法 | 触发时机 | 典型应用场景 |
| -------- | -------- | -------- | -------- |
| 团队级     | onTeamStart     | 提示词就绪、Talent 部署后，Flow 引擎启动前     | 初始化外部 TraceId、分配全局上下文、记录审计起点     |
| 流程级     | onNodeStart / onNodeEnd     | 协作图中每个节点的起止（继承自 FlowInterceptor）     | 节点粒度的耗时统计、图执行链路观测     |
| 决策级     | shouldSupervisorContinue     | 主管节点入口，早于 maxTurns 与协议判定     | 准入熔断：步数超限、Token 余额不足时返回 false     |
|      | onModelStart     | 主管的 ChatRequest 组装完成、发起 LLM 调用前     | 动态微调 Temperature / MaxTokens、注入约束     |
|      | onModelEnd     | LLM 返回后、决策文本解析前（已完成 Usage 累计）     | 内容安全审计、原始 Token 统计     |
|      | onSupervisorDecision     | 决策文本解析完成、提交物理路由前     | 路径追踪，观察任务被派发给谁     |
| 成员级     | shouldAgentContinue     | 成员 Agent 运行前（已回填 lastAgentName）     | 权限校验。返回 false 则跳过该成员     |
|      | onAgentEnd     | 成员执行完毕、轨迹记录已写入后     | 统计单个 Agent 的耗时与资源消耗     |
| 团队级     | onTeamEnd     | 最终答案收敛、Session 快照更新后     | 将 TeamTrace 持久化到数据库或推送监控平台     |

几个容易踩的行为差异：

- **onTeamEnd 不是强闭环**。它位于成功返回路径上，协作过程抛异常时不会被调用。若需要「无论成败都执行」的清理，应放在协议的 `onTeamFinished`（它在 finally 块中，不受 onTeamEnd 异常影响）。
- **shouldSupervisorContinue 早于 maxTurns 检查**。拦截器是主管节点的第一道关卡，可以在框架的回合数熔断之前先行介入。返回 false 时会写入一条 `[Skipped] Intercepted by ...` 记录，并在当前路由指向主管时转向 END。
- **shouldAgentContinue 返回 false 只跳过该成员**。框架写入一条 `[Skipped] Cancelled by ...` 记录后结束该节点，协作流程继续按图流转（通常回到主管重新决策），并非中止整个团队。
- **onModelStart / onModelEnd / onSupervisorDecision 之后都有挂起检查**。若拦截器在这些时机点里把会话置为 Pending（如触发人工介入），后续步骤会立即中断，不会提交路由。
- **onAgentEnd 在轨迹写入之后触发**，所以此时 `trace.getLastAgentContent()`、`getLastAgentDuration()` 已包含该成员本次的产出。协议的同名钩子先于拦截器执行。

由于 TeamInterceptor 具备多重身份，还可以覆盖以下底层方法实现更精细的控制：


| 继承自 | 拦截方法 | 作用描述 |
| -------- | -------- | -------- |
| FlowInterceptor     | doFlowIntercept     | 包裹整个协作图的执行，可做全局 try/catch 或上下文透传。     |
| ChatInterceptor     | onPrepare     | 在构建 ChatModel 请求之前触发，可动态调整 ChatOptions。     |
| ChatInterceptor     | interceptCall / interceptStream     | 作用于最底层的 ChatModel 同步 / 流式调用。     |
| ToolInterceptor     | interceptTool     | 拦截工具执行链，可改写工具返回值。     |
| ToolInterceptor     | isEnabled / setEnabled     | 拦截器启停开关。     |


### 4、TeamTrace 常用观测入口

拦截器的所有判断都围绕 `TeamTrace` 展开，常用方法：


| 方法 | 说明 |
| -------- | -------- |
| getTurnCount()     | 当前协作回合数（框架 maxTurns 熔断即基于此）     |
| getRecordCount()     | 轨迹记录条数（含系统记录）     |
| getRecords()     | 全部执行足迹，`TeamRecord.isAgent()` 可筛出成员产出     |
| getMetrics()     | 指标聚合，含 Token 用量与总耗时     |
| getLastAgentName() / getLastAgentContent() / getLastAgentDuration()     | 最近一个成员的名称、产出与耗时     |
| getRoute() / getLastDecision()     | 当前物理路由目标与最近一次决策文本     |
| getFinalAnswer()     | 最终答案（收敛后可用）     |
| getOptions().getMaxTurns()     | 本次运行的最大回合上限     |

> 注意：`TeamTrace` 没有 `getStepCount()` 方法。计步请用 `getTurnCount()`（回合）或 `getRecordCount()`（记录条数）。


### 5、应用场景示例

#### 场景 A：全局成本与回合熔断

```java
public class CostLimitInterceptor extends AbsTeamInterceptor {
    @Override
    public boolean shouldSupervisorContinue(TeamTrace trace) {
        //回合超过 10 轮，或 Token 消耗超预算，则熔断
        if (trace.getTurnCount() >= 10) {
            return false;
        }

        return trace.getMetrics().getTotalTokens() < 100_000;
    }
}
```

#### 场景 B：敏感成员的权限准入

```java
public class PermissionInterceptor extends AbsTeamInterceptor {
    @Override
    public boolean shouldAgentContinue(TeamTrace trace, Agent agent) {
        if ("RefundAgent".equals(agent.name())) {
            //无审批权限时跳过该成员，协作流程会回到主管重新决策
            return currentUserHasRole("refund:approve");
        }

        return true;
    }
}
```

#### 场景 C：协作轨迹持久化

```java
public class TraceArchiveInterceptor extends AbsTeamInterceptor {
    @Override
    public void onTeamEnd(TeamTrace trace) {
        //仅成功路径触发；需要覆盖异常场景请用协议的 onTeamFinished
        archiveService.save(trace.getRunId(), trace.getRecords(), trace.getMetrics());
    }
}
```

#### 场景 D：决策路径观测

```java
public class RoutingLogInterceptor extends AbsTeamInterceptor {
    @Override
    public void onSupervisorDecision(TeamTrace trace, String decision) {
        LOG.info("turn={}, decision={}", trace.getTurnCount(), decision);
    }
}
```


### 6、TeamInterceptor 接口参考

TeamInterceptor 同时继承了 `AgentInterceptor`、`FlowInterceptor` 和 `ChatInterceptor`（而 ChatInterceptor 又继承 `ToolInterceptor`），所以它除了 Team 协作生命周期，还可以拦截流程节点、聊天模型与工具的执行。


TeamInterceptor

```java
package org.noear.solon.ai.agent.team;

import org.noear.solon.ai.agent.Agent;
import org.noear.solon.ai.agent.AgentInterceptor;
import org.noear.solon.ai.chat.ChatRequestDesc;
import org.noear.solon.ai.chat.ChatResponse;
import org.noear.solon.ai.chat.interceptor.ChatInterceptor;
import org.noear.solon.flow.intercept.FlowInterceptor;
import org.noear.solon.lang.Preview;

/**
 * 团队协作拦截器 (Team Interceptor)
 * <p>核心职责：提供对 TeamAgent 协作全生命周期的观察与干预能力。支持团队、决策、成员三个维度的切面注入。</p>
 *
 * @author noear
 * @since 3.8.1
 */
@Preview("3.8.1")
public interface TeamInterceptor extends AgentInterceptor, FlowInterceptor, ChatInterceptor {

    // --- [维度 1：团队级 (Team Level)] ---

    /**
     * 团队协作开始
     */
    default void onTeamStart(TeamTrace trace) {}

    /**
     * 团队协作结束
     */
    default void onTeamEnd(TeamTrace trace) {}


    // --- [维度 2：决策级 (Supervisor Level)] ---

    /**
     * 决策准入校验（主管发起思考前）
     *
     * @return true: 继续执行; false: 熔断并中止协作
     */
    default boolean shouldSupervisorContinue(TeamTrace trace) {
        return true;
    }

    /**
     * 模型请求前置（LLM 调用前）
     * <p>常用于动态调整 Request 参数（如 Temperature, MaxTokens 等）。</p>
     */
    default void onModelStart(TeamTrace trace, ChatRequestDesc req) {}

    /**
     * 模型响应后置（LLM 返回后，解析前）
     * <p>常用于内容安全审计或原始 Token 统计。</p>
     */
    default void onModelEnd(TeamTrace trace, ChatResponse resp) {}

    /**
     * 决策结果输出（指令解析后）
     *
     * @param decision 经解析确定的目标 Agent 名称或终结指令
     */
    default void onSupervisorDecision(TeamTrace trace, String decision) {}


    // --- [维度 3：成员级 (Agent Level)] ---

    /**
     * 成员执行准入校验（Agent 运行前）
     *
     * @param agent 即将运行的智能体
     * @return true: 允许运行; false: 跳过并回滚至决策层
     */
    default boolean shouldAgentContinue(TeamTrace trace, Agent agent) {
        return true;
    }

    /**
     * 成员执行结束
     */
    default void onAgentEnd(TeamTrace trace, Agent agent) {}
}
```

AbsTeamInterceptor

```java
package org.noear.solon.ai.agent.team;

/**
 *
 * @author noear
 * @since 4.0.0
 */
public abstract class AbsTeamInterceptor implements TeamInterceptor {
    private volatile boolean enabled = true;

    @Override
    public void setEnabled(Boolean enabled) {
        if (enabled != null) {
            this.enabled = enabled;
        }
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}
```
