Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-agents/src/lexigram/ai/agents/di/provider.py: 35%

169 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""AgentsProvider — registers agent infrastructure with full integration.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Any, cast 

6 

7from lexigram.ai.agents.config import AgentConfig 

8from lexigram.ai.agents.speculation.draft_verify import DraftVerifyExecutor 

9from lexigram.ai.agents.speculation.predictor import KeywordToolCallPredictor 

10from lexigram.ai.agents.speculation.prefetcher import SpeculativeToolPreFetcher 

11from lexigram.contracts.core.health import HealthCheckResult, HealthStatus 

12from lexigram.contracts.core.provider import ProviderPriority 

13from lexigram.contracts.exceptions.provider import ModuleVisibilityError 

14from lexigram.di.provider import Provider 

15from lexigram.logging import ( 

16 get_logger, 

17) 

18 

19if TYPE_CHECKING: 

20 from lexigram.contracts.core.di import ( 

21 ContainerRegistrarProtocol, 

22 ContainerResolverProtocol, 

23 ) 

24 

25logger = get_logger(__name__) 

26 

27 

28class AgentsProvider(Provider): 

29 """Registers agent infrastructure with full Lexigram integration. 

30 

31 Registers: 

32 - ``ToolRegistry`` — tool storage with module visibility 

33 - ``AgentExecutorImpl`` — execution engine with governance, memory, 

34 metrics, tracing, and events 

35 - ``AgentMetrics`` — agent metrics collector 

36 - ``AgentTracer`` — agent distributed tracing 

37 

38 Auto-discovers at boot: 

39 - ``LLMClientProtocol`` (required) 

40 - ``AIGovernanceManager`` (optional — budget/rate control) 

41 - ``ConversationBuffer`` (optional — multi-turn memory) 

42 - ``MetricsRecorderProtocol`` (optional — from lexigram-monitor) 

43 - ``TracerProtocol`` (optional — from lexigram-monitor) 

44 - ``EventBusProtocol`` (optional — from lexigram-events) 

45 - ``CompiledModuleGraph`` (optional — for tool visibility) 

46 """ 

47 

48 name = "ai-agents" 

49 priority = ProviderPriority.DOMAIN 

50 config_key: str | None = "ai_agents" 

51 config_model: type | None = AgentConfig 

52 

53 def __init__( 

54 self, 

55 config: AgentConfig | None = None, 

56 enable_multi_agent: bool = False, 

57 ) -> None: 

58 """Initialise the provider. 

59 

60 Args: 

61 config: Agent configuration. Defaults to ``AgentConfig()``. 

62 enable_multi_agent: Enable multi-agent orchestration support. 

63 """ 

64 super().__init__() 

65 self._requested_config = config 

66 self._config = config or AgentConfig() 

67 self.enable_multi_agent = enable_multi_agent 

68 

69 @classmethod 

70 def from_config(cls, config: AgentConfig, **context: object) -> AgentsProvider: 

71 """Create an AgentsProvider from the resolved config.""" 

72 return cls(config=config) 

73 

74 async def register(self, container: ContainerRegistrarProtocol) -> None: 

75 """Register agent infrastructure.""" 

76 from lexigram.contracts.ai import ( 

77 AgentExecutorProtocol, 

78 ToolRegistryProtocol, 

79 ) 

80 

81 self._config = self._requested_config or self._config or AgentConfig() 

82 container.singleton(AgentConfig, self._config) 

83 

84 if not self._config.enabled: 

85 logger.info("agents_disabled", reason="AgentConfig.enabled=False") 

86 return 

87 

88 from lexigram.ai.agents.executor.executor import ( 

89 AgentExecutorImpl, 

90 ) 

91 from lexigram.ai.agents.observability import AgentMetrics, AgentTracer 

92 from lexigram.ai.agents.tools.registry import ToolRegistryImpl 

93 

94 # Register a shared registry instance for both tokens so boot-time 

95 # mutations (module graph wiring) are visible from protocol resolution. 

96 tool_registry = ToolRegistryImpl() 

97 container.singleton(ToolRegistryImpl, tool_registry) 

98 container.singleton(ToolRegistryProtocol, tool_registry) 

99 container.singleton(AgentExecutorImpl, AgentExecutorImpl) 

100 container.singleton(AgentExecutorProtocol, AgentExecutorImpl) 

101 container.singleton(AgentMetrics, AgentMetrics) 

102 container.singleton(AgentTracer, AgentTracer) 

103 from lexigram.ai.agents.strategies.strategy_registry import ( 

104 AgentStrategyRegistry, 

105 ) 

106 

107 strategy_registry = AgentStrategyRegistry.with_defaults() 

108 container.singleton(AgentStrategyRegistry, strategy_registry) 

109 

110 container.singleton(KeywordToolCallPredictor, KeywordToolCallPredictor) 

