mirror of https://gitee.com/dromara/liteFlow
feat(agent): AgentStateStoreResolver 映射 MemoryStorageMode 到 v2 AgentStateStore (NONE/JVM/LOCAL_FILE/REDIS/MYSQL/OSS)
- 新增 AgentStateStoreResolver.resolve(AgentConfig): 按 MemoryStorageMode 选 v2 AgentStateStore,替代 Task 0 删除的 AgentSessionFactory SPI 层。 - NONE→null(v2 stateStore 默认 null,save/load 短路,不造 NoOp 类); JVM→InMemoryAgentStateStore;LOCAL_FILE→JsonFileAgentStateStore(root/.agent-session); REDIS→反射构造 RedisAgentStateStore(Redisson/Jedis/Lettuce 三选一,沿用 1.0 延迟失败范式); MYSQL→反射构造 MysqlAgentStateStore(DataSource + 可选 db/table/createIfNotExist 重载)。 - findings R1-ext 新增 Redis/MySQL/OSS 扩展 store 反射探针签名(含 OSS,供将来扩展枚举复用)。 - 测试 5 例(NONE/JVM/LOCAL_FILE + REDIS/MYSQL beanName 缺失快速失败)全绿, 用 javac + JUnit console-standalone 跑(sibling 测试 WIP,留待 Task 3.2 修后正式 mvn 跑)。 - react-agent-core / liteflow-core 均 BUILD SUCCESS(green commit 不破)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
70d7bbadc4
commit
f0616e8fb8
|
|
@ -57,6 +57,61 @@ default void close(); // 默认 no
|
|||
|
||||
---
|
||||
|
||||
## R1-ext:Redis / MySQL / OSS 扩展 `AgentStateStore` 实现签名(Task 2.1 探针确认)
|
||||
|
||||
**来源:** 对 `~/.m2/repository/io/agentscope/agentscope-extensions-{redis,mysql,oss}/2.0.0-RC3/*.jar` 跑 `javap -p`(Task 2.1,JDK 21)。三个扩展 jar 已在 `liteflow-react-agent-core` pom 以 `<optional>true</optional>` 声明,故 core 编译期不产生硬依赖;resolver 用反射构造,运行期缺失驱动时按"延迟失败"抛 `AgentConfigException`。
|
||||
|
||||
### Redis — `io.agentscope.extensions.redis.state.RedisAgentStateStore`
|
||||
- 接口:`implements io.agentscope.core.state.AgentStateStore`。
|
||||
- 构造:**私有的 `RedisAgentStateStore(Builder)`**,对外只有 `public static Builder builder()`。
|
||||
- `RedisAgentStateStore.Builder`(实测全量方法):
|
||||
```java
|
||||
Builder(); // 无参
|
||||
Builder keyPrefix(String);
|
||||
Builder jedisClient(redis.clients.jedis.UnifiedJedis);
|
||||
Builder lettuceClient(io.lettuce.core.RedisClient);
|
||||
Builder lettuceClusterClient(io.lettuce.core.cluster.RedisClusterClient); // v2 新增(集群)
|
||||
Builder redissonClient(org.redisson.api.RedissonClient);
|
||||
Builder clientAdapter(io.agentscope.extensions.redis.state.RedisClientAdapter); // 通用 SPI 逃生口
|
||||
RedisAgentStateStore build();
|
||||
```
|
||||
- **另有 `RedissonAgentStateStore`(`...state.redisson`)/ `JedisAgentStateStore`(`...state.jedis`)两个独立实现**,各自带 `Builder`、各自只接单一客户端类型。Task 2.1 **不使用**这两个(用户配的 `clientType` 三选一 + 单一 `RedisAgentStateStore.Builder` 已覆盖 REDISSON/JEDIS/LETTUCE 全部场景,语义与 1.0 `RedisAgentSessionFactory` 的反射路径一致)。
|
||||
- `RedisClientAdapter` 是内部 SPI 接口(`set/get/rightPushList/rangeList/getListLength/deleteKeys/addToSet/getSetMembers/getSetSize/keyExists/findKeysByPattern/close`),用户通常不直接实现。
|
||||
- 对 `RedisMemoryConfig.RedisClientType` 的映射(沿用 1.0 范式):
|
||||
- `REDISSON` → `redissonClient(RedissonClient)`
|
||||
- `JEDIS` → `jedisClient(UnifiedJedis)`
|
||||
- `LETTUCE` → `lettuceClient(RedisClient)`(**非集群**;集群需 v2 新的 `lettuceClusterClient`,但 `RedisClientType` 枚举目前未区分集群,按非集群走)
|
||||
|
||||
### MySQL — `io.agentscope.extensions.mysql.state.MysqlAgentStateStore`
|
||||
- 接口:`implements io.agentscope.core.state.AgentStateStore`。
|
||||
- 构造:**没有 Builder,直接 public 构造器**:
|
||||
```java
|
||||
MysqlAgentStateStore(javax.sql.DataSource);
|
||||
MysqlAgentStateStore(javax.sql.DataSource, boolean createIfNotExist);
|
||||
MysqlAgentStateStore(javax.sql.DataSource, String databaseName, String tableName, boolean createIfNotExist);
|
||||
```
|
||||
- 默认库名 `agentscope`、表名 `agentscope_sessions`(`DEFAULT_DATABASE_NAME`/`DEFAULT_TABLE_NAME` 私有常量)。
|
||||
- 对 `MysqlMemoryConfig` 的映射:
|
||||
- `dataSourceBeanName`(必填)→ `ContextAwareHolder.getBean(...)` 取 `DataSource`。
|
||||
- `databaseName`/`tableName` 均留空 → 走 `(DataSource, createIfNotExist)` 双参重载。
|
||||
- 任一非空 → 走 `(DataSource, databaseName, tableName, createIfNotExist)` 四参重载(留空的那个传 null,由 store 内部回退默认)。
|
||||
- `createIfNotExist` → 第 2/4 参数。
|
||||
|
||||
### OSS — `io.agentscope.extensions.oss.OssAgentStateStore`
|
||||
- 接口:`implements io.agentscope.core.state.AgentStateStore`。
|
||||
- 构造:私有的 `OssAgentStateStore(Builder)`,对外 `public static Builder builder()`。
|
||||
- `OssAgentStateStore.Builder`(实测全量方法):
|
||||
```java
|
||||
Builder();
|
||||
Builder ossClient(com.aliyun.oss.OSS);
|
||||
Builder bucketName(String);
|
||||
Builder keyPrefix(String);
|
||||
OssAgentStateStore build();
|
||||
```
|
||||
- **注意:** `MemoryStorageMode` 枚举目前只有 NONE/JVM/LOCAL_FILE/REDIS/MYSQL 五档,**没有 OSS 档**。Task 2.1 resolver 不实现 OSS(无对应 mode 可映射);此签名记录于此供将来扩展 `MemoryStorageMode.OSS` 时直接复用,避免重复探针。
|
||||
|
||||
---
|
||||
|
||||
## R4:各 vendor `XxxChatModel.builder()` 方法名(名字有分歧!)
|
||||
|
||||
**全部位于 `io.agentscope.core.model` 包**(不在独立 vendor jar,都在 core 里)。`public static Builder builder()` 工厂每个都有。**关键差异:stream 标志与 generateOptions 的方法名因 vendor 不同**。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
package com.yomahub.liteflow.agent.state;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.LocalFileMemoryConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import com.yomahub.liteflow.property.agent.MysqlMemoryConfig;
|
||||
import com.yomahub.liteflow.property.agent.RedisMemoryConfig;
|
||||
import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
|
||||
import io.agentscope.core.state.AgentStateStore;
|
||||
import io.agentscope.core.state.InMemoryAgentStateStore;
|
||||
import io.agentscope.core.state.JsonFileAgentStateStore;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 把 liteflow 的 {@link MemoryStorageMode} 映射到 agentscope v2 的
|
||||
* {@link AgentStateStore},替代 Task 0 删除的整个 {@code AgentSessionFactory} SPI 层。
|
||||
*
|
||||
* <p>这是会话/状态层重设计(迁移 spec §4.4)的第一块:resolver 只负责
|
||||
* "按 mode 选 store 实现",不负责拼存储 key——v2 store 内部按
|
||||
* {@code (userId, sessionId)} 对寻址({@code userId = conversationId}、
|
||||
* {@code sessionId = agentKey},由 Task 2.3 在 {@code RuntimeContext} 里填)。
|
||||
*
|
||||
* <h2>mode 映射</h2>
|
||||
* <ul>
|
||||
* <li>{@link MemoryStorageMode#NONE} — 返回 {@code null}。v2 {@code ReActAgent.builder()}
|
||||
* 的 {@code stateStore} 字段默认即 {@code null},{@code ReActAgent} 构造期对
|
||||
* {@code stateStore} 全部用 {@code if (stateStore != null)} 守卫,null 时
|
||||
* save/load/exists 全部短路——等价于 NONE(findings R1 实测)。<b>不造 NoOp 类。</b></li>
|
||||
* <li>{@link MemoryStorageMode#JVM} — {@code new InMemoryAgentStateStore()}(core 自带)。</li>
|
||||
* <li>{@link MemoryStorageMode#LOCAL_FILE} —
|
||||
* {@code new JsonFileAgentStateStore(<workspace.root/.agent-session>)}(core 自带)。
|
||||
* 子目录名固定为 {@link LocalFileMemoryConfig#SUB_DIR},与各 session 的 workspace 平级。</li>
|
||||
* <li>{@link MemoryStorageMode#REDIS} — 反射构造
|
||||
* {@code io.agentscope.extensions.redis.state.RedisAgentStateStore}(optional 扩展 jar)。
|
||||
* 客户端 bean 由用户在容器中注册(Redisson/Jedis/Lettuce 三选一),按
|
||||
* {@code liteflow.agent.session.memory.redis.beanName} 借 {@link ContextAwareHolder} 查找。</li>
|
||||
* <li>{@link MemoryStorageMode#MYSQL} — 反射构造
|
||||
* {@code io.agentscope.extensions.mysql.state.MysqlAgentStateStore}(optional 扩展 jar)。
|
||||
* {@code javax.sql.DataSource} bean 同样由用户注册、按 {@code dataSourceBeanName} 查找。</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>延迟失败语义(沿用 1.0 {@code RedisAgentSessionFactory}/{@code MysqlAgentSessionFactory}
|
||||
* 范式):</b> core 模块编译期对 Redis/MySQL 驱动与扩展 jar 都是 optional、无硬依赖。
|
||||
* 若用户选了 REDIS/MYSQL 但 classpath 缺扩展 jar、或容器里找不到对应 bean,resolver 在
|
||||
* {@code process()} 首次调用时(而不是框架启动时)抛 {@link AgentConfigException}。
|
||||
*
|
||||
* <p>{@code MemoryStorageMode} 目前没有 OSS 档(见 findings R1-ext),故本类不处理 OSS;
|
||||
* 将来若新增 OSS 档,其构造签名({@code OssAgentStateStore.builder().ossClient(OSS)
|
||||
* .bucketName(String).keyPrefix(String).build()})已在 R1-ext 记录,可直接复用。
|
||||
*/
|
||||
public final class AgentStateStoreResolver {
|
||||
|
||||
/** Redis 扩展 store 全名(core 对其无编译期依赖,反射加载)。 */
|
||||
private static final String REDIS_STORE_CLASS =
|
||||
"io.agentscope.extensions.redis.state.RedisAgentStateStore";
|
||||
|
||||
/** MySQL 扩展 store 全名(core 对其无编译期依赖,反射加载)。 */
|
||||
private static final String MYSQL_STORE_CLASS =
|
||||
"io.agentscope.extensions.mysql.state.MysqlAgentStateStore";
|
||||
|
||||
private AgentStateStoreResolver() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 {@code cfg.session.memory.mode} 选 v2 {@link AgentStateStore}。
|
||||
*
|
||||
* @param cfg agent 配置(读 {@code workspace.root}、{@code session.memory.*})
|
||||
* @return 对应的 state store;{@link MemoryStorageMode#NONE} 返回 {@code null}
|
||||
* (调用方 {@code builder.stateStore(null)} 即 NONE 语义)
|
||||
* @throws AgentConfigException REDIS/MYSQL 模式下扩展 jar/bean 缺失或类型不匹配
|
||||
*/
|
||||
public static AgentStateStore resolve(AgentConfig cfg) {
|
||||
if (cfg == null) {
|
||||
// 防御:无配置时按 NONE 语义(不持久化)处理,与 1.0 默认行为一致。
|
||||
return null;
|
||||
}
|
||||
MemoryStorageMode mode = cfg.getSession().getMemory().getMode();
|
||||
if (mode == null) {
|
||||
return null;
|
||||
}
|
||||
switch (mode) {
|
||||
case NONE:
|
||||
return null;
|
||||
case JVM:
|
||||
return new InMemoryAgentStateStore();
|
||||
case LOCAL_FILE:
|
||||
return newLocalFileStore(cfg);
|
||||
case REDIS:
|
||||
return newRedisStore(cfg);
|
||||
case MYSQL:
|
||||
return newMysqlStore(cfg);
|
||||
default:
|
||||
// 未来新增 mode(如 OSS)在此显式报错,而非静默返回 null。
|
||||
throw new AgentConfigException(
|
||||
"Unsupported MemoryStorageMode: " + mode
|
||||
+ " (AgentStateStoreResolver 仅支持 NONE/JVM/LOCAL_FILE/REDIS/MYSQL)");
|
||||
}
|
||||
}
|
||||
|
||||
/* ----- LOCAL_FILE ----- */
|
||||
|
||||
private static AgentStateStore newLocalFileStore(AgentConfig cfg) {
|
||||
String root = cfg.getWorkspace().getRoot();
|
||||
if (root == null || root.trim().isEmpty()) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.workspace.root is required when memory.mode=LOCAL_FILE");
|
||||
}
|
||||
Path dir = Paths.get(root).resolve(LocalFileMemoryConfig.SUB_DIR);
|
||||
return new JsonFileAgentStateStore(dir);
|
||||
}
|
||||
|
||||
/* ----- REDIS(反射构造,core 不硬依赖 Redisson/Jedis/Lettuce/扩展 jar)----- */
|
||||
|
||||
private static AgentStateStore newRedisStore(AgentConfig cfg) {
|
||||
RedisMemoryConfig rc = cfg.getSession().getMemory().getRedis();
|
||||
if (rc == null) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.session.memory.redis is required when mode=REDIS");
|
||||
}
|
||||
String beanName = rc.getBeanName();
|
||||
if (beanName == null || beanName.trim().isEmpty()) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.session.memory.redis.beanName is required when mode=REDIS");
|
||||
}
|
||||
Object client = ContextAwareHolder.loadContextAware().getBean(beanName);
|
||||
if (client == null) {
|
||||
throw new AgentConfigException("Redis client bean not found: " + beanName);
|
||||
}
|
||||
|
||||
RedisMemoryConfig.RedisClientType clientType = rc.getClientType();
|
||||
String builderMethod;
|
||||
String clientFqn;
|
||||
if (clientType == null) {
|
||||
clientType = RedisMemoryConfig.RedisClientType.REDISSON;
|
||||
}
|
||||
switch (clientType) {
|
||||
case REDISSON:
|
||||
builderMethod = "redissonClient";
|
||||
clientFqn = "org.redisson.api.RedissonClient";
|
||||
break;
|
||||
case JEDIS:
|
||||
builderMethod = "jedisClient";
|
||||
clientFqn = "redis.clients.jedis.UnifiedJedis";
|
||||
break;
|
||||
case LETTUCE:
|
||||
builderMethod = "lettuceClient";
|
||||
clientFqn = "io.lettuce.core.RedisClient";
|
||||
break;
|
||||
default:
|
||||
throw new AgentConfigException("Unsupported redis client type: " + clientType);
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> storeClass = Class.forName(REDIS_STORE_CLASS);
|
||||
Object builder = storeClass.getMethod("builder").invoke(null);
|
||||
Class<?> clientTypeClass = Class.forName(clientFqn);
|
||||
if (!clientTypeClass.isInstance(client)) {
|
||||
throw new AgentConfigException("Bean '" + beanName + "' is not a "
|
||||
+ clientFqn + "; got " + client.getClass().getName());
|
||||
}
|
||||
Method setter = builder.getClass().getMethod(builderMethod, clientTypeClass);
|
||||
setter.invoke(builder, client);
|
||||
if (rc.getKeyPrefix() != null && !rc.getKeyPrefix().isEmpty()) {
|
||||
builder.getClass().getMethod("keyPrefix", String.class).invoke(builder, rc.getKeyPrefix());
|
||||
}
|
||||
return (AgentStateStore) builder.getClass().getMethod("build").invoke(builder);
|
||||
} catch (AgentConfigException e) {
|
||||
throw e;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AgentConfigException(
|
||||
"Class not found while building RedisAgentStateStore: " + e.getMessage()
|
||||
+ ". Add agentscope-extensions-redis (+ Redisson/Jedis/Lettuce driver) to the classpath.", e);
|
||||
} catch (Exception e) {
|
||||
throw new AgentConfigException("Failed to build RedisAgentStateStore", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* ----- MYSQL(反射构造,core 不硬依赖扩展 jar)----- */
|
||||
|
||||
private static AgentStateStore newMysqlStore(AgentConfig cfg) {
|
||||
MysqlMemoryConfig mc = cfg.getSession().getMemory().getMysql();
|
||||
if (mc == null) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.session.memory.mysql is required when mode=MYSQL");
|
||||
}
|
||||
String dsBeanName = mc.getDataSourceBeanName();
|
||||
if (dsBeanName == null || dsBeanName.trim().isEmpty()) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.session.memory.mysql.dataSourceBeanName is required when mode=MYSQL");
|
||||
}
|
||||
Object ds = ContextAwareHolder.loadContextAware().getBean(dsBeanName);
|
||||
if (ds == null) {
|
||||
throw new AgentConfigException("DataSource bean not found: " + dsBeanName);
|
||||
}
|
||||
if (!(ds instanceof DataSource)) {
|
||||
throw new AgentConfigException("Bean '" + dsBeanName + "' is not a javax.sql.DataSource; got "
|
||||
+ ds.getClass().getName());
|
||||
}
|
||||
DataSource dataSource = (DataSource) ds;
|
||||
boolean createIfNotExist = mc.isCreateIfNotExist();
|
||||
String db = mc.getDatabaseName();
|
||||
String table = mc.getTableName();
|
||||
|
||||
try {
|
||||
Class<?> storeClass = Class.forName(MYSQL_STORE_CLASS);
|
||||
// 三个 public 构造器:见 findings R1-ext。
|
||||
// 任一自定义库名/表名非空 -> 走四参重载(留空那个传 null,store 内部回退默认)。
|
||||
if ((db != null && !db.isEmpty()) || (table != null && !table.isEmpty())) {
|
||||
return (AgentStateStore) storeClass
|
||||
.getConstructor(DataSource.class, String.class, String.class, boolean.class)
|
||||
.newInstance(dataSource, nullIfEmpty(db), nullIfEmpty(table), createIfNotExist);
|
||||
}
|
||||
return (AgentStateStore) storeClass
|
||||
.getConstructor(DataSource.class, boolean.class)
|
||||
.newInstance(dataSource, createIfNotExist);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AgentConfigException(
|
||||
"Class not found while building MysqlAgentStateStore: " + e.getMessage()
|
||||
+ ". Add agentscope-extensions-mysql to the classpath.", e);
|
||||
} catch (Exception e) {
|
||||
throw new AgentConfigException("Failed to build MysqlAgentStateStore", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String nullIfEmpty(String s) {
|
||||
return (s == null || s.isEmpty()) ? null : s;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.agent.state.AgentStateStoreResolver;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Task 2.1 REDIS/MYSQL 配置校验测试。
|
||||
*
|
||||
* <p>这里覆盖的是 <b>配置缺失/bean 缺失的快速失败路径</b>(不需要真实的 Redis/MySQL
|
||||
* 驱动或扩展 jar,纯单元测试)。反射构造的 happy-path(bean 存在 + 扩展 jar 在
|
||||
* classpath)属于集成测试范畴,需要把 {@code agentscope-extensions-redis/mysql}
|
||||
* 作为 test 依赖显式引入——留待后续 Task 3.2 在 react-agent 集成测试里覆盖。
|
||||
*
|
||||
* <p>Resolver 的反射构造签名见 findings R1-ext:
|
||||
* <ul>
|
||||
* <li>Redis:{@code RedisAgentStateStore.builder().redissonClient|jedisClient|lettuceClient(...).keyPrefix(...).build()}</li>
|
||||
* <li>MySQL:{@code new MysqlAgentStateStore(DataSource[, createIfNotExist])} 或
|
||||
* {@code new MysqlAgentStateStore(DataSource, db, table, createIfNotExist)}</li>
|
||||
* </ul>
|
||||
*/
|
||||
class AgentStateStoreResolverExtTest {
|
||||
|
||||
private AgentConfig cfg(MemoryStorageMode mode) {
|
||||
AgentConfig c = new AgentConfig();
|
||||
c.getWorkspace().setRoot("/tmp/unused");
|
||||
c.getSession().getMemory().setMode(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Test
|
||||
void redis_missingBeanName_throwsConfigException() {
|
||||
// beanName 未配 -> 启动期配置校验直接报错(不会走到反射)。
|
||||
AgentConfig c = cfg(MemoryStorageMode.REDIS);
|
||||
// redis.beanName 默认 null
|
||||
AgentConfigException ex = assertThrows(AgentConfigException.class,
|
||||
() -> AgentStateStoreResolver.resolve(c));
|
||||
assertTrue(ex.getMessage().contains("beanName"),
|
||||
"missing beanName must be reported; got: " + ex.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mysql_missingDataSourceBeanName_throwsConfigException() {
|
||||
AgentConfig c = cfg(MemoryStorageMode.MYSQL);
|
||||
AgentConfigException ex = assertThrows(AgentConfigException.class,
|
||||
() -> AgentStateStoreResolver.resolve(c));
|
||||
assertTrue(ex.getMessage().contains("dataSourceBeanName"),
|
||||
"missing dataSourceBeanName must be reported; got: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.yomahub.liteflow.test.agent.v2;
|
||||
|
||||
import com.yomahub.liteflow.agent.state.AgentStateStoreResolver;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.state.AgentStateStore;
|
||||
import io.agentscope.core.state.InMemoryAgentStateStore;
|
||||
import io.agentscope.core.state.JsonFileAgentStateStore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Task 2.1 单元测试:验证 {@link AgentStateStoreResolver} 把
|
||||
* {@link MemoryStorageMode} 正确映射到 v2 {@link AgentStateStore}。
|
||||
*
|
||||
* <p>仅覆盖 NONE/JVM/LOCAL_FILE 三档——这三档的签名已在 findings R1 实测确认,
|
||||
* 无外部依赖、纯单元测试(不启动 Spring)。REDIS/MYSQL 的 mock-bean 覆盖见
|
||||
* {@code AgentStateStoreResolverRedisTest}/{@code ...MysqlTest}(可选,依赖扩展 jar 在 classpath)。
|
||||
*/
|
||||
class AgentStateStoreResolverTest {
|
||||
|
||||
@TempDir
|
||||
Path tmp;
|
||||
|
||||
private AgentConfig cfg(MemoryStorageMode mode) {
|
||||
AgentConfig c = new AgentConfig();
|
||||
c.getWorkspace().setRoot(tmp.toString());
|
||||
c.getSession().getMemory().setMode(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Test
|
||||
void none_returnsNull() {
|
||||
// NONE 语义 = 不设 stateStore(resolver 返回 null,调用方 builder.stateStore(null))。
|
||||
assertNull(AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.NONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jvm_mapsTo_inMemory() {
|
||||
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.JVM));
|
||||
assertNotNull(s);
|
||||
assertTrue(s instanceof InMemoryAgentStateStore, s.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void localFile_mapsTo_jsonFile() {
|
||||
AgentStateStore s = AgentStateStoreResolver.resolve(cfg(MemoryStorageMode.LOCAL_FILE));
|
||||
assertNotNull(s);
|
||||
assertTrue(s instanceof JsonFileAgentStateStore, s.getClass().getName());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue