一站式讲清Spring AI Alibaba的OverAllState和RunnableConfig

引言

在 Spring AI Alibaba(以及更广泛的 Spring AI 和 LangChain 生态系统)中,RunnableConfigOverallState是构建Agent(智能体)或Workflow(工作流)时的两个核心概念,那它们到底有什么区别呢?

RunnableConfig (运行时配置)

RunnableConfig 是一个配置对象,用于在执行链(Chain)或 Agent 的各个步骤之间传递运行时的元数据和控制参数。它类似于一个"背包",伴随着请求在整个系统中流动,但它不包含业务数据,只包含"如何执行"的指令与参数。

核心作用:

  1. 控制递归与循环 (Recursion Limit)

    • 在 Agent 循环(例如:思考 -> 工具 -> 思考)中,为了防止死循环,RunnableConfig 可以设置最大递归次数(例如 25 次)。如果超过这个次数,系统会强制停止并报错。
  2. 回调机制 (Callbacks)

    • 它携带了 CallbackManager。你可以通过它挂载监听器,监控 Token 消耗、流式输出(Streaming)、或者在某个步骤开始/结束时触发日志。
  3. 元数据与追踪 (Metadata & Tags)

    • 你可以给当前的执行请求打上 tags(标签)或 metadata(元数据)。这对于链路追踪(如接入 LangSmith 或 Zipkin)非常重要。例如,标记 userId: 123environment: production
  4. 并发控制与超时

    • 在某些实现中,它可以携带取消信号(Cancellation Signals)或超时设置

关键理解

你可以将RunnableConfig理解为"显式的、跨线程安全的 ThreadLocal"。

在 Spring AI / LangChain 的架构中,RunnableConfig 起到了同样的作用:

  • 隐式传递:虽然它作为参数传递,但在 Agent 的内部组件(如各个 Tool、各个 Chain 节点)之间,它往往是作为一个"环境上下文"存在的。
  • 存放"带外数据" (Out-of-band Data) :它专门设计了一个 metadata 字段(是一个 Map),允许你往里面塞一些非业务逻辑核心、但全链路都需要的数据。

异步与跨线程安全性 (这是最大的区别)

  • ThreadLocal : 它是绑定在特定线程 上的。Spring AI 底层(特别是在流式输出 Streaming 或调用大模型 API 时)经常是异步的 (使用 WebFluxCompletableFuture)。一旦代码切换了线程(例如从 Tomcat 线程切换到 Netty I/O 线程),ThreadLocal 里的数据就会丢失,除非你做非常复杂的 Context 复制。
  • RunnableConfig : 它是一个普通的 Java 对象 。当 AI 任务在不同的线程、不同的 Reactor 流之间传递时,这个对象会作为一个参数一直跟着走。无论线程怎么切,数据都在那里,不会丢。

作用域清晰 (Scope)

  • ThreadLocal : 如果忘记 remove(),容易造成内存泄漏(Memory Leak),或者在线程池复用时造成数据污染(上一个请求的数据留到了下一个请求)。
  • RunnableConfig : 它的生命周期严格绑定在单次执行链(Run)上。也就是 agent.invoke() 结束,这个 Config 对象也就完成了使命,被 GC 回收,非常干净

示例

下面是Spring AI Alibaba的一段示例

  1. toolContext.getContext() 中取出 RunnableConfig

  2. 从中读取 metadata("user_id")

  3. 根据user_id

    返回不同位置:

    • 用户 ID 为 "1""Florida"
    • 其他 → "San Francisco"
java 复制代码
// 用户位置工具 - 使用上下文
public class UserLocationTool implements BiFunction<String, ToolContext, String> {
    @Override
    public String apply(
        @ToolParam(description = "User query") String query,
        ToolContext toolContext) {
        // 从上下文中获取用户信息
        String userId = "";
        if (toolContext != null && toolContext.getContext() != null) {
			RunnableConfig runnableConfig = (RunnableConfig) toolContext.getContext().get(AGENT_CONFIG_CONTEXT_KEY);
			Optional<Object> userIdObjOptional = runnableConfig.metadata("user_id");
			if (userIdObjOptional.isPresent()) {
				userId = (String) userIdObjOptional.get();
			}
		}
		if (userId == null) {
			userId = "1";
		}
        return "1".equals(userId) ? "Florida" : "San Francisco";
    }
}

