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.
- openai_sdk_helpers/__init__.py +85 -10
- openai_sdk_helpers/agent/__init__.py +8 -4
- openai_sdk_helpers/agent/base.py +81 -46
- openai_sdk_helpers/agent/config.py +6 -4
- openai_sdk_helpers/agent/{project_manager.py → coordination.py} +29 -45
- openai_sdk_helpers/agent/prompt_utils.py +7 -1
- openai_sdk_helpers/agent/runner.py +67 -141
- openai_sdk_helpers/agent/search/__init__.py +33 -0
- openai_sdk_helpers/agent/search/base.py +297 -0
- openai_sdk_helpers/agent/{vector_search.py → search/vector.py} +89 -157
- openai_sdk_helpers/agent/{web_search.py → search/web.py} +82 -162
- openai_sdk_helpers/agent/summarizer.py +29 -8
- openai_sdk_helpers/agent/translator.py +40 -13
- openai_sdk_helpers/agent/validation.py +32 -8
- openai_sdk_helpers/async_utils.py +132 -0
- openai_sdk_helpers/config.py +74 -36
- openai_sdk_helpers/context_manager.py +241 -0
- openai_sdk_helpers/enums/__init__.py +9 -1
- openai_sdk_helpers/enums/base.py +67 -8
- openai_sdk_helpers/environment.py +33 -6
- openai_sdk_helpers/errors.py +133 -0
- openai_sdk_helpers/logging_config.py +105 -0
- openai_sdk_helpers/prompt/__init__.py +10 -71
- openai_sdk_helpers/prompt/base.py +172 -0
- openai_sdk_helpers/response/__init__.py +37 -5
- openai_sdk_helpers/response/base.py +427 -189
- openai_sdk_helpers/response/config.py +176 -0
- openai_sdk_helpers/response/messages.py +104 -40
- openai_sdk_helpers/response/runner.py +79 -35
- openai_sdk_helpers/response/tool_call.py +75 -12
- openai_sdk_helpers/response/vector_store.py +29 -16
- openai_sdk_helpers/retry.py +175 -0
- openai_sdk_helpers/streamlit_app/__init__.py +30 -0
- openai_sdk_helpers/streamlit_app/app.py +345 -0
- openai_sdk_helpers/streamlit_app/config.py +502 -0
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +68 -0
- openai_sdk_helpers/structure/__init__.py +69 -3
- openai_sdk_helpers/structure/agent_blueprint.py +82 -19
- openai_sdk_helpers/structure/base.py +245 -91
- openai_sdk_helpers/structure/plan/__init__.py +15 -1
- openai_sdk_helpers/structure/plan/enum.py +41 -5
- openai_sdk_helpers/structure/plan/plan.py +101 -45
- openai_sdk_helpers/structure/plan/task.py +38 -6
- openai_sdk_helpers/structure/prompt.py +21 -2
- openai_sdk_helpers/structure/responses.py +52 -11
- openai_sdk_helpers/structure/summary.py +55 -7
- openai_sdk_helpers/structure/validation.py +34 -6
- openai_sdk_helpers/structure/vector_search.py +132 -18
- openai_sdk_helpers/structure/web_search.py +128 -12
- openai_sdk_helpers/types.py +57 -0
- openai_sdk_helpers/utils/__init__.py +32 -1
- openai_sdk_helpers/utils/core.py +200 -32
- openai_sdk_helpers/validation.py +302 -0
- openai_sdk_helpers/vector_storage/__init__.py +21 -1
- openai_sdk_helpers/vector_storage/cleanup.py +25 -13
- openai_sdk_helpers/vector_storage/storage.py +124 -66
- openai_sdk_helpers/vector_storage/types.py +20 -19
- openai_sdk_helpers-0.0.9.dist-info/METADATA +550 -0
- openai_sdk_helpers-0.0.9.dist-info/RECORD +66 -0
- openai_sdk_helpers-0.0.7.dist-info/METADATA +0 -193
- openai_sdk_helpers-0.0.7.dist-info/RECORD +0 -51
- {openai_sdk_helpers-0.0.7.dist-info → openai_sdk_helpers-0.0.9.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.0.7.dist-info → openai_sdk_helpers-0.0.9.dist-info}/licenses/LICENSE +0 -0
openai_sdk_helpers/utils/core.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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 :
|
|
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
|
|
25
|
-
Converted float value or
|
|
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:
|
|
47
|
-
"""Return an int when the provided value can be coerced, otherwise
|
|
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 :
|
|
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
|
|
57
|
-
Converted integer value or
|
|
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:
|
|
81
|
-
"""Return a string-keyed dictionary built from
|
|
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 :
|
|
86
|
-
Mapping-like value to convert.
|
|
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
|
|
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) ->
|
|
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.
|
|
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
|
|
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
|
|
139
|
-
Path object to validate. Mutually exclusive with
|
|
140
|
-
fullfilepath : str
|
|
141
|
-
String path to validate. Mutually exclusive with
|
|
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
|
|
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
|
|
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
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
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) ->
|
|
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
|
|
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",
|