在构建基于Spring AI Alibaba的智能体(Agent)和工作流(Workflow)时,理解RunnableConfig和OverallState这两个核心概念的区别与联系至关重要。作为在AI应用架构中频繁出现的两种设计模式,它们分别承担着不同的职责,但又协同工作以确保系统的可靠性和灵活性。
RunnableConfig是Spring AI框架提供的标准接口,主要负责运行时控制参数的传递。它就像是一个"执行指挥官",不直接参与业务数据处理,而是专注于如何执行任务。与之形成对比的是,OverallState通常是开发者自定义的业务状态容器,它更像是一个"数据货车",在流程的各个节点间运输和共享业务数据。
这种职责分离的设计使得系统能够:
RunnableConfig作为Spring AI执行链中的控制中心,主要包含以下关键功能组件:
执行控制模块
监控与回调模块
元数据管理模块
这种模块化设计使得RunnableConfig能够灵活应对各种执行场景,同时保持接口的简洁性。
RunnableConfig解决了一个关键问题:在异步编程环境下如何安全地传递上下文。传统ThreadLocal方案在以下场景会失效:
RunnableConfig通过以下设计确保线程安全:
java复制// 线程安全使用示例
public CompletableFuture<String> asyncProcess(RunnableConfig config) {
// 创建配置的深拷贝以确保线程安全
RunnableConfig threadSafeConfig = config.copy();
return CompletableFuture.supplyAsync(() -> {
// 在新线程中安全使用配置
String userId = threadSafeConfig.metadata("user_id")
.orElse("default");
return process(userId);
});
}
通过RunnableConfig的metadata可以实现分布式追踪:
java复制// 添加追踪信息
runnableConfig = runnableConfig.withMetadata(
"traceId", MDC.get("traceId")
).withMetadata(
"spanId", MDC.get("spanId")
);
// 在工具类中获取追踪信息
public class TracingTool {
public String execute(ToolContext context) {
RunnableConfig config = extractConfig(context);
String traceId = config.metadata("traceId").orElse("");
// 使用traceId进行日志记录
}
}
利用context实现动态限流:
java复制@HookPosition(BEFORE_MODEL)
public class RateLimitHook implements ModelHook {
private final RateLimiter limiter;
public CompletableFuture<Map<String, Object>> beforeModel(
OverAllState state,
RunnableConfig config
) {
// 从context获取调用计数
int calls = (int) config.context()
.getOrDefault("calls", 0);
if (limiter.shouldLimit(calls)) {
throw new RateLimitExceededException();
}
// 更新计数
config.context().put("calls", calls + 1);
return CompletableFuture.completedFuture(Map.of());
}
}
虽然OverallState没有固定接口,但常见的实现通常包含以下核心字段:
java复制public class CustomState {
// 输入输出
private String input;
private String output;
// 对话历史
private List<Message> chatHistory;
// 中间步骤
private List<IntermediateStep> steps;
// 自定义业务数据
private Map<String, Object> businessData;
// 执行状态
private WorkflowStatus status;
}
推荐采用不可变设计来避免并发问题:
java复制public class ImmutableState {
private final String input;
private final List<Message> history;
// 通过with方法创建新状态
public ImmutableState withInput(String newInput) {
return new ImmutableState(newInput, this.history);
}
}
对于复杂工作流,可以实现状态版本管理:
java复制public class VersionedState {
private final UUID versionId;
private final Instant createdAt;
private final CustomState state;
// 每次修改生成新版本
public VersionedState nextVersion(CustomState newState) {
return new VersionedState(UUID.randomUUID(), Instant.now(), newState);
}
}
根据状态内容决定工作流走向:
java复制public class RouterNode implements WorkflowNode {
public OverallState execute(OverallState state) {
if (state.requiresToolCall()) {
return toolNode.execute(state);
} else {
return llmNode.execute(state);
}
}
}
实现工作流的持久化和恢复:
java复制public class StatePersistence {
public void saveSnapshot(OverallState state) {
String json = serialize(state);
repository.save(state.getId(), json);
}
public OverallState restoreSnapshot(String id) {
String json = repository.findById(id);
return deserialize(json);
}
}
| 维度 | RunnableConfig | OverallState |
|---|---|---|
| 设计目标 | 控制执行过程 | 承载业务数据 |
| 可变性 | 有限可变(部分字段) | 高度可变 |
| 生命周期 | 单次执行 | 可能跨多个执行 |
| 线程安全 | 内置保证 | 需要开发者实现 |
| 序列化支持 | 通常不需要 | 通常需要 |
java复制public class CustomerSupportAgent {
public OverallState handleRequest(OverallState state, RunnableConfig config) {
// 使用config控制执行
int maxTurns = config.metadata("maxTurns").orElse(3);
// 使用state传递业务数据
if (state.getTurnCount() >= maxTurns) {
state.setOutput("Maximum turns reached");
return state;
}
// 处理逻辑...
return processedState;
}
}
java复制public class DecisionWorkflow {
public OverallState execute(OverallState state, RunnableConfig config) {
// 初始化监控
config.callbacks().add(new PerformanceMonitor());
// 多阶段处理
state = analysisPhase.execute(state, config);
state = decisionPhase.execute(state, config);
state = outputPhase.execute(state, config);
// 记录执行指标
recordMetrics(config);
return state;
}
}
java复制// 状态优化示例
public class OptimizedState {
@Getter(lazy = true)
private final List<Message> fullHistory = loadFullHistory();
private List<Message> loadFullHistory() {
// 按需从数据库加载
}
}
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 配置未传递 | 线程切换丢失上下文 | 确保显式传递RunnableConfig |
| 状态不一致 | 并发修改 | 采用不可变状态设计 |
| 内存泄漏 | 未清理的状态引用 | 实现状态生命周期管理 |
| 执行流程中断 | 递归限制触发 | 调整recursionLimit参数 |
| 回调未触发 | 未正确注册CallbackManager | 检查回调注册逻辑 |
java复制config = config.withMetadata("debug", true);
java复制logger.debug("State snapshot: {}", state.copy());
java复制config = config.withCallbacks(LangSmithCallback.create());
java复制public class StateValidator {
public static void validate(OverallState state) {
Assert.notNull(state.getInput(), "Input cannot be null");
// 更多验证...
}
}
在实际项目中使用这些模式时,我总结了以下几点经验:
一个典型的演进路径可能是:
java复制// 成熟期架构示例
public class MatureWorkflow {
public VersionedState execute(VersionedState versionedState,
RunnableConfig config) {
// 获取当前状态
CustomState current = versionedState.getState();
// 执行处理
CustomState newState = process(current, config);
// 生成新版本
return versionedState.nextVersion(newState);
}
}
对于需要处理高并发场景的系统,建议考虑:
最终,良好的状态管理设计应该做到:
✓ 控制逻辑与业务逻辑分离
✓ 线程安全得到保证
✓ 系统行为可预测
✓ 便于调试和监控
✓ 能够适应需求变化