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
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""AgentsProvider — registers agent infrastructure with full integration."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, Any, cast
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)
19if TYPE_CHECKING:
20 from lexigram.contracts.core.di import (
21 ContainerRegistrarProtocol,
22 ContainerResolverProtocol,
23 )
25logger = get_logger(__name__)
28class AgentsProvider(Provider):
29 """Registers agent infrastructure with full Lexigram integration.
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
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 """
48 name = "ai-agents"
49 priority = ProviderPriority.DOMAIN
50 config_key: str | None = "ai_agents"
51 config_model: type | None = AgentConfig
53 def __init__(
54 self,
55 config: AgentConfig | None = None,
56 enable_multi_agent: bool = False,
57 ) -> None:
58 """Initialise the provider.
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
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)
74 async def register(self, container: ContainerRegistrarProtocol) -> None:
75 """Register agent infrastructure."""
76 from lexigram.contracts.ai import (
77 AgentExecutorProtocol,
78 ToolRegistryProtocol,
79 )
81 self._config = self._requested_config or self._config or AgentConfig()
82 container.singleton(AgentConfig, self._config)
84 if not self._config.enabled:
85 logger.info("agents_disabled", reason="AgentConfig.enabled=False")
86 return
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
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 )
107 strategy_registry = AgentStrategyRegistry.with_defaults()
108 container.singleton(AgentStrategyRegistry, strategy_registry)
110 container.singleton(KeywordToolCallPredictor, KeywordToolCallPredictor)
111 container.singleton(SpeculativeToolPreFetcher, SpeculativeToolPreFetcher)
112 container.singleton(DraftVerifyExecutor, DraftVerifyExecutor)
114 await self._discover_strategies(container)
116 logger.info("agents_provider_registered")
118 async def boot(self, container: ContainerResolverProtocol) -> None:
119 """Boot agent infrastructure — resolve all integrations."""
120 if not self._config.enabled:
121 return
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
133 # Required: LLM client
134 resolver = cast("Any", container)
136 llm = await resolver.resolve(LLMClientProtocol)
138 # Optional: governance
139 governance = None
140 try:
141 from lexigram.contracts.ai.governance import AIGovernanceProtocol
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")
148 # Optional: guard pipeline (from lexigram-ai-guard)
149 guard_pipeline = None
150 try:
151 from lexigram.contracts.ai.guards import GuardPipelineProtocol
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")
158 # Optional: memory
159 memory = None
160 try:
161 from lexigram.contracts.ai.memory import WorkingMemoryProtocol
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")
175 # Optional: metrics
176 metrics_recorder = None
177 try:
178 from lexigram.contracts.observability.metrics import MetricsRecorderProtocol
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")
185 agent_metrics = AgentMetrics(recorder=metrics_recorder)
186 if hasattr(resolver, "bind"):
187 resolver.bind(AgentMetrics, agent_metrics)
189 # Optional: tracing
190 tracer = None
191 try:
192 from lexigram.contracts.observability.tracing import TracerProtocol
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")
199 agent_tracer = AgentTracer(tracer=tracer)
200 if hasattr(resolver, "bind"):
201 resolver.bind(AgentTracer, agent_tracer)
203 # Optional: event bus
204 event_bus = None
205 try:
206 from lexigram.contracts.events.protocols import EventBusProtocol
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")
213 # Optional: module graph for tool visibility
214 tool_registry = await resolver.resolve(ToolRegistryImpl)
215 try:
216 from lexigram.di.module import CompiledModuleGraph
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")
226 # Optional: working memory (from lexigram-ai-memory)
227 working_memory = None
228 try:
229 from lexigram.contracts.ai.memory import WorkingMemoryProtocol
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")
242 # Optional: session manager (from lexigram-ai-session)
243 session_manager = None
244 try:
245 from lexigram.contracts.ai.session import SessionManagerProtocol
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")
258 # Optional: skill executor (from lexigram-ai-skills)
259 skill_executor = None
260 try:
261 from lexigram.contracts.ai.skills import SkillExecutorProtocol
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")
274 # Optional: skill registry (from lexigram-ai-skills)
275 skill_registry = None
276 try:
277 from lexigram.contracts.ai.skills import SkillRegistryProtocol
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")
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 )
309 if hasattr(resolver, "bind"):
310 resolver.bind(AgentExecutorImpl, executor)
311 resolver.bind(AgentExecutorProtocol, executor)
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 )
327 async def _discover_strategies(self, container: ContainerRegistrarProtocol) -> None:
328 """Auto-discover agent strategy providers via entry-points."""
329 import importlib.metadata as _meta
331 from lexigram.di.provider import Provider as _Provider
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)
348 async def shutdown(self) -> None:
349 """Shutdown agent infrastructure."""
350 logger.info("agents_provider_shutdown")
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 )