"""Standalone Runner - 自动发现并启动配置驱动的 Agent 扫描 configs/agents/ 目录下的 YAML 文件,自动注册和启动 Agent。 支持命令行启动:python -m agentkit.core.standalone """ import asyncio import logging import os import sys from pathlib import Path from agentkit.core.config_driven import AgentConfig, ConfigDrivenAgent from agentkit.tools.registry import ToolRegistry logger = logging.getLogger(__name__) DEFAULT_CONFIG_DIR = "configs/agents" class StandaloneRunner: """自动发现并启动配置驱动的 Agent 用法:: runner = StandaloneRunner(config_dir="configs/agents") runner.add_tool(FunctionTool.from_func(my_tool_func)) await runner.start_all(redis_url="redis://localhost:6379") """ def __init__( self, config_dir: str = DEFAULT_CONFIG_DIR, tool_registry: ToolRegistry | None = None, llm_client=None, custom_handlers: dict | None = None, ): self._config_dir = config_dir self._tool_registry = tool_registry or ToolRegistry() self._llm_client = llm_client self._custom_handlers = custom_handlers or {} self._agents: dict[str, ConfigDrivenAgent] = {} @property def agents(self) -> dict[str, ConfigDrivenAgent]: return self._agents def add_tool(self, tool) -> "StandaloneRunner": """添加工具到注册中心""" self._tool_registry.register(tool) return self def add_custom_handler(self, name: str, handler) -> "StandaloneRunner": """注册自定义 handler""" self._custom_handlers[name] = handler return self def discover_configs(self) -> list[AgentConfig]: """扫描配置目录,发现所有 YAML 配置""" configs = [] config_path = Path(self._config_dir) if not config_path.exists(): logger.warning(f"Config directory '{self._config_dir}' not found") return configs for yaml_file in sorted(config_path.glob("*.yaml")): try: config = AgentConfig.from_yaml(str(yaml_file)) configs.append(config) logger.info(f"Discovered agent config: {config.name} from {yaml_file.name}") except Exception as e: logger.error(f"Failed to load config '{yaml_file}': {e}") for yml_file in sorted(config_path.glob("*.yml")): try: config = AgentConfig.from_yaml(str(yml_file)) configs.append(config) logger.info(f"Discovered agent config: {config.name} from {yml_file.name}") except Exception as e: logger.error(f"Failed to load config '{yml_file}': {e}") return configs def build_agents(self) -> dict[str, ConfigDrivenAgent]: """从发现的配置构建 Agent 实例""" configs = self.discover_configs() self._agents.clear() for config in configs: try: agent = ConfigDrivenAgent( config=config, tool_registry=self._tool_registry, llm_client=self._llm_client, custom_handlers=self._custom_handlers, ) self._agents[config.name] = agent logger.info(f"Built agent: {config.name} (mode={config.task_mode})") except Exception as e: logger.error(f"Failed to build agent '{config.name}': {e}") return self._agents async def start_all(self, redis_url: str = "") -> None: """启动所有已构建的 Agent""" if not self._agents: self.build_agents() for name, agent in self._agents.items(): try: await agent.start(redis_url=redis_url) logger.info(f"Agent '{name}' started") except Exception as e: logger.error(f"Failed to start agent '{name}': {e}") async def stop_all(self) -> None: """停止所有 Agent""" for name, agent in self._agents.items(): try: await agent.stop() logger.info(f"Agent '{name}' stopped") except Exception as e: logger.error(f"Failed to stop agent '{name}': {e}") async def execute_task(self, agent_name: str, task) -> dict | None: """在指定 Agent 上执行任务(本地模式)""" if agent_name not in self._agents: logger.error(f"Agent '{agent_name}' not found") return None agent = self._agents[agent_name] result = await agent.execute(task) return result.to_dict() async def main(): """命令行入口""" logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", ) config_dir = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_CONFIG_DIR redis_url = os.environ.get("REDIS_URL", "") runner = StandaloneRunner(config_dir=config_dir) agents = runner.build_agents() if not agents: logger.error("No agents discovered. Check your config directory.") sys.exit(1) logger.info(f"Discovered {len(agents)} agent(s): {list(agents.keys())}") try: await runner.start_all(redis_url=redis_url) logger.info("All agents started. Press Ctrl+C to stop.") # 保持运行 while True: await asyncio.sleep(1) except KeyboardInterrupt: logger.info("Shutting down...") finally: await runner.stop_all() if __name__ == "__main__": asyncio.run(main())