111 container.singleton(SpeculativeToolPreFetcher, SpeculativeToolPreFetcher) 

112 container.singleton(DraftVerifyExecutor, DraftVerifyExecutor) 

113 

114 await self._discover_strategies(container) 

115 

116 logger.info("agents_provider_registered") 

117 

118 async def boot(self, container: ContainerResolverProtocol) -> None: 

119 """Boot agent infrastructure — resolve all integrations.""" 

120 if not self._config.enabled: 

121 return 

122 

123 from lexigram.ai.agents.executor.executor import ( 

124 AgentExecutorImpl, 

125 AgentObservability, 

126 AgentSafetyInfra, 

127 ) 

128 from lexigram.ai.agents.observability import AgentMetrics, AgentTracer 

129 from lexigram.ai.agents.tools.registry import ToolRegistryImpl 

130 from lexigram.contracts.ai import AgentExecutorProtocol 

131 from lexigram.contracts.ai.llm import LLMClientProtocol 

132 

133 # Required: LLM client 

134 resolver = cast("Any", container) 

135 

136 llm = await resolver.resolve(LLMClientProtocol) 

137 

138 # Optional: governance 

139 governance = None 

140 try: 

141 from lexigram.contracts.ai.governance import AIGovernanceProtocol 

142 

143 governance = await resolver.resolve_optional(AIGovernanceProtocol) 

144 logger.debug("agents_governance_available") 

145 except (LookupError, RuntimeError, AttributeError, ModuleVisibilityError): 

146 logger.debug("agents_governance_not_available") 

147 

148 # Optional: guard pipeline (from lexigram-ai-guard) 

149 guard_pipeline = None 

150 try: 

151 from lexigram.contracts.ai.guards import GuardPipelineProtocol 

152 

153 guard_pipeline = await resolver.resolve_optional(GuardPipelineProtocol) 

154 logger.debug("agents_guard_pipeline_available") 

155 except (LookupError, RuntimeError, AttributeError, ModuleVisibilityError): 

156 logger.debug("agents_guard_pipeline_not_available") 

157 

158 # Optional: memory 

159 memory = None 

160 try: 

161 from lexigram.contracts.ai.memory import WorkingMemoryProtocol 

162 

163 memory = await resolver.resolve_optional(WorkingMemoryProtocol) 

164 logger.debug("agents_memory_initialized") 

165 except ( 

166 LookupError, 

167 RuntimeError, 

168 TypeError, 

169 ValueError, 

170 AttributeError, 

171 ModuleVisibilityError, 

172 ): 

173 logger.debug("agents_memory_not_available") 

174 

175 # Optional: metrics 

176 metrics_recorder = None 

177 try: 

178 from lexigram.contracts.observability.metrics import MetricsRecorderProtocol 

179 

180 metrics_recorder = await resolver.resolve_optional(MetricsRecorderProtocol) 

181 logger.debug("agents_metrics_available") 

182 except (LookupError, RuntimeError, AttributeError, ModuleVisibilityError): 

183 logger.debug("agents_metrics_not_available") 

184 

185 agent_metrics = AgentMetrics(recorder=metrics_recorder) 

186 if hasattr(resolver, "bind"): 

187 resolver.bind(AgentMetrics, agent_metrics) 

188 

189 # Optional: tracing 

190 tracer = None 

191 try: 

192 from lexigram.contracts.observability.tracing import TracerProtocol 

193 

194 tracer = await resolver.resolve_optional(TracerProtocol) 

195 logger.debug("agents_tracing_available") 

196 except (LookupError, RuntimeError, AttributeError, ModuleVisibilityError): 

197 logger.debug("agents_tracing_not_available") 

198 

199 agent_tracer = AgentTracer(tracer=tracer) 

200 if hasattr(resolver, "bind"): 

201 resolver.bind(AgentTracer, agent_tracer) 

202 

203 # Optional: event bus 

204 event_bus = None 

205 try: 

206 from lexigram.contracts.events.protocols import EventBusProtocol 

207 

208 event_bus = await resolver.resolve_optional(EventBusProtocol) 

209 logger.debug("agents_event_bus_available") 

210 except (LookupError, RuntimeError, AttributeError, ModuleVisibilityError): 

211 logger.debug("agents_event_bus_not_available") 

212 

213 # Optional: module graph for tool visibility 

214 tool_registry = await resolver.resolve(ToolRegistryImpl) 

215 try: 

216 from lexigram.di.module import CompiledModuleGraph 

217 

218 graph = await resolver.resolve_optional(CompiledModuleGraph) 

219 maybe_set = tool_registry.set_module_graph(graph) 

220 if hasattr(maybe_set, "__await__"): 

221 await maybe_set 

222 logger.debug("agents_module_graph_available") 

223 except (LookupError, RuntimeError, AttributeError, ModuleVisibilityError): 

