dataknobs-bots 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. dataknobs_bots/__init__.py +42 -0
  2. dataknobs_bots/api/__init__.py +42 -0
  3. dataknobs_bots/api/dependencies.py +140 -0
  4. dataknobs_bots/api/exceptions.py +289 -0
  5. dataknobs_bots/bot/__init__.py +15 -0
  6. dataknobs_bots/bot/base.py +1091 -0
  7. dataknobs_bots/bot/context.py +102 -0
  8. dataknobs_bots/bot/manager.py +430 -0
  9. dataknobs_bots/bot/registry.py +629 -0
  10. dataknobs_bots/config/__init__.py +39 -0
  11. dataknobs_bots/config/resolution.py +353 -0
  12. dataknobs_bots/knowledge/__init__.py +82 -0
  13. dataknobs_bots/knowledge/query/__init__.py +25 -0
  14. dataknobs_bots/knowledge/query/expander.py +262 -0
  15. dataknobs_bots/knowledge/query/transformer.py +288 -0
  16. dataknobs_bots/knowledge/rag.py +738 -0
  17. dataknobs_bots/knowledge/retrieval/__init__.py +23 -0
  18. dataknobs_bots/knowledge/retrieval/formatter.py +249 -0
  19. dataknobs_bots/knowledge/retrieval/merger.py +279 -0
  20. dataknobs_bots/memory/__init__.py +56 -0
  21. dataknobs_bots/memory/base.py +38 -0
  22. dataknobs_bots/memory/buffer.py +58 -0
  23. dataknobs_bots/memory/vector.py +188 -0
  24. dataknobs_bots/middleware/__init__.py +11 -0
  25. dataknobs_bots/middleware/base.py +92 -0
  26. dataknobs_bots/middleware/cost.py +421 -0
  27. dataknobs_bots/middleware/logging.py +184 -0
  28. dataknobs_bots/reasoning/__init__.py +65 -0
  29. dataknobs_bots/reasoning/base.py +50 -0
  30. dataknobs_bots/reasoning/react.py +299 -0
  31. dataknobs_bots/reasoning/simple.py +51 -0
  32. dataknobs_bots/registry/__init__.py +41 -0
  33. dataknobs_bots/registry/backend.py +181 -0
  34. dataknobs_bots/registry/memory.py +244 -0
  35. dataknobs_bots/registry/models.py +102 -0
  36. dataknobs_bots/registry/portability.py +210 -0
  37. dataknobs_bots/tools/__init__.py +5 -0
  38. dataknobs_bots/tools/knowledge_search.py +113 -0
  39. dataknobs_bots/utils/__init__.py +1 -0
  40. dataknobs_bots-0.2.4.dist-info/METADATA +591 -0
  41. dataknobs_bots-0.2.4.dist-info/RECORD +42 -0
  42. dataknobs_bots-0.2.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,42 @@
