import asyncio import logging from agentcade import settings_page from agentcade.game_client import connect_games from agentcade.main_agent import MainAgent from agentcade.microphone import MicrophoneInput from agentcade.output import OutputCoordinator from agentcade.speech_input import SpeechInput from observability.server import serve class RobotApplication: def __init__(self, app, settings, games, microphone, output): self.app, self.settings = app, settings self.input = microphone self.agent = MainAgent(settings, games) self.output = output self.listener = None async def listen(self, ready): self.listener = SpeechInput(self.settings, self.agent, self.input, self.app.logger) await self.listener.run(ready) async def run(self): required = {"expression.runtime.v3", "microphone", "audio.stream"} for game in self.agent.games.values(): required.update(game.descriptor.required_capabilities) try: missing = [name for name in required if not self.app.robot.supports(name)] if missing: raise RuntimeError("机器人未连接或缺少能力:" + ", ".join(sorted(missing))) await self.output.clear() async with asyncio.TaskGroup() as group: tasks = [ group.create_task(self.input.capture()), group.create_task(self.output.run(self.agent)), ] ready = asyncio.Event() tasks.append(group.create_task(self.listen(ready))) await ready.wait() game_names = "、".join(game.descriptor.name for game in self.agent.games.values()) greeting = ( f"你好呀,我是小懒!现在可以玩{game_names}。想玩哪个?也可以和我聊聊天。" if game_names else "你好呀,我是小懒!现在还没有可玩的游戏,我们可以聊聊天。" ) await self.agent.reply(greeting) while not self.app.shutdown_requested: # 麦克风、识别和播报出错时各自恢复;通道断开后设备指令全部失效,只能退出。 if self.app.connection_lost: raise RuntimeError("与机器人守护进程的连接已经断开,无法继续运行") if (not self.output.speaking and not self.listener.busy and not self.listener.input_speaking): await self.agent.tick() await self.output.progress.check() await asyncio.sleep(0.1) for task in tasks: task.cancel() finally: try: await self.agent.close() finally: await self.output.close() def route_logs(app): """各模块的告警和错误写入应用日志,和运行记录出现在同一个地方。""" for name in ("agentcade", "games", "observability"): target = logging.getLogger(name) target.setLevel(logging.INFO) target.propagate = False target.handlers = list(app.logger.handlers) async def run_application(app): route_logs(app) # 页面先启动:服务配置还没填好时,用户要在它的设置页面里填写。 async with serve(settings_page.port(), routes=settings_page.routes()) as url: app.logger.info("对话页面:%s", url) settings = await settings_page.ready(app, url) if settings is None: return microphone = MicrophoneInput(app.robot) output = OutputCoordinator(app, settings, microphone) async with connect_games(settings.game_catalog, output.execute) as games: await RobotApplication(app, settings, games, microphone, output).run()