openai-sdk-helpers 0.4.1__py3-none-any.whl → 0.4.3__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 (45) hide show
  1. openai_sdk_helpers/__init__.py +10 -36
  2. openai_sdk_helpers/agent/__init__.py +5 -6
  3. openai_sdk_helpers/agent/base.py +184 -39
  4. openai_sdk_helpers/agent/{config.py → configuration.py} +50 -75
  5. openai_sdk_helpers/agent/{coordination.py → coordinator.py} +12 -10
  6. openai_sdk_helpers/agent/search/__init__.py +4 -4
  7. openai_sdk_helpers/agent/search/base.py +16 -16
  8. openai_sdk_helpers/agent/search/vector.py +66 -42
  9. openai_sdk_helpers/agent/search/web.py +33 -29
  10. openai_sdk_helpers/agent/summarizer.py +6 -4
  11. openai_sdk_helpers/agent/translator.py +9 -5
  12. openai_sdk_helpers/agent/{validation.py → validator.py} +6 -4
  13. openai_sdk_helpers/cli.py +8 -22
  14. openai_sdk_helpers/environment.py +17 -0
  15. openai_sdk_helpers/prompt/vector_planner.jinja +7 -0
  16. openai_sdk_helpers/prompt/vector_search.jinja +6 -0
  17. openai_sdk_helpers/prompt/vector_writer.jinja +7 -0
  18. openai_sdk_helpers/response/__init__.py +1 -1
  19. openai_sdk_helpers/response/base.py +4 -4
  20. openai_sdk_helpers/response/{config.py → configuration.py} +9 -9
  21. openai_sdk_helpers/response/planner.py +12 -0
  22. openai_sdk_helpers/response/prompter.py +12 -0
  23. openai_sdk_helpers/streamlit_app/__init__.py +1 -1
  24. openai_sdk_helpers/streamlit_app/app.py +16 -17
  25. openai_sdk_helpers/streamlit_app/{config.py → configuration.py} +13 -13
  26. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +3 -3
  27. openai_sdk_helpers/types.py +3 -3
  28. openai_sdk_helpers/utils/__init__.py +2 -6
  29. openai_sdk_helpers/utils/json/base_model.py +1 -1
  30. openai_sdk_helpers/utils/json/data_class.py +1 -1
  31. openai_sdk_helpers/utils/json/ref.py +3 -0
  32. openai_sdk_helpers/utils/registry.py +19 -15
  33. openai_sdk_helpers/vector_storage/storage.py +1 -1
  34. {openai_sdk_helpers-0.4.1.dist-info → openai_sdk_helpers-0.4.3.dist-info}/METADATA +8 -8
  35. {openai_sdk_helpers-0.4.1.dist-info → openai_sdk_helpers-0.4.3.dist-info}/RECORD +40 -40
  36. openai_sdk_helpers/agent/prompt_utils.py +0 -15
  37. openai_sdk_helpers/context_manager.py +0 -241
  38. openai_sdk_helpers/deprecation.py +0 -167
  39. openai_sdk_helpers/retry.py +0 -175
  40. openai_sdk_helpers/utils/deprecation.py +0 -167
  41. /openai_sdk_helpers/{logging_config.py → logging.py} +0 -0
  42. /openai_sdk_helpers/{config.py → settings.py} +0 -0
  43. {openai_sdk_helpers-0.4.1.dist-info → openai_sdk_helpers-0.4.3.dist-info}/WHEEL +0 -0
  44. {openai_sdk_helpers-0.4.1.dist-info → openai_sdk_helpers-0.4.3.dist-info}/entry_points.txt +0 -0
  45. {openai_sdk_helpers-0.4.1.dist-info → openai_sdk_helpers-0.4.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,241 +0,0 @@
1
- """Context manager utilities for resource cleanup.
2
-
3
- Provides base classes and utilities for proper resource management
4
- with guaranteed cleanup on exit or exception.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import asyncio
10
- from contextlib import asynccontextmanager
11
- from types import TracebackType
12
- from typing import Any, AsyncIterator, Generic, Optional, TypeVar
13
-
14
- from openai_sdk_helpers.logging_config import log
15
-
16
- T = TypeVar("T")
17
-
18
-
19
- class ManagedResource(Generic[T]):
20
- """Base class for resources that need cleanup.
21
-
22
- Provides context manager support for guaranteed resource cleanup
23
- even when exceptions occur.
24
-
25
- Examples
26
- --------
27
- >>> class DatabaseConnection(ManagedResource[Connection]):
28
- ... def __init__(self, connection):
29
- ... self.connection = connection
30
- ...
31
- ... def close(self) -> None:
32
- ... if self.connection:
33
- ... self.connection.close()
34
-
35
- >>> with DatabaseConnection(connect()) as db:
36
- ... db.query("SELECT ...")
37
- """
38
-
39
- def __enter__(self) -> T:
40
- """Enter context manager.
41
-
42
- Returns
43
- -------
44
- T
45
- The resource instance (self cast appropriately).
46
- """
47
- return self # type: ignore
48
-
49
- def __exit__(
50
- self,
51
- exc_type: Optional[type[BaseException]],
52
- exc_val: Optional[BaseException],
53
- exc_tb: Optional[TracebackType],
54
- ) -> bool:
55
- """Exit context manager with cleanup.
56
-
57
- Parameters
58
- ----------
59
- exc_type : type[BaseException] | None
60
- Type of exception if one was raised, None otherwise.
61
- exc_val : BaseException | None
62
- Exception instance if one was raised, None otherwise.
63
- exc_tb : TracebackType | None
64
- Traceback if exception was raised, None otherwise.
65
-
66
- Returns
67
- -------
68
- bool
69
- False to re-raise exceptions, True to suppress them.
70
- """
71
- try:
72
- self.close()
73
- except Exception as exc:
74
- log(f"Error during cleanup: {exc}", level=30) # logging.WARNING
75
- # Don't suppress cleanup errors
76
- if exc_type is None:
77
- raise
78
-
79
- return False # Re-raise exceptions
80
-
81
- def close(self) -> None:
82
- """Close and cleanup the resource.
83
-
84
- Should be overridden by subclasses to perform actual cleanup.
85
- Should not raise exceptions, but may log them.
86
-
87
- Raises
88
- ------
89
- Exception
90
- May raise if cleanup fails catastrophically.
91
- """
92
- pass
93
-
94
-
95
- class AsyncManagedResource(Generic[T]):
96
- """Base class for async resources that need cleanup.
97
-
98
- Provides async context manager support for guaranteed resource cleanup
99
- even when exceptions occur.
100
-
101
- Examples
102
- --------
103
- >>> class AsyncDatabaseConnection(AsyncManagedResource[AsyncConnection]):
104
- ... def __init__(self, connection):
105
- ... self.connection = connection
106
- ...
107
- ... async def close(self) -> None:
108
- ... if self.connection:
109
- ... await self.connection.close()
110
-
111
- >>> async with AsyncDatabaseConnection(await connect()) as db:
112
- ... await db.query("SELECT ...")
113
- """
114
-
115
- async def __aenter__(self) -> T:
116
- """Enter async context manager.
117
-
118
- Returns
119
- -------
120
- T
121
- The resource instance (self cast appropriately).
122
- """
123
- return self # type: ignore
124
-
125
- async def __aexit__(
126
- self,
127
- exc_type: Optional[type[BaseException]],
128
- exc_val: Optional[BaseException],
129
- exc_tb: Optional[TracebackType],
130
- ) -> bool:
131
- """Exit async context manager with cleanup.
132
-
133
- Parameters
134
- ----------
135
- exc_type : type[BaseException] | None
136
- Type of exception if one was raised, None otherwise.
137
- exc_val : BaseException | None
138
- Exception instance if one was raised, None otherwise.
139
- exc_tb : TracebackType | None
140
- Traceback if exception was raised, None otherwise.
141
-
142
- Returns
143
- -------
144
- bool
145
- False to re-raise exceptions, True to suppress them.
146
- """
147
- try:
148
- await self.close()
149
- except Exception as exc:
150
- log(f"Error during async cleanup: {exc}", level=30) # logging.WARNING
151
- # Don't suppress cleanup errors
152
- if exc_type is None:
153
- raise
154
-
155
- return False # Re-raise exceptions
156
-
157
- async def close(self) -> None:
158
- """Close and cleanup the resource asynchronously.
159
-
160
- Should be overridden by subclasses to perform actual cleanup.
161
- Should not raise exceptions, but may log them.
162
-
163
- Raises
164
- ------
165
- Exception
166
- May raise if cleanup fails catastrophically.
167
- """
168
- pass
169
-
170
-
171
- def ensure_closed(resource: Any) -> None:
172
- """Safely close a resource if it has a close method.
173
-
174
- Logs errors but doesn't raise them.
175
-
176
- Parameters
177
- ----------
178
- resource : Any
179
- Object that may have a close() method.
180
- """
181
- if resource is None:
182
- return
183
-
184
- close_method = getattr(resource, "close", None)
185
- if callable(close_method):
186
- try:
187
- close_method()
188
- except Exception as exc:
189
- log(f"Error closing {type(resource).__name__}: {exc}", level=30)
190
-
191
-
192
- async def ensure_closed_async(resource: Any) -> None:
193
- """Safely close a resource asynchronously if it has an async close method.
194
-
195
- Logs errors but doesn't raise them.
196
-
197
- Parameters
198
- ----------
199
- resource : Any
200
- Object that may have an async close() method.
201
- """
202
- if resource is None:
203
- return
204
-
205
- close_method = getattr(resource, "close", None)
206
- if callable(close_method):
207
- try:
208
- if asyncio.iscoroutinefunction(close_method):
209
- await close_method()
210
- else:
211
- close_method()
212
- except Exception as exc:
213
- log(
214
- f"Error closing async {type(resource).__name__}: {exc}",
215
- level=30,
216
- )
217
-
218
-
219
- @asynccontextmanager
220
- async def async_context(resource: AsyncManagedResource[T]) -> AsyncIterator[T]:
221
- """Context manager for async resources.
222
-
223
- Parameters
224
- ----------
225
- resource : AsyncManagedResource
226
- Async resource to manage.
227
-
228
- Yields
229
- ------
230
- T
231
- The resource instance.
232
-
233
- Examples
234
- --------
235
- >>> async with async_context(my_resource) as resource:
236
- ... await resource.do_something()
237
- """
238
- try:
239
- yield await resource.__aenter__()
240
- finally:
241
- await resource.__aexit__(None, None, None)
@@ -1,167 +0,0 @@
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)
@@ -1,175 +0,0 @@
1
- """Retry decorators with exponential backoff for API operations.
2
-
3
- Provides decorators for retrying async and sync functions with
4
- exponential backoff and jitter when rate limiting or transient
5
- errors occur.
6
- """
7
-
8
- import asyncio
9
- import logging
10
- import random
11
- import time
12
- from functools import wraps
13
- from typing import Any, Callable, ParamSpec, TypeVar
14
-
15
- from openai import APIError, RateLimitError
16
-
17
- from openai_sdk_helpers.errors import AsyncExecutionError
18
- from openai_sdk_helpers.logging_config import log
19
-
20
- P = ParamSpec("P")
21
- T = TypeVar("T")
22
-
23
- # Default retry configuration constants
24
- DEFAULT_MAX_RETRIES = 3
25
- DEFAULT_BASE_DELAY = 1.0
26
- DEFAULT_MAX_DELAY = 60.0
27
-
28
- # HTTP status codes for transient errors
29
- TRANSIENT_HTTP_STATUS_CODES = frozenset({408, 429, 500, 502, 503})
30
-
31
-
32
- def with_exponential_backoff(
33
- max_retries: int = DEFAULT_MAX_RETRIES,
34
- base_delay: float = DEFAULT_BASE_DELAY,
35
- max_delay: float = DEFAULT_MAX_DELAY,
36
- ) -> Callable[[Callable[P, T]], Callable[P, T]]:
37
- """Decorate functions with exponential backoff on transient errors.
38
-
39
- Retries on RateLimitError or transient API errors (5xx, 408, 429).
40
- Uses exponential backoff with jitter to avoid thundering herd.
41
-
42
- Parameters
43
- ----------
44
- max_retries : int
45
- Maximum number of retry attempts (total attempts = max_retries + 1).
46
- Default is 3.
47
- base_delay : float
48
- Initial delay in seconds before first retry. Default is 1.0.
49
- max_delay : float
50
- Maximum delay in seconds between retries. Default is 60.0.
51
-
52
- Returns
53
- -------
54
- Callable
55
- Decorator function.
56
-
57
- Examples
58
- --------
59
- >>> @with_exponential_backoff(max_retries=3, base_delay=1.0)
60
- ... def call_api(query: str) -> str:
61
- ... # API call that may fail with rate limiting
62
- ... return client.call(query)
63
- """
64
-
65
- def decorator(func: Callable[P, T]) -> Callable[P, T]:
66
- """Apply retry logic to function."""
67
- if asyncio.iscoroutinefunction(func):
68
-
69
- @wraps(func)
70
- async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
71
- """Async wrapper with retry logic."""
72
- last_exc: Exception | None = None
73
- for attempt in range(max_retries + 1):
74
- try:
75
- return await func(*args, **kwargs)
76
- except RateLimitError as exc:
77
- last_exc = exc
78
- if attempt >= max_retries:
79
- raise
80
- delay = min(
81
- base_delay * (2**attempt) + random.uniform(0, 1),
82
- max_delay,
83
- )
84
- log(
85
- f"Rate limited on {func.__name__}, retrying in "
86
- f"{delay:.2f}s (attempt {attempt + 1}/{max_retries + 1})",
87
- level=logging.WARNING,
88
- )
89
- await asyncio.sleep(delay)
90
- except APIError as exc:
91
- last_exc = exc
92
- status_code: int | None = getattr(exc, "status_code", None)
93
- # Only retry on transient errors
94
- if (
95
- not status_code
96
- or status_code not in TRANSIENT_HTTP_STATUS_CODES
97
- ):
98
- raise
99
- if attempt >= max_retries:
100
- raise
101
- delay = min(
102
- base_delay * (2**attempt),
103
- max_delay,
104
- )
105
- log(
106
- f"Transient API error on {func.__name__}: "
107
- f"{status_code}, retrying in {delay:.2f}s "
108
- f"(attempt {attempt + 1}/{max_retries + 1})",
109
- level=logging.WARNING,
110
- )
111
- await asyncio.sleep(delay)
112
-
113
- # Should never reach here, but handle edge case
114
- if last_exc:
115
- raise last_exc
116
- raise AsyncExecutionError(
117
- f"Unexpected state in {func.__name__} after retries"
118
- )
119
-
120
- return async_wrapper # type: ignore
121
-
122
- @wraps(func)
123
- def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
124
- """Sync wrapper with retry logic."""
125
- last_exc: Exception | None = None
126
- for attempt in range(max_retries + 1):
127
- try:
128
- return func(*args, **kwargs)
129
- except RateLimitError as exc:
130
- last_exc = exc
131
- if attempt >= max_retries:
132
- raise
133
- delay = min(
134
- base_delay * (2**attempt) + random.uniform(0, 1),
135
- max_delay,
136
- )
137
- log(
138
- f"Rate limited on {func.__name__}, retrying in "
139
- f"{delay:.2f}s (attempt {attempt + 1}/{max_retries + 1})",
140
- level=logging.WARNING,
141
- )
142
- time.sleep(delay)
143
- except APIError as exc:
144
- last_exc = exc
145
- status_code: int | None = getattr(exc, "status_code", None)
146
- # Only retry on transient errors
147
- if (
148
- not status_code
149
- or status_code not in TRANSIENT_HTTP_STATUS_CODES
150
- ):
151
- raise
152
- if attempt >= max_retries:
153
- raise
154
- delay = min(
155
- base_delay * (2**attempt),
156
- max_delay,
157
- )
158
- log(
159
- f"Transient API error on {func.__name__}: "
160
- f"{status_code}, retrying in {delay:.2f}s "
161
- f"(attempt {attempt + 1}/{max_retries + 1})",
162
- level=logging.WARNING,
163
- )
164
- time.sleep(delay)
165
-
166
- # Should never reach here, but handle edge case
167
- if last_exc:
168
- raise last_exc
169
- raise AsyncExecutionError(
170
- f"Unexpected state in {func.__name__} after retries"
171
- )
172
-
173
- return sync_wrapper # type: ignore
174
-
175
- return decorator