1
+ """DataKnobs Bots - Configuration-driven AI agents."""
2
+
3
+ from .bot import BotContext, BotManager, BotRegistry, DynaBot
4
+ from .knowledge import RAGKnowledgeBase, create_knowledge_base_from_config
5
+ from .memory import BufferMemory, Memory, VectorMemory, create_memory_from_config
6
+ from .middleware import CostTrackingMiddleware, LoggingMiddleware, Middleware
7
+ from .reasoning import (
8
+ ReActReasoning,
9
+ ReasoningStrategy,
10
+ SimpleReasoning,
11
+ create_reasoning_from_config,
12
+ )
13
+ from .tools import KnowledgeSearchTool
14
+
15
+ __version__ = "0.2.4"
16
+
17
+ __all__ = [
18
+ # Bot
19
+ "DynaBot",
20
+ "BotContext",
21
+ "BotManager",
22
+ "BotRegistry",
23
+ # Memory
24
+ "Memory",
25
+ "BufferMemory",
26
+ "VectorMemory",
27
+ "create_memory_from_config",
28
+ # Knowledge
29
+ "RAGKnowledgeBase",
30
+ "create_knowledge_base_from_config",
31
+ # Tools
32
+ "KnowledgeSearchTool",
33
+ # Reasoning
34
+ "ReasoningStrategy",
35
+ "SimpleReasoning",
36
+ "ReActReasoning",
37
+ "create_reasoning_from_config",
38
+ # Middleware
39
+ "Middleware",
40
+ "CostTrackingMiddleware",
41
+ "LoggingMiddleware",
42
+ ]
@@ -0,0 +1,42 @@
1
+ """FastAPI integration components for dataknobs_bots."""
2
+
3
+ from .dependencies import (
4
+ BotManagerDep,
5
+ get_bot_manager,
6
+ init_bot_manager,
7
+ reset_bot_manager,
8
+ )
9
+ from .exceptions import (
10
+ APIError,
11
+ BotCreationError,
12
+ BotNotFoundError,
13
+ ConfigurationError,
14
+ ConversationNotFoundError,
15
+ RateLimitError,
16
+ ValidationError,
17
+ api_error_handler,
18
+ general_exception_handler,
19
+ http_exception_handler,
20
+ register_exception_handlers,
21
+ )
22
+
23
+ __all__ = [
24
+ # Dependencies
25
+ "get_bot_manager",
26
+ "init_bot_manager",
27
+ "reset_bot_manager",
28
+ "BotManagerDep",
29
+ # Exceptions
30
+ "APIError",
31
+ "BotNotFoundError",
32
+ "BotCreationError",
33
+ "ConversationNotFoundError",
34
+ "ValidationError",
35
+ "ConfigurationError",
36
+ "RateLimitError",
37
+ # Handlers
38
+ "api_error_handler",
39
+ "http_exception_handler",
40
+ "general_exception_handler",
41
+ "register_exception_handlers",
42
+ ]
@@ -0,0 +1,140 @@
1
+ """Dependency injection helpers for FastAPI.
2
+
3
+ This module provides singleton management and FastAPI dependency injection
4
+ for bot-related services.
5
+
6
+ Example:
7
+ ```python
8
+ from fastapi import FastAPI
9
+ from dataknobs_bots.api.dependencies import (
10
+ init_bot_manager,
11
+ BotManagerDep,
12
+ )
13
+
14
+ app = FastAPI()
15
+
16
+ @app.on_event("startup")
17
+ async def startup():
18
+ # Initialize with a config loader
19
+ init_bot_manager(config_loader=my_loader)
20
+
21
+ @app.post("/chat/{bot_id}")
22
+ async def chat(
23
+ bot_id: str,
24
+ message: str,
25
+ manager: BotManagerDep,
26
+ ):
27
+ bot = await manager.get_or_create(bot_id)
28
+ return await bot.chat(message, context)
29
+ ```
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import logging
35
+ from typing import Annotated, Any
36
+
37
+ from dataknobs_bots.bot.manager import BotManager, ConfigLoaderType
38
+
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class _BotManagerSingleton:
44
+ """Singleton container for BotManager instance.
45
+
46
+ Using a class-based approach avoids global statement warnings
47
+ while maintaining singleton semantics.
48
+ """
49
+
50
+ _instance: BotManager | None = None
51
+
52
+ @classmethod
53
+ def get(cls) -> BotManager:
54
+ """Get the singleton instance, creating with defaults if needed."""
55
+ if cls._instance is None:
56
+ cls._instance = BotManager()
57
+ logger.info("Created default BotManager singleton (no config loader)")
58
+ return cls._instance
59
+
60
+ @classmethod
61
+ def init(
62
+ cls,
63
+ config_loader: ConfigLoaderType | None = None,
64
+ **kwargs: Any,
65
+ ) -> BotManager:
66
+ """Initialize the singleton with configuration."""
67
+ cls._instance = BotManager(config_loader=config_loader, **kwargs)
68
+ logger.info("Initialized BotManager singleton")
69
+ return cls._instance
70
+
71
+ @classmethod
72
+ def reset(cls) -> None:
73
+ """Reset the singleton instance."""
74
+ cls._instance = None
75
+ logger.info("Reset BotManager singleton")
76
+
77
+
78
+ def get_bot_manager() -> BotManager:
79
+ """Get or create BotManager singleton instance.
80
+
81
+ Returns:
82
+ BotManager instance
83
+
84
+ Note:
85
+ Call `init_bot_manager()` during app startup to configure
86
+ the singleton before using this dependency.
87
+ """
88
+ return _BotManagerSingleton.get()
89
+
90
+
91
+ def init_bot_manager(
92
+ config_loader: ConfigLoaderType | None = None,
93
+ **kwargs: Any,
94
+ ) -> BotManager:
95
+ """Initialize the BotManager singleton with configuration.
96
+
97
+ Call this during application startup to configure the singleton.
98
+
99
+ Args:
100
+ config_loader: Optional configuration loader for bots
101
+ **kwargs: Additional arguments passed to BotManager
102
+
103
+ Returns:
104
+ Configured BotManager instance
105
+
106
+ Example:
107
+ ```python
108
+ @app.on_event("startup")
109
+ async def startup():
110
+ init_bot_manager(
111
+ config_loader=MyConfigLoader("./configs")
112
+ )
113
+ ```
114
+ """
115
+ return _BotManagerSingleton.init(config_loader=config_loader, **kwargs)
116
+
117
+
118
+ def reset_bot_manager() -> None:
119
+ """Reset the BotManager singleton.
120
+
121
+ Useful for testing or when reconfiguring the application.
122
+ """
123
+ _BotManagerSingleton.reset()
124
+
125
+
126
+ # Dependency function for FastAPI
127
+ def _get_bot_manager_dep() -> BotManager:
128
+ """Dependency function for FastAPI."""
129
+ return get_bot_manager()
130
+
131
+
132
+ # Type alias for FastAPI dependency injection
133
+ # Usage: async def endpoint(manager: BotManagerDep):
134
+ try:
135
+ from fastapi import Depends
136
+
137
+ BotManagerDep = Annotated[BotManager, Depends(_get_bot_manager_dep)]
138
+ except ImportError:
139
+ # FastAPI not installed - provide a placeholder
140
+ BotManagerDep = BotManager # type: ignore
@@ -0,0 +1,289 @@
1
+ """Custom exceptions and exception handlers for FastAPI applications.
2
+
3
+ This module provides a consistent exception hierarchy and handlers
4
+ for bot-related API errors. The exceptions extend from dataknobs_common
5
+ for consistency across the codebase.
6
+
7
+ Example:
8
+ ```python
9
+ from fastapi import FastAPI
10
+ from dataknobs_bots.api.exceptions import (
11
+ register_exception_handlers,
12
+ BotNotFoundError,
13
+ )
14
+
15
+ app = FastAPI()
16
+ register_exception_handlers(app)
17
+
18
+ @app.get("/bots/{bot_id}")
19
+ async def get_bot(bot_id: str):
20
+ bot = await manager.get(bot_id)
21
+ if not bot:
22
+ raise BotNotFoundError(bot_id)
23
+ return {"bot_id": bot_id}
24
+ ```
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from datetime import datetime, timezone
30
+ from typing import TYPE_CHECKING, Any
31
+
32
+ from dataknobs_common.exceptions import (
33
+ ConfigurationError as CommonConfigurationError,
34
+ )
35
+ from dataknobs_common.exceptions import (
36
+ DataknobsError,
37
+ )
38
+ from dataknobs_common.exceptions import (
39
+ NotFoundError as CommonNotFoundError,
40
+ )
41
+ from dataknobs_common.exceptions import (
42
+ ValidationError as CommonValidationError,
43
+ )
44
+
45
+ if TYPE_CHECKING:
46
+ from fastapi import FastAPI, HTTPException, Request
47
+ from fastapi.responses import JSONResponse
48
+
49
+
50
+ class APIError(DataknobsError):
51
+ """Base exception for API errors.
52
+
53
+ Extends DataknobsError to provide HTTP-specific error handling
54
+ with status codes and structured error responses.
55
+
56
+ Attributes:
57
+ message: Error message
58
+ status_code: HTTP status code
59
+ detail: Error details (maps to DataknobsError.context)
60
+ error_code: Machine-readable error code
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ message: str,
66
+ status_code: int = 500,
67
+ detail: dict[str, Any] | None = None,
68
+ error_code: str | None = None,
69
+ ):
70
+ """Initialize API error.
71
+
72
+ Args:
73
+ message: Human-readable error message
74
+ status_code: HTTP status code (default: 500)
75
+ detail: Optional dictionary with error details
76
+ error_code: Optional machine-readable error code
77
+ """
78
+ # Pass detail as context to DataknobsError
79
+ super().__init__(message, context=detail)
80
+ self.status_code = status_code
81
+ self.error_code = error_code or self.__class__.__name__
82
+
83
+ @property
84
+ def detail(self) -> dict[str, Any]:
85
+ """Alias for context to maintain API compatibility."""
86
+ return self.context
87
+
88
+ def to_dict(self) -> dict[str, Any]:
89
+ """Convert error to dictionary for JSON response.
90
+
91
+ Returns:
92
+ Dictionary representation of the error
93
+ """
94
+ return {
95
+ "error": self.error_code,
96
+ "message": str(self),
97
+ "detail": self.context,
98
+ "timestamp": datetime.now(timezone.utc).isoformat(),
99
+ }
100
+
101
+
102
+ class BotNotFoundError(APIError, CommonNotFoundError):
103
+ """Exception raised when bot instance is not found."""
104
+
105
+ def __init__(self, bot_id: str):
106
+ APIError.__init__(
107
+ self,
108
+ message=f"Bot with ID '{bot_id}' not found",
109
+ status_code=404,
110
+ detail={"bot_id": bot_id},
111
+ )
112
+
113
+
114
+ class BotCreationError(APIError):
115
+ """Exception raised when bot creation fails."""
116
+
117
+ def __init__(self, bot_id: str, reason: str):
118
+ super().__init__(
119
+ message=f"Failed to create bot '{bot_id}': {reason}",
120
+ status_code=500,
121
+ detail={"bot_id": bot_id, "reason": reason},
122
+ )
123
+
124
+
125
+ class ConversationNotFoundError(APIError, CommonNotFoundError):
126
+ """Exception raised when conversation is not found."""
127
+
128
+ def __init__(self, conversation_id: str):
129
+ APIError.__init__(
130
+ self,
131
+ message=f"Conversation with ID '{conversation_id}' not found",
132
+ status_code=404,
133
+ detail={"conversation_id": conversation_id},
134
+ )
135
+
136
+
137
+ class ValidationError(APIError, CommonValidationError):
138
+ """Exception raised when input validation fails."""
139
+
140
+ def __init__(self, message: str, detail: dict[str, Any] | None = None):
141
+ APIError.__init__(
142
+ self,
143
+ message=message,
144
+ status_code=422,
145
+ detail=detail,
146
+ )
147
+
148
+
149
+ class ConfigurationError(APIError, CommonConfigurationError):
150
+ """Exception raised when configuration is invalid."""
151
+
152
+ def __init__(self, message: str, config_key: str | None = None):
153
+ detail = {}
154
+ if config_key:
155
+ detail["config_key"] = config_key
156
+ APIError.__init__(
157
+ self,
158
+ message=message,
159
+ status_code=500,
160
+ detail=detail,
161
+ )
162
+
163
+
164
+ class RateLimitError(APIError):
165
+ """Exception raised when rate limit is exceeded."""
166
+
167
+ def __init__(
168
+ self,
169
+ message: str = "Rate limit exceeded",
170
+ retry_after: int | None = None,
171
+ ):
172
+ detail = {}
173
+ if retry_after:
174
+ detail["retry_after"] = retry_after
175
+ super().__init__(
176
+ message=message,
177
+ status_code=429,
178
+ detail=detail,
179
+ )
180
+
181
+
182
+ # Exception Handlers
183
+ # Note: These use TYPE_CHECKING imports to avoid requiring FastAPI at import time
184
+
185
+
186
+ async def api_error_handler(
187
+ request: Request, # type: ignore[name-defined]
188
+ exc: APIError,
189
+ ) -> JSONResponse: # type: ignore[name-defined]
190
+ """Handle API errors with standardized response format.
191
+
192
+ Args:
193
+ request: FastAPI request object
194
+ exc: API error exception
195
+
196
+ Returns:
197
+ JSON response with error details
198
+ """
199
+ from fastapi.responses import JSONResponse
200
+
201
+ return JSONResponse(
202
+ status_code=exc.status_code,
203
+ content=exc.to_dict(),
204
+ )
205
+
206
+
207
+ async def http_exception_handler(
208
+ request: Request, # type: ignore[name-defined]
209
+ exc: HTTPException, # type: ignore[name-defined]
210
+ ) -> JSONResponse: # type: ignore[name-defined]
211
+ """Handle FastAPI HTTP exceptions.
212
+
213
+ Args:
214
+ request: FastAPI request object
215
+ exc: HTTP exception
216
+
217
+ Returns:
218
+ JSON response with error details
219
+ """
220
+ from fastapi.responses import JSONResponse
221
+
222
+ return JSONResponse(
223
+ status_code=exc.status_code,
224
+ content={
225
+ "error": "HTTPException",
226
+ "message": str(exc.detail),
227
+ "detail": {},
228
+ "timestamp": datetime.now(timezone.utc).isoformat(),
229
+ },
230
+ )
231
+
232
+
233
+ async def general_exception_handler(
234
+ request: Request, # type: ignore[name-defined]
235
+ exc: Exception,
236
+ ) -> JSONResponse: # type: ignore[name-defined]
237
+ """Handle unexpected exceptions.
238
+
239
+ Args:
240
+ request: FastAPI request object
241
+ exc: Generic exception
242
+
243
+ Returns:
244
+ JSON response with error details
245
+
246
+ Note:
247
+ This handler logs the full exception but returns a generic
248
+ message to avoid leaking internal details.
249
+ """
250
+ import logging
251
+
252
+ from fastapi.responses import JSONResponse
253
+
254
+ logger = logging.getLogger(__name__)
255
+ logger.exception(f"Unhandled exception: {exc}")
256
+
257
+ return JSONResponse(
258
+ status_code=500,
259
+ content={
260
+ "error": "InternalServerError",
261
+ "message": "An unexpected error occurred",
262
+ "detail": {"exception_type": type(exc).__name__},
263
+ "timestamp": datetime.now(timezone.utc).isoformat(),
264
+ },
265
+ )
266
+
267
+
268
+ def register_exception_handlers(
269
+ app: FastAPI, # type: ignore[name-defined]
270
+ ) -> None:
271
+ """Register all exception handlers with a FastAPI app.
272
+
273
+ Args:
274
+ app: FastAPI application instance
275
+
276
+ Example:
277
+ ```python
278
+ from fastapi import FastAPI
279
+ from dataknobs_bots.api.exceptions import register_exception_handlers
280
+
281
+ app = FastAPI()
282
+ register_exception_handlers(app)
283
+ ```
284
+ """
285
+ from fastapi import HTTPException
286
+
287
+ app.add_exception_handler(APIError, api_error_handler) # type: ignore
288
+ app.add_exception_handler(HTTPException, http_exception_handler) # type: ignore
289
+ app.add_exception_handler(Exception, general_exception_handler)
@@ -0,0 +1,15 @@
1
+ """Bot core components."""
2
+
3
+ from .base import DynaBot
4
+ from .context import BotContext
5
+ from .manager import BotManager
6
+ from .registry import BotRegistry, InMemoryBotRegistry, create_memory_registry
7
+
8
+ __all__ = [
9
+ "DynaBot",
10
+ "BotContext",
11
+ "BotManager",
12
+ "BotRegistry",
13
+ "InMemoryBotRegistry",
14
+ "create_memory_registry",
15
+ ]