RunnableConfig 提供了一个 context() 方法,允许你在同一个执行流程中的多个 Hook 调用、多轮模型或工具调用之间共享数据。这对于实现计数器、累积统计信息或跨多次调用维护状态非常有用。

适用场景

  • 跟踪模型或工具调用次数
  • 累积性能指标(总耗时、平均响应时间等)
  • 在 before/after Hook 之间传递临时数据
  • 实现基于计数的限流或断路器

示例:使用 RunnableConfig.context() 实现调用计数器

java 复制代码
@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());  
}  
}

OverallState (全局状态/上下文)

OverallState 通常不是 Spring AI 框架内的一个固定类名(不像 RunnableConfig 属于核心接口),它通常是开发者在构建 基于图(Graph-based)状态机(Stateful) 的 Agent 时自定义的一个类。

在 Spring AI Alibaba 的高级应用(如仿照 LangGraph 的模式)中,Agent 的执行过程被视为一系列节点的流转。OverallState 就是在这些节点之间传递的共享内存对象

它一般包含的就是应用的业务数据。

核心作用:

  1. 统一的数据总线

    • 所有的节点(Node)都从 OverallState 读取数据,处理完后将结果写回 OverallState
  2. 记忆管理 (Memory)

    • 它通常包含:

      • input:用户的原始问题。
      • chat_history:对话的历史消息列表。
      • intermediate_steps:Agent 的中间思考过程或工具调用结果。
      • output:最终生成的答案。
  3. 状态流转

    • 在 Spring AI Alibaba 对接 Qwen(通义千问)时,模型生成的回复会更新到 State 中,下一个节点(比如工具执行节点)会读取 State 中的模型指令去执行工具。

工作流

下面我给出一个示例工作流。

  • Start -> 传入初始 OverallState(包含用户问题)。

  • LLM Node -> 读取 State,调用 Qwen 模型,将模型的回复(比如"去调用天气API")写入 State。

  • Tool Node -> 检测到 State 里有调用请求,执行天气 API,将结果"25度"写入 State。

  • LLM Node -> 再次读取 State(包含问题+工具结果),生成最终回答"今天25度",写入 State。

  • End -> 返回 State 中的最终回答。

总结对比

特性 RunnableConfig OverallState (自定义状态)
关注点 过程控制 (Process Control) 业务数据 (Business Data)
内容示例 递归限制、回调函数、元数据、标签 用户问题、LLM 回复、工具结果、数据库查询值
可变性 通常在运行开始时设定,传递过程中较少修改 在每个步骤(Node)中不断被读取和修改(累加信息)
类比 交通规则与信号灯(红绿灯、限速) 货车上的货物(每一站可能会装卸货物)
相关推荐
NAGNIP1 分钟前
一文搞懂深度学习中的损失函数设计!
人工智能·算法
殷紫川2 分钟前
高并发系统性能优化全链路实战:端到端榨干系统性能,百万 QPS 零卡顿
性能优化·架构
千桐科技4 分钟前
大模型幻觉难解?2026深度解析:知识图谱如何成为LLM落地的“刚需”与高薪新赛道
人工智能·大模型·llm·知识图谱·大模型幻觉·qknow·行业深度ai应用
Hello.Reader5 分钟前
词语没有位置感?用“音乐节拍“给 Transformer 装上时钟——Positional Encoding 图解
人工智能·深度学习·transformer
我叫果冻7 分钟前
ai-assist:基于 LangChain4j 的 RAG 智能助手,本地化部署更安全
人工智能·安全
Monday学长11 分钟前
2026年全维度AI论文写作工具测评:基于实测数据与用户真实反馈
人工智能
Rorsion22 分钟前
CNN经典神经网络架构
人工智能·深度学习·cnn
KG_LLM图谱增强大模型23 分钟前
MedXIAOHE:医学多模态大模型的完整解决方案,字节跳动小荷医学推出
人工智能
天一生水water25 分钟前
科研龙虾 Research-Claw 使用教程
人工智能
熊猫钓鱼>_>1 小时前
WorkBuddy使用心得:腾讯版“免部署小龙虾“的办公新体验
人工智能·ai·腾讯云·agent·wechat·openclaw·workbuddy