refactor(agent): remove legacy ConfigLoader and related factory; refactor agent launch flow

This commit is contained in:
2026-04-09 10:35:13 +08:00
parent 427d224f65
commit 1e46149d0a
14 changed files with 46 additions and 372 deletions

View File

@@ -1,16 +1,12 @@
package work.slhaf.partner;
import work.slhaf.partner.common.config.PartnerAgentConfigLoader;
import work.slhaf.partner.framework.agent.Agent;
import work.slhaf.partner.runtime.exception.PartnerExceptionCallback;
import work.slhaf.partner.runtime.interaction.WebSocketGateway;
import work.slhaf.partner.runtime.interaction.WebSocketGatewayRegistration;
public class Main {
public static void main(String[] args) {
Agent.newAgent(Main.class)
.setAgentConfigManager(PartnerAgentConfigLoader.class)
.setGateway(WebSocketGateway.class)
.setAgentExceptionCallback(PartnerExceptionCallback.class)
.addGatewayRegistration(WebSocketGatewayRegistration.INSTANCE)
.launch();
}
}
}

View File

@@ -1,41 +0,0 @@
package work.slhaf.partner.common.config;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import work.slhaf.partner.common.exception.ConfigLoadFailedException;
import work.slhaf.partner.framework.agent.config.FileAgentConfigLoader;
import work.slhaf.partner.framework.agent.factory.config.exception.ConfigNotExistException;
import java.io.File;
import java.nio.charset.StandardCharsets;
@EqualsAndHashCode(callSuper = true)
@Data
public final class PartnerAgentConfigLoader extends FileAgentConfigLoader {
private static final String COMMON_CONFIG_FILE = CONFIG_DIR + "common_config.json";
private Config config;
@Override
public void load() {
loadWebSocketConfig();
super.load();
}
private void loadWebSocketConfig() {
File file = new File(COMMON_CONFIG_FILE);
if (!file.exists()) {
throw new ConfigNotExistException("Partner Config Not Exist: " + COMMON_CONFIG_FILE);
}
config = JSONUtil.readJSONObject(file, StandardCharsets.UTF_8).toBean(Config.class);
if (config == null || config.getAgentId() == null) {
throw new ConfigLoadFailedException("Partner Config Load Failed: " + COMMON_CONFIG_FILE);
}
int port = config.getWebSocketConfig().getPort();
if (port <= 0 || port > 65535) {
throw new ConfigLoadFailedException("Invalid Websocket port: " + port);
}
}
}

View File

