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

1"""Resource resolution utilities for DynaBot configuration. 

2 

3This module provides utilities to create a ConfigBindingResolver with 

4DynaBot-specific factories registered, enabling direct resource instantiation 

5from logical names. 

6 

7Example: 

8 ```python 

9 from dataknobs_config import EnvironmentConfig 

10 from dataknobs_bots.config import create_bot_resolver 

11 

12 # Load environment 

13 env = EnvironmentConfig.load("production") 

14 

15 # Create resolver with all DynaBot factories registered 

16 resolver = create_bot_resolver(env) 

17 

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""" 

25 

26from __future__ import annotations 

27 

28import logging 

29from typing import TYPE_CHECKING, Any 

30 

31if TYPE_CHECKING: 

32 from dataknobs_config import ConfigBindingResolver, EnvironmentConfig 

33 

34logger = logging.getLogger(__name__) 

35 

36 

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. 

43 

44 This resolver can instantiate resources directly from logical names 

45 defined in environment configuration. It registers factories for: 

46 

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()) 

51 

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. 

57 

58 Returns: 

59 ConfigBindingResolver with registered factories 

60 

61 Example: 

62 ```python 

63 from dataknobs_config import EnvironmentConfig 

64 from dataknobs_bots.config import create_bot_resolver 

65 

66 # Auto-detect environment from DATAKNOBS_ENVIRONMENT 

67 env = EnvironmentConfig.load() 

68 resolver = create_bot_resolver(env) 

69 

70 # Resolve an LLM provider 

71 llm = resolver.resolve("llm_providers", "default") 

72 await llm.initialize() 

73 

74 # Resolve a database asynchronously 

75 db = await resolver.resolve_async("databases", "conversations") 

76 

77 # Resolve a vector store 

78 vs = resolver.resolve("vector_stores", "knowledge") 

79 await vs.initialize() 

80 ``` 

81 

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 

87 

88 resolver = ConfigBindingResolver(environment, resolve_env_vars=resolve_env_vars) 

89 

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") 

96 

97 return resolver 

98 

99 

100def register_llm_factory(resolver: ConfigBindingResolver) -> None: 

101 """Register LLM provider factory with the resolver. 

102 

103 Args: 

104 resolver: ConfigBindingResolver to register with 

105 

106 Example: 

107 ```python 

108 resolver = ConfigBindingResolver(env) 

109 register_llm_factory(resolver) 

110 

111 # Now can resolve LLM providers 

112 llm = resolver.resolve("llm_providers", "default") 

113 ``` 

114 """ 

115 from dataknobs_llm.llm import LLMProviderFactory 

116 

117 factory = LLMProviderFactory(is_async=True) 

118 resolver.register_factory("llm_providers", factory) 

119 logger.debug("Registered LLM provider factory") 

120 

121 

122def register_database_factory(resolver: ConfigBindingResolver) -> None: 

123 """Register async database factory with the resolver. 

124 

125 Args: 

126 resolver: ConfigBindingResolver to register with 

127 

128 Example: 

129 ```python 

130 resolver = ConfigBindingResolver(env) 

131 register_database_factory(resolver) 

132 

133 # Now can resolve databases 

134 db = await resolver.resolve_async("databases", "conversations") 

135 ``` 

136 """ 

137 from dataknobs_data.factory import AsyncDatabaseFactory 

138 

139 factory = AsyncDatabaseFactory() 

140 resolver.register_factory("databases", factory) 

141 logger.debug("Registered database factory") 

142 

143 

144def register_vector_store_factory(resolver: ConfigBindingResolver) -> None: 

145 """Register vector store factory with the resolver. 

146 

147 Args: 

148 resolver: ConfigBindingResolver to register with 

149 

150 Example: 

151 ```python 

152 resolver = ConfigBindingResolver(env) 

153 register_vector_store_factory(resolver) 

154 

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 

161 

162 factory = VectorStoreFactory() 

163 resolver.register_factory("vector_stores", factory) 

164 logger.debug("Registered vector store factory") 

165 

166 

167def register_embedding_factory(resolver: ConfigBindingResolver) -> None: 

168 """Register embedding provider factory with the resolver. 

169 

170 Embedding providers use the LLM provider factory since most LLM 

171 providers (OpenAI, Ollama, etc.) support embedding via their 

172 embed() method. 

173 

174 Args: 

175 resolver: ConfigBindingResolver to register with 

176 

177 Example: 

178 ```python 

179 resolver = ConfigBindingResolver(env) 

180 register_embedding_factory(resolver) 

181 

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 ``` 

187 

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 

193 

194 factory = LLMProviderFactory(is_async=True) 

195 resolver.register_factory("embedding_providers", factory) 

196 logger.debug("Registered embedding provider factory") 

197 

198 

199class BotResourceResolver: 

200 """High-level resource resolver for DynaBot. 

201 

202 Provides convenient async methods for resolving and initializing 

203 DynaBot resources. Wraps ConfigBindingResolver with DynaBot-specific 

204 initialization logic. 

205 

206 Example: 

207 ```python 

208 from dataknobs_config import EnvironmentConfig 

209 from dataknobs_bots.config import BotResourceResolver 

210 

211 env = EnvironmentConfig.load("production") 

212 resolver = BotResourceResolver(env) 

213 

214 # Get initialized LLM provider 

215 llm = await resolver.get_llm("default") 

216 

217 # Get initialized database 

218 db = await resolver.get_database("conversations") 

219 

220 # Get initialized vector store 

221 vs = await resolver.get_vector_store("knowledge") 

222 ``` 

223 """ 

224 

225 def __init__( 

226 self, 

227 environment: EnvironmentConfig, 

228 resolve_env_vars: bool = True, 

229 ): 

230 """Initialize the resource resolver. 

231 

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 

241 

242 @property 

243 def environment(self) -> EnvironmentConfig: 

244 """Get the environment configuration.""" 

245 return self._environment 

246 

247 @property 

248 def resolver(self) -> ConfigBindingResolver: 

249 """Get the underlying ConfigBindingResolver.""" 

250 return self._resolver 

251 

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. 

259 

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 

264 

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 

273 

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. 

281 

282 Args: 

283 name: Logical name of the database 

284 use_cache: Whether to return cached instance 

285 **overrides: Config overrides for this resolution 

286 

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 

296 

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. 

304 

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 

309 

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 

319 

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. 

327 

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 

332 

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 

341 

342 def clear_cache(self, resource_type: str | None = None) -> None: 

343 """Clear cached resource instances. 

344 

345 Args: 

346 resource_type: Specific type to clear, or None for all 

347 """ 

348 self._resolver.clear_cache(resource_type) 

349 

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})"