openai-sdk-helpers 0.0.9__py3-none-any.whl → 0.1.1__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 (46) hide show
  1. openai_sdk_helpers/__init__.py +63 -5
  2. openai_sdk_helpers/agent/base.py +5 -1
  3. openai_sdk_helpers/agent/coordination.py +4 -5
  4. openai_sdk_helpers/agent/runner.py +4 -1
  5. openai_sdk_helpers/agent/search/base.py +1 -0
  6. openai_sdk_helpers/agent/search/vector.py +2 -0
  7. openai_sdk_helpers/cli.py +265 -0
  8. openai_sdk_helpers/config.py +120 -31
  9. openai_sdk_helpers/context_manager.py +1 -1
  10. openai_sdk_helpers/deprecation.py +167 -0
  11. openai_sdk_helpers/environment.py +3 -2
  12. openai_sdk_helpers/errors.py +0 -12
  13. openai_sdk_helpers/logging_config.py +24 -95
  14. openai_sdk_helpers/prompt/base.py +56 -6
  15. openai_sdk_helpers/response/__init__.py +5 -2
  16. openai_sdk_helpers/response/base.py +84 -115
  17. openai_sdk_helpers/response/config.py +142 -0
  18. openai_sdk_helpers/response/messages.py +1 -0
  19. openai_sdk_helpers/response/tool_call.py +15 -4
  20. openai_sdk_helpers/retry.py +1 -1
  21. openai_sdk_helpers/streamlit_app/app.py +14 -3
  22. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +15 -8
  23. openai_sdk_helpers/structure/__init__.py +3 -0
  24. openai_sdk_helpers/structure/base.py +6 -6
  25. openai_sdk_helpers/structure/plan/__init__.py +15 -1
  26. openai_sdk_helpers/structure/plan/helpers.py +173 -0
  27. openai_sdk_helpers/structure/plan/plan.py +13 -9
  28. openai_sdk_helpers/structure/plan/task.py +7 -7
  29. openai_sdk_helpers/structure/plan/types.py +15 -0
  30. openai_sdk_helpers/tools.py +296 -0
  31. openai_sdk_helpers/utils/__init__.py +82 -31
  32. openai_sdk_helpers/{async_utils.py → utils/async_utils.py} +5 -6
  33. openai_sdk_helpers/utils/coercion.py +138 -0
  34. openai_sdk_helpers/utils/deprecation.py +167 -0
  35. openai_sdk_helpers/utils/json_utils.py +98 -0
  36. openai_sdk_helpers/utils/output_validation.py +448 -0
  37. openai_sdk_helpers/utils/path_utils.py +46 -0
  38. openai_sdk_helpers/{validation.py → utils/validation.py} +7 -3
  39. openai_sdk_helpers/vector_storage/storage.py +9 -6
  40. {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/METADATA +59 -3
  41. openai_sdk_helpers-0.1.1.dist-info/RECORD +76 -0
  42. openai_sdk_helpers-0.1.1.dist-info/entry_points.txt +2 -0
  43. openai_sdk_helpers/utils/core.py +0 -468
  44. openai_sdk_helpers-0.0.9.dist-info/RECORD +0 -66
  45. {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/WHEEL +0 -0
  46. {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,167 @@
1
+ """Deprecation utilities for managing deprecated features.
2
+
3
+ This module provides infrastructure for marking and managing deprecated
4
+ functions, classes, and features with consistent warning messages.
5
+
6
+ Functions
7
+ ---------
8
+ deprecated
9
+ Decorator to mark functions or classes as deprecated.
10
+ warn_deprecated
11
+ Emit a deprecation warning with optional custom message.
12
+
13
+ Classes
14
+ -------
15
+ DeprecationHelper
16
+ Utility class for managing deprecation warnings and versions.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import functools
22
+ import warnings
23
+ from typing import Any, Callable, TypeVar
24
+
25
+ F = TypeVar("F", bound=Callable[..., Any])
26
+
27
+
28
+ class DeprecationHelper:
29
+ """Utility class for managing deprecation warnings.
30
+
31
+ Provides consistent formatting and control of deprecation warnings
32
+ across the package.
33
+
34
+ Methods
35
+ -------
36
+ warn
37
+ Emit a deprecation warning with standard formatting.
38
+ """
39
+
40
+ @staticmethod
41
+ def warn(
42
+ feature_name: str,
43
+ removal_version: str,
44
+ alternative: str | None = None,
45
+ extra_message: str | None = None,
46
+ ) -> None:
47
+ """Emit a deprecation warning for a feature.
48
+
49
+ Parameters
50
+ ----------
51
+ feature_name : str
52
+ Name of the deprecated feature (e.g., "MyClass.old_method").
53
+ removal_version : str
54
+ Version in which the feature will be removed.
55
+ alternative : str, optional
56
+ Recommended alternative to use instead.
57
+ extra_message : str, optional
58
+ Additional context or migration instructions.
59
+
60
+ Raises
61
+ ------
62
+ DeprecationWarning
63
+ Always issues a DeprecationWarning to stderr.
64
+ """
65
+ msg = f"{feature_name} is deprecated and will be removed in version {removal_version}."
66
+ if alternative:
67
+ msg += f" Use {alternative} instead."
68
+ if extra_message:
69
+ msg += f" {extra_message}"
70
+
71
+ warnings.warn(msg, DeprecationWarning, stacklevel=3)
72
+
73
+
74
+ def deprecated(
75
+ removal_version: str,
76
+ alternative: str | None = None,
77
+ extra_message: str | None = None,
78
+ ) -> Callable[[F], F]:
79
+ """Mark a function or class as deprecated.
80
+
81
+ Parameters
82
+ ----------
83
+ removal_version : str
84
+ Version in which the decorated feature will be removed.
85
+ alternative : str, optional
86
+ Recommended alternative to use instead.
87
+ extra_message : str, optional
88
+ Additional context or migration instructions.
89
+
90
+ Returns
91
+ -------
92
+ Callable
93
+ Decorator function that wraps the target function or class.
94
+
95
+ Examples
96
+ --------
97
+ >>> @deprecated("1.0.0", "new_function")
98
+ ... def old_function():
99
+ ... pass
100
+
101
+ >>> class OldClass:
102
+ ... @deprecated("1.0.0", "NewClass")
103
+ ... def old_method(self):
104
+ ... pass
105
+ """
106
+
107
+ def decorator(func_or_class: F) -> F:
108
+ feature_name = f"{func_or_class.__module__}.{func_or_class.__qualname__}"
109
+
110
+ if isinstance(func_or_class, type):
111
+ # Handle class deprecation
112
+ original_init = func_or_class.__init__
113
+
114
+ @functools.wraps(original_init)
115
+ def new_init(self: Any, *args: Any, **kwargs: Any) -> None:
116
+ DeprecationHelper.warn(
117
+ feature_name,
118
+ removal_version,
119
+ alternative,
120
+ extra_message,
121
+ )
122
+ original_init(self, *args, **kwargs)
123
+
124
+ func_or_class.__init__ = new_init
125
+ else:
126
+ # Handle function deprecation
127
+ @functools.wraps(func_or_class)
128
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
129
+ DeprecationHelper.warn(
130
+ feature_name,
131
+ removal_version,
132
+ alternative,
133
+ extra_message,
134
+ )
135
+ return func_or_class(*args, **kwargs)
136
+
137
+ return wrapper # type: ignore
138
+
139
+ return func_or_class # type: ignore[return-value]
140
+
141
+ return decorator
142
+
143
+
144
+ def warn_deprecated(
145
+ feature_name: str,
146
+ removal_version: str,
147
+ alternative: str | None = None,
148
+ extra_message: str | None = None,
149
+ ) -> None:
150
+ """Issue a deprecation warning.
151
+
152
+ Parameters
153
+ ----------
154
+ feature_name : str
155
+ Name of the deprecated feature.
156
+ removal_version : str
157
+ Version in which the feature will be removed.
158
+ alternative : str, optional
159
+ Recommended alternative to use instead.
160
+ extra_message : str, optional
161
+ Additional context or migration instructions.
162
+
163
+ Examples
164
+ --------
165
+ >>> warn_deprecated("old_config_key", "1.0.0", "new_config_key")
166
+ """
167
+ DeprecationHelper.warn(feature_name, removal_version, alternative, extra_message)
@@ -20,6 +20,8 @@ from __future__ import annotations
20
20
 
21
21
  from pathlib import Path
22
22
 
23
+ from openai_sdk_helpers.utils import ensure_directory
24
+
23
25
  DATETIME_FMT = "%Y%m%d_%H%M%S"
24
26
  DEFAULT_MODEL = "gpt-4o-mini"
25
27
 
@@ -50,5 +52,4 @@ def get_data_path(name: str) -> Path:
50
52
  """
51
53
  base = Path.home() / ".openai-sdk-helpers"
52
54
  path = base / name
53
- path.mkdir(parents=True, exist_ok=True)
54
- return path
55
+ return ensure_directory(path)
@@ -4,11 +4,8 @@ Provides specific exception types for different error scenarios,
4
4
  improving error handling and debugging capabilities.
5
5
  """
6
6
 
7
- import logging
8
7
  from collections.abc import Mapping
9
8
 
10
- from openai_sdk_helpers.utils.core import log
11
-
12
9
 
13
10
  class OpenAISDKError(Exception):
14
11
  """Base exception for openai-sdk-helpers library.
@@ -40,15 +37,6 @@ class OpenAISDKError(Exception):
40
37
  """Initialize the exception with message and optional context."""
41
38
  super().__init__(message)
42
39
  self.context = dict(context) if context is not None else {}
43
- self._log_context()
44
-
45
- def _log_context(self) -> None:
46
- """Log error with context for debugging."""
47
- context_str = f"\nContext: {self.context}" if self.context else ""
48
- log(
49
- f"{self.__class__.__name__}: {str(self)}{context_str}",
50
- level=logging.ERROR,
51
- )
52
40
 
53
41
 
54
42
  class ConfigurationError(OpenAISDKError):
@@ -1,105 +1,34 @@
1
- """Centralized logging configuration for openai-sdk-helpers.
2
-
3
- Provides a centralized factory for creating and configuring loggers
4
- with consistent formatting and handler management.
5
- """
1
+ """Centralized logging configuration for openai-sdk-helpers."""
6
2
 
7
3
  import logging
8
- import threading
9
- from typing import Any
10
-
11
4
 
12
- class LoggerFactory:
13
- """Centralized logger creation and configuration.
14
5
 
15
- Manages logger initialization and configuration to ensure consistent
16
- logging behavior across the entire SDK. Thread-safe.
6
+ def log(
7
+ message: str,
8
+ level: int = logging.INFO,
9
+ *,
10
+ logger_name: str = "openai_sdk_helpers",
11
+ ) -> None:
12
+ """Log a message using Python's standard logging.
13
+
14
+ Parameters
15
+ ----------
16
+ message : str
17
+ The message to log.
18
+ level : int
19
+ Logging level (e.g., logging.DEBUG, logging.INFO).
20
+ Default is logging.INFO.
21
+ logger_name : str
22
+ Name of the logger. Default is "openai_sdk_helpers".
17
23
 
18
24
  Examples
19
25
  --------
20
- Configure logging once at application startup:
21
-
22
- >>> from openai_sdk_helpers.logging_config import LoggerFactory
23
- >>> import logging
24
- >>> LoggerFactory.configure(
25
- ... level=logging.DEBUG,
26
- ... handlers=[logging.StreamHandler()],
27
- ... )
28
-
29
- Get a logger instance in your module:
30
-
31
- >>> logger = LoggerFactory.get_logger("openai_sdk_helpers.agent")
32
- >>> logger.debug("Debug message")
26
+ >>> from openai_sdk_helpers.logging_config import log
27
+ >>> log("Operation completed")
28
+ >>> log("Debug info", level=logging.DEBUG)
33
29
  """
30
+ logger = logging.getLogger(logger_name)
31
+ logger.log(level, message)
34
32
 
35
- _initialized = False
36
- _log_level = logging.INFO
37
- _handlers: list[logging.Handler] = []
38
- _lock = threading.Lock()
39
-
40
- @classmethod
41
- def configure(
42
- cls,
43
- level: int = logging.INFO,
44
- handlers: list[logging.Handler] | None = None,
45
- ) -> None:
46
- """Configure logging globally.
47
-
48
- Parameters
49
- ----------
50
- level : int
51
- Logging level (e.g., logging.DEBUG, logging.INFO).
52
- Default is logging.INFO.
53
- handlers : list[logging.Handler] | None
54
- List of logging handlers. If None, a default
55
- StreamHandler is created. Default is None.
56
-
57
- Notes
58
- -----
59
- This method is thread-safe and can be called multiple times.
60
- """
61
- with cls._lock:
62
- cls._log_level = level
63
- if handlers:
64
- cls._handlers = handlers
65
- else:
66
- handler = logging.StreamHandler()
67
- handler.setLevel(level)
68
- formatter = logging.Formatter(
69
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
70
- )
71
- handler.setFormatter(formatter)
72
- cls._handlers = [handler]
73
- cls._initialized = True
74
-
75
- @classmethod
76
- def get_logger(cls, name: str) -> logging.Logger:
77
- """Get configured logger instance.
78
-
79
- Parameters
80
- ----------
81
- name : str
82
- Logger name, typically __name__ of calling module.
83
-
84
- Returns
85
- -------
86
- logging.Logger
87
- Configured logger instance.
88
- """
89
- logger = logging.getLogger(name)
90
-
91
- # Skip configuration if already configured
92
- if logger.handlers:
93
- return logger
94
-
95
- with cls._lock:
96
- if not cls._initialized:
97
- cls.configure()
98
-
99
- for handler in cls._handlers:
100
- logger.addHandler(handler)
101
-
102
- logger.setLevel(cls._log_level)
103
- logger.propagate = False
104
33
 
105
- return logger
34
+ __all__ = ["log"]
@@ -2,12 +2,14 @@
2
2
 
3
3
  This module provides the PromptRenderer class for loading and rendering
4
4
  Jinja2 templates with context variables. Templates can be loaded from a
5
- specified directory or by absolute path.
5
+ specified directory or by absolute path. Includes template caching for
6
+ improved performance.
6
7
  """
7
8
 
8
9
  from __future__ import annotations
9
10
 
10
11
  import warnings
12
+ from functools import lru_cache
11
13
  from pathlib import Path
12
14
  from typing import Any
13
15
 
@@ -23,7 +25,8 @@ class PromptRenderer:
23
25
 
24
26
  Loads and renders Jinja2 templates from a base directory or by absolute
25
27
  path. The renderer supports variable substitution, template inheritance,
26
- and all standard Jinja2 features for creating dynamic prompts.
28
+ and all standard Jinja2 features for creating dynamic prompts. Templates
29
+ are cached using LRU cache for improved performance on repeated renders.
27
30
 
28
31
  Templates are loaded from a base directory (defaulting to the built-in
29
32
  prompt package directory) or can be specified with absolute paths.
@@ -38,6 +41,8 @@ class PromptRenderer:
38
41
  -------
39
42
  render(template_path, context=None)
40
43
  Render a Jinja2 template with the given context variables.
44
+ clear_cache()
45
+ Clear the template compilation cache.
41
46
 
42
47
  Examples
43
48
  --------
@@ -103,12 +108,30 @@ class PromptRenderer:
103
108
  autoescape=False, # Prompts are plain text
104
109
  )
105
110
 
111
+ @lru_cache(maxsize=128)
112
+ def _compile_template(self, template_path_str: str) -> Template:
113
+ """Compile a template by path with LRU caching.
114
+
115
+ Parameters
116
+ ----------
117
+ template_path_str : str
118
+ Absolute path to the template file.
119
+
120
+ Returns
121
+ -------
122
+ Template
123
+ Compiled Jinja2 template ready for rendering.
124
+ """
125
+ template_text = Path(template_path_str).read_text()
126
+ return Template(template_text)
127
+
106
128
  def render(self, template_path: str, context: dict[str, Any] | None = None) -> str:
107
129
  """Render a Jinja2 template with the given context variables.
108
130
 
109
131
  Loads the template from either an absolute path or a path relative
110
132
  to the base directory. The template is rendered with the provided
111
- context dictionary using Jinja2's template engine.
133
+ context dictionary using Jinja2's template engine. Templates are
134
+ cached for improved performance on repeated renders.
112
135
 
113
136
  For security, relative paths are validated to prevent path traversal
114
137
  attacks. Absolute paths are allowed but should be used with caution
@@ -135,6 +158,8 @@ class PromptRenderer:
135
158
  InputValidationError
136
159
  If the path contains suspicious patterns or attempts to escape
137
160
  the base directory.
161
+ TemplateNotFound
162
+ If the template cannot be loaded by Jinja2.
138
163
 
139
164
  Examples
140
165
  --------
@@ -151,7 +176,7 @@ class PromptRenderer:
151
176
  ... context={"key": "value"}
152
177
  ... )
153
178
  """
154
- from openai_sdk_helpers.validation import validate_safe_path
179
+ from openai_sdk_helpers.utils.validation import validate_safe_path
155
180
 
156
181
  path = Path(template_path)
157
182
  if path.is_absolute():
@@ -164,9 +189,34 @@ class PromptRenderer:
164
189
  base_dir=self.base_dir,
165
190
  field_name="template_path",
166
191
  )
167
- template_path_text = template_path_.read_text()
168
- template = Template(template_path_text)
192
+
193
+ # Check if template exists and provide clear error message
194
+ if not template_path_.exists():
195
+ raise FileNotFoundError(
196
+ f"Template not found: {template_path_}. "
197
+ f"Ensure the template exists in {self.base_dir} or provide an absolute path."
198
+ )
199
+
200
+ # Cache-compile template by path (not by content)
201
+ template = self._compile_template(str(template_path_))
169
202
  return template.render(context or {})
170
203
 
204
+ def clear_cache(self) -> None:
205
+ """Clear the template compilation cache.
206
+
207
+ Useful when templates are modified during runtime and need to be
208
+ reloaded. Call this method to force re-compilation of all templates
209
+ on next render.
210
+
211
+ Examples
212
+ --------
213
+ >>> renderer = PromptRenderer()
214
+ >>> renderer.render("template.jinja", {}) # Compiles and caches
215
+ >>> # ... modify template.jinja ...
216
+ >>> renderer.clear_cache() # Clear cache
217
+ >>> renderer.render("template.jinja", {}) # Re-compiles
218
+ """
219
+ self._compile_template.cache_clear()
220
+
171
221
 
172
222
  __all__ = ["PromptRenderer"]
@@ -33,20 +33,23 @@ attach_vector_store
33
33
  from __future__ import annotations
34
34
 
35
35
  from .base import BaseResponse
36
- from .config import ResponseConfiguration
36
+ from .config import ResponseConfiguration, ResponseRegistry, get_default_registry
37
37
  from .messages import ResponseMessage, ResponseMessages
38
38
  from .runner import run_async, run_streamed, run_sync
39
- from .tool_call import ResponseToolCall
39
+ from .tool_call import ResponseToolCall, parse_tool_arguments
40
40
  from .vector_store import attach_vector_store
41
41
 
42
42
  __all__ = [
43
43
  "BaseResponse",
44
44
  "ResponseConfiguration",
45
+ "ResponseRegistry",
46
+ "get_default_registry",
45
47
  "ResponseMessage",
46
48
  "ResponseMessages",
47
49
  "run_sync",
48
50
  "run_async",
49
51
  "run_streamed",
50
52
  "ResponseToolCall",
53
+ "parse_tool_arguments",
51
54
  "attach_vector_store",
52
55
  ]