openai-sdk-helpers 0.0.7__py3-none-any.whl → 0.0.9__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 (63) hide show
  1. openai_sdk_helpers/__init__.py +85 -10
  2. openai_sdk_helpers/agent/__init__.py +8 -4
  3. openai_sdk_helpers/agent/base.py +81 -46
  4. openai_sdk_helpers/agent/config.py +6 -4
  5. openai_sdk_helpers/agent/{project_manager.py → coordination.py} +29 -45
  6. openai_sdk_helpers/agent/prompt_utils.py +7 -1
  7. openai_sdk_helpers/agent/runner.py +67 -141
  8. openai_sdk_helpers/agent/search/__init__.py +33 -0
  9. openai_sdk_helpers/agent/search/base.py +297 -0
  10. openai_sdk_helpers/agent/{vector_search.py → search/vector.py} +89 -157
  11. openai_sdk_helpers/agent/{web_search.py → search/web.py} +82 -162
  12. openai_sdk_helpers/agent/summarizer.py +29 -8
  13. openai_sdk_helpers/agent/translator.py +40 -13
  14. openai_sdk_helpers/agent/validation.py +32 -8
  15. openai_sdk_helpers/async_utils.py +132 -0
  16. openai_sdk_helpers/config.py +74 -36
  17. openai_sdk_helpers/context_manager.py +241 -0
  18. openai_sdk_helpers/enums/__init__.py +9 -1
  19. openai_sdk_helpers/enums/base.py +67 -8
  20. openai_sdk_helpers/environment.py +33 -6
  21. openai_sdk_helpers/errors.py +133 -0
  22. openai_sdk_helpers/logging_config.py +105 -0
  23. openai_sdk_helpers/prompt/__init__.py +10 -71
  24. openai_sdk_helpers/prompt/base.py +172 -0
  25. openai_sdk_helpers/response/__init__.py +37 -5
  26. openai_sdk_helpers/response/base.py +427 -189
  27. openai_sdk_helpers/response/config.py +176 -0
  28. openai_sdk_helpers/response/messages.py +104 -40
  29. openai_sdk_helpers/response/runner.py +79 -35
  30. openai_sdk_helpers/response/tool_call.py +75 -12
  31. openai_sdk_helpers/response/vector_store.py +29 -16
  32. openai_sdk_helpers/retry.py +175 -0
  33. openai_sdk_helpers/streamlit_app/__init__.py +30 -0
  34. openai_sdk_helpers/streamlit_app/app.py +345 -0
  35. openai_sdk_helpers/streamlit_app/config.py +502 -0
  36. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +68 -0
  37. openai_sdk_helpers/structure/__init__.py +69 -3
  38. openai_sdk_helpers/structure/agent_blueprint.py +82 -19
  39. openai_sdk_helpers/structure/base.py +245 -91
  40. openai_sdk_helpers/structure/plan/__init__.py +15 -1
  41. openai_sdk_helpers/structure/plan/enum.py +41 -5
  42. openai_sdk_helpers/structure/plan/plan.py +101 -45
  43. openai_sdk_helpers/structure/plan/task.py +38 -6
  44. openai_sdk_helpers/structure/prompt.py +21 -2
  45. openai_sdk_helpers/structure/responses.py +52 -11
  46. openai_sdk_helpers/structure/summary.py +55 -7
  47. openai_sdk_helpers/structure/validation.py +34 -6
  48. openai_sdk_helpers/structure/vector_search.py +132 -18
  49. openai_sdk_helpers/structure/web_search.py +128 -12
  50. openai_sdk_helpers/types.py +57 -0
  51. openai_sdk_helpers/utils/__init__.py +32 -1
  52. openai_sdk_helpers/utils/core.py +200 -32
  53. openai_sdk_helpers/validation.py +302 -0
  54. openai_sdk_helpers/vector_storage/__init__.py +21 -1
  55. openai_sdk_helpers/vector_storage/cleanup.py +25 -13
  56. openai_sdk_helpers/vector_storage/storage.py +124 -66
  57. openai_sdk_helpers/vector_storage/types.py +20 -19
  58. openai_sdk_helpers-0.0.9.dist-info/METADATA +550 -0
  59. openai_sdk_helpers-0.0.9.dist-info/RECORD +66 -0
  60. openai_sdk_helpers-0.0.7.dist-info/METADATA +0 -193
  61. openai_sdk_helpers-0.0.7.dist-info/RECORD +0 -51
  62. {openai_sdk_helpers-0.0.7.dist-info → openai_sdk_helpers-0.0.9.dist-info}/WHEEL +0 -0
  63. {openai_sdk_helpers-0.0.7.dist-info → openai_sdk_helpers-0.0.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,28 +1,37 @@
1
- """Core utility helpers for openai-sdk-helpers."""
1
+ """Core utility helpers for openai-sdk-helpers.
2
+
3
+ This module provides foundational utility functions for type coercion,
4
+ file path validation, JSON serialization, and logging. These utilities
5
+ support consistent data handling across the package.
6
+ """
2
7
 
3
8
  from __future__ import annotations
4
9
 
5
10
  import json
6
11
  import logging
12
+ import ast
13
+ from collections.abc import Iterable, Mapping
7
14
  from dataclasses import asdict, is_dataclass
8
15
  from datetime import datetime
9
16
  from enum import Enum
10
17
  from pathlib import Path
11
- from typing import Any, Dict, Iterable, List, Mapping, Optional, TypeVar
18
+ from typing import Any, TypeVar
19
+
12
20
 
21
+ def coerce_optional_float(value: object) -> float | None:
22
+ """Return a float when the provided value can be coerced, otherwise None.
13
23
 
14
- def coerce_optional_float(value: Any) -> Optional[float]:
15
- """Return a float when the provided value can be coerced, otherwise ``None``.
24
+ Handles float, int, and string inputs. Empty strings or None return None.
16
25
 
17
26
  Parameters
18
27
  ----------
19
- value : Any
28
+ value : object
20
29
  Value to convert into a float. Strings must be parseable as floats.
21
30
 
22
31
  Returns
23
32
  -------
24
- float | None
25
- Converted float value or ``None`` if the input is ``None``.
33
+ float or None
34
+ Converted float value or None if the input is None.
26
35
 
27
36
  Raises
28
37
  ------
@@ -30,6 +39,15 @@ def coerce_optional_float(value: Any) -> Optional[float]:
30
39
  If a non-empty string cannot be converted to a float.
31
40
  TypeError
32
41
  If the value is not a float-compatible type.
42
+
43
+ Examples
44
+ --------
45
+ >>> coerce_optional_float(3.14)
46
+ 3.14
47
+ >>> coerce_optional_float("2.5")
48
+ 2.5
49
+ >>> coerce_optional_float(None) is None
50
+ True
33
51
  """
34
52
  if value is None:
35
53
  return None
@@ -43,18 +61,21 @@ def coerce_optional_float(value: Any) -> Optional[float]:
43
61
  raise TypeError("timeout must be a float, int, str, or None")
44
62
 
45
63
 
46
- def coerce_optional_int(value: Any) -> Optional[int]:
47
- """Return an int when the provided value can be coerced, otherwise ``None``.
64
+ def coerce_optional_int(value: object) -> int | None:
65
+ """Return an int when the provided value can be coerced, otherwise None.
66
+
67
+ Handles int, float (if whole number), and string inputs. Empty strings
68
+ or None return None. Booleans are not considered valid integers.
48
69
 
49
70
  Parameters
50
71
  ----------
51
- value : Any
72
+ value : object
52
73
  Value to convert into an int. Strings must be parseable as integers.
53
74
 
54
75
  Returns
55
76
  -------
56
- int | None
57
- Converted integer value or ``None`` if the input is ``None``.
77
+ int or None
78
+ Converted integer value or None if the input is None.
58
79
 
59
80
  Raises
60
81
  ------
@@ -62,6 +83,17 @@ def coerce_optional_int(value: Any) -> Optional[int]:
62
83
  If a non-empty string cannot be converted to an integer.
63
84
  TypeError
64
85
  If the value is not an int-compatible type.
86
+
87
+ Examples
88
+ --------
89
+ >>> coerce_optional_int(42)
90
+ 42
91
+ >>> coerce_optional_int("100")
92
+ 100
93
+ >>> coerce_optional_int(3.0)
94
+ 3
95
+ >>> coerce_optional_int(None) is None
96
+ True
65
97
  """
66
98
  if value is None:
67
99
  return None
@@ -77,23 +109,32 @@ def coerce_optional_int(value: Any) -> Optional[int]:
77
109
  raise TypeError("max_retries must be an int, str, or None")
78
110
 
79
111
 
80
- def coerce_dict(value: Any) -> Dict[str, Any]:
81
- """Return a string-keyed dictionary built from ``value`` if possible.
112
+ def coerce_dict(value: object) -> dict[str, Any]:
113
+ """Return a string-keyed dictionary built from value if possible.
114
+
115
+ Converts Mapping objects to dictionaries. None returns an empty dict.
82
116
 
83
117
  Parameters
84
118
  ----------
85
- value : Any
86
- Mapping-like value to convert. ``None`` yields an empty dictionary.
119
+ value : object
120
+ Mapping-like value to convert. None yields an empty dictionary.
87
121
 
88
122
  Returns
89
123
  -------
90
124
  dict[str, Any]
91
- Dictionary representation of ``value``.
125
+ Dictionary representation of value.
92
126
 
93
127
  Raises
94
128
  ------
95
129
  TypeError
96
130
  If the value cannot be treated as a mapping.
131
+
132
+ Examples
133
+ --------
134
+ >>> coerce_dict({"a": 1})
135
+ {'a': 1}
136
+ >>> coerce_dict(None)
137
+ {}
97
138
  """
98
139
  if value is None:
99
140
  return {}
@@ -106,18 +147,32 @@ T = TypeVar("T")
106
147
  _configured_logging = False
107
148
 
108
149
 
109
- def ensure_list(value: Iterable[T] | T | None) -> List[T]:
150
+ def ensure_list(value: Iterable[T] | T | None) -> list[T]:
110
151
  """Normalize a single item or iterable into a list.
111
152
 
153
+ Converts None to empty list, tuples to lists, and wraps single
154
+ items in a list.
155
+
112
156
  Parameters
113
157
  ----------
114
158
  value : Iterable[T] | T | None
115
- Item or iterable to wrap. ``None`` yields an empty list.
159
+ Item or iterable to wrap. None yields an empty list.
116
160
 
117
161
  Returns
118
162
  -------
119
163
  list[T]
120
- Normalized list representation of ``value``.
164
+ Normalized list representation of value.
165
+
166
+ Examples
167
+ --------
168
+ >>> ensure_list(None)
169
+ []
170
+ >>> ensure_list(5)
171
+ [5]
172
+ >>> ensure_list([1, 2, 3])
173
+ [1, 2, 3]
174
+ >>> ensure_list(("a", "b"))
175
+ ['a', 'b']
121
176
  """
122
177
  if value is None:
123
178
  return []
@@ -133,12 +188,15 @@ def check_filepath(
133
188
  ) -> Path:
134
189
  """Ensure the parent directory for a file path exists.
135
190
 
191
+ Creates parent directories as needed. Exactly one of filepath or
192
+ fullfilepath must be provided.
193
+
136
194
  Parameters
137
195
  ----------
138
- filepath : Path | None, optional
139
- Path object to validate. Mutually exclusive with ``fullfilepath``.
140
- fullfilepath : str | None, optional
141
- String path to validate. Mutually exclusive with ``filepath``.
196
+ filepath : Path or None, optional
197
+ Path object to validate. Mutually exclusive with fullfilepath.
198
+ fullfilepath : str or None, optional
199
+ String path to validate. Mutually exclusive with filepath.
142
200
 
143
201
  Returns
144
202
  -------
@@ -148,7 +206,14 @@ def check_filepath(
148
206
  Raises
149
207
  ------
150
208
  ValueError
151
- If neither ``filepath`` nor ``fullfilepath`` is provided.
209
+ If neither filepath nor fullfilepath is provided.
210
+
211
+ Examples
212
+ --------
213
+ >>> from pathlib import Path
214
+ >>> path = check_filepath(filepath=Path("/tmp/test.txt"))
215
+ >>> isinstance(path, Path)
216
+ True
152
217
  """
153
218
  if filepath is None and fullfilepath is None:
154
219
  raise ValueError("filepath or fullfilepath is required.")
@@ -165,6 +230,9 @@ def check_filepath(
165
230
  def _to_jsonable(value: Any) -> Any:
166
231
  """Convert common helper types to JSON-serializable forms.
167
232
 
233
+ Handles Enum, Path, datetime, dataclasses, Pydantic models, dicts,
234
+ lists, tuples, and sets.
235
+
168
236
  Parameters
169
237
  ----------
170
238
  value : Any
@@ -173,7 +241,11 @@ def _to_jsonable(value: Any) -> Any:
173
241
  Returns
174
242
  -------
175
243
  Any
176
- A JSON-safe representation of ``value``.
244
+ A JSON-safe representation of value.
245
+
246
+ Notes
247
+ -----
248
+ This is an internal helper function. Use coerce_jsonable for public API.
177
249
  """
178
250
  if value is None:
179
251
  return None
@@ -195,17 +267,72 @@ def _to_jsonable(value: Any) -> Any:
195
267
  return value
196
268
 
197
269
 
270
+ def coerce_jsonable(value: Any) -> Any:
271
+ """Convert value into a JSON-serializable representation.
272
+
273
+ Handles BaseStructure, BaseResponse, dataclasses, and other complex
274
+ types by recursively converting them to JSON-compatible forms.
275
+
276
+ Parameters
277
+ ----------
278
+ value : Any
279
+ Object to convert into a JSON-friendly structure.
280
+
281
+ Returns
282
+ -------
283
+ Any
284
+ JSON-serializable representation of value.
285
+
286
+ Examples
287
+ --------
288
+ >>> from datetime import datetime
289
+ >>> result = coerce_jsonable({"date": datetime(2024, 1, 1)})
290
+ >>> isinstance(result, dict)
291
+ True
292
+ """
293
+ from openai_sdk_helpers.response.base import BaseResponse
294
+ from openai_sdk_helpers.structure.base import BaseStructure
295
+
296
+ if value is None:
297
+ return None
298
+ if isinstance(value, BaseStructure):
299
+ return value.model_dump()
300
+ if isinstance(value, BaseResponse):
301
+ return coerce_jsonable(value.messages.to_json())
302
+ if is_dataclass(value) and not isinstance(value, type):
303
+ return {key: coerce_jsonable(item) for key, item in asdict(value).items()}
304
+ coerced = _to_jsonable(value)
305
+ try:
306
+ json.dumps(coerced)
307
+ return coerced
308
+ except TypeError:
309
+ return str(coerced)
310
+
311
+
198
312
  class customJSONEncoder(json.JSONEncoder):
199
- """Encode common helper types like enums and paths.
313
+ """JSON encoder for common helper types like enums and paths.
314
+
315
+ Extends json.JSONEncoder to handle Enum, Path, datetime, dataclasses,
316
+ and Pydantic models automatically.
200
317
 
201
318
  Methods
202
319
  -------
203
320
  default(o)
204
- Return a JSON-serializable representation of ``o``.
321
+ Return a JSON-serializable representation of o.
322
+
323
+ Examples
324
+ --------
325
+ >>> import json
326
+ >>> from pathlib import Path
327
+ >>> json.dumps({"path": Path("/tmp")}, cls=customJSONEncoder)
328
+ '{"path": "/tmp"}'
205
329
  """
206
330
 
207
331
  def default(self, o: Any) -> Any:
208
- """Return a JSON-serializable representation of ``o``.
332
+ """Return a JSON-serializable representation of o.
333
+
334
+ Called by the json module when the default serialization fails.
335
+ Delegates to _to_jsonable for type-specific conversions.
209
336
 
210
337
  Parameters
211
338
  ----------
@@ -215,7 +342,7 @@ class customJSONEncoder(json.JSONEncoder):
215
342
  Returns
216
343
  -------
217
344
  Any
218
- JSON-safe representation of ``o``.
345
+ JSON-safe representation of o.
219
346
  """
220
347
  return _to_jsonable(o)
221
348
 
@@ -223,21 +350,44 @@ class customJSONEncoder(json.JSONEncoder):
223
350
  class JSONSerializable:
224
351
  """Mixin for classes that can be serialized to JSON.
225
352
 
353
+ Provides to_json() and to_json_file() methods for any class. Works
354
+ with dataclasses, Pydantic models, and regular classes with __dict__.
355
+
226
356
  Methods
227
357
  -------
228
358
  to_json()
229
359
  Return a JSON-compatible dict representation of the instance.
230
360
  to_json_file(filepath)
231
361
  Write serialized JSON data to a file path.
362
+
363
+ Examples
364
+ --------
365
+ >>> from dataclasses import dataclass
366
+ >>> @dataclass
367
+ ... class MyClass(JSONSerializable):
368
+ ... value: int
369
+ >>> obj = MyClass(value=42)
370
+ >>> obj.to_json()
371
+ {'value': 42}
232
372
  """
233
373
 
234
- def to_json(self) -> Dict[str, Any]:
374
+ def to_json(self) -> dict[str, Any]:
235
375
  """Return a JSON-compatible dict representation.
236
376
 
377
+ Automatically handles dataclasses, Pydantic models, and objects
378
+ with __dict__ attributes.
379
+
237
380
  Returns
238
381
  -------
239
382
  dict[str, Any]
240
383
  Mapping with only JSON-serializable values.
384
+
385
+ Examples
386
+ --------
387
+ >>> obj = JSONSerializable()
388
+ >>> result = obj.to_json()
389
+ >>> isinstance(result, dict)
390
+ True
241
391
  """
242
392
  if is_dataclass(self) and not isinstance(self, type):
243
393
  return {k: _to_jsonable(v) for k, v in asdict(self).items()}
@@ -249,6 +399,9 @@ class JSONSerializable:
249
399
  def to_json_file(self, filepath: str | Path) -> str:
250
400
  """Write serialized JSON data to a file path.
251
401
 
402
+ Creates parent directories as needed. Uses customJSONEncoder for
403
+ handling special types.
404
+
252
405
  Parameters
253
406
  ----------
254
407
  filepath : str | Path
@@ -258,6 +411,11 @@ class JSONSerializable:
258
411
  -------
259
412
  str
260
413
  String representation of the file path written.
414
+
415
+ Examples
416
+ --------
417
+ >>> obj = JSONSerializable()
418
+ >>> path = obj.to_json_file("/tmp/output.json") # doctest: +SKIP
261
419
  """
262
420
  target = Path(filepath)
263
421
  check_filepath(fullfilepath=str(target))
@@ -275,12 +433,21 @@ class JSONSerializable:
275
433
  def log(message: str, level: int = logging.INFO) -> None:
276
434
  """Log a message with a basic configuration.
277
435
 
436
+ Configures logging on first use with a simple timestamp format.
437
+ Subsequent calls use the existing configuration.
438
+
278
439
  Parameters
279
440
  ----------
280
441
  message : str
281
442
  Message to emit.
282
443
  level : int, optional
283
- Logging level, by default ``logging.INFO``.
444
+ Logging level (e.g., logging.INFO, logging.WARNING), by default
445
+ logging.INFO.
446
+
447
+ Examples
448
+ --------
449
+ >>> import logging
450
+ >>> log("Test message", level=logging.INFO) # doctest: +SKIP
284
451
  """
285
452
  global _configured_logging
286
453
  if not _configured_logging:
@@ -294,6 +461,7 @@ def log(message: str, level: int = logging.INFO) -> None:
294
461
  __all__ = [
295
462
  "ensure_list",
296
463
  "check_filepath",
464
+ "coerce_jsonable",
297
465
  "JSONSerializable",
298
466
  "customJSONEncoder",
299
467
  "log",