Coverage for src / dataknobs_bots / config / resolution.py: 100%
64 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-16 10:54 -0700
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-16 10:54 -0700
1"""Resource resolution utilities for DynaBot configuration.
3This module provides utilities to create a ConfigBindingResolver with
4DynaBot-specific factories registered, enabling direct resource instantiation
5from logical names.
7Example:
8 ```python
9 from dataknobs_config import EnvironmentConfig
10 from dataknobs_bots.config import create_bot_resolver
12 # Load environment
13 env = EnvironmentConfig.load("production")
15 # Create resolver with all DynaBot factories registered
16 resolver = create_bot_resolver(env)
18 # Resolve resources by logical name
19 llm = resolver.resolve("llm_providers", "default")
20 db = await resolver.resolve_async("databases", "conversations")
21 vector_store = resolver.resolve("vector_stores", "knowledge")
22 embedding = resolver.resolve("embedding_providers", "default")
23 ```
24"""
26from __future__ import annotations
28import logging
29from typing import TYPE_CHECKING, Any
31if TYPE_CHECKING:
32 from dataknobs_config import ConfigBindingResolver, EnvironmentConfig
34logger = logging.getLogger(__name__)
37def create_bot_resolver(
38 environment: EnvironmentConfig,
39 resolve_env_vars: bool = True,
40 register_defaults: bool = True,
41) -> ConfigBindingResolver:
42 """Create a ConfigBindingResolver with DynaBot-specific factories.
44 This resolver can instantiate resources directly from logical names
45 defined in environment configuration. It registers factories for:
47 - **llm_providers**: LLM providers (OpenAI, Anthropic, Ollama, etc.)
48 - **databases**: Database backends (memory, sqlite, postgres, etc.)
49 - **vector_stores**: Vector store backends (FAISS, Chroma, memory, etc.)
50 - **embedding_providers**: Embedding providers (uses LLM providers with embed())
52 Args:
53 environment: Environment configuration for resource lookup
54 resolve_env_vars: Whether to resolve environment variables in configs
55 register_defaults: If True, register all default DynaBot factories.
56 Set to False to manually register only needed factories.
58 Returns:
59 ConfigBindingResolver with registered factories
61 Example:
62 ```python
63 from dataknobs_config import EnvironmentConfig
64 from dataknobs_bots.config import create_bot_resolver
66 # Auto-detect environment from DATAKNOBS_ENVIRONMENT
67 env = EnvironmentConfig.load()
68 resolver = create_bot_resolver(env)
70 # Resolve an LLM provider
71 llm = resolver.resolve("llm_providers", "default")
72 await llm.initialize()
74 # Resolve a database asynchronously
75 db = await resolver.resolve_async("databases", "conversations")
77 # Resolve a vector store
78 vs = resolver.resolve("vector_stores", "knowledge")
79 await vs.initialize()
80 ```
82 Note:
83 The resolver caches created instances by default. Use
84 `resolver.resolve(..., use_cache=False)` to create fresh instances.
85 """
86 from dataknobs_config import ConfigBindingResolver
88 resolver = ConfigBindingResolver(environment, resolve_env_vars=resolve_env_vars)
90 if register_defaults:
91 register_llm_factory(resolver)
92 register_database_factory(resolver)
93 register_vector_store_factory(resolver)
94 register_embedding_factory(resolver)
95 logger.debug("Registered all DynaBot resource factories")
97 return resolver
100def register_llm_factory(resolver: ConfigBindingResolver) -> None:
101 """Register LLM provider factory with the resolver.
103 Args:
104 resolver: ConfigBindingResolver to register with
106 Example:
107 ```python
108 resolver = ConfigBindingResolver(env)
109 register_llm_factory(resolver)
111 # Now can resolve LLM providers
112 llm = resolver.resolve("llm_providers", "default")
113 ```
114 """
115 from dataknobs_llm.llm import LLMProviderFactory
117 factory = LLMProviderFactory(is_async=True)
118 resolver.register_factory("llm_providers", factory)
119 logger.debug("Registered LLM provider factory")
122def register_database_factory(resolver: ConfigBindingResolver) -> None:
123 """Register async database factory with the resolver.
125 Args:
126 resolver: ConfigBindingResolver to register with
128 Example:
129 ```python
130 resolver = ConfigBindingResolver(env)
131 register_database_factory(resolver)
133 # Now can resolve databases
134 db = await resolver.resolve_async("databases", "conversations")
135 ```
136 """
137 from dataknobs_data.factory import AsyncDatabaseFactory
139 factory = AsyncDatabaseFactory()
140 resolver.register_factory("databases", factory)
141 logger.debug("Registered database factory")
144def register_vector_store_factory(resolver: ConfigBindingResolver) -> None:
145 """Register vector store factory with the resolver.
147 Args:
148 resolver: ConfigBindingResolver to register with
150 Example:
151 ```python
152 resolver = ConfigBindingResolver(env)
153 register_vector_store_factory(resolver)
155 # Now can resolve vector stores
156 vs = resolver.resolve("vector_stores", "knowledge")
157 await vs.initialize()
158 ```
159 """
160 from dataknobs_data.vector.stores import VectorStoreFactory
162 factory = VectorStoreFactory()
163 resolver.register_factory("vector_stores", factory)
164 logger.debug("Registered vector store factory")
167def register_embedding_factory(resolver: ConfigBindingResolver) -> None:
168 """Register embedding provider factory with the resolver.
170 Embedding providers use the LLM provider factory since most LLM
171 providers (OpenAI, Ollama, etc.) support embedding via their
172 embed() method.
174 Args:
175 resolver: ConfigBindingResolver to register with
177 Example:
178 ```python
179 resolver = ConfigBindingResolver(env)
180 register_embedding_factory(resolver)
182 # Now can resolve embedding providers
183 embedder = resolver.resolve("embedding_providers", "default")
184 await embedder.initialize()
185 embedding = await embedder.embed("Hello world")
186 ```
188 Note:
189 The resolved provider should have an `embed()` method. Standard
190 LLM providers like OpenAI, Anthropic, and Ollama support this.
191 """
192 from dataknobs_llm.llm import LLMProviderFactory
194 factory = LLMProviderFactory(is_async=True)
195 resolver.register_factory("embedding_providers", factory)
196 logger.debug("Registered embedding provider factory")
199class BotResourceResolver:
200 """High-level resource resolver for DynaBot.
202 Provides convenient async methods for resolving and initializing
203 DynaBot resources. Wraps ConfigBindingResolver with DynaBot-specific
204 initialization logic.
206 Example:
207 ```python
208 from dataknobs_config import EnvironmentConfig
209 from dataknobs_bots.config import BotResourceResolver
211 env = EnvironmentConfig.load("production")
212 resolver = BotResourceResolver(env)
214 # Get initialized LLM provider
215 llm = await resolver.get_llm("default")
217 # Get initialized database
218 db = await resolver.get_database("conversations")
220 # Get initialized vector store
221 vs = await resolver.get_vector_store("knowledge")
222 ```
223 """
225 def __init__(
226 self,
227 environment: EnvironmentConfig,
228 resolve_env_vars: bool = True,
229 ):
230 """Initialize the resource resolver.
232 Args:
233 environment: Environment configuration
234 resolve_env_vars: Whether to resolve env vars in configs
235 """
236 self._resolver = create_bot_resolver(
237 environment,
238 resolve_env_vars=resolve_env_vars,
239 )
240 self._environment = environment
242 @property
243 def environment(self) -> EnvironmentConfig:
244 """Get the environment configuration."""
245 return self._environment
247 @property
248 def resolver(self) -> ConfigBindingResolver:
249 """Get the underlying ConfigBindingResolver."""
250 return self._resolver
252 async def get_llm(
253 self,
254 name: str = "default",
255 use_cache: bool = True,
256 **overrides: Any,
257 ) -> Any:
258 """Get an initialized LLM provider.
260 Args:
261 name: Logical name of the LLM provider
262 use_cache: Whether to return cached instance
263 **overrides: Config overrides for this resolution
265 Returns:
266 Initialized AsyncLLMProvider instance
267 """
268 llm = self._resolver.resolve(
269 "llm_providers", name, use_cache=use_cache, **overrides
270 )
271 await llm.initialize()
272 return llm
274 async def get_database(
275 self,
276 name: str = "default",
277 use_cache: bool = True,
278 **overrides: Any,
279 ) -> Any:
280 """Get an initialized database backend.
282 Args:
283 name: Logical name of the database
284 use_cache: Whether to return cached instance
285 **overrides: Config overrides for this resolution
287 Returns:
288 Initialized database backend instance
289 """
290 db = self._resolver.resolve(
291 "databases", name, use_cache=use_cache, **overrides
292 )
293 if hasattr(db, "connect"):
294 await db.connect()
295 return db
297 async def get_vector_store(
298 self,
299 name: str = "default",
300 use_cache: bool = True,
301 **overrides: Any,
302 ) -> Any:
303 """Get an initialized vector store.
305 Args:
306 name: Logical name of the vector store
307 use_cache: Whether to return cached instance
308 **overrides: Config overrides for this resolution
310 Returns:
311 Initialized VectorStore instance
312 """
313 vs = self._resolver.resolve(
314 "vector_stores", name, use_cache=use_cache, **overrides
315 )
316 if hasattr(vs, "initialize"):
317 await vs.initialize()
318 return vs
320 async def get_embedding_provider(
321 self,
322 name: str = "default",
323 use_cache: bool = True,
324 **overrides: Any,
325 ) -> Any:
326 """Get an initialized embedding provider.
328 Args:
329 name: Logical name of the embedding provider
330 use_cache: Whether to return cached instance
331 **overrides: Config overrides for this resolution
333 Returns:
334 Initialized provider with embed() method
335 """
336 provider = self._resolver.resolve(
337 "embedding_providers", name, use_cache=use_cache, **overrides
338 )
339 await provider.initialize()
340 return provider
342 def clear_cache(self, resource_type: str | None = None) -> None:
343 """Clear cached resource instances.
345 Args:
346 resource_type: Specific type to clear, or None for all
347 """
348 self._resolver.clear_cache(resource_type)
350 def __repr__(self) -> str:
351 """String representation."""
352 types = self._resolver.get_registered_types()
353 return f"BotResourceResolver(environment={self._environment.name!r}, types={types})"