1"""Resource resolution utilities for DynaBot configuration.

2

3 This module provides utilities to create a ConfigBindingResolver with

4 DynaBot-specific factories registered, enabling direct resource instantiation

5 from logical names.

6

7 Example:

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

26 from __future__ import annotations

27

28 import logging

29 from typing import TYPE_CHECKING, Any

30

31 if TYPE_CHECKING:

32 from dataknobs_config import ConfigBindingResolver, EnvironmentConfig

33

34 logger = logging.getLogger(__name__)

35

36

37 def 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

100 def 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

122 def 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

144 def 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

167 def 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

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