RunnableConfig 提供了一个 context() 方法,允许你在同一个执行流程中的多个 Hook 调用、多轮模型或工具调用之间共享数据。这对于实现计数器、累积统计信息或跨多次调用维护状态非常有用。
适用场景:
- 跟踪模型或工具调用次数;
- 累积性能指标(总耗时、平均响应时间等);
- 在 before/after Hook 之间传递临时数据;
- 实现基于计数的限流或断路器。
示例:使用 RunnableConfig.context() 实现调用计数器
import com.alibaba.cloud.ai.graph.agent.hook.ModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.OverAllState;
import java.util.concurrent.CompletableFuture;
import java.util.Map;
@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL})
public class ModelCallCounterHook extends ModelHook {
private static final String CALL_COUNT_KEY = "__model_call_count__";
private static final String TOTAL_TIME_KEY = "__total_model_time__";
private static final String START_TIME_KEY = "__call_start_time__";
@Override
public String getName() {
return "model_call_counter";
}
@Override
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
// 从 context 读取当前计数(如果不存在则默认为 0)
int currentCount = config.context().containsKey(CALL_COUNT_KEY)
? (int) config.context().get(CALL_COUNT_KEY) : 0;
System.out.println("模型调用 #" + (currentCount + 1));
// 记录开始时间
config.context().put(START_TIME_KEY, System.currentTimeMillis());
return CompletableFuture.completedFuture(Map.of());
}
@Override
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
// 读取当前计数并递增
int currentCount = config.context().containsKey(CALL_COUNT_KEY)
? (int) config.context().get(CALL_COUNT_KEY) : 0;
config.context().put(CALL_COUNT_KEY, currentCount + 1);
// 计算本次调用耗时并累加到总耗时
if (config.context().containsKey(START_TIME_KEY)) {
long startTime = (long) config.context().get(START_TIME_KEY);
long duration = System.currentTimeMillis() - startTime;
long totalTime = config.context().containsKey(TOTAL_TIME_KEY)
? (long) config.context().get(TOTAL_TIME_KEY) : 0L;
config.context().put(TOTAL_TIME_KEY, totalTime + duration);
// 输出统计信息
int newCount = currentCount + 1;
long newTotalTime = totalTime + duration;
System.out.println("模型调用完成: " + duration + "ms");
System.out.println("累计统计 - 调用次数: " + newCount + ", 总耗时: " + newTotalTime + "ms, 平均: " + (newTotalTime / newCount) + "ms");
}
return CompletableFuture.completedFuture(Map.of());
}
}
示例:基于 context 实现调用次数限制
import com.alibaba.cloud.ai.graph.agent.hook.ModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.JumpTo;
import org.springframework.ai.chat.messages.AssistantMessage;
import java.util.List;
import java.util.ArrayList;
@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL})
public class ModelCallLimiterHook extends ModelHook {
private static final String CALL_COUNT_KEY = "__model_call_count__";
private final int maxCalls;
public ModelCallLimiterHook(int maxCalls) {
this.maxCalls = maxCalls;
}
@Override
public String getName() {
return "model_call_limiter";
}
@Override
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
// 读取当前调用次数
int callCount = config.context().containsKey(CALL_COUNT_KEY)
? (int) config.context().get(CALL_COUNT_KEY) : 0;
// 检查是否超过限制
if (callCount >= maxCalls) {
System.out.println("达到模型调用次数限制: " + maxCalls);
// 添加终止消息
List<Message> messages = new ArrayList<>(
(List<Message>) state.value("messages").orElse(new ArrayList<>())
);
messages.add(new AssistantMessage(
"已达到模型调用次数限制 (" + callCount + "/" + maxCalls + "),Agent 执行终止。"
));
// 返回更新并跳转到结束
return CompletableFuture.completedFuture(Map.of("messages", messages));
}
return CompletableFuture.completedFuture(Map.of());
}
@Override
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
// 递增计数器
int callCount = config.context().containsKey(CALL_COUNT_KEY)
? (int) config.context().get(CALL_COUNT_KEY) : 0;
config.context().put(CALL_COUNT_KEY, callCount + 1);
return CompletableFuture.completedFuture(Map.of());
}
@Override
public List<JumpTo> canJumpTo() {
return List.of(JumpTo.end);
}
}
使用示例:
ReactAgent agent = ReactAgent.builder()
.name("limited_agent")
.model(chatModel)
.tools(tools)
.hooks(new ModelCallCounterHook()) // 监控调用统计
.hooks(new ModelCallLimiterHook(5)) // 限制最多调用 5 次
.build();
关键要点:
- context() 是共享的: 同一个执行流程中的所有 Hook 共享同一个 context;
- 数据持久性: context 中的数据在整个 Agent 执行期间保持有效;
- 类型安全: 需要自己管理 context 中数据的类型转换;
- 命名约定: 建议使用双下划线前缀命名 context key(如 __model_call_count__)以避免与用户数据冲突。
使用多个 Hooks 和 Interceptors 时,理解执行顺序很重要:
ReactAgent agent = ReactAgent.builder()
.name("my_agent")
.model(chatModel)
.hooks(hook1, hook2, hook3)
.interceptors(interceptor1, interceptor2)
.interceptors(toolInterceptor1, toolInterceptor2)
.build();
执行流程:
- Before Agent Hooks(按顺序):
hook1.beforeAgent()
hook2.beforeAgent()
hook3.beforeAgent() - Agent 循环开始
- Before Model Hooks(按顺序):
hook1.beforeModel()
hook2.beforeModel()
hook3.beforeModel() - Model Interceptors(嵌套调用):
interceptor1 → interceptor2 → 模型调用
- After Model Hooks(逆序):
hook3.afterModel()
hook2.afterModel()
hook1.afterModel() - Tool Interceptors(如果有工具调用,嵌套调用):
toolInterceptor1 → toolInterceptor2 → 工具执行
- Agent 循环结束
- After Agent Hooks(逆序):
hook3.afterAgent()
hook2.afterAgent()
hook1.afterAgent()
关键规则:
- before_* hooks: 从第一个到最后一个
- after_* hooks: 从最后一个到第一个(逆序)
- Interceptors: 嵌套调用(第一个拦截器包装所有其他的)
本文整理自 Spring AI Alibaba 官方文档《Hooks 与 Interceptors》:https://java2ai.com/docs/frameworks/agent-framework/tutorials/hooks

AI项目博客