224 logger.debug("agents_module_graph_not_available") 

225 

226 # Optional: working memory (from lexigram-ai-memory) 

227 working_memory = None 

228 try: 

229 from lexigram.contracts.ai.memory import WorkingMemoryProtocol 

230 

231 working_memory = await resolver.resolve_optional(WorkingMemoryProtocol) 

232 logger.debug("agents_working_memory_available") 

233 except ( 

234 LookupError, 

235 RuntimeError, 

236 AttributeError, 

237 ImportError, 

238 ModuleVisibilityError, 

239 ): 

240 logger.debug("agents_working_memory_not_available") 

241 

242 # Optional: session manager (from lexigram-ai-session) 

243 session_manager = None 

244 try: 

245 from lexigram.contracts.ai.session import SessionManagerProtocol 

246 

247 session_manager = await resolver.resolve_optional(SessionManagerProtocol) 

248 logger.debug("agents_session_manager_available") 

249 except ( 

250 LookupError, 

251 RuntimeError, 

252 AttributeError, 

253 ImportError, 

254 ModuleVisibilityError, 

255 ): 

256 logger.debug("agents_session_manager_not_available") 

257 

258 # Optional: skill executor (from lexigram-ai-skills) 

259 skill_executor = None 

260 try: 

261 from lexigram.contracts.ai.skills import SkillExecutorProtocol 

262 

263 skill_executor = await resolver.resolve_optional(SkillExecutorProtocol) 

264 logger.debug("agents_skill_executor_available") 

265 except ( 

266 LookupError, 

267 RuntimeError, 

268 AttributeError, 

269 ImportError, 

270 ModuleVisibilityError, 

271 ): 

272 logger.debug("agents_skill_executor_not_available") 

273 

274 # Optional: skill registry (from lexigram-ai-skills) 

275 skill_registry = None 

276 try: 

277 from lexigram.contracts.ai.skills import SkillRegistryProtocol 

278 

279 skill_registry = await resolver.resolve_optional(SkillRegistryProtocol) 

280 logger.debug("agents_skill_registry_available") 

281 except ( 

282 LookupError, 

283 RuntimeError, 

284 AttributeError, 

285 ImportError, 

286 ModuleVisibilityError, 

287 ): 

288 logger.debug("agents_skill_registry_not_available") 

289 

290 # Create executor with all integrations 

291 executor = AgentExecutorImpl( 

292 llm=llm, 

293 memory=memory, 

294 observability=AgentObservability( 

295 metrics=agent_metrics, 

296 tracer=agent_tracer, 

297 event_bus=event_bus, 

298 ), 

299 safety=AgentSafetyInfra( 

300 governance=governance, 

301 guard_pipeline=guard_pipeline, 

302 ), 

303 working_memory=working_memory, 

304 session_manager=session_manager, 

305 skill_executor=skill_executor, 

306 skill_registry=skill_registry, 

307 ) 

308 

309 if hasattr(resolver, "bind"): 

310 resolver.bind(AgentExecutorImpl, executor) 

311 resolver.bind(AgentExecutorProtocol, executor) 

312 

313 logger.info( 

314 "agents_provider_booted", 

315 governance=governance is not None, 

316 memory=memory is not None, 

317 metrics=metrics_recorder is not None, 

318 tracing=tracer is not None, 

319 events=event_bus is not None, 

320 working_memory=working_memory is not None, 

321 session_manager=session_manager is not None, 

322 skill_executor=skill_executor is not None, 

323 skill_registry=skill_registry is not None, 

324 guard_pipeline=guard_pipeline is not None, 

325 ) 

326 

327 async def _discover_strategies(self, container: ContainerRegistrarProtocol) -> None: 

328 """Auto-discover agent strategy providers via entry-points.""" 

329 import importlib.metadata as _meta 

330 

331 from lexigram.di.provider import Provider as _Provider 

332 

333 eps = _meta.entry_points(group="lexigram.agent.strategies") 

334 for ep in eps: 

335 try: 

336 candidate = ep.load() 

337 except (ImportError, AttributeError, TypeError, ValueError) as exc: 

338 logger.warning( 

339 "agent_strategy_ep_load_failed", name=ep.name, error=str(exc) 

340 ) 

341 continue 

342 if isinstance(candidate, type) and issubclass(candidate, _Provider): 

343 await candidate().register(container) 

344 logger.info("agent_strategy_ep_loaded", name=ep.name) 

345 else: 

346 logger.debug("agent_strategy_ep_skipped", name=ep.name) 

347 

348 async def shutdown(self) -> None: 

349 """Shutdown agent infrastructure.""" 

350 logger.info("agents_provider_shutdown") 

351 

352 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult: 

353 """Agent system health check.""" 

354 return HealthCheckResult( 

355 component=self.name, 

356 status=HealthStatus.HEALTHY, 

357 )