@@ -1,18 +1,17 @@
package work.slhaf.partner.framework.agent;
import lombok.extern.slf4j.Slf4j;
import work.slhaf.partner.framework.agent.config.AgentConfigLoader;
import work.slhaf.partner.framework.agent.exception.AgentExceptionCallback;
import work.slhaf.partner.framework.agent.config.ConfigCenter;
import work.slhaf.partner.framework.agent.exception.AgentLaunchFailedException;
import work.slhaf.partner.framework.agent.exception.GlobalExceptionHandler;
import work.slhaf.partner.framework.agent.factory.AgentRegisterFactory;
import work.slhaf.partner.framework.agent.interaction.AgentGateway;
import work.slhaf.partner.framework.agent.interaction.AgentGatewayRegistration;
import work.slhaf.partner.framework.agent.interaction.AgentGatewayRegistry;
import work.slhaf.partner.framework.agent.model.ModelRuntimeRegistry;
import work.slhaf.partner.framework.agent.state.StateCenter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* <h2>Agent 启动入口</h2>
@@ -21,133 +20,51 @@ import java.util.concurrent.Executors;
@Slf4j
public final class Agent {
public static AgentConfigManagerStep newAgent(Class<?> clazz) {
public static AgentStep newAgent(Class<?> clazz) {
if (clazz == null) {
throw new AgentLaunchFailedException("Agent class 和 interaction flow context 不能为 null");
}
return new AgentApp(clazz);
}
public interface AgentConfigManagerStep {
AgentGatewayStep setAgentConfigManager(Class<? extends AgentConfigLoader> agentConfigManager);
}
public interface AgentGatewayStep {
AgentStep setGateway(Class<? extends AgentGateway> gateway);
}
public interface AgentStep {
AgentStep addBeforeLaunchRunners(Runnable... runners);
AgentStep addAfterLaunchRunners(Runnable... runners);
AgentStep setAgentExceptionCallback(Class<? extends AgentExceptionCallback> agentExceptionCallback);
AgentStep addScanPackage(String packageName);
AgentStep addScanDir(String externalPackagePath);
AgentStep addGatewayRegistration(AgentGatewayRegistration... registrations);
void launch();
}
public static class AgentApp implements AgentStep {
public static class AgentApp implements AgentStep, AgentGatewayStep, AgentConfigManagerStep {
private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
private final List<Runnable> beforeLaunchRunners = new ArrayList<>();
private final List<Runnable> afterLaunchRunners = new ArrayList<>();
private final Class<?> applicationClass;
private final CountDownLatch latch = new CountDownLatch(1);
private AgentGateway gateway;
private Class<? extends AgentConfigLoader> agentConfigManagerClass;
private Class<? extends AgentGateway> gatewayClass;
private Class<? extends AgentExceptionCallback> agentExceptionCallbackClass;
private final Set<AgentGatewayRegistration> gatewayRegistrations = new LinkedHashSet<>();
private AgentApp(Class<?> clazz) {
this.applicationClass = clazz;
}
@Override
public AgentStep setGateway(Class<? extends AgentGateway> gateway) {
this.gatewayClass = gateway;
return this;
}
@Override
public AgentStep addBeforeLaunchRunners(Runnable... runners) {
this.beforeLaunchRunners.addAll(List.of(runners));
return this;
}
@Override
public AgentStep addAfterLaunchRunners(Runnable... runners) {
this.afterLaunchRunners.addAll(List.of(runners));
return this;
}
@Override
public AgentGatewayStep setAgentConfigManager(Class<? extends AgentConfigLoader> agentConfigManager) {
this.agentConfigManagerClass = agentConfigManager;
return this;
}
@Override
public AgentStep setAgentExceptionCallback(Class<? extends AgentExceptionCallback> agentExceptionCallback) {
agentExceptionCallbackClass = agentExceptionCallback;
return this;
}
@Override
public AgentStep addScanPackage(String packageName) {
AgentRegisterFactory.addScanPackage(packageName);
return this;
}
@Override
public AgentStep addScanDir(String externalPackagePath) {
AgentRegisterFactory.addScanDir(externalPackagePath);
public AgentStep addGatewayRegistration(AgentGatewayRegistration... registrations) {
this.gatewayRegistrations.addAll(Set.of(registrations));
return this;
}
@Override
public void launch() {
beforeLaunch();
AgentRegisterFactory.launch(applicationClass.getPackageName());
afterLaunch();
}
private void afterLaunch() {
try {
this.gateway = gatewayClass.getDeclaredConstructor().newInstance();
executorService.execute(() -> {
gateway.launch();
latch.countDown();
log.info("Gateway 启动完毕: {}", gatewayClass.getSimpleName());
});
latch.await();
launchRunners(afterLaunchRunners);
log.info("后置任务启动完毕");
// Keep startup order explicit so registries are ready before component scanning.
StateCenter.INSTANCE.toString();
ModelRuntimeRegistry.INSTANCE.register();
AgentGatewayRegistry.INSTANCE.register();
for (AgentGatewayRegistration registration : gatewayRegistrations) {
registration.register();
}
Path externalModuleDir = ConfigCenter.INSTANCE.getPaths().getResourcesDir().resolve("module");
AgentRegisterFactory.addScanDir(externalModuleDir.toString());
AgentRegisterFactory.launch(applicationClass.getPackageName());
ConfigCenter.INSTANCE.initAll();
ConfigCenter.INSTANCE.start();
} catch (Exception e) {
throw new AgentLaunchFailedException("Agent 后置任务启动失败", e);
}
}
private void beforeLaunch() {
try {
AgentConfigLoader.setINSTANCE(agentConfigManagerClass.getDeclaredConstructor().newInstance());
log.info("配置管理器设置完毕: {}", agentConfigManagerClass.getSimpleName());
GlobalExceptionHandler.setExceptionCallback(agentExceptionCallbackClass.getDeclaredConstructor().newInstance());
log.info("异常处理回调设置完毕: {}", agentExceptionCallbackClass.getSimpleName());
launchRunners(beforeLaunchRunners);
log.info("前置任务启动完毕");
} catch (Exception e) {
throw new AgentLaunchFailedException("Agent 前置任务启动失败", e);
}
}
private void launchRunners(List<Runnable> runners) {
for (Runnable runner : runners) {
executorService.execute(runner);
throw new AgentLaunchFailedException("Agent 启动失败", e);
}
}
}

View File

@@ -1,36 +0,0 @@
package work.slhaf.partner.framework.agent.config;
import lombok.Data;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import work.slhaf.partner.framework.agent.factory.config.pojo.ModelConfig;
import java.util.HashMap;
@Slf4j
@Data
public abstract class AgentConfigLoader {
private static final String DEFAULT_KEY = "default";
@Setter
public static AgentConfigLoader INSTANCE;
protected HashMap<String, ModelConfig> modelConfigMap;
public void load() {
modelConfigMap = loadModelConfig();
}
protected abstract HashMap<String, ModelConfig> loadModelConfig();
// Keep explicit getters for Kotlin compilation phase (without Lombok-generated methods).
public HashMap<String, ModelConfig> getModelConfigMap() {
return modelConfigMap;
}
public ModelConfig loadModelConfig(String modelKey) {
if (!modelConfigMap.containsKey(modelKey)) {
return modelConfigMap.get(DEFAULT_KEY);
}
return modelConfigMap.get(modelKey);
}
}

View File

@@ -1,45 +0,0 @@
package work.slhaf.partner.framework.agent.config;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import work.slhaf.partner.framework.agent.factory.config.exception.ConfigDirNotExistException;
import work.slhaf.partner.framework.agent.factory.config.exception.ConfigNotExistException;
import work.slhaf.partner.framework.agent.factory.config.pojo.ModelConfig;
import work.slhaf.partner.framework.agent.factory.config.pojo.PrimaryModelConfig;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* 默认配置工厂
* 将从当前运行目录的config文件夹下创建并读取配置
*/
@Slf4j
public class FileAgentConfigLoader extends AgentConfigLoader {
protected static final String CONFIG_DIR = "./config/";
protected static final String MODEL_CONFIG_DIR = "./config/model/";
@Override
protected HashMap<String, ModelConfig> loadModelConfig() {
File file = new File(MODEL_CONFIG_DIR);
if (!file.exists() || !file.isDirectory()) {
throw new ConfigDirNotExistException("未找到配置目录: " + MODEL_CONFIG_DIR + " 请手动创建!");
}
File[] files = file.listFiles();
if (files == null || files.length == 0) {
throw new ConfigNotExistException("在目录" + MODEL_CONFIG_DIR + "中未找到配置文件!");
}
//遍历文件获取所有配置文件并返回
HashMap<String, ModelConfig> configMap = new HashMap<>();
for (File f : files) {
if (f.isDirectory()) {
continue;
}
PrimaryModelConfig primaryModelConfig = JSONUtil.readJSONObject(f, StandardCharsets.UTF_8).toBean(PrimaryModelConfig.class);
configMap.put(primaryModelConfig.getKey(), primaryModelConfig.getModelConfig());
}
return configMap;
}
}

View File

@@ -8,11 +8,9 @@ import work.slhaf.partner.framework.agent.factory.component.ComponentAnnotationV
import work.slhaf.partner.framework.agent.factory.component.ComponentInitHookExecutorFactory
import work.slhaf.partner.framework.agent.factory.component.ComponentInjectorFactory
import work.slhaf.partner.framework.agent.factory.component.ComponentRegisterFactory
import work.slhaf.partner.framework.agent.factory.config.ConfigLoaderFactory
import work.slhaf.partner.framework.agent.factory.context.AgentRegisterContext
import work.slhaf.partner.framework.agent.factory.context.ShutdownHookCollectorFactory
import work.slhaf.partner.framework.agent.factory.exception.ExternalModuleLoadFailedException
import work.slhaf.partner.framework.agent.factory.exception.ExternalModulePathNotExistException
import java.io.File
import java.net.URL
@@ -20,15 +18,14 @@ import java.net.URL
* Agent 注册总入口,按固定顺序串联各 Factory。
*
* 启动流程:
* 1. 加载配置
* 2. 校验 Component 注解
* 3. 注册 Component/Module
* 4. 完成 Module
* 5. 校验 Capability 注解
* 6. 注 Capability 代理与路由
* 7. 注入 Capability
* 8. 执行 Init Hook
* 9. 收集 Shutdown Hook
* 1. 校验 Component 注解
* 2. 注册 Component/Module
* 3. 完成 Module 注入
* 4. 校验 Capability
* 5. 注册 Capability 代理与路由
* 6. 注 Capability
* 7. 执行 Init Hook
* 8. 收集 Shutdown Hook
*/
object AgentRegisterFactory {
private val urls: MutableList<URL> = mutableListOf()
@@ -37,8 +34,6 @@ object AgentRegisterFactory {
fun launch(packageName: String) {
urls.addAll(packageNameToURL(packageName))
val registerContext = AgentRegisterContext(urls)
// 0. 加载配置
ConfigLoaderFactory().execute(registerContext)
// 1. 校验 Component 级别注解是否合规,避免注入到异常位置
ComponentAnnotationValidatorFactory().execute(registerContext)
// 2. 收集所有的 AgentComponent 实例
@@ -66,13 +61,12 @@ object AgentRegisterFactory {
fun addScanDir(externalPackagePath: String) {
val file = File(externalPackagePath)
if (!file.exists() || !file.isDirectory) {
throw ExternalModulePathNotExistException("不存在的外部模块目录: $externalPackagePath")
return
}
try {
val files = file.listFiles()
?: throw ExternalModulePathNotExistException("外部模块目录为空: $externalPackagePath")
val files = file.listFiles() ?: return
if (files.isEmpty()) {
throw ExternalModulePathNotExistException("外部模块目录为空: $externalPackagePath")
return
}
files.asSequence()
.filter { it.name.endsWith(".jar") }

View File

@@ -7,7 +7,6 @@ import work.slhaf.partner.framework.agent.factory.AgentBaseFactory
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule
import work.slhaf.partner.framework.agent.factory.component.annotation.AgentComponent
import work.slhaf.partner.framework.agent.factory.component.exception.ModuleFactoryInitFailedException
import work.slhaf.partner.framework.agent.factory.config.pojo.ModelConfig
import work.slhaf.partner.framework.agent.factory.context.AgentContext
import work.slhaf.partner.framework.agent.factory.context.AgentRegisterContext
import work.slhaf.partner.framework.agent.factory.context.ModuleContextData
@@ -30,12 +29,8 @@ class ComponentRegisterFactory : AgentBaseFactory() {
override fun execute(context: AgentRegisterContext) {
val reflections = context.reflections
val configFactoryContext = context.configFactoryContext
val agentContext = context.agentContext
val modelConfigMap = configFactoryContext.modelConfigMap
val defaultConfig = modelConfigMap["default"]!!
reflections.getTypesAnnotatedWith(AgentComponent::class.java)
.asSequence()
.filter { isConcreteClass(it) }
@@ -52,9 +47,7 @@ class ComponentRegisterFactory : AgentBaseFactory() {
registerModule(
agentContext,
componentClass,
componentInstance,
modelConfigMap,
defaultConfig
componentInstance
)
} else {
addAdditionalComponent(agentContext, componentClass, componentInstance)
@@ -66,9 +59,7 @@ class ComponentRegisterFactory : AgentBaseFactory() {
private fun registerModule(
agentContext: AgentContext,
componentClass: Class<*>,
module: AbstractAgentModule,
modelConfigMap: Map<String, ModelConfig>,
defaultConfig: ModelConfig
module: AbstractAgentModule
) {
if (agentContext.modules.containsKey(module.moduleName)) {
throw ModuleFactoryInitFailedException(
@@ -78,11 +69,8 @@ class ComponentRegisterFactory : AgentBaseFactory() {
val launchTime = ZonedDateTime.now()
val modelInfo = if (module is ActivateModel) {
val modelKey = module.modelKey()
val modelConfig = modelConfigMap[modelKey] ?: defaultConfig
ModuleContextData.ModelInfo(
modelConfig.baseUrl,
modelConfig.model,
module.modelKey(),
JSONArray.parseArray(JSONObject.toJSONString(module.modulePrompt()))
)
} else {

View File

@@ -1,67 +0,0 @@
package work.slhaf.partner.framework.agent.factory.config
import org.slf4j.LoggerFactory
import work.slhaf.partner.framework.agent.config.AgentConfigLoader
import work.slhaf.partner.framework.agent.config.FileAgentConfigLoader
import work.slhaf.partner.framework.agent.factory.AgentBaseFactory
import work.slhaf.partner.framework.agent.factory.config.exception.ConfigNotExistException
import work.slhaf.partner.framework.agent.factory.context.AgentRegisterContext
import java.lang.reflect.Modifier
/**
* Agent 启动阶段的配置加载工厂。
*
* 行为:
* - 使用全局 `AgentConfigLoader.INSTANCE`,为空时退回 [FileAgentConfigLoader]。
* - 加载并写入 `modelConfigMap` 到 `ConfigFactoryContext`。
* - 校验 `default` 配置是否存在。
* - 反射读取配置加载器实现类(相对基类新增)的静态字段,并写入 `AgentContext.metadata`。
*/
class ConfigLoaderFactory : AgentBaseFactory() {
companion object {
private val log = LoggerFactory.getLogger(ConfigLoaderFactory::class.java)
}
override fun execute(context: AgentRegisterContext) {
val agentConfigLoader = AgentConfigLoader.INSTANCE ?: FileAgentConfigLoader().also {
AgentConfigLoader.INSTANCE = it
}
agentConfigLoader.load()
val configFactoryContext = context.configFactoryContext
configFactoryContext.modelConfigMap.putAll(agentConfigLoader.modelConfigMap)
check(configFactoryContext.modelConfigMap.keys)
collectLoaderMetadata(context, agentConfigLoader)
}
private fun check(configKeys: Set<String>) {
log.info("执行config检测...")
if (!configKeys.contains("default")) {
throw ConfigNotExistException("缺少默认配置! 需确保存在一个模型配置的key为`default`")
}
log.info("检测完毕.")
}
private fun collectLoaderMetadata(context: AgentRegisterContext, loader: AgentConfigLoader) {
val fieldNamesInBaseType = AgentConfigLoader::class.java.declaredFields
.asSequence()
.filterNot { it.isSynthetic }
.map { it.name }
.toSet()
val implementationType = loader::class.java
implementationType.declaredFields
.asSequence()
.filterNot { it.isSynthetic }
.filterNot { fieldNamesInBaseType.contains(it.name) }
.filterNot { !Modifier.isStatic(it.modifiers) }
.forEach { field ->
field.isAccessible = true
val value = field.get(loader)
context.agentContext.addMetadata(field.name, value)
}
}
}

View File

@@ -1,10 +0,0 @@
package work.slhaf.partner.framework.agent.factory.config.pojo
import lombok.Data
@Data
data class ModelConfig(
val baseUrl: String,
val apikey: String,
val model: String
)

View File

@@ -1,9 +0,0 @@
package work.slhaf.partner.framework.agent.factory.config.pojo;
import lombok.Data;
@Data
public class PrimaryModelConfig {
private String key;
private ModelConfig modelConfig;
}

View File

@@ -217,8 +217,7 @@ sealed class ModuleContextData<out T : AbstractAgentModule> {
) : ModuleContextData<T>()
data class ModelInfo(
val baseUrl: String,
val model: String,
val modelKey: String,
val basePrompt: JSONArray
)
}

View File

@@ -3,7 +3,6 @@ package work.slhaf.partner.framework.agent.factory.context
import org.reflections.Reflections
import org.reflections.scanners.Scanners
import org.reflections.util.ConfigurationBuilder
import work.slhaf.partner.framework.agent.factory.config.pojo.ModelConfig
import java.lang.reflect.Method
import java.net.URL
@@ -17,16 +16,11 @@ class AgentRegisterContext(urls: List<URL>) {
).setUrls(urls)
)
val configFactoryContext: ConfigFactoryContext = ConfigFactoryContext()
val capabilityFactoryContext: CapabilityFactoryContext = CapabilityFactoryContext()
val componentFactoryContext: ComponentFactoryContext = ComponentFactoryContext()
val agentContext: AgentContext = AgentContext
}
class ConfigFactoryContext {
val modelConfigMap: HashMap<String, ModelConfig> = HashMap()
}
class CapabilityFactoryContext {
val cores: MutableSet<Class<*>> = LinkedHashSet()
val capabilities: MutableSet<Class<*>> = LinkedHashSet()

View File

@@ -11,9 +11,7 @@ interface AgentGatewayRegistration {
}
fun shutdown(instance: AgentGateway<*, *>) {
if (instance is AutoCloseable) {
instance.close()
}
instance.close()
}
fun register() {

View File

@@ -18,10 +18,6 @@ object AgentGatewayRegistry : Configurable, ConfigRegistration<AgentGatewayRegis
private val registrations = linkedMapOf<String, AgentGatewayRegistration>()
private val runningChannels = linkedMapOf<String, RunningGateway>()
init {
register()
}
override fun declare(): Map<Path, ConfigRegistration<out Config>> {
return mapOf(Path.of("gateway", "gateway.json") to this)
}