mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-10 03:07:32 +08:00
feat(react-agent): add session factory infrastructure, memory storage config, and integration tests
- Add MemoryStorageConfig/MemoryStorageMode and per-backend configs (Redis, MySQL, workspace file) - Add AgentSessionFactoryRegistry with NONE, JVM, WORKSPACE_FILE, REDIS, MYSQL implementations - Add integration test suite with EL-orchestrated Spring Boot tests - Remove per-module READMEs in favor of unified guide - Update POMs, CLAUDE.md, AGENTS.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,6 +24,7 @@
|
||||
*.del
|
||||
*.pmd
|
||||
.tern-project
|
||||
*.factorypath
|
||||
|
||||
|
||||
# Logs and databases #
|
||||
@@ -52,8 +53,10 @@ Thumbs.db
|
||||
# Build output directies
|
||||
/target
|
||||
*/target
|
||||
**/target
|
||||
/build
|
||||
*/build
|
||||
**/build
|
||||
|
||||
|
||||
# IntelliJ specific files/directories
|
||||
|
||||
237
AGENTS.md
Normal file
237
AGENTS.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
LiteFlow (v2.15.3) is a lightweight rules engine framework for complex component-based business orchestration. It uses a DSL to drive workflows with support for hot reload and 11 scripting languages. The project targets Java 8+ (up to JDK 25) and has 2000+ test cases.
|
||||
|
||||
**Official Documentation**: https://liteflow.cc/pages/5816c5/
|
||||
|
||||
## Commands
|
||||
|
||||
### Build
|
||||
```bash
|
||||
# Build entire project (uses 'compile' profile by default, includes test modules)
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# Build with tests
|
||||
mvn clean package
|
||||
|
||||
# Build specific module
|
||||
mvn clean package -DskipTests -pl liteflow-core
|
||||
|
||||
# Build for release (production modules only, excludes tests)
|
||||
mvn clean package -DskipTests -P release
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
```bash
|
||||
# Run all tests
|
||||
mvn test
|
||||
|
||||
# Run tests for specific module (30+ test modules in liteflow-testcase-el/)
|
||||
mvn test -pl liteflow-testcase-el/liteflow-testcase-el-springboot
|
||||
|
||||
# Run single test class
|
||||
mvn test -pl liteflow-core -Dtest=FlowExecutorTest
|
||||
|
||||
# Run specific test method
|
||||
mvn test -pl liteflow-core -Dtest=FlowExecutorTest#testExecute
|
||||
```
|
||||
|
||||
### Other Commands
|
||||
```bash
|
||||
# Dependency tree
|
||||
mvn dependency:tree
|
||||
|
||||
# View module structure
|
||||
ls liteflow-*/pom.xml
|
||||
```
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
### Core Execution Model
|
||||
|
||||
**FlowExecutor** → **Chain** → **Condition Tree** → **Node Components**
|
||||
|
||||
1. **FlowExecutor**: Entry point for executing workflows (`execute2Resp(chainId, param)`)
|
||||
2. **FlowBus**: Central metadata registry for all chains and nodes (thread-safe, supports hot reload)
|
||||
3. **Chain**: Named workflow composed of an EL expression that compiles to a Condition tree
|
||||
4. **Slot/DataBus**: Thread-safe context management using slot pooling (configurable `slotSize`)
|
||||
5. **NodeComponent**: Base class for all business logic components
|
||||
|
||||
### Key Architectural Patterns
|
||||
|
||||
#### 1. Two-Stage Parsing
|
||||
Chains are built in two phases to handle circular dependencies:
|
||||
- **Phase 1**: Register chain IDs (creates placeholder chains)
|
||||
- **Phase 2**: Build complete condition trees with EL parsing
|
||||
|
||||
#### 2. EL Expression Language
|
||||
Uses QLExpress to parse declarative workflows. Example operators:
|
||||
- **THEN(a, b, c)**: Sequential execution
|
||||
- **WHEN(a, b, c)**: Parallel execution (async)
|
||||
- **IF(condition, trueNode, falseNode)**: Conditional branching
|
||||
- **SWITCH(selector).to(a, b, c)**: Multi-way branching
|
||||
- **FOR(count).DO(loop)**: Fixed-count loop
|
||||
- **WHILE(condition).DO(loop)**: Condition-based loop
|
||||
- **ITERATOR(iterator).DO(loop)**: Iterator-based loop
|
||||
- **RETRY(node).times(3).forException(Ex.class)**: Retry mechanism
|
||||
- **CATCH(node).DO(handler)**: Exception handling
|
||||
- **TIMEOUT(node).time(1000)**: Execution timeout (ms)
|
||||
- **PRE(a, b)**: Pre-conditions (always run before chain, even on error)
|
||||
- **FINALLY(a, b)**: Finally-conditions (always run after chain)
|
||||
- **AND(a, b)**, **OR(a, b)**, **NOT(a)**: Boolean logic for IF conditions
|
||||
- **node.tag("t")**, **.data("k","v")**, **.id("id")**: Per-node modifiers
|
||||
|
||||
Rules are defined in XML/JSON/YML:
|
||||
```xml
|
||||
<chain name="myChain">
|
||||
THEN(a, WHEN(b, c).maxWaitSeconds(5), IF(e, f, g));
|
||||
</chain>
|
||||
```
|
||||
|
||||
#### 3. Component Types
|
||||
All extend `NodeComponent` but have specialized behaviors:
|
||||
- **NodeComponent**: Standard synchronous component (`process()` method)
|
||||
- **NodeBooleanComponent**: Returns boolean for IF conditions (`processBoolean()`)
|
||||
- **NodeSwitchComponent**: Returns string for SWITCH routing (`processSwitch()`)
|
||||
- **NodeIteratorComponent**: Provides iteration logic for ITERATOR construct
|
||||
- **NodeForComponent**: Controls FOR loop behavior
|
||||
- **ScriptComponent**: Script-based components (Groovy, JS, Python, etc.)
|
||||
|
||||
**Declarative Component Pattern**: Any Spring bean can become a component without extending base classes by using `@LiteflowCmpDefine` (class-level, specifies `NodeTypeEnum`) and `@LiteflowMethod` (method-level, maps to `LiteFlowMethodEnum`). This avoids class hierarchy constraints.
|
||||
|
||||
**Component Lifecycle Hooks** (override in NodeComponent subclasses or via `@LiteflowMethod`):
|
||||
- `isAccess()` – gate check before execution; return `false` to skip
|
||||
- `beforeProcess()` / `afterProcess()` – pre/post hooks per component
|
||||
- `onSuccess()` / `onError()` – outcome callbacks
|
||||
- `isContinueOnError()` – whether WHEN continues if this node fails
|
||||
- `isEnd()` – signals chain should stop after this node
|
||||
- `rollback()` – called in reverse order on failure
|
||||
|
||||
**`@FallbackCmp`**: Annotate a fallback component that activates when the primary component is not found or throws.
|
||||
|
||||
#### 4. Slot-Based Context
|
||||
Thread-safe execution context:
|
||||
- `DataBus.offerSlot(chainId)` acquires a slot from pool
|
||||
- Slot contains execution metadata, context beans, and step tracking
|
||||
- `DataBus.releaseSlot(slotIndex)` returns slot to pool
|
||||
- Pool size configurable via `slotSize` property
|
||||
|
||||
#### 5. Parse Mode Strategies
|
||||
Three modes (`ParseModeEnum`):
|
||||
- **PARSE_ALL_ON_START**: Parse all chains at startup (default)
|
||||
- **PARSE_ONE_ON_FIRST_EXEC**: Lazy parse each chain on first use (faster startup)
|
||||
- **PARSE_ALL_ON_FIRST_EXEC**: Parse all chains on first any chain execution
|
||||
|
||||
### Module Structure
|
||||
|
||||
#### Core Modules
|
||||
- **liteflow-core**: Core engine (FlowExecutor, FlowBus, DataBus, Slot, Condition system, component model)
|
||||
- **liteflow-el-builder**: Programmatic chain builder API using QLExpress
|
||||
|
||||
#### Rule Source Plugins (6 implementations in `liteflow-rule-plugin/`)
|
||||
- **liteflow-rule-zk**: ZooKeeper configuration source
|
||||
- **liteflow-rule-sql**: SQL database configuration source
|
||||
- **liteflow-rule-nacos**: Nacos configuration center
|
||||
- **liteflow-rule-etcd**: etcd configuration source
|
||||
- **liteflow-rule-apollo**: Apollo configuration center
|
||||
- **liteflow-rule-redis**: Redis configuration source
|
||||
|
||||
#### Script Plugins (11 languages in `liteflow-script-plugin/`)
|
||||
- **liteflow-script-groovy**: Groovy scripting
|
||||
- **liteflow-script-javascript**: Rhino JavaScript (JSR223)
|
||||
- **liteflow-script-graaljs**: GraalVM JavaScript
|
||||
- **liteflow-script-qlexpress**: Alibaba QLExpress
|
||||
- **liteflow-script-python**: Jython (Python on JVM)
|
||||
- **liteflow-script-lua**: LuaJ
|
||||
- **liteflow-script-aviator**: Aviator expression language
|
||||
- **liteflow-script-java**: Janino (Java compiler)
|
||||
- **liteflow-script-javax**: JSR223 standard Java compiler
|
||||
- **liteflow-script-javax-pro**: Liquor-based Java compiler (enhanced)
|
||||
- **liteflow-script-kotlin**: Kotlin scripting
|
||||
|
||||
#### Framework Integration
|
||||
- **liteflow-spring**: Spring framework integration (component scanning, bean lifecycle, AOP)
|
||||
- **liteflow-spring-boot-starter**: Spring Boot auto-configuration with `@ConfigurationProperties`
|
||||
- **liteflow-solon-plugin**: Solon framework integration (lightweight alternative to Spring)
|
||||
|
||||
#### Testing Infrastructure
|
||||
30+ test modules in `liteflow-testcase-el/` organized by:
|
||||
1. **Framework**: springboot, springnative, solon, nospring
|
||||
2. **Config Sources**: zk, nacos, etcd, apollo, redis, sql
|
||||
3. **Scripts**: One module per language + multi-language scenarios
|
||||
4. **Features**: builder, declare, routechain, monitoring, timeout, etc.
|
||||
|
||||
Test pattern:
|
||||
```java
|
||||
@SpringBootTest
|
||||
@TestPropertySource(value = "classpath:/application.properties")
|
||||
public class MyTest {
|
||||
@Resource private FlowExecutor flowExecutor;
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
LiteflowResponse response = flowExecutor.execute2Resp("chainId", "arg");
|
||||
Assertions.assertTrue(response.isSuccess());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Important Design Patterns
|
||||
|
||||
#### SPI Pattern for Extensibility
|
||||
Used extensively for:
|
||||
- `ContextAware`: Framework abstraction (Spring vs non-Spring)
|
||||
- `PathContentParser`: Custom file path resolution
|
||||
- `CmpAroundAspect`: Global component lifecycle hooks
|
||||
- `DeclComponentParser`: Declarative component parsing
|
||||
- Script executors for each language
|
||||
|
||||
#### Lifecycle Hooks
|
||||
Multiple extension points:
|
||||
- `PostProcessChainBuildLifeCycle`: Before/after chain building
|
||||
- `PostProcessNodeBuildLifeCycle`: Before/after node building
|
||||
- `PostProcessChainExecuteLifeCycle`: Before/after chain execution
|
||||
- `PostProcessFlowExecuteLifeCycle`: Before/after flow execution
|
||||
|
||||
#### Rollback Mechanism
|
||||
Components implement `rollback()` for automatic rollback on failure (executed in reverse order via `executeSteps` deque).
|
||||
|
||||
#### Hot Reload Support
|
||||
- `MonitorFile` watches rule files for changes
|
||||
- `reloadRule()` refreshes without restart
|
||||
- Copy-on-write collections (unless `fastLoad=true`) prevent concurrent modification
|
||||
|
||||
#### Metadata Caching
|
||||
- `FlowBus` uses CopyOnWriteHashMap (or regular HashMap with `fastLoad`)
|
||||
- EL MD5 caching for expression reuse
|
||||
- Chain caching configurable (`chainCacheEnabled`, `chainCacheCapacity`)
|
||||
|
||||
### Critical Configuration (`LiteflowConfig`)
|
||||
- **ruleSource**: Rule file locations (supports XML, JSON, YML)
|
||||
- **parseMode**: Parsing strategy (affects startup performance)
|
||||
- **slotSize**: Context slot pool size
|
||||
- **enableMonitorFile**: Hot reload of rule files
|
||||
- **supportMultipleType**: Mix XML/JSON/YML rules
|
||||
- **whenMaxWaitTime**: Timeout for WHEN parallel execution
|
||||
- **fastLoad**: Disable CopyOnWrite for faster startup
|
||||
- **enableVirtualThread**: Use virtual threads (JDK 21+)
|
||||
|
||||
### Key File Locations
|
||||
- Core engine: `liteflow-core/src/main/java/com/yomahub/liteflow/flow/`
|
||||
- FlowExecutor: `liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java`
|
||||
- FlowBus: `liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java`
|
||||
- Component base: `liteflow-core/src/main/java/com/yomahub/liteflow/core/NodeComponent.java`
|
||||
- Condition system: `liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/condition/`
|
||||
- EL parser: `liteflow-core/src/main/java/com/yomahub/liteflow/parser/el/`
|
||||
|
||||
### Important Conventions
|
||||
- **Naming**: Components identified by `nodeId`, chains by `chainId`
|
||||
- **Thread Safety**: Extensive ThreadLocal and concurrent collections
|
||||
- **Fail-Fast**: Validation at parse time with detailed error messages
|
||||
- **Fluent APIs**: Builder pattern for chain construction (EL builder)
|
||||
- **Namespaces**: Chains can be organized into namespaces
|
||||
- **Versioning**: Uses `${revision}` placeholder (currently 2.15.3) via flatten-maven-plugin
|
||||
27
CLAUDE.md
27
CLAUDE.md
@@ -71,12 +71,19 @@ Chains are built in two phases to handle circular dependencies:
|
||||
#### 2. EL Expression Language
|
||||
Uses QLExpress to parse declarative workflows. Example operators:
|
||||
- **THEN(a, b, c)**: Sequential execution
|
||||
- **WHEN(a, b, c)**: Parallel execution
|
||||
- **IF(condition, THEN(x), ELSE(y))**: Conditional branching
|
||||
- **WHEN(a, b, c)**: Parallel execution (async)
|
||||
- **IF(condition, trueNode, falseNode)**: Conditional branching
|
||||
- **SWITCH(selector).to(a, b, c)**: Multi-way branching
|
||||
- **FOR(count).DO(loop)**: Loop execution
|
||||
- **RETRY(node).times(3).forException()**: Retry mechanism
|
||||
- **FOR(count).DO(loop)**: Fixed-count loop
|
||||
- **WHILE(condition).DO(loop)**: Condition-based loop
|
||||
- **ITERATOR(iterator).DO(loop)**: Iterator-based loop
|
||||
- **RETRY(node).times(3).forException(Ex.class)**: Retry mechanism
|
||||
- **CATCH(node).DO(handler)**: Exception handling
|
||||
- **TIMEOUT(node).time(1000)**: Execution timeout (ms)
|
||||
- **PRE(a, b)**: Pre-conditions (always run before chain, even on error)
|
||||
- **FINALLY(a, b)**: Finally-conditions (always run after chain)
|
||||
- **AND(a, b)**, **OR(a, b)**, **NOT(a)**: Boolean logic for IF conditions
|
||||
- **node.tag("t")**, **.data("k","v")**, **.id("id")**: Per-node modifiers
|
||||
|
||||
Rules are defined in XML/JSON/YML:
|
||||
```xml
|
||||
@@ -94,6 +101,18 @@ All extend `NodeComponent` but have specialized behaviors:
|
||||
- **NodeForComponent**: Controls FOR loop behavior
|
||||
- **ScriptComponent**: Script-based components (Groovy, JS, Python, etc.)
|
||||
|
||||
**Declarative Component Pattern**: Any Spring bean can become a component without extending base classes by using `@LiteflowCmpDefine` (class-level, specifies `NodeTypeEnum`) and `@LiteflowMethod` (method-level, maps to `LiteFlowMethodEnum`). This avoids class hierarchy constraints.
|
||||
|
||||
**Component Lifecycle Hooks** (override in NodeComponent subclasses or via `@LiteflowMethod`):
|
||||
- `isAccess()` – gate check before execution; return `false` to skip
|
||||
- `beforeProcess()` / `afterProcess()` – pre/post hooks per component
|
||||
- `onSuccess()` / `onError()` – outcome callbacks
|
||||
- `isContinueOnError()` – whether WHEN continues if this node fails
|
||||
- `isEnd()` – signals chain should stop after this node
|
||||
- `rollback()` – called in reverse order on failure
|
||||
|
||||
**`@FallbackCmp`**: Annotate a fallback component that activates when the primary component is not found or throws.
|
||||
|
||||
#### 4. Slot-Based Context
|
||||
Thread-safe execution context:
|
||||
- `DataBus.offerSlot(chainId)` acquires a slot from pool
|
||||
|
||||
@@ -90,7 +90,7 @@ class ModelSpecTest {
|
||||
|
||||
/** 仅用于测试的最小 ModelSpec 子类。 */
|
||||
static class TestSpec extends ModelSpec<TestSpec> {
|
||||
@Override protected Model resolve(AgentConfig cfg) { return null; }
|
||||
@Override public Model resolve(AgentConfig cfg) { return null; }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -192,11 +192,16 @@ public abstract class ModelSpec<SELF extends ModelSpec<SELF>> {
|
||||
* 把本描述符解析为 agentscope {@link Model} 实例。
|
||||
* 实现需从 {@link AgentConfig} 中读取对应平台的 credential,
|
||||
* 并把共性 + 个性参数翻译成 agentscope 的 GenerateOptions。
|
||||
* <p>
|
||||
* 本方法是框架 SPI:{@code ReActAgentComponent} 在不同包中调用,
|
||||
* 因此必须为 {@code public}(subclass override 同样需要 {@code public})。
|
||||
*/
|
||||
protected abstract Model resolve(AgentConfig cfg);
|
||||
public abstract Model resolve(AgentConfig cfg);
|
||||
}
|
||||
```
|
||||
|
||||
> **重要**:`resolve` 必须是 `public`,因为 Task 8 中 `ReActAgentComponent`(在 `com.yomahub.liteflow.agent.component` 包)会调用 `model(ctx).resolve(agentConfig())`。Java 不允许 override 缩小可见性,所以后续 Tasks 3-7 中所有 spec 子类的 `resolve` override 也必须是 `public`,测试中的 `TestSpec.resolve` 同理。
|
||||
|
||||
- [ ] **Step 4: 再次运行测试,确认通过**
|
||||
|
||||
```bash
|
||||
@@ -513,7 +518,7 @@ public class OpenAISpec extends ModelSpec<OpenAISpec> {
|
||||
public Double getPresencePenalty() { return presencePenalty; }
|
||||
|
||||
@Override
|
||||
protected Model resolve(AgentConfig cfg) {
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred = CredentialResolver.requireFirstClass(
|
||||
cfg.getOpenai(), "liteflow.agent.openai");
|
||||
return buildModel(cred.getApiKey(), cred.getBaseUrl());
|
||||
@@ -740,7 +745,7 @@ public class OpenAICompatibleSpec extends OpenAISpec {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Model resolve(AgentConfig cfg) {
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred = CredentialResolver.requireCompatible(
|
||||
cfg.getOpenaiCompatible(), configKey, "liteflow.agent.openai-compatible");
|
||||
String baseUrl = (cred.getBaseUrl() != null && !cred.getBaseUrl().isBlank())
|
||||
@@ -1018,7 +1023,7 @@ public class AnthropicSpec extends ModelSpec<AnthropicSpec> {
|
||||
public Boolean getThinkingEnabled() { return thinkingEnabled; }
|
||||
|
||||
@Override
|
||||
protected Model resolve(AgentConfig cfg) {
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred;
|
||||
if (compatibleConfigKey == null) {
|
||||
cred = CredentialResolver.requireFirstClass(
|
||||
@@ -1239,7 +1244,7 @@ public class GeminiSpec extends ModelSpec<GeminiSpec> {
|
||||
public Integer getThinkingBudget() { return thinkingBudget; }
|
||||
|
||||
@Override
|
||||
protected Model resolve(AgentConfig cfg) {
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred = CredentialResolver.requireFirstClass(
|
||||
cfg.getGemini(), "liteflow.agent.gemini");
|
||||
|
||||
@@ -1429,7 +1434,7 @@ public class DashScopeSpec extends ModelSpec<DashScopeSpec> {
|
||||
public Integer getThinkingBudget() { return thinkingBudget; }
|
||||
|
||||
@Override
|
||||
protected Model resolve(AgentConfig cfg) {
|
||||
public Model resolve(AgentConfig cfg) {
|
||||
PlatformCredential cred = CredentialResolver.requireFirstClass(
|
||||
cfg.getDashscope(), "liteflow.agent.dashscope");
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ public class AgentConfig {
|
||||
private PlatformCredential gemini = new PlatformCredential();
|
||||
private PlatformCredential dashscope = new PlatformCredential();
|
||||
private Map<String, PlatformCredential> openaiCompatible = new LinkedHashMap<>();
|
||||
private Map<String, PlatformCredential> anthropicCompatible = new LinkedHashMap<>();
|
||||
|
||||
public WorkspaceConfig getWorkspace() { return workspace; }
|
||||
public void setWorkspace(WorkspaceConfig v) { this.workspace = v; }
|
||||
@@ -32,4 +33,6 @@ public class AgentConfig {
|
||||
public void setDashscope(PlatformCredential v) { this.dashscope = v; }
|
||||
public Map<String, PlatformCredential> getOpenaiCompatible() { return openaiCompatible; }
|
||||
public void setOpenaiCompatible(Map<String, PlatformCredential> v) { this.openaiCompatible = v; }
|
||||
public Map<String, PlatformCredential> getAnthropicCompatible() { return anthropicCompatible; }
|
||||
public void setAnthropicCompatible(Map<String, PlatformCredential> v) { this.anthropicCompatible = v; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.yomahub.liteflow.property.agent;
|
||||
|
||||
/**
|
||||
* Memory persistence settings for ReActAgent sessions.
|
||||
*
|
||||
* <p>This config is intentionally orthogonal to {@link SessionConfig} (which
|
||||
* controls JVM-side session caching, idle timeout, LRU eviction). Memory
|
||||
* storage decides <em>where</em> the agent's conversation history is durably
|
||||
* kept; session config decides <em>how long</em> a hot agent is held in memory.
|
||||
*/
|
||||
public class MemoryStorageConfig {
|
||||
/** Default is {@link MemoryStorageMode#JVM} so existing deployments behave unchanged. */
|
||||
private MemoryStorageMode mode = MemoryStorageMode.JVM;
|
||||
|
||||
private WorkspaceMemoryConfig workspace = new WorkspaceMemoryConfig();
|
||||
private RedisMemoryConfig redis = new RedisMemoryConfig();
|
||||
private MysqlMemoryConfig mysql = new MysqlMemoryConfig();
|
||||
|
||||
/** Whether to lazily load existing session state on first {@code process()}. */
|
||||
private boolean loadOnFirstUse = true;
|
||||
/** Whether to save session state after a successful {@code process()}. */
|
||||
private boolean saveAfterCall = true;
|
||||
/** Whether to save session state when {@code process()} throws. */
|
||||
private boolean saveOnError = true;
|
||||
|
||||
public MemoryStorageMode getMode() { return mode; }
|
||||
public void setMode(MemoryStorageMode mode) { this.mode = mode; }
|
||||
public WorkspaceMemoryConfig getWorkspace() { return workspace; }
|
||||
public void setWorkspace(WorkspaceMemoryConfig workspace) { this.workspace = workspace; }
|
||||
public RedisMemoryConfig getRedis() { return redis; }
|
||||
public void setRedis(RedisMemoryConfig redis) { this.redis = redis; }
|
||||
public MysqlMemoryConfig getMysql() { return mysql; }
|
||||
public void setMysql(MysqlMemoryConfig mysql) { this.mysql = mysql; }
|
||||
public boolean isLoadOnFirstUse() { return loadOnFirstUse; }
|
||||
public void setLoadOnFirstUse(boolean loadOnFirstUse) { this.loadOnFirstUse = loadOnFirstUse; }
|
||||
public boolean isSaveAfterCall() { return saveAfterCall; }
|
||||
public void setSaveAfterCall(boolean saveAfterCall) { this.saveAfterCall = saveAfterCall; }
|
||||
public boolean isSaveOnError() { return saveOnError; }
|
||||
public void setSaveOnError(boolean saveOnError) { this.saveOnError = saveOnError; }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yomahub.liteflow.property.agent;
|
||||
|
||||
/**
|
||||
* Storage backend used to persist a ReActAgent's memory across executions
|
||||
* within a session.
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #NONE} – do not persist or even hold memory; equivalent to a stateless agent</li>
|
||||
* <li>{@link #JVM} – keep memory in JVM heap only (default; behaviour identical to pre-2.15.4 releases)</li>
|
||||
* <li>{@link #WORKSPACE_FILE} – persist memory as JSON files under each session's workspace directory
|
||||
* using AgentScope's JsonSession</li>
|
||||
* <li>{@link #REDIS} – persist memory through AgentScope's RedisSession; requires the user to
|
||||
* provide a {@code RedissonClient} / {@code UnifiedJedis} / {@code RedisClient} bean</li>
|
||||
* <li>{@link #MYSQL} – persist memory through AgentScope's MysqlSession; requires the user to
|
||||
* provide a {@code javax.sql.DataSource} bean</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum MemoryStorageMode {
|
||||
NONE,
|
||||
JVM,
|
||||
WORKSPACE_FILE,
|
||||
REDIS,
|
||||
MYSQL
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.yomahub.liteflow.property.agent;
|
||||
|
||||
/**
|
||||
* Settings that only apply when {@link MemoryStorageMode#MYSQL} is selected.
|
||||
*
|
||||
* <p>The DataSource is supplied by the user via {@code beanName} and looked up
|
||||
* through ContextAware. LiteFlow never builds a JDBC connection pool itself.
|
||||
*/
|
||||
public class MysqlMemoryConfig {
|
||||
/** Bean name of the {@link javax.sql.DataSource} to look up via ContextAware. */
|
||||
private String dataSourceBeanName;
|
||||
|
||||
/** Database name passed to {@code MysqlSession}. Empty means use AgentScope's default ({@code agentscope}). */
|
||||
private String databaseName;
|
||||
|
||||
/** Table name passed to {@code MysqlSession}. Empty means use AgentScope's default ({@code agentscope_sessions}). */
|
||||
private String tableName;
|
||||
|
||||
/** When true, AgentScope auto-creates database & table; defaults to false to respect production constraints. */
|
||||
private boolean createIfNotExist = false;
|
||||
|
||||
public String getDataSourceBeanName() { return dataSourceBeanName; }
|
||||
public void setDataSourceBeanName(String dataSourceBeanName) { this.dataSourceBeanName = dataSourceBeanName; }
|
||||
public String getDatabaseName() { return databaseName; }
|
||||
public void setDatabaseName(String databaseName) { this.databaseName = databaseName; }
|
||||
public String getTableName() { return tableName; }
|
||||
public void setTableName(String tableName) { this.tableName = tableName; }
|
||||
public boolean isCreateIfNotExist() { return createIfNotExist; }
|
||||
public void setCreateIfNotExist(boolean createIfNotExist) { this.createIfNotExist = createIfNotExist; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.yomahub.liteflow.property.agent;
|
||||
|
||||
/**
|
||||
* Settings that only apply when {@link MemoryStorageMode#REDIS} is selected.
|
||||
*
|
||||
* <p>Connections are not created by LiteFlow. Users must provide a Redis client
|
||||
* bean (Redisson / Jedis / Lettuce) through the framework's {@code ContextAware}
|
||||
* (Spring bean lookup, Solon container, etc.).
|
||||
*/
|
||||
public class RedisMemoryConfig {
|
||||
/** Bean name of the Redis client to look up via ContextAware. */
|
||||
private String beanName;
|
||||
|
||||
/** Type of the configured client; affects how AgentScope adapts it. */
|
||||
private RedisClientType clientType = RedisClientType.REDISSON;
|
||||
|
||||
/** Key prefix used inside Redis. */
|
||||
private String keyPrefix = "liteflow:agent:session";
|
||||
|
||||
public String getBeanName() { return beanName; }
|
||||
public void setBeanName(String beanName) { this.beanName = beanName; }
|
||||
public RedisClientType getClientType() { return clientType; }
|
||||
public void setClientType(RedisClientType clientType) { this.clientType = clientType; }
|
||||
public String getKeyPrefix() { return keyPrefix; }
|
||||
public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; }
|
||||
|
||||
public enum RedisClientType { REDISSON, JEDIS, LETTUCE }
|
||||
}
|
||||
@@ -6,6 +6,7 @@ public class SessionConfig {
|
||||
private Duration idleTimeout = Duration.ofMinutes(30);
|
||||
private Duration cleanupInterval = Duration.ofMinutes(1);
|
||||
private int maxSessions = 10_000;
|
||||
private MemoryStorageConfig memory = new MemoryStorageConfig();
|
||||
|
||||
public Duration getIdleTimeout() { return idleTimeout; }
|
||||
public void setIdleTimeout(Duration v) { this.idleTimeout = v; }
|
||||
@@ -13,4 +14,6 @@ public class SessionConfig {
|
||||
public void setCleanupInterval(Duration v) { this.cleanupInterval = v; }
|
||||
public int getMaxSessions() { return maxSessions; }
|
||||
public void setMaxSessions(int v) { this.maxSessions = v; }
|
||||
public MemoryStorageConfig getMemory() { return memory; }
|
||||
public void setMemory(MemoryStorageConfig memory) { this.memory = memory; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.yomahub.liteflow.property.agent;
|
||||
|
||||
/**
|
||||
* Settings that only apply when {@link MemoryStorageMode#WORKSPACE_FILE} is selected.
|
||||
*
|
||||
* <p>The persistence sub-directory is hard-coded to {@code .agent-session} so it
|
||||
* stays out of the way of tool-produced files in the per-session workspace and
|
||||
* is not configurable on purpose; users who want a custom location can plug in
|
||||
* their own {@code AgentSessionFactory} via SPI.
|
||||
*/
|
||||
public class WorkspaceMemoryConfig {
|
||||
/** Fixed sub-directory created under the workspace root that holds session JSON files. */
|
||||
public static final String SUB_DIR = ".agent-session";
|
||||
}
|
||||
@@ -15,6 +15,8 @@ class AgentConfigTest {
|
||||
assertNotNull(c.getDefaults());
|
||||
assertNotNull(c.getOpenaiCompatible());
|
||||
assertTrue(c.getOpenaiCompatible().isEmpty());
|
||||
assertNotNull(c.getAnthropicCompatible());
|
||||
assertTrue(c.getAnthropicCompatible().isEmpty());
|
||||
|
||||
WorkspaceConfig w = c.getWorkspace();
|
||||
assertNull(w.getRoot());
|
||||
@@ -43,8 +45,12 @@ class AgentConfigTest {
|
||||
void platform_credentials_are_independent_instances() {
|
||||
AgentConfig c = new AgentConfig();
|
||||
c.getOpenai().setApiKey("k1");
|
||||
c.getOpenaiCompatible().put("compatible-openai", new PlatformCredential());
|
||||
c.getAnthropicCompatible().put("compatible-anthropic", new PlatformCredential());
|
||||
assertNull(c.getAnthropic().getApiKey());
|
||||
assertNull(c.getGemini().getApiKey());
|
||||
assertNull(c.getDashscope().getApiKey());
|
||||
assertFalse(c.getOpenaiCompatible().containsKey("compatible-anthropic"));
|
||||
assertFalse(c.getAnthropicCompatible().containsKey("compatible-openai"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# liteflow-react-agent-anthropic
|
||||
|
||||
Anthropic Claude 模型支持。
|
||||
|
||||
## 使用
|
||||
|
||||
```java
|
||||
AnthropicChatModel model = AnthropicModelFactory.of(apiKey, "claude-sonnet-4-6");
|
||||
```
|
||||
@@ -14,8 +14,8 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -34,23 +34,5 @@
|
||||
<artifactId>anthropic-java</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -11,4 +11,12 @@ public final class AnthropicModelFactory {
|
||||
.modelName(modelName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static AnthropicChatModel custom(String apiKey, String baseUrl, String modelName) {
|
||||
return AnthropicChatModel.builder()
|
||||
.apiKey(apiKey)
|
||||
.baseUrl(baseUrl)
|
||||
.modelName(modelName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# liteflow-react-agent-core
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 1. 添加依赖(选择至少一个平台模块)
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-react-agent-openai</artifactId>
|
||||
<version>${liteflow.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 2. 配置
|
||||
|
||||
```yaml
|
||||
liteflow:
|
||||
agent:
|
||||
workspace:
|
||||
root: /var/lib/liteflow/agent-workspaces
|
||||
shell:
|
||||
mode: whitelist
|
||||
whitelist: [ls, cat, grep]
|
||||
openai-compatible:
|
||||
deepseek:
|
||||
api-key: ${DEEPSEEK_API_KEY}
|
||||
base-url: https://api.deepseek.com/v1
|
||||
```
|
||||
|
||||
### 3. 定义 Agent
|
||||
|
||||
```java
|
||||
@LiteflowComponent("reviewAgent")
|
||||
public class ReviewAgent extends ReActAgentComponent {
|
||||
@Override protected Model buildModel(ReActAgentContext ctx) {
|
||||
return OpenAICompatiblePresets.deepseek(
|
||||
agentConfig().getOpenaiCompatible().get("deepseek").getApiKey(),
|
||||
"deepseek-chat"
|
||||
);
|
||||
}
|
||||
@Override protected String systemPrompt(ReActAgentContext ctx) { return "你是审核专家"; }
|
||||
@Override protected String userPrompt(ReActAgentContext ctx) {
|
||||
return ctx.getSlot().getRequestData(String.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. EL 编排
|
||||
|
||||
```xml
|
||||
<chain name="reviewChain">
|
||||
THEN(prepare, reviewAgent, notify);
|
||||
</chain>
|
||||
```
|
||||
|
||||
## 核心概念
|
||||
|
||||
- **Session**:由 `resolveSessionId` 决定;默认 `slot.getRequestId()`(一次性)。覆写后可复用 memory 与 workspace 实现多轮对话。
|
||||
- **Workspace**:每 session 一个目录,在 `liteflow.agent.workspace.root` 之下。内置 `WorkspaceFileTools` 强制路径围栏。
|
||||
- **Shell**:`ManagedShellCommandTool` 按 `liteflow.agent.shell.mode` 做 whitelist/blacklist/disabled 校验,首 token 不在策略内即拒绝。
|
||||
|
||||
## 可选覆写
|
||||
|
||||
| 方法 | 默认行为 | 说明 |
|
||||
|------|----------|------|
|
||||
| `tools(ctx)` | 空列表 | 注册自定义 agentscope @Tool 对象 |
|
||||
| `resolveSessionId(slot)` | `slot.getRequestId()` | 覆写以实现多轮对话 |
|
||||
| `maxIterations()` | 配置文件默认值 (15) | ReAct 最大推理轮数 |
|
||||
| `enableShellTool()` | true | 是否启用受管 shell 工具 |
|
||||
| `enableWorkspaceFileTools()` | true | 是否启用受管文件工具 |
|
||||
| `hooks(ctx)` | 空列表 | agentscope Hook 列表 |
|
||||
| `handleReply(reply, ctx)` | 写入 slot.responseData | 自定义回复处理 |
|
||||
@@ -16,8 +16,8 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -43,36 +43,5 @@
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-console-standalone</artifactId>
|
||||
<version>1.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* LiteFlow ReAct Agent core module.
|
||||
* LiteFlow ReAct Agent 核心模块。
|
||||
*/
|
||||
package com.yomahub.liteflow.agent;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.yomahub.liteflow.agent.session;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.agent.session.factory.AgentSessionFactoryRegistry;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.ReActAgent;
|
||||
import io.agentscope.core.session.Session;
|
||||
import io.agentscope.core.session.SessionManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
@@ -18,6 +24,19 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 跟踪当前 JVM 中存活的 AgentSession,并桥接到可插拔的
|
||||
* {@link Session}(由 {@link AgentSessionFactoryRegistry} 提供)。
|
||||
*
|
||||
* <p>这里将两类职责保持独立:
|
||||
* <ul>
|
||||
* <li>JVM 内缓存、加锁和 LRU 淘汰(本类负责)</li>
|
||||
* <li>持久化存储(委托给 AgentScope 的 Session 抽象)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>淘汰(空闲或超过容量)只移除缓存的 agent 实例。
|
||||
* workspace 文件以及磁盘、Redis、MySQL 中的持久化 session 数据会被保留。
|
||||
*/
|
||||
public class AgentSessionManager implements AutoCloseable {
|
||||
|
||||
private static final Pattern SAFE = Pattern.compile("[a-zA-Z0-9_\\-]+");
|
||||
@@ -26,6 +45,8 @@ public class AgentSessionManager implements AutoCloseable {
|
||||
private final Path root;
|
||||
private final Map<String, AgentSession> sessions = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService cleaner;
|
||||
/** memory 模式为 NONE 时可能为 null。 */
|
||||
private final Session storage;
|
||||
|
||||
public AgentSessionManager(AgentConfig config) {
|
||||
this.config = config;
|
||||
@@ -42,6 +63,7 @@ public class AgentSessionManager implements AutoCloseable {
|
||||
} else if (!Files.isDirectory(root)) {
|
||||
throw new AgentConfigException("workspace root does not exist: " + root);
|
||||
}
|
||||
this.storage = AgentSessionFactoryRegistry.createSession(config);
|
||||
long every = Math.max(20, config.getSession().getCleanupInterval().toMillis());
|
||||
this.cleaner = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "liteflow-agent-session-cleaner");
|
||||
@@ -68,6 +90,35 @@ public class AgentSessionManager implements AutoCloseable {
|
||||
return sessions.containsKey(safeId(sessionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将之前持久化的状态懒加载恢复到 agent 中。
|
||||
* 同一个 session id 在当前 JVM 生命周期内应只调用一次,并且应在 agent
|
||||
* 构建完成后、首次 {@code agent.call(...)} 前调用。
|
||||
*/
|
||||
public void loadIfExists(AgentSession session, ReActAgent agent) {
|
||||
if (storage == null || agent == null) return;
|
||||
MemoryStorageConfig mc = config.getSession().getMemory();
|
||||
if (!mc.isLoadOnFirstUse()) return;
|
||||
if (mc.getMode() == MemoryStorageMode.NONE) return;
|
||||
SessionManager.forSessionId(session.getSessionId())
|
||||
.withSession(storage)
|
||||
.addComponent(agent)
|
||||
.loadIfExists();
|
||||
}
|
||||
|
||||
/** 持久化 agent 当前状态。失败会向调用方暴露。 */
|
||||
public void save(AgentSession session, ReActAgent agent) {
|
||||
if (storage == null || agent == null) return;
|
||||
MemoryStorageConfig mc = config.getSession().getMemory();
|
||||
if (mc.getMode() == MemoryStorageMode.NONE) return;
|
||||
SessionManager.forSessionId(session.getSessionId())
|
||||
.withSession(storage)
|
||||
.addComponent(agent)
|
||||
.saveSession();
|
||||
}
|
||||
|
||||
public Session storage() { return storage; }
|
||||
|
||||
static String safeId(String raw) {
|
||||
if (raw == null || raw.isEmpty()) return "_";
|
||||
if (SAFE.matcher(raw).matches()) return raw;
|
||||
@@ -79,7 +130,8 @@ public class AgentSessionManager implements AutoCloseable {
|
||||
while (sessions.size() > max) {
|
||||
sessions.values().stream()
|
||||
.min(Comparator.comparing(AgentSession::getLastActive))
|
||||
.ifPresent(victim -> remove(victim, true));
|
||||
// LRU 淘汰只移除 JVM 内缓存;持久化数据保持不变。
|
||||
.ifPresent(victim -> evictFromCache(victim, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,14 +141,20 @@ public class AgentSessionManager implements AutoCloseable {
|
||||
if (s.getLastActive().isAfter(cutoff)) continue;
|
||||
if (!s.getLock().tryLock()) continue;
|
||||
try {
|
||||
remove(s, config.getWorkspace().isCleanupOnSessionExpire());
|
||||
evictFromCache(s, config.getWorkspace().isCleanupOnSessionExpire());
|
||||
} finally {
|
||||
s.getLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(AgentSession s, boolean cleanWorkspace) {
|
||||
/**
|
||||
* @param cleanWorkspace 为 true 时,同时删除磁盘上的 workspace 目录
|
||||
* (保留历史行为)。存储在其他位置的持久化 session
|
||||
* 状态(例如 workspaceRoot/.agent-session、Redis、
|
||||
* MySQL)不会在这里被删除。
|
||||
*/
|
||||
private void evictFromCache(AgentSession s, boolean cleanWorkspace) {
|
||||
sessions.remove(s.getSessionId(), s);
|
||||
if (cleanWorkspace) {
|
||||
deleteRecursively(s.getWorkspaceDir());
|
||||
@@ -118,6 +176,9 @@ public class AgentSessionManager implements AutoCloseable {
|
||||
if (config.getWorkspace().isCleanupOnJvmShutdown()) {
|
||||
sessions.values().forEach(s -> deleteRecursively(s.getWorkspaceDir()));
|
||||
}
|
||||
if (storage != null) {
|
||||
try { storage.close(); } catch (Exception ignored) {}
|
||||
}
|
||||
sessions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.session.Session;
|
||||
|
||||
/**
|
||||
* 用于为 {@link io.agentscope.core.session.Session} 接入额外持久化后端的 SPI。
|
||||
*
|
||||
* <p>框架内置模式({@code JVM}、{@code WORKSPACE_FILE}、{@code REDIS}、
|
||||
* {@code MYSQL}、{@code NONE})。需要其他后端(例如 PostgreSQL、OSS、
|
||||
* 加密 JSON)的用户,可以在 {@code META-INF/services/}{@link AgentSessionFactory}
|
||||
* 下注册自定义工厂。
|
||||
*/
|
||||
public interface AgentSessionFactory {
|
||||
|
||||
/**
|
||||
* 当前工厂处理的模式。所有已注册工厂之间必须唯一。
|
||||
*/
|
||||
MemoryStorageMode mode();
|
||||
|
||||
/**
|
||||
* 根据 agent 配置构建底层 {@link Session}。该方法会在首次
|
||||
* {@code process()} 时懒调用,而不是在框架启动时调用。
|
||||
*
|
||||
* @return 非 null 的 Session;如果需要跳过持久化则返回 {@code null}
|
||||
* ({@link MemoryStorageMode#NONE} 对应的工厂会返回 {@code null})。
|
||||
*/
|
||||
Session create(AgentConfig agentConfig);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.session.Session;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* 根据指定模式解析合适的 {@link AgentSessionFactory}。
|
||||
*
|
||||
* <p>解析顺序:
|
||||
* <ol>
|
||||
* <li>通过 {@link ServiceLoader} 注册的外部工厂</li>
|
||||
* <li>框架内置工厂(JVM、workspace、Redis、MySQL、none)</li>
|
||||
* </ol>
|
||||
* 出现冲突时外部工厂优先,因此用户可以覆盖内置实现
|
||||
* (例如用自定义加密 JSON 工厂替换默认 workspace 工厂)。
|
||||
*/
|
||||
public final class AgentSessionFactoryRegistry {
|
||||
|
||||
private static final Map<MemoryStorageMode, AgentSessionFactory> FACTORIES = new EnumMap<>(MemoryStorageMode.class);
|
||||
|
||||
static {
|
||||
// 先注册内置实现;如果存在 SPI 实现,再由 SPI 覆盖。
|
||||
register(new InMemoryAgentSessionFactory());
|
||||
register(new WorkspaceAgentSessionFactory());
|
||||
register(new RedisAgentSessionFactory());
|
||||
register(new MysqlAgentSessionFactory());
|
||||
register(new NoneAgentSessionFactory());
|
||||
for (AgentSessionFactory f : ServiceLoader.load(AgentSessionFactory.class)) {
|
||||
register(f);
|
||||
}
|
||||
}
|
||||
|
||||
private AgentSessionFactoryRegistry() { }
|
||||
|
||||
private static void register(AgentSessionFactory f) {
|
||||
FACTORIES.put(f.mode(), f);
|
||||
}
|
||||
|
||||
/** 根据配置模式构建 Session。{@link MemoryStorageMode#NONE} 可能返回 {@code null}。 */
|
||||
public static Session createSession(AgentConfig cfg) {
|
||||
MemoryStorageMode mode = cfg.getSession().getMemory().getMode();
|
||||
AgentSessionFactory f = FACTORIES.get(mode);
|
||||
if (f == null) {
|
||||
throw new AgentConfigException("No AgentSessionFactory registered for mode: " + mode);
|
||||
}
|
||||
return f.create(cfg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.session.InMemorySession;
|
||||
import io.agentscope.core.session.Session;
|
||||
|
||||
/**
|
||||
* 使用 AgentScope 的内存存储支持 {@link MemoryStorageMode#JVM} 模式。
|
||||
*
|
||||
* <p>注意:状态仍会在同一个 JVM 内跨调用保留(适合希望在单进程内保留多轮记忆的场景),
|
||||
* 但进程退出后会丢失。如果需要跨重启持久化,请选择
|
||||
* {@code WORKSPACE_FILE}、{@code REDIS} 或 {@code MYSQL}。
|
||||
*/
|
||||
public class InMemoryAgentSessionFactory implements AgentSessionFactory {
|
||||
|
||||
@Override
|
||||
public MemoryStorageMode mode() {
|
||||
return MemoryStorageMode.JVM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session create(AgentConfig agentConfig) {
|
||||
return new InMemorySession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import com.yomahub.liteflow.property.agent.MysqlMemoryConfig;
|
||||
import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
|
||||
import io.agentscope.core.session.Session;
|
||||
import io.agentscope.core.session.mysql.MysqlSession;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* 支持 {@link MemoryStorageMode#MYSQL} 模式。使用用户提供的
|
||||
* {@link DataSource} bean;LiteFlow 不自行创建 JDBC 连接池。
|
||||
*/
|
||||
public class MysqlAgentSessionFactory implements AgentSessionFactory {
|
||||
|
||||
@Override
|
||||
public MemoryStorageMode mode() {
|
||||
return MemoryStorageMode.MYSQL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session create(AgentConfig cfg) {
|
||||
MysqlMemoryConfig mc = cfg.getSession().getMemory().getMysql();
|
||||
if (mc.getDataSourceBeanName() == null || mc.getDataSourceBeanName().trim().isEmpty()) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.session.memory.mysql.dataSourceBeanName is required when mode=MYSQL");
|
||||
}
|
||||
Object bean = ContextAwareHolder.loadContextAware().getBean(mc.getDataSourceBeanName());
|
||||
if (bean == null) {
|
||||
throw new AgentConfigException("DataSource bean not found: " + mc.getDataSourceBeanName());
|
||||
}
|
||||
if (!(bean instanceof DataSource)) {
|
||||
throw new AgentConfigException("Bean '" + mc.getDataSourceBeanName() + "' is not a DataSource; got "
|
||||
+ bean.getClass().getName());
|
||||
}
|
||||
DataSource ds = (DataSource) bean;
|
||||
String db = mc.getDatabaseName();
|
||||
String table = mc.getTableName();
|
||||
boolean hasCustom = (db != null && !db.isEmpty()) || (table != null && !table.isEmpty());
|
||||
try {
|
||||
if (hasCustom) {
|
||||
return new MysqlSession(ds, db, table, mc.isCreateIfNotExist());
|
||||
}
|
||||
return new MysqlSession(ds, mc.isCreateIfNotExist());
|
||||
} catch (Exception e) {
|
||||
throw new AgentConfigException("Failed to build MysqlSession", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import io.agentscope.core.session.Session;
|
||||
|
||||
/**
|
||||
* 返回 {@code null} Session,用于通知 AgentSessionManager 跳过所有加载和保存操作。
|
||||
* 供 {@link MemoryStorageMode#NONE} 使用。
|
||||
*/
|
||||
public class NoneAgentSessionFactory implements AgentSessionFactory {
|
||||
|
||||
@Override
|
||||
public MemoryStorageMode mode() {
|
||||
return MemoryStorageMode.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session create(AgentConfig agentConfig) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import com.yomahub.liteflow.property.agent.RedisMemoryConfig;
|
||||
import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
|
||||
import io.agentscope.core.session.Session;
|
||||
|
||||
/**
|
||||
* 通过把用户提供的 Redis 客户端 bean(Redisson、Jedis、Lettuce)
|
||||
* 适配到 AgentScope 的 RedisSession 来支持 {@link MemoryStorageMode#REDIS}。
|
||||
*
|
||||
* <p>Redis 客户端类通过反射查找,因此 core 模块不会对 Redisson、Jedis、Lettuce
|
||||
* 产生硬性的编译期依赖。如果选择 REDIS 模式但 classpath 中缺少匹配驱动,
|
||||
* 会在首次 {@code process()} 时失败,而不是在框架启动时失败。
|
||||
*/
|
||||
public class RedisAgentSessionFactory implements AgentSessionFactory {
|
||||
|
||||
private static final String REDIS_SESSION_CLASS = "io.agentscope.core.session.redis.RedisSession";
|
||||
|
||||
@Override
|
||||
public MemoryStorageMode mode() {
|
||||
return MemoryStorageMode.REDIS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session create(AgentConfig cfg) {
|
||||
RedisMemoryConfig rc = cfg.getSession().getMemory().getRedis();
|
||||
if (rc.getBeanName() == null || rc.getBeanName().trim().isEmpty()) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.session.memory.redis.beanName is required when mode=REDIS");
|
||||
}
|
||||
Object client = ContextAwareHolder.loadContextAware().getBean(rc.getBeanName());
|
||||
if (client == null) {
|
||||
throw new AgentConfigException("Redis client bean not found: " + rc.getBeanName());
|
||||
}
|
||||
String builderMethod;
|
||||
String clientFqn;
|
||||
switch (rc.getClientType()) {
|
||||
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: " + rc.getClientType());
|
||||
}
|
||||
try {
|
||||
Class<?> sessionClass = Class.forName(REDIS_SESSION_CLASS);
|
||||
Object builder = sessionClass.getMethod("builder").invoke(null);
|
||||
Class<?> clientType = Class.forName(clientFqn);
|
||||
if (!clientType.isInstance(client)) {
|
||||
throw new AgentConfigException("Bean '" + rc.getBeanName() + "' is not a "
|
||||
+ clientFqn + "; got " + client.getClass().getName());
|
||||
}
|
||||
builder.getClass().getMethod(builderMethod, clientType).invoke(builder, client);
|
||||
if (rc.getKeyPrefix() != null && !rc.getKeyPrefix().isEmpty()) {
|
||||
builder.getClass().getMethod("keyPrefix", String.class).invoke(builder, rc.getKeyPrefix());
|
||||
}
|
||||
return (Session) builder.getClass().getMethod("build").invoke(builder);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AgentConfigException(
|
||||
"Class not found while building RedisSession: " + e.getMessage()
|
||||
+ ". Add the matching driver dependency (Redisson/Jedis/Lettuce).", e);
|
||||
} catch (AgentConfigException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new AgentConfigException("Failed to build RedisSession", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.yomahub.liteflow.agent.session.factory;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.property.agent.AgentConfig;
|
||||
import com.yomahub.liteflow.property.agent.MemoryStorageMode;
|
||||
import com.yomahub.liteflow.property.agent.WorkspaceMemoryConfig;
|
||||
import io.agentscope.core.session.JsonSession;
|
||||
import io.agentscope.core.session.Session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 通过把 JSON 文件存储在 {@code workspace.root/.agent-session/<sessionId>/}
|
||||
* 下来支持 {@link MemoryStorageMode#WORKSPACE_FILE}。
|
||||
*
|
||||
* <p>session 存储子目录特意与 {@code workspace.root/<sessionId>/}
|
||||
* 形式的单 session 工具 workspace 分离,避免
|
||||
* {@link com.yomahub.liteflow.agent.tool.WorkspaceFileTools} 读取或覆盖
|
||||
* agent 自身记忆。
|
||||
*/
|
||||
public class WorkspaceAgentSessionFactory implements AgentSessionFactory {
|
||||
|
||||
@Override
|
||||
public MemoryStorageMode mode() {
|
||||
return MemoryStorageMode.WORKSPACE_FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session create(AgentConfig cfg) {
|
||||
if (cfg.getWorkspace() == null || cfg.getWorkspace().getRoot() == null) {
|
||||
throw new AgentConfigException(
|
||||
"liteflow.agent.workspace.root is required when session.memory.mode=WORKSPACE_FILE");
|
||||
}
|
||||
Path root = Paths.get(cfg.getWorkspace().getRoot()).toAbsolutePath().normalize()
|
||||
.resolve(WorkspaceMemoryConfig.SUB_DIR);
|
||||
try {
|
||||
Files.createDirectories(root);
|
||||
} catch (IOException e) {
|
||||
throw new AgentConfigException("cannot create session storage dir: " + root, e);
|
||||
}
|
||||
return new JsonSession(root);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# liteflow-react-agent-dashscope
|
||||
|
||||
阿里云 DashScope / Qwen 模型支持。
|
||||
|
||||
## 使用
|
||||
|
||||
```java
|
||||
DashScopeChatModel model = DashScopeModelFactory.of(apiKey, "qwen3-max");
|
||||
```
|
||||
@@ -12,8 +12,8 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -22,23 +22,5 @@
|
||||
<artifactId>liteflow-react-agent-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,13 +0,0 @@
|
||||
# liteflow-react-agent-gemini
|
||||
|
||||
Google Gemini 模型支持。
|
||||
|
||||
## 使用
|
||||
|
||||
```java
|
||||
// 基础
|
||||
GeminiChatModel m1 = GeminiModelFactory.of(apiKey, "gemini-3-flash-preview");
|
||||
|
||||
// 带 thinking level
|
||||
GeminiChatModel m2 = GeminiModelFactory.of(apiKey, "gemini-3-flash-preview", "high");
|
||||
```
|
||||
@@ -12,8 +12,8 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -32,23 +32,5 @@
|
||||
<artifactId>google-genai</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,25 +0,0 @@
|
||||
# liteflow-react-agent-openai
|
||||
|
||||
OpenAI 及 OpenAI 兼容协议厂商支持。
|
||||
|
||||
## 使用
|
||||
|
||||
```java
|
||||
// 标准 OpenAI
|
||||
OpenAIChatModel m1 = OpenAIModelFactory.openai(apiKey, "gpt-4o-mini");
|
||||
|
||||
// DeepSeek
|
||||
OpenAIChatModel m2 = OpenAICompatiblePresets.deepseek(apiKey, "deepseek-chat");
|
||||
|
||||
// Kimi (Moonshot)
|
||||
OpenAIChatModel m3 = OpenAICompatiblePresets.kimi(apiKey, "moonshot-v1-8k");
|
||||
|
||||
// GLM (智谱)
|
||||
OpenAIChatModel m4 = OpenAICompatiblePresets.glm(apiKey, "glm-4");
|
||||
|
||||
// MiniMax
|
||||
OpenAIChatModel m5 = OpenAICompatiblePresets.minimax(apiKey, "abab6.5s-chat");
|
||||
|
||||
// 自定义 OpenAI 兼容端点
|
||||
OpenAIChatModel m6 = OpenAIModelFactory.custom(apiKey, "https://your.own/v1", "your-model");
|
||||
```
|
||||
@@ -14,8 +14,8 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -24,23 +24,5 @@
|
||||
<artifactId>liteflow-react-agent-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>liteflow-testcase-el</artifactId>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>liteflow-testcase-el-react-agent</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-react-agent-openai</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-react-agent-anthropic</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-react-agent-gemini</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.yomahub</groupId>
|
||||
<artifactId>liteflow-react-agent-dashscope</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -5,4 +5,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class AnthropicModelFactoryTest {
|
||||
@Test void construct_ok() { assertNotNull(AnthropicModelFactory.of("k", "claude-sonnet-4-6")); }
|
||||
}
|
||||
@Test void custom_base_url_ok() {
|
||||
assertNotNull(AnthropicModelFactory.custom("k", "https://anthropic-proxy.example.com", "claude-sonnet-4-6"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.yomahub.liteflow.test.agent;
|
||||
|
||||
/**
|
||||
* 示例测试 API Key 工具:环境变量优先,再回退到 Spring 注入的 properties。
|
||||
* 留空时调用 {@link org.junit.jupiter.api.Assumptions#assumeTrue} 跳过用例。
|
||||
*/
|
||||
public final class ApiKeys {
|
||||
|
||||
private ApiKeys() {}
|
||||
|
||||
public static String resolve(String envName, String configured) {
|
||||
String env = System.getenv(envName);
|
||||
if (env != null && !env.isBlank()) return env.trim();
|
||||
return configured == null ? "" : configured.trim();
|
||||
}
|
||||
|
||||
public static boolean isPresent(String s) {
|
||||
return s != null && !s.isBlank();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.yomahub.liteflow.test.agent;
|
||||
|
||||
import com.yomahub.liteflow.core.FlowExecutor;
|
||||
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||
import com.yomahub.liteflow.property.LiteflowConfig;
|
||||
import com.yomahub.liteflow.test.agent.cmp.RecordReplyCmp;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 走 LiteFlow EL 编排的 ReActAgent 示例测试。
|
||||
* <p>
|
||||
* 每个用例都通过 {@code flowExecutor.execute2Resp(chainId, ...)} 跑一条 chain,
|
||||
* Agent 节点是 {@link com.yomahub.liteflow.agent.component.ReActAgentComponent}
|
||||
* 的具体子类,apiKey 通过 {@code application.properties} 中的
|
||||
* {@code liteflow.agent.<platform>.api-key} 注入。留空则用例自动 skip。
|
||||
*/
|
||||
@TestPropertySource(value = "classpath:/agent/application.properties")
|
||||
@SpringBootTest(classes = ReActAgentELSpringbootTest.class)
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan({
|
||||
"com.yomahub.liteflow.test.agent.cmp"
|
||||
})
|
||||
public class ReActAgentELSpringbootTest {
|
||||
|
||||
@Resource
|
||||
private FlowExecutor flowExecutor;
|
||||
|
||||
@Resource
|
||||
private LiteflowConfig liteflowConfig;
|
||||
|
||||
/** 真正运行链路并断言成功,回复非空。 */
|
||||
private void runChainExpectingReply(String chainId, String question) {
|
||||
LiteflowResponse response = flowExecutor.execute2Resp(chainId, question);
|
||||
if (!response.isSuccess() && response.getCause() != null) {
|
||||
response.getCause().printStackTrace();
|
||||
}
|
||||
Assertions.assertTrue(response.isSuccess(),
|
||||
"chain failed: " + (response.getCause() == null ? "" : response.getCause().getMessage()));
|
||||
Object reply = response.getSlot().getOutput(RecordReplyCmp.NODE_ID);
|
||||
Assertions.assertNotNull(reply, "agent reply must be recorded");
|
||||
System.out.println(">>> [" + chainId + "] reply=" + reply);
|
||||
}
|
||||
|
||||
private String openaiKey() { return ApiKeys.resolve("OPENAI_API_KEY", liteflowConfig.getAgent().getOpenai().getApiKey()); }
|
||||
private String anthropicKey() { return ApiKeys.resolve("ANTHROPIC_API_KEY", liteflowConfig.getAgent().getAnthropic().getApiKey()); }
|
||||
private String geminiKey() { return ApiKeys.resolve("GEMINI_API_KEY", liteflowConfig.getAgent().getGemini().getApiKey()); }
|
||||
private String dashscopeKey() { return ApiKeys.resolve("DASHSCOPE_API_KEY", liteflowConfig.getAgent().getDashscope().getApiKey()); }
|
||||
private String deepseekKey() {
|
||||
return ApiKeys.resolve("DEEPSEEK_API_KEY",
|
||||
liteflowConfig.getAgent().getOpenaiCompatible()
|
||||
.getOrDefault("deepseek", new com.yomahub.liteflow.property.agent.PlatformCredential())
|
||||
.getApiKey());
|
||||
}
|
||||
|
||||
/** 上下文:在 BeforeAll 里把环境变量回写到 LiteflowConfig,避免 properties 留空时无 key 可用。 */
|
||||
private void syncEnvKeys() {
|
||||
if (ApiKeys.isPresent(openaiKey())) liteflowConfig.getAgent().getOpenai().setApiKey(openaiKey());
|
||||
if (ApiKeys.isPresent(anthropicKey())) liteflowConfig.getAgent().getAnthropic().setApiKey(anthropicKey());
|
||||
if (ApiKeys.isPresent(geminiKey())) liteflowConfig.getAgent().getGemini().setApiKey(geminiKey());
|
||||
if (ApiKeys.isPresent(dashscopeKey())) liteflowConfig.getAgent().getDashscope().setApiKey(dashscopeKey());
|
||||
if (ApiKeys.isPresent(deepseekKey())) {
|
||||
liteflowConfig.getAgent().getOpenaiCompatible()
|
||||
.computeIfAbsent("deepseek", k -> {
|
||||
com.yomahub.liteflow.property.agent.PlatformCredential c =
|
||||
new com.yomahub.liteflow.property.agent.PlatformCredential();
|
||||
c.setBaseUrl("https://api.deepseek.com/v1");
|
||||
return c;
|
||||
})
|
||||
.setApiKey(deepseekKey());
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* 单平台单 Agent 链路:THEN(prepare, <agent>, recordReply)
|
||||
* ===================================================================== */
|
||||
|
||||
@Test
|
||||
public void testDeepSeekChain() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(deepseekKey()),
|
||||
"deepseek api-key 未配置,跳过 deepseekChain");
|
||||
runChainExpectingReply("deepseekChain", "用一句话总结 ReAct 模式的核心思想。");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenAIChain() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(openaiKey()),
|
||||
"openai api-key 未配置,跳过 openaiChain");
|
||||
runChainExpectingReply("openaiChain", "用一句话介绍 LiteFlow。");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnthropicChain() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(anthropicKey()),
|
||||
"anthropic api-key 未配置,跳过 anthropicChain");
|
||||
runChainExpectingReply("anthropicChain", "什么是规则引擎?一句话回答。");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDashScopeChain() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(dashscopeKey()),
|
||||
"dashscope api-key 未配置,跳过 dashscopeChain");
|
||||
runChainExpectingReply("dashscopeChain", "用一句话介绍通义千问。");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeminiChain() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(geminiKey()),
|
||||
"gemini api-key 未配置,跳过 geminiChain");
|
||||
runChainExpectingReply("geminiChain", "用一句中文介绍 Gemini 模型。");
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* 自定义工具:mathChain → mathAgent 注册 CalculatorTool
|
||||
* ===================================================================== */
|
||||
|
||||
@Test
|
||||
public void testMathChainWithCustomTool() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(deepseekKey()),
|
||||
"deepseek api-key 未配置,跳过 mathChain(mathAgent 后端用 deepseek)");
|
||||
runChainExpectingReply("mathChain",
|
||||
"请用 calculator 工具计算 (123 + 456) * 7 - 89,并用一句话给出答案。");
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* IF 路由:routerChain,根据 isMath 选 mathAgent 或 deepseekAgent
|
||||
* ===================================================================== */
|
||||
|
||||
@Test
|
||||
public void testRouterChainPicksMathAgent() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(deepseekKey()),
|
||||
"deepseek api-key 未配置,跳过 routerChain");
|
||||
runChainExpectingReply("routerChain", "12*34 等于多少?");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRouterChainPicksChatAgent() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(ApiKeys.isPresent(deepseekKey()),
|
||||
"deepseek api-key 未配置,跳过 routerChain");
|
||||
runChainExpectingReply("routerChain", "你好,简单介绍下你自己。");
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
* WHEN 并行:parallelChain 同时跑 deepseek + dashscope,谁后到 recordReply 取谁
|
||||
* ===================================================================== */
|
||||
|
||||
@Test
|
||||
public void testParallelChain() {
|
||||
syncEnvKeys();
|
||||
Assumptions.assumeTrue(
|
||||
ApiKeys.isPresent(deepseekKey()) && ApiKeys.isPresent(dashscopeKey()),
|
||||
"需要同时配置 deepseek 和 dashscope 的 api-key 才能运行 parallelChain");
|
||||
runChainExpectingReply("parallelChain", "用一句话介绍 LiteFlow 的应用场景。");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.yomahub.liteflow.test.agent.tool;
|
||||
|
||||
import io.agentscope.core.tool.Tool;
|
||||
import io.agentscope.core.tool.ToolParam;
|
||||
|
||||
/**
|
||||
* 自定义工具示例:计算器。被 ReActAgentComponent.tools() 注册后,
|
||||
* agent 在推理时会被 toolkit 自动发现并按需调用。
|
||||
*/
|
||||
public class CalculatorTool {
|
||||
|
||||
@Tool(name = "calculator", description = "Evaluate a basic arithmetic expression like '1+2*3'")
|
||||
public String calc(@ToolParam(name = "expression",
|
||||
description = "Arithmetic expression with +, -, *, /, and parentheses") String expression) {
|
||||
try {
|
||||
return String.valueOf(Eval.run(expression));
|
||||
} catch (Exception e) {
|
||||
return "ERROR: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/** 极简递归下降表达式求值器(仅供示例,不保证健壮性)。 */
|
||||
static final class Eval {
|
||||
private final String s;
|
||||
private int pos;
|
||||
private Eval(String s) { this.s = s; }
|
||||
|
||||
static double run(String s) {
|
||||
Eval e = new Eval(s.replaceAll("\\s+", ""));
|
||||
double v = e.expr();
|
||||
if (e.pos < e.s.length()) throw new IllegalArgumentException("Unexpected: " + e.s.substring(e.pos));
|
||||
return v;
|
||||
}
|
||||
private double expr() {
|
||||
double v = term();
|
||||
while (pos < s.length() && (s.charAt(pos) == '+' || s.charAt(pos) == '-')) {
|
||||
char op = s.charAt(pos++);
|
||||
double r = term();
|
||||
v = (op == '+') ? v + r : v - r;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
private double term() {
|
||||
double v = factor();
|
||||
while (pos < s.length() && (s.charAt(pos) == '*' || s.charAt(pos) == '/')) {
|
||||
char op = s.charAt(pos++);
|
||||
double r = factor();
|
||||
v = (op == '*') ? v * r : v / r;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
private double factor() {
|
||||
if (pos < s.length() && s.charAt(pos) == '(') {
|
||||
pos++;
|
||||
double v = expr();
|
||||
if (pos >= s.length() || s.charAt(pos) != ')') throw new IllegalArgumentException("Missing )");
|
||||
pos++;
|
||||
return v;
|
||||
}
|
||||
int start = pos;
|
||||
if (pos < s.length() && (s.charAt(pos) == '+' || s.charAt(pos) == '-')) pos++;
|
||||
while (pos < s.length() && (Character.isDigit(s.charAt(pos)) || s.charAt(pos) == '.')) pos++;
|
||||
if (start == pos) throw new IllegalArgumentException("Number expected at " + pos);
|
||||
return Double.parseDouble(s.substring(start, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
liteflow.rule-source=agent/flow.el.xml
|
||||
liteflow.print-banner=false
|
||||
|
||||
# =============================================================================
|
||||
# ReAct Agent 测试 API Key —— 用户在此填入,留空则对应链路测试被 Assumptions 跳过
|
||||
# 也可通过环境变量覆盖(OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY /
|
||||
# DASHSCOPE_API_KEY / DEEPSEEK_API_KEY),环境变量优先级更高
|
||||
# =============================================================================
|
||||
liteflow.agent.workspace.root=${java.io.tmpdir}/liteflow-react-agent-test
|
||||
liteflow.agent.shell.mode=disabled
|
||||
|
||||
liteflow.agent.openai.api-key=
|
||||
liteflow.agent.anthropic.api-key=
|
||||
liteflow.agent.gemini.api-key=
|
||||
liteflow.agent.dashscope.api-key=
|
||||
liteflow.agent.openai-compatible.deepseek.api-key=
|
||||
liteflow.agent.openai-compatible.deepseek.base-url=https://api.deepseek.com/v1
|
||||
liteflow.agent.anthropic-compatible.gateway.api-key=
|
||||
liteflow.agent.anthropic-compatible.gateway.base-url=https://anthropic-gateway.example.com
|
||||
|
||||
# 模型名(可按需覆盖)
|
||||
test.openai.model=gpt-4o-mini
|
||||
test.anthropic.model=claude-sonnet-4-5
|
||||
test.gemini.model=gemini-2.5-flash
|
||||
test.dashscope.model=qwen-plus
|
||||
test.deepseek.model=deepseek-chat
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
|
||||
<flow>
|
||||
|
||||
<!-- 1. 最简单的 Agent 调用链:准备 -> Agent 推理 -> 记录回复 -->
|
||||
<chain name="deepseekChain">
|
||||
THEN(prepare, deepseekAgent, recordReply);
|
||||
</chain>
|
||||
|
||||
<chain name="openaiChain">
|
||||
THEN(prepare, openaiAgent, recordReply);
|
||||
</chain>
|
||||
|
||||
<chain name="anthropicChain">
|
||||
THEN(prepare, anthropicAgent, recordReply);
|
||||
</chain>
|
||||
|
||||
<chain name="dashscopeChain">
|
||||
THEN(prepare, dashscopeAgent, recordReply);
|
||||
</chain>
|
||||
|
||||
<chain name="geminiChain">
|
||||
THEN(prepare, geminiAgent, recordReply);
|
||||
</chain>
|
||||
|
||||
<!-- 2. 自定义工具示例:mathAgent 注册了 CalculatorTool -->
|
||||
<chain name="mathChain">
|
||||
THEN(prepare, mathAgent, recordReply);
|
||||
</chain>
|
||||
|
||||
<!-- 3. 用 IF EL 路由:算术题走 mathAgent,其他走 deepseekAgent -->
|
||||
<chain name="routerChain">
|
||||
THEN(
|
||||
prepare,
|
||||
IF(isMath, mathAgent, deepseekAgent),
|
||||
recordReply
|
||||
);
|
||||
</chain>
|
||||
|
||||
<!-- 4. 并行 WHEN:同时跑两个不同后端的 Agent,用 maxWaitSeconds 限时 -->
|
||||
<chain name="parallelChain">
|
||||
THEN(
|
||||
prepare,
|
||||
WHEN(deepseekAgent, dashscopeAgent).maxWaitSeconds(60),
|
||||
recordReply
|
||||
);
|
||||
</chain>
|
||||
|
||||
</flow>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{40} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="io.agentscope" level="INFO"/>
|
||||
<logger name="com.yomahub.liteflow" level="INFO"/>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -46,4 +46,17 @@
|
||||
<module>liteflow-testcase-el-sql-springboot-sharding-jdbc</module>
|
||||
<module>liteflow-testcase-el-script-javaxpro-springboot</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<!-- ReAct Agent 测试模块依赖 JDK 21(agentscope-java),仅在 JDK 17+ 构建中拉入 -->
|
||||
<profile>
|
||||
<id>testcase-react-agent</id>
|
||||
<activation>
|
||||
<jdk>[17,)</jdk>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>liteflow-testcase-el-react-agent</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
139
pom.xml
139
pom.xml
@@ -39,7 +39,7 @@
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<revision>2.15.3.2</revision>
|
||||
<revision>2.16.0</revision>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
@@ -82,7 +82,7 @@
|
||||
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
|
||||
<apache-commons-test.version>1.14.0</apache-commons-test.version>
|
||||
<caffeine.version>2.9.3</caffeine.version>
|
||||
<agentscope.version>1.0.9</agentscope.version>
|
||||
<agentscope.version>1.0.11</agentscope.version>
|
||||
<google-genai.version>1.38.0</google-genai.version>
|
||||
</properties>
|
||||
|
||||
@@ -425,11 +425,64 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!-- Release 插件统一定义,release-main 和 release-react-agent 共用 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<passphrase>${gpg.passphrase}</passphrase>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.11.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.8.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>oss</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>compile</id>
|
||||
<id>compile-8-to-16</id>
|
||||
<modules>
|
||||
<module>liteflow-core</module>
|
||||
<module>liteflow-script-plugin</module>
|
||||
@@ -442,12 +495,12 @@
|
||||
<module>liteflow-benchmark</module>
|
||||
</modules>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
<jdk>[1.8,17)</jdk>
|
||||
</activation>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<id>release-main</id>
|
||||
<modules>
|
||||
<module>liteflow-core</module>
|
||||
<module>liteflow-script-plugin</module>
|
||||
@@ -463,53 +516,46 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.4</version>
|
||||
</plugin>
|
||||
<!-- GPG -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<passphrase>${gpg.passphrase}</passphrase>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Javadoc -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.11.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalOptions>-Xdoclint:none</additionalOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.8.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>oss</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release-react-agent</id>
|
||||
<modules>
|
||||
<module>liteflow-react-agent</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -517,11 +563,20 @@
|
||||
|
||||
<!-- ReAct Agent 模块:需要 Java 21+(agentscope-java 要求),JDK 版本满足时自动激活 -->
|
||||
<profile>
|
||||
<id>react-agent</id>
|
||||
<id>compile-17+</id>
|
||||
<activation>
|
||||
<jdk>[21,)</jdk>
|
||||
<jdk>[17,)</jdk>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>liteflow-core</module>
|
||||
<module>liteflow-script-plugin</module>
|
||||
<module>liteflow-rule-plugin</module>
|
||||
<module>liteflow-spring-boot-starter</module>
|
||||
<module>liteflow-spring</module>
|
||||
<module>liteflow-solon-plugin</module>
|
||||
<module>liteflow-testcase-el</module>
|
||||
<module>liteflow-el-builder</module>
|
||||
<module>liteflow-benchmark</module>
|
||||
<module>liteflow-react-agent</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
||||
Reference in New Issue
Block a user