From 22c4c96547347359897e7c314b1b5876f7cd2f44 Mon Sep 17 00:00:00 2001 From: jiangzhen Date: Wed, 31 Dec 2025 11:19:04 +0800 Subject: [PATCH 1/5] Support Loading Configuration from .env Files and Environment Variables --- ...gentScopeDeployWithCommandLineExample.java | 48 + pom.xml | 17 +- web/pom.xml | 4 + .../io/agentscope/runtime/app/AgentApp.java | 833 +++++++++++------- .../AgentScopeDeployWithCommandLineTests.java | 51 ++ 5 files changed, 650 insertions(+), 303 deletions(-) create mode 100644 examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java create mode 100644 web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java diff --git a/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java b/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java new file mode 100644 index 00000000..44b1dcc8 --- /dev/null +++ b/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java @@ -0,0 +1,48 @@ +package io.agentscope; + +import java.util.Objects; +import java.util.Properties; + +import io.agentscope.runtime.adapters.AgentHandler; +import io.agentscope.runtime.app.AgentApp; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.KubernetesClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; + +public class AgentScopeDeployWithCommandLineExample { + + public static void main(String[] args) { + String[] commandLine = new String[2]; + commandLine[0] = "-f"; + commandLine[1] = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(".env")).getPath(); + AgentApp app = new AgentApp(commandLine); + app.run(); + } + + public static class MyAgentHandlerProvider implements AgentApp.AgentHandlerProvider{ + + @Override + public AgentHandler get(Properties properties, AgentApp.ServiceComponentManager serviceComponentManager) { + MyAgentScopeAgentHandler handler = new MyAgentScopeAgentHandler(); + handler.setStateService(serviceComponentManager.getStateService()); + handler.setSandboxService(serviceComponentManager.getSandboxService()); + handler.setMemoryService(serviceComponentManager.getMemoryService()); + handler.setSessionHistoryService(serviceComponentManager.getSessionHistoryService()); + return handler; + } + } + + public static class MySandboxServiceProvider implements AgentApp.SandboxServiceProvider { + @Override + public SandboxService get(Properties properties) { + BaseClientConfig clientConfig = KubernetesClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + return new SandboxService( + new SandboxManager(managerConfig)); + } + } +} diff --git a/pom.xml b/pom.xml index d06b01ba..82fbc2eb 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,9 @@ 1.21.3 0.7.0 + + + 4.7.7 @@ -313,6 +316,11 @@ ${spring-boot.version} test + + info.picocli + picocli + ${picocli.version} + @@ -644,6 +652,14 @@ + + + src/main/resources + + **/* + + + @@ -732,5 +748,4 @@ - diff --git a/web/pom.xml b/web/pom.xml index e1c6999c..0fc819b9 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -112,6 +112,10 @@ ${project.version} test + + info.picocli + picocli + org.apache.rocketmq diff --git a/web/src/main/java/io/agentscope/runtime/app/AgentApp.java b/web/src/main/java/io/agentscope/runtime/app/AgentApp.java index 21ee96bd..54068969 100644 --- a/web/src/main/java/io/agentscope/runtime/app/AgentApp.java +++ b/web/src/main/java/io/agentscope/runtime/app/AgentApp.java @@ -16,10 +16,18 @@ package io.agentscope.runtime.app; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; import java.util.function.Consumer; import java.util.function.Function; @@ -27,18 +35,34 @@ import io.agentscope.runtime.adapters.AgentHandler; import io.agentscope.runtime.engine.DeployManager; import io.agentscope.runtime.engine.Runner; +import io.agentscope.runtime.engine.services.agent_state.InMemoryStateService; +import io.agentscope.runtime.engine.services.agent_state.StateService; +import io.agentscope.runtime.engine.services.memory.persistence.memory.service.InMemoryMemoryService; +import io.agentscope.runtime.engine.services.memory.persistence.session.InMemorySessionHistoryService; +import io.agentscope.runtime.engine.services.memory.service.MemoryService; +import io.agentscope.runtime.engine.services.memory.service.SessionHistoryService; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; import io.agentscope.runtime.protocol.ProtocolConfig; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import lombok.Getter; +import lombok.Setter; +import okio.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.springframework.web.servlet.config.annotation.CorsRegistry; +import picocli.CommandLine; +import picocli.CommandLine.Option; + + /** * AgentApp class represents an application that runs as an agent. - * + * *

This class serves as the server startup entry point for starting Spring Boot Server, * assembling configuration and components (Controller/Endpoint, etc.), and * initializing Runner (which proxies AgentAdapter).

- * + * *

Key responsibilities:

*
    *
  • Initialize Runner with AgentAdapter
  • @@ -46,7 +70,7 @@ *
  • Manage application lifecycle
  • *
  • Configure endpoints and protocols
  • *
- * + * *

Usage example:

*
{@code
  * AgentAdapter adapter = new AgentScopeAgentAdapter(...);
@@ -56,304 +80,509 @@
  * }
*/ public class AgentApp { - private static final Logger logger = LoggerFactory.getLogger(AgentApp.class); - - private final AgentHandler adapter; - private volatile Runner runner; - private DeployManager deployManager; - - // Configuration - private String endpointPath; - private String host = "0.0.0.0"; - private int port = 8090; - private boolean stream = true; - private String responseType = "sse"; - private Consumer corsConfigurer; - - private List customEndpoints = new ArrayList<>(); - private List protocolConfigs; - - /** - * Constructor with AgentAdapter and Celery configuration. - * - * @param adapter the AgentAdapter instance to use - */ - public AgentApp(AgentHandler adapter) { - if (adapter == null) { - throw new IllegalArgumentException("AgentAdapter cannot be null"); - } - this.adapter = adapter; - - // Initialize protocol adapters (simplified) - // In real implementation, these would be actual adapter instances - this.protocolConfigs = new ArrayList<>(); - } - - /** - * Set the endpoint path for the agent service. - * - * @param endpointPath the endpoint path (default: "/process") - * @return this AgentApp instance for method chaining - */ - public AgentApp endpointPath(String endpointPath) { - this.endpointPath = endpointPath; - return this; - } - - /** - * Set the host address to bind to. - * - * @param host the host address (default: "0.0.0.0") - * @return this AgentApp instance for method chaining - */ - public AgentApp host(String host) { - this.host = host; - return this; - } - - /** - * Set the port number to serve on. - * - * @param port the port number (default: 8090) - * @return this AgentApp instance for method chaining - */ - public AgentApp port(int port) { - this.port = port; - return this; - } - - /** - * Set whether to enable streaming responses. - * - * @param stream true to enable streaming (default: true) - * @return this AgentApp instance for method chaining - */ - public AgentApp stream(boolean stream) { - this.stream = stream; - return this; - } - - /** - * Set the response type. - * - * @param responseType the response type: "sse", "json", or "text" (default: "sse") - * @return this AgentApp instance for method chaining - */ - public AgentApp responseType(String responseType) { - this.responseType = responseType; - return this; - } - - /** - * Set the DeployManager to use for deployment. - * - * @param deployManager the DeployManager instance - * @return this AgentApp instance for method chaining - */ - public AgentApp deployManager(DeployManager deployManager) { - this.deployManager = deployManager; - return this; - } - - /** - * Configure Cross-Origin Resource Sharing (CORS). - * - *

Usage example:

- *
{@code
-     * app.cors(registry -> registry.addMapping("/**")
-     *         .allowedOrigins("https://example.com")
-     *         .allowedMethods("GET", "POST")
-     *         .allowCredentials(true));
-     * }
- * - * @param corsConfigurer consumer to customize {@link CorsRegistry} - * @return this AgentApp instance for method chaining - */ - public AgentApp cors(Consumer corsConfigurer) { - this.corsConfigurer = corsConfigurer; - return this; - } - - /** - * Build the Runner instance by proxying the AgentAdapter. - * - *

This method creates a Runner instance that proxies calls to the AgentAdapter.

- * - * @return this AgentApp instance for method chaining - */ - public synchronized AgentApp buildRunner() { - if (this.runner == null) { - this.runner = new Runner(adapter); - logger.info("[AgentApp] Runner built with adapter framework type: {}", adapter.getFrameworkType()); - } - return this; - } - - /** - * Get the Runner instance. - * - * @return the Runner instance, or null if not built yet - */ - public Runner getRunner() { - return runner; - } - - - /** - * Register a query handler with optional framework type. - * - *

The handler will be called to process agent queries.

- * - *

Supported framework types: "agentscope", "saa"

- * - *

Usage example:

- *
{@code
-     * AgentApp app = new AgentApp(adapter);
-     * app.query("agentscope", (request) -> {
-     *     // Process the request and return response
-     *     return processAgentRequest(request);
-     * });
-     * }
- * - * @param framework the framework type (must be one of: agentscope, saa, langchain4j) - * @param handler the query handler function that takes a request map and returns a response - * @return this AgentApp instance for method chaining - * @throws IllegalArgumentException if framework type is not supported - */ - public AgentApp query(String framework, Function, Object> handler) { - if (framework == null || framework.isEmpty()) { - framework = "agentscope"; // Default framework - } - - // Validate framework type - List allowedFrameworks = Arrays.asList("agentscope", "saa"); - if (!allowedFrameworks.contains(framework.toLowerCase())) { - throw new IllegalArgumentException( - String.format("framework must be one of %s", allowedFrameworks) - ); - } - - return this; - } - - /** - * Register a query handler with default framework type ("agentscope"). - * - * @param handler the query handler function - * @return this AgentApp instance for method chaining - */ - public AgentApp query(java.util.function.Function, Object> handler) { - return query("agentscope", handler); - } - - /** - * Run the application by starting the Spring Boot Server. - * - *

This method corresponds to AgentApp.run() in the Python version. - * It builds the runner, initializes and starts it, then deploys via DeployManager.

- * - *

If no DeployManager is set, this method will throw an IllegalStateException. - * You should set a DeployManager (e.g., LocalDeployManager) before calling this method.

- * - * @return a CompletableFuture that completes when the server is running - * @throws IllegalStateException if DeployManager is not set - */ - public void run() { - run(host, port); - } - - public void run(int port) { - run("0.0.0.0", port); - } - - /** - * Run the application with specified host and port. - * - * @param host the host address to bind to - * @param port the port number to serve on - * @return a CompletableFuture that completes when the server is running - * @throws IllegalStateException if DeployManager is not set - */ - public void run(String host, int port) { - if (deployManager == null) { - this.deployManager = LocalDeployManager.builder() - .host(host) - .port(port) - .endpointName(endpointPath) - .protocolConfigs(protocolConfigs) - .corsConfigurer(corsConfigurer) - .build(); - } - - buildRunner(); - logger.info("[AgentApp] Starting AgentApp with endpoint: {}, host: {}, port: {}", endpointPath, host, port); - - // Deploy via DeployManager - deployManager.deploy(runner); - logger.info("[AgentApp] AgentApp started successfully on {}:{}{}", host, port, endpointPath); - } - - /** - * Register custom endpoint. - */ - public AgentApp endpoint(String path, List methods, Function, Object> handler) { - if (methods == null || methods.isEmpty()) { - methods = Arrays.asList("POST"); - } - - EndpointInfo endpointInfo = new EndpointInfo(); - endpointInfo.path = path; - endpointInfo.handler = handler; - endpointInfo.methods = methods; - customEndpoints.add(endpointInfo); - - return this; - } - - // Getters and setters - public String getEndpointPath() { - return endpointPath; - } - - public void setEndpointPath(String endpointPath) { - this.endpointPath = endpointPath; - } - - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - - public String getResponseType() { - return responseType; - } - - public void setResponseType(String responseType) { - this.responseType = responseType; - } - - public boolean isStream() { - return stream; - } - - public void setStream(boolean stream) { - this.stream = stream; - } - - public List getCustomEndpoints() { - return customEndpoints; - } - - /** - * Endpoint information. - */ - public static class EndpointInfo { - public String path; - public Function, Object> handler; - public List methods; - } + private static final Logger logger = LoggerFactory.getLogger(AgentApp.class); + + private AgentHandler adapter; + private volatile Runner runner; + private DeployManager deployManager; + + // Configuration + private String endpointPath; + private String host = "0.0.0.0"; + private int port = 8090; + private boolean stream = true; + private String responseType = "sse"; + private Consumer corsConfigurer; + + private List customEndpoints = new ArrayList<>(); + private List protocolConfigs; + private static final String DEFAULT_ENV_FILE_PATH = ".env"; + private static final String CLASS_SUFFIX = ".class"; + private static final String PROVIDER_SUFFIX = ".provider"; + private static final String PROVIDER_CLASS_SUFFIX = PROVIDER_SUFFIX + CLASS_SUFFIX; + private static final String SERVICE_SUFFIX = ".service"; + private static final String DEFAULT_PREFIX = "agent.app."; + private static final String AGENT_APP_PORT = DEFAULT_PREFIX + "port"; + private static final String AGENT_HANDLER_PROVIDER = DEFAULT_PREFIX + "handler" + PROVIDER_CLASS_SUFFIX; + private static final String STATE_SERVICE_PROVIDER = DEFAULT_PREFIX + "state" + SERVICE_SUFFIX + PROVIDER_CLASS_SUFFIX; + private static final String SESSION_HISTORY_SERVICE_PROVIDER = DEFAULT_PREFIX + "sessionHistory" + SERVICE_SUFFIX + PROVIDER_CLASS_SUFFIX; + private static final String MEMORY_SERVICE_PROVIDER = DEFAULT_PREFIX + "memory" + SERVICE_SUFFIX + PROVIDER_CLASS_SUFFIX; + private static final String SANDBOX_SERVICE_PROVIDER = DEFAULT_PREFIX + "sandbox" + SERVICE_SUFFIX + PROVIDER_CLASS_SUFFIX; + + /** + * Constructor with command line arguments + * @param args Command line arguments + */ + public AgentApp(String[] args) { + String path; + if (Objects.isNull(args) || args.length == 0) { + path = DEFAULT_ENV_FILE_PATH; + } + else { + Argument argument = new Argument(); + CommandLine cmd = new CommandLine(argument); + CommandLine.ParseResult parseResult = cmd.parseArgs(args); + List errors = parseResult.errors(); + if (Objects.nonNull(errors) && !errors.isEmpty()) { + StringBuilder errMsg = new StringBuilder(); + for (Exception exception : errors) { + errMsg.append(exception.getMessage()).append(System.lineSeparator()); + } + logger.warn("Parse config error,cause:{},will be use default configuration file:{}", errMsg, DEFAULT_ENV_FILE_PATH); + path = DEFAULT_ENV_FILE_PATH; + } + else { + path = argument.configFilePath; + } + } + + Path filePath = Path.get(path); + File file = filePath.toFile(); + Properties properties = new Properties(); + //Inject environment variables, and if a configuration file exists, the corresponding variables will be overwritten + properties.putAll(System.getenv()); + properties.putAll(System.getProperties()); + if (!file.exists() || file.isDirectory()) { + logger.warn("File [{}] is not exits or is not a file,please check it", file.getAbsolutePath()); + } + else { + try (FileInputStream input = new FileInputStream(file)) { + properties.load(input); + } + catch (IOException e) { + logger.error("Load config file [{}] error,please check it", file.getAbsolutePath()); + return; + } + } + this.port = port(properties.getOrDefault(AGENT_APP_PORT, this.port), this.port); + try { + StateService stateService = component(properties, STATE_SERVICE_PROVIDER, StateServiceProvider.class, new InMemoryStateService()); + SessionHistoryService sessionHistoryService = component(properties, SESSION_HISTORY_SERVICE_PROVIDER, SessionHistoryServiceProvider.class, new InMemorySessionHistoryService()); + MemoryService memoryService = component(properties, MEMORY_SERVICE_PROVIDER, MemoryServiceProvider.class, new InMemoryMemoryService()); + SandboxService sandboxService = component(properties, SANDBOX_SERVICE_PROVIDER, SandboxServiceProvider.class, new SandboxService(new SandboxManager())); + ServiceComponentManager serviceComponentManager = new ServiceComponentManager(); + serviceComponentManager.setStateService(stateService); + serviceComponentManager.setSessionHistoryService(sessionHistoryService); + serviceComponentManager.setMemoryService(memoryService); + serviceComponentManager.setSandboxService(sandboxService); + this.adapter = agentHandler(properties, serviceComponentManager); + } + catch (Exception e) { + logger.error("Can not init handler,please check it", e); + return; + } + this.protocolConfigs = new ArrayList<>(); + } + + /** + * + * @param properties Full configuration information + * @param key Service component provider class property + * @param superClass Service component provider interface + * @param defaultInstance If process error,will be fall back + * @return A Service component instance + * @param Service component real type + * @throws ClassNotFoundException If classLoader can not load service component provider class instance + * @throws InstantiationException If reflect process error + * @throws IllegalAccessException If reflect process error + * @throws InvocationTargetException If reflect process error + */ + private T component(Properties properties, String key, Class> superClass, T defaultInstance) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException { + Object providerName = properties.get(key); + if (Objects.nonNull(providerName)) { + Class providerClass = Class.forName(providerName.toString()); + if (!superClass.isAssignableFrom(providerClass)) { + throw new RuntimeException(String.format("Except class[%s] is a %s implementation,but it is not corresponding implementation", providerName, superClass.getName())); + } + Optional> optionalConstructor = Arrays.stream(providerClass.getConstructors()) + .filter(e -> e.getParameterCount() == 0).findFirst(); + if (optionalConstructor.isEmpty()) { + throw new RuntimeException(String.format("Except class[%s] has a none parameter constructor,but it is not exists", providerClass.getName())); + } + @SuppressWarnings("unchecked") + ComponentProvider serviceProvider = (ComponentProvider) optionalConstructor.get().newInstance(); + return serviceProvider.get(properties); + } + else { + return defaultInstance; + } + } + + /*** + * + * @param properties Full configuration information + * @param serviceComponentManager Service component management, including state, memory, session,sandbox and the like + * @return An io.agentscope.runtime.adapters.AgentHandler instance + * @throws ClassNotFoundException If classLoader can not load AgentHandlerProvider instance + * @throws InvocationTargetException If reflect process error + * @throws InstantiationException If reflect process error + * @throws IllegalAccessException If reflect process error + */ + + private AgentHandler agentHandler(Properties properties, ServiceComponentManager serviceComponentManager) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + Object providerName = properties.get(AgentApp.AGENT_HANDLER_PROVIDER); + if (Objects.isNull(providerName)) { + throw new RuntimeException(String.format("Can not init agentHandler because property[%s] is not exists", AgentApp.AGENT_HANDLER_PROVIDER)); + } + Class providerClass = Class.forName(providerName.toString()); + if (!AgentHandlerProvider.class.isAssignableFrom(providerClass)) { + throw new RuntimeException(String.format("Except class[%s] is a implementation", AgentHandlerProvider.class.getName())); + } + Optional> optionalConstructor = Arrays.stream(providerClass.getConstructors()) + .filter(e -> e.getParameterCount() == 0).findFirst(); + if (optionalConstructor.isEmpty()) { + throw new RuntimeException(String.format("Except class[%s] has a none parameter constructor,but it is not exists", providerClass.getName())); + } + AgentHandlerProvider provider = (AgentHandlerProvider) optionalConstructor.get().newInstance(); + return provider.get(properties, serviceComponentManager); + } + + /** + * Constructor with AgentAdapter and Celery configuration. + * + * @param adapter the AgentAdapter instance to use + */ + public AgentApp(AgentHandler adapter) { + if (adapter == null) { + throw new IllegalArgumentException("AgentAdapter cannot be null"); + } + this.adapter = adapter; + + // Initialize protocol adapters (simplified) + // In real implementation, these would be actual adapter instances + this.protocolConfigs = new ArrayList<>(); + } + + /** + * Set the endpoint path for the agent service. + * + * @param endpointPath the endpoint path (default: "/process") + * @return this AgentApp instance for method chaining + */ + public AgentApp endpointPath(String endpointPath) { + this.endpointPath = endpointPath; + return this; + } + + /** + * Set the host address to bind to. + * + * @param host the host address (default: "0.0.0.0") + * @return this AgentApp instance for method chaining + */ + public AgentApp host(String host) { + this.host = host; + return this; + } + + /** + * Set the port number to serve on. + * + * @param port the port number (default: 8090) + * @return this AgentApp instance for method chaining + */ + public AgentApp port(int port) { + this.port = port; + return this; + } + + /** + * Set whether to enable streaming responses. + * + * @param stream true to enable streaming (default: true) + * @return this AgentApp instance for method chaining + */ + public AgentApp stream(boolean stream) { + this.stream = stream; + return this; + } + + /** + * Set the response type. + * + * @param responseType the response type: "sse", "json", or "text" (default: "sse") + * @return this AgentApp instance for method chaining + */ + public AgentApp responseType(String responseType) { + this.responseType = responseType; + return this; + } + + /** + * Set the DeployManager to use for deployment. + * + * @param deployManager the DeployManager instance + * @return this AgentApp instance for method chaining + */ + public AgentApp deployManager(DeployManager deployManager) { + this.deployManager = deployManager; + return this; + } + + /** + * Configure Cross-Origin Resource Sharing (CORS). + * + *

Usage example:

+ *
{@code
+	 * app.cors(registry -> registry.addMapping("/**")
+	 *         .allowedOrigins("https://example.com")
+	 *         .allowedMethods("GET", "POST")
+	 *         .allowCredentials(true));
+	 * }
+ * + * @param corsConfigurer consumer to customize {@link CorsRegistry} + * @return this AgentApp instance for method chaining + */ + public AgentApp cors(Consumer corsConfigurer) { + this.corsConfigurer = corsConfigurer; + return this; + } + + /** + * Build the Runner instance by proxying the AgentAdapter. + * + *

This method creates a Runner instance that proxies calls to the AgentAdapter.

+ * + * @return this AgentApp instance for method chaining + */ + public synchronized AgentApp buildRunner() { + if (this.runner == null) { + this.runner = new Runner(adapter); + logger.info("[AgentApp] Runner built with adapter framework type: {}", adapter.getFrameworkType()); + } + return this; + } + + /** + * Get the Runner instance. + * + * @return the Runner instance, or null if not built yet + */ + public Runner getRunner() { + return runner; + } + + + /** + * Register a query handler with optional framework type. + * + *

The handler will be called to process agent queries.

+ * + *

Supported framework types: "agentscope", "saa"

+ * + *

Usage example:

+ *
{@code
+	 * AgentApp app = new AgentApp(adapter);
+	 * app.query("agentscope", (request) -> {
+	 *     // Process the request and return response
+	 *     return processAgentRequest(request);
+	 * });
+	 * }
+ * + * @param framework the framework type (must be one of: agentscope, saa, langchain4j) + * @param handler the query handler function that takes a request map and returns a response + * @return this AgentApp instance for method chaining + * @throws IllegalArgumentException if framework type is not supported + */ + public AgentApp query(String framework, Function, Object> handler) { + if (framework == null || framework.isEmpty()) { + framework = "agentscope"; // Default framework + } + + // Validate framework type + List allowedFrameworks = Arrays.asList("agentscope", "saa"); + if (!allowedFrameworks.contains(framework.toLowerCase())) { + throw new IllegalArgumentException( + String.format("framework must be one of %s", allowedFrameworks) + ); + } + + return this; + } + + /** + * Register a query handler with default framework type ("agentscope"). + * + * @param handler the query handler function + * @return this AgentApp instance for method chaining + */ + public AgentApp query(java.util.function.Function, Object> handler) { + return query("agentscope", handler); + } + + /** + * Run the application by starting the Spring Boot Server. + * + *

This method corresponds to AgentApp.run() in the Python version. + * It builds the runner, initializes and starts it, then deploys via DeployManager.

+ * + *

If no DeployManager is set, this method will throw an IllegalStateException. + * You should set a DeployManager (e.g., LocalDeployManager) before calling this method.

+ * + * @return a CompletableFuture that completes when the server is running + * @throws IllegalStateException if DeployManager is not set + */ + public void run() { + run(host, port); + } + + public void run(int port) { + run("0.0.0.0", port); + } + + /** + * Run the application with specified host and port. + * + * @param host the host address to bind to + * @param port the port number to serve on + * @return a CompletableFuture that completes when the server is running + * @throws IllegalStateException if DeployManager is not set + */ + public void run(String host, int port) { + if (deployManager == null) { + this.deployManager = LocalDeployManager.builder() + .host(host) + .port(port) + .endpointName(endpointPath) + .protocolConfigs(protocolConfigs) + .corsConfigurer(corsConfigurer) + .build(); + } + + buildRunner(); + logger.info("[AgentApp] Starting AgentApp with endpoint: {}, host: {}, port: {}", endpointPath, host, port); + + // Deploy via DeployManager + deployManager.deploy(runner); + logger.info("[AgentApp] AgentApp started successfully on {}:{}{}", host, port, endpointPath); + } + + /** + * Register custom endpoint. + */ + public AgentApp endpoint(String path, List methods, Function, Object> handler) { + if (methods == null || methods.isEmpty()) { + methods = Arrays.asList("POST"); + } + + EndpointInfo endpointInfo = new EndpointInfo(); + endpointInfo.path = path; + endpointInfo.handler = handler; + endpointInfo.methods = methods; + customEndpoints.add(endpointInfo); + + return this; + } + + // Getters and setters + public String getEndpointPath() { + return endpointPath; + } + + public void setEndpointPath(String endpointPath) { + this.endpointPath = endpointPath; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getResponseType() { + return responseType; + } + + public void setResponseType(String responseType) { + this.responseType = responseType; + } + + public boolean isStream() { + return stream; + } + + public void setStream(boolean stream) { + this.stream = stream; + } + + public List getCustomEndpoints() { + return customEndpoints; + } + + /** + * Endpoint information. + */ + public static class EndpointInfo { + public String path; + public Function, Object> handler; + public List methods; + } + + private static class Argument { + @Option(names = {"-f", "--file"}) + public String configFilePath; + } + + protected int port(Object value, int defaultValue) { + int validated = validatePort(value); + if (validated != -1 && validated <= 65535) { + return validated; + } + return defaultValue; + } + + + private int validatePort(Object value) { + if (value instanceof Integer val) { + return val; + } + try { + return Integer.parseInt(value.toString()); + } + catch (Exception e) { + //ignore + return -1; + } + } + + public interface ComponentProvider { + T get(Properties properties); + } + + @FunctionalInterface + public interface StateServiceProvider extends ComponentProvider { + StateService get(Properties properties); + } + + @FunctionalInterface + public interface SessionHistoryServiceProvider extends ComponentProvider { + SessionHistoryService get(Properties properties); + } + + @FunctionalInterface + public interface MemoryServiceProvider extends ComponentProvider { + MemoryService get(Properties properties); + } + + @FunctionalInterface + public interface SandboxServiceProvider extends ComponentProvider { + SandboxService get(Properties properties); + } + + @FunctionalInterface + public interface AgentHandlerProvider { + AgentHandler get(Properties properties, ServiceComponentManager serviceComponentManager); + } + + @Setter + @Getter + public static class ServiceComponentManager { + private StateService stateService; + private SessionHistoryService sessionHistoryService; + private MemoryService memoryService; + private SandboxService sandboxService; + } } diff --git a/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java b/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java new file mode 100644 index 00000000..b27cb2b7 --- /dev/null +++ b/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java @@ -0,0 +1,51 @@ +package io.agentscope.runtime.deployer; + +import java.util.Objects; +import java.util.Properties; + +import io.agentscope.runtime.adapters.AgentHandler; +import io.agentscope.runtime.adapters.agentscope.MyAgentScopeAgentHandler; +import io.agentscope.runtime.app.AgentApp; +import io.agentscope.runtime.engine.services.sandbox.SandboxService; +import io.agentscope.runtime.sandbox.manager.SandboxManager; +import io.agentscope.runtime.sandbox.manager.client.config.BaseClientConfig; +import io.agentscope.runtime.sandbox.manager.client.config.KubernetesClientConfig; +import io.agentscope.runtime.sandbox.manager.model.ManagerConfig; +import org.junit.jupiter.api.Test; + +public class AgentScopeDeployWithCommandLineTests { + + @Test + void testRunWithCommandLine(){ + String[] commandLine = new String[2]; + commandLine[0] = "-f"; + commandLine[1] = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(".env")).getPath(); + AgentApp app = new AgentApp(commandLine); + app.run(); + } + + public static class MyAgentHandlerProvider implements AgentApp.AgentHandlerProvider{ + + @Override + public AgentHandler get(Properties properties, AgentApp.ServiceComponentManager serviceComponentManager) { + MyAgentScopeAgentHandler handler = new MyAgentScopeAgentHandler(); + handler.setStateService(serviceComponentManager.getStateService()); + handler.setSandboxService(serviceComponentManager.getSandboxService()); + handler.setMemoryService(serviceComponentManager.getMemoryService()); + handler.setSessionHistoryService(serviceComponentManager.getSessionHistoryService()); + return handler; + } + } + + public static class MySandboxServiceProvider implements AgentApp.SandboxServiceProvider { + @Override + public SandboxService get(Properties properties) { + BaseClientConfig clientConfig = KubernetesClientConfig.builder().build(); + ManagerConfig managerConfig = ManagerConfig.builder() + .containerDeployment(clientConfig) + .build(); + return new SandboxService( + new SandboxManager(managerConfig)); + } + } +} From c83b5ae619e76f7a83272ffe837a6f66f03542ab Mon Sep 17 00:00:00 2001 From: jiangzhen Date: Sun, 4 Jan 2026 10:37:35 +0800 Subject: [PATCH 2/5] remove lombok,add use example into document --- cookbook/en/deployment/agent_app.md | 15 ++++++++ cookbook/zh/deployment/agent_app.md | 13 +++++++ .../io/agentscope/runtime/app/AgentApp.java | 37 +++++++++++++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/cookbook/en/deployment/agent_app.md b/cookbook/en/deployment/agent_app.md index dc3ed9b6..5e48ab7e 100644 --- a/cookbook/en/deployment/agent_app.md +++ b/cookbook/en/deployment/agent_app.md @@ -46,6 +46,21 @@ agentApp.run("localhost",10001); ------ +```java +//Use launch parameters or environment variables +// required items +// 1、Specify attributes in environment variables or startup parameters[agent.app.handler.provider.class],It is an implementation of io.agentscope.runtime.app.AgentApp.AgentHandlerProvider,used for create an io.agentscope.runtime.adapters.AgentHandler instance. +// 2、Specify attributes in environment variables or startup parameters[agent.app.sandbox.service.provider.class],It is an implementation of io.agentscope.runtime.app.AgentApp.SandboxServiceProvider,used for create an io.agentscope.runtime.engine.services.sandbox.SandboxService instance. +String[] commandLine = new String[2]; +commandLine[0] = "-f"; +commandLine[1] = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(".env")).getPath();//The path can be arbitrarily specified +AgentApp agentApp = new AgentApp(commandLine); +agentApp.run(); + +``` + +------ + ## Configuring Cross-Origin Resource Sharing (CORS) **Feature** diff --git a/cookbook/zh/deployment/agent_app.md b/cookbook/zh/deployment/agent_app.md index 1ddee884..670b5b82 100644 --- a/cookbook/zh/deployment/agent_app.md +++ b/cookbook/zh/deployment/agent_app.md @@ -46,6 +46,19 @@ agentApp.run("localhost",10001); ------ +```java +//使用启动参数或环境变量 +// 必要项 +// 1、在环境变量或启动参数中指定属性[agent.app.handler.provider.class],它是一个io.agentscope.runtime.app.AgentApp.AgentHandlerProvider的实现,用于实例化io.agentscope.runtime.adapters.AgentHandler +// 2、在环境变量或启动参数中指定属性[agent.app.sandbox.service.provider.class],它是一个io.agentscope.runtime.app.AgentApp.SandboxServiceProvider的实现,用于实例化io.agentscope.runtime.engine.services.sandbox.SandboxService +String[] commandLine = new String[2]; +commandLine[0] = "-f"; +commandLine[1] = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(".env")).getPath();//The path can be arbitrarily specified +AgentApp agentApp = new AgentApp(commandLine); +agentApp.run(); + +``` + ## 配置跨域(CORS) **功能** diff --git a/web/src/main/java/io/agentscope/runtime/app/AgentApp.java b/web/src/main/java/io/agentscope/runtime/app/AgentApp.java index 54068969..7ebd5880 100644 --- a/web/src/main/java/io/agentscope/runtime/app/AgentApp.java +++ b/web/src/main/java/io/agentscope/runtime/app/AgentApp.java @@ -44,8 +44,7 @@ import io.agentscope.runtime.engine.services.sandbox.SandboxService; import io.agentscope.runtime.protocol.ProtocolConfig; import io.agentscope.runtime.sandbox.manager.SandboxManager; -import lombok.Getter; -import lombok.Setter; + import okio.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -576,13 +575,43 @@ public interface AgentHandlerProvider { AgentHandler get(Properties properties, ServiceComponentManager serviceComponentManager); } - @Setter - @Getter public static class ServiceComponentManager { private StateService stateService; private SessionHistoryService sessionHistoryService; private MemoryService memoryService; private SandboxService sandboxService; + + public StateService getStateService() { + return stateService; + } + + public void setStateService(StateService stateService) { + this.stateService = stateService; + } + + public SessionHistoryService getSessionHistoryService() { + return sessionHistoryService; + } + + public void setSessionHistoryService(SessionHistoryService sessionHistoryService) { + this.sessionHistoryService = sessionHistoryService; + } + + public MemoryService getMemoryService() { + return memoryService; + } + + public void setMemoryService(MemoryService memoryService) { + this.memoryService = memoryService; + } + + public SandboxService getSandboxService() { + return sandboxService; + } + + public void setSandboxService(SandboxService sandboxService) { + this.sandboxService = sandboxService; + } } } From 820c79a0c9427f6b851d17d78564fa61ee805b36 Mon Sep 17 00:00:00 2001 From: jiangzhen Date: Sun, 4 Jan 2026 11:26:01 +0800 Subject: [PATCH 3/5] license header --- .../AgentScopeDeployWithCommandLineExample.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java b/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java index 44b1dcc8..32270d58 100644 --- a/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java +++ b/examples/simple_agent_use_examples/agentscope_use_example/src/main/java/io/agentscope/AgentScopeDeployWithCommandLineExample.java @@ -1,3 +1,18 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.agentscope; import java.util.Objects; From 975be5900ca658584870e0be4a6a295c97bdf980 Mon Sep 17 00:00:00 2001 From: jiangzhen Date: Sun, 4 Jan 2026 11:37:43 +0800 Subject: [PATCH 4/5] license header --- .../AgentScopeDeployWithCommandLineTests.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java b/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java index b27cb2b7..98dba80d 100644 --- a/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java +++ b/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.agentscope.runtime.deployer; import java.util.Objects; From 0480da853ed3a3d34b9ff5515b85fb0680637ed9 Mon Sep 17 00:00:00 2001 From: jiangzhen Date: Sun, 4 Jan 2026 15:55:24 +0800 Subject: [PATCH 5/5] ci process,use system property inject required items --- pom.xml | 8 -------- .../AgentScopeDeployWithCommandLineTests.java | 20 +++++++++++++++---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 82fbc2eb..8dfc5e24 100644 --- a/pom.xml +++ b/pom.xml @@ -652,14 +652,6 @@ - - - src/main/resources - - **/* - - - diff --git a/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java b/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java index 98dba80d..f8c48a1f 100644 --- a/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java +++ b/web/src/test/java/io/agentscope/runtime/deployer/AgentScopeDeployWithCommandLineTests.java @@ -16,6 +16,7 @@ package io.agentscope.runtime.deployer; +import java.net.URL; import java.util.Objects; import java.util.Properties; @@ -33,10 +34,21 @@ public class AgentScopeDeployWithCommandLineTests { @Test void testRunWithCommandLine(){ - String[] commandLine = new String[2]; - commandLine[0] = "-f"; - commandLine[1] = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(".env")).getPath(); - AgentApp app = new AgentApp(commandLine); + URL resource = Thread.currentThread().getContextClassLoader().getResource(".env"); + AgentApp app; + if (Objects.nonNull(resource)){ + String[] commandLine = new String[2]; + commandLine[0] = "-f"; + commandLine[1] = Objects.requireNonNull(resource).getPath(); + app = new AgentApp(commandLine); + }else{ + // ci can not get the .env file, here it is injected through the system properties + System.setProperty("agent.app.port","7779"); + System.setProperty("agent.app.handler.provider.class","io.agentscope.runtime.deployer.AgentScopeDeployWithCommandLineTests$MyAgentHandlerProvider"); + System.setProperty("agent.app.sandbox.service.provider.class","io.agentscope.runtime.deployer.AgentScopeDeployWithCommandLineTests$MySandboxServiceProvider"); + System.setProperty("AI_DASHSCOPE_API_KEY","your key"); + app = new AgentApp(new String[0]); + } app.run(); }