openai-sdk-helpers 0.3.0__py3-none-any.whl → 0.4.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.
- openai_sdk_helpers/__init__.py +6 -6
- openai_sdk_helpers/agent/__init__.py +4 -4
- openai_sdk_helpers/agent/base.py +254 -113
- openai_sdk_helpers/agent/config.py +91 -37
- openai_sdk_helpers/agent/coordination.py +64 -28
- openai_sdk_helpers/agent/runner.py +16 -15
- openai_sdk_helpers/agent/search/base.py +94 -45
- openai_sdk_helpers/agent/search/vector.py +86 -58
- openai_sdk_helpers/agent/search/web.py +71 -40
- openai_sdk_helpers/agent/summarizer.py +32 -7
- openai_sdk_helpers/agent/translator.py +57 -24
- openai_sdk_helpers/agent/validation.py +34 -4
- openai_sdk_helpers/cli.py +42 -0
- openai_sdk_helpers/config.py +0 -1
- openai_sdk_helpers/environment.py +3 -2
- openai_sdk_helpers/files_api.py +35 -3
- openai_sdk_helpers/prompt/base.py +6 -0
- openai_sdk_helpers/response/__init__.py +3 -3
- openai_sdk_helpers/response/base.py +142 -73
- openai_sdk_helpers/response/config.py +63 -58
- openai_sdk_helpers/response/files.py +5 -5
- openai_sdk_helpers/response/messages.py +3 -3
- openai_sdk_helpers/response/runner.py +7 -7
- openai_sdk_helpers/response/tool_call.py +94 -4
- openai_sdk_helpers/response/vector_store.py +3 -3
- openai_sdk_helpers/streamlit_app/__init__.py +4 -4
- openai_sdk_helpers/streamlit_app/app.py +16 -16
- openai_sdk_helpers/streamlit_app/config.py +82 -70
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +2 -2
- openai_sdk_helpers/structure/__init__.py +6 -2
- openai_sdk_helpers/structure/agent_blueprint.py +2 -2
- openai_sdk_helpers/structure/base.py +8 -99
- openai_sdk_helpers/structure/plan/plan.py +2 -2
- openai_sdk_helpers/structure/plan/task.py +9 -9
- openai_sdk_helpers/structure/prompt.py +2 -2
- openai_sdk_helpers/structure/responses.py +15 -15
- openai_sdk_helpers/structure/summary.py +3 -3
- openai_sdk_helpers/structure/translation.py +32 -0
- openai_sdk_helpers/structure/validation.py +2 -2
- openai_sdk_helpers/structure/vector_search.py +7 -7
- openai_sdk_helpers/structure/web_search.py +6 -6
- openai_sdk_helpers/tools.py +41 -15
- openai_sdk_helpers/utils/__init__.py +19 -5
- openai_sdk_helpers/utils/json/__init__.py +55 -0
- openai_sdk_helpers/utils/json/base_model.py +181 -0
- openai_sdk_helpers/utils/{json_utils.py → json/data_class.py} +33 -68
- openai_sdk_helpers/utils/json/ref.py +113 -0
- openai_sdk_helpers/utils/json/utils.py +203 -0
- openai_sdk_helpers/utils/output_validation.py +21 -1
- openai_sdk_helpers/utils/path_utils.py +34 -1
- openai_sdk_helpers/utils/registry.py +46 -8
- openai_sdk_helpers/vector_storage/storage.py +10 -0
- {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/METADATA +7 -7
- openai_sdk_helpers-0.4.1.dist-info/RECORD +86 -0
- openai_sdk_helpers-0.3.0.dist-info/RECORD +0 -81
- {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/entry_points.txt +0 -0
- {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,14 +11,15 @@ import importlib.util
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from types import ModuleType
|
|
13
13
|
from typing import Callable, Sequence, cast
|
|
14
|
-
from pydantic import
|
|
14
|
+
from pydantic import ConfigDict, Field, field_validator, model_validator
|
|
15
15
|
|
|
16
|
-
from openai_sdk_helpers.response.base import
|
|
17
|
-
from openai_sdk_helpers.structure.base import
|
|
18
|
-
from openai_sdk_helpers.utils import ensure_list
|
|
16
|
+
from openai_sdk_helpers.response.base import ResponseBase
|
|
17
|
+
from openai_sdk_helpers.structure.base import StructureBase
|
|
18
|
+
from openai_sdk_helpers.utils import RegistryBase, ensure_list
|
|
19
|
+
from ..utils.json import BaseModelJSONSerializable
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
class StreamlitAppConfig(
|
|
22
|
+
class StreamlitAppConfig(BaseModelJSONSerializable):
|
|
22
23
|
"""Validated configuration for Streamlit chat applications.
|
|
23
24
|
|
|
24
25
|
Manages all settings required to run a configuration-driven Streamlit
|
|
@@ -28,7 +29,9 @@ class StreamlitAppConfig(BaseModel):
|
|
|
28
29
|
|
|
29
30
|
Attributes
|
|
30
31
|
----------
|
|
31
|
-
|
|
32
|
+
name : str
|
|
33
|
+
Unique configuration identifier. Default is ``"streamlit_app"``.
|
|
34
|
+
response : ResponseBase, type[ResponseBase], Callable, or None
|
|
32
35
|
Response handler as an instance, class, or callable factory.
|
|
33
36
|
display_title : str
|
|
34
37
|
Title displayed at the top of the Streamlit page.
|
|
@@ -46,9 +49,7 @@ class StreamlitAppConfig(BaseModel):
|
|
|
46
49
|
normalized_vector_stores()
|
|
47
50
|
Return configured system vector stores as a list.
|
|
48
51
|
create_response()
|
|
49
|
-
Instantiate and return the configured
|
|
50
|
-
load_app_config(config_path)
|
|
51
|
-
Load, validate, and return configuration from a Python module.
|
|
52
|
+
Instantiate and return the configured ResponseBase.
|
|
52
53
|
|
|
53
54
|
Examples
|
|
54
55
|
--------
|
|
@@ -62,11 +63,15 @@ class StreamlitAppConfig(BaseModel):
|
|
|
62
63
|
|
|
63
64
|
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
name: str = Field(
|
|
67
|
+
default="streamlit_app",
|
|
68
|
+
description="Unique configuration identifier used for registry lookup.",
|
|
69
|
+
)
|
|
70
|
+
response: ResponseBase[StructureBase] | type[ResponseBase] | Callable | None = (
|
|
66
71
|
Field(
|
|
67
72
|
default=None,
|
|
68
73
|
description=(
|
|
69
|
-
"Configured ``
|
|
74
|
+
"Configured ``ResponseBase`` subclass, instance, or callable that returns"
|
|
70
75
|
" a response instance."
|
|
71
76
|
),
|
|
72
77
|
)
|
|
@@ -130,37 +135,37 @@ class StreamlitAppConfig(BaseModel):
|
|
|
130
135
|
@field_validator("response")
|
|
131
136
|
@classmethod
|
|
132
137
|
def validate_response(
|
|
133
|
-
cls, value:
|
|
134
|
-
) ->
|
|
138
|
+
cls, value: ResponseBase[StructureBase] | type[ResponseBase] | Callable | None
|
|
139
|
+
) -> ResponseBase[StructureBase] | type[ResponseBase] | Callable | None:
|
|
135
140
|
"""Validate that the response field is a valid handler source.
|
|
136
141
|
|
|
137
|
-
Ensures the provided response can be used to create a
|
|
142
|
+
Ensures the provided response can be used to create a ResponseBase
|
|
138
143
|
instance for handling chat interactions.
|
|
139
144
|
|
|
140
145
|
Parameters
|
|
141
146
|
----------
|
|
142
|
-
value :
|
|
147
|
+
value : ResponseBase, type[ResponseBase], Callable, or None
|
|
143
148
|
Response handler as instance, class, or factory function.
|
|
144
149
|
|
|
145
150
|
Returns
|
|
146
151
|
-------
|
|
147
|
-
|
|
152
|
+
ResponseBase, type[ResponseBase], Callable, or None
|
|
148
153
|
Validated response handler.
|
|
149
154
|
|
|
150
155
|
Raises
|
|
151
156
|
------
|
|
152
157
|
TypeError
|
|
153
|
-
If value is not a
|
|
158
|
+
If value is not a ResponseBase, subclass, or callable.
|
|
154
159
|
"""
|
|
155
160
|
if value is None:
|
|
156
161
|
return None
|
|
157
|
-
if isinstance(value,
|
|
162
|
+
if isinstance(value, ResponseBase):
|
|
158
163
|
return value
|
|
159
|
-
if isinstance(value, type) and issubclass(value,
|
|
164
|
+
if isinstance(value, type) and issubclass(value, ResponseBase):
|
|
160
165
|
return value
|
|
161
166
|
if callable(value):
|
|
162
167
|
return value
|
|
163
|
-
raise TypeError("response must be a
|
|
168
|
+
raise TypeError("response must be a ResponseBase, subclass, or callable")
|
|
164
169
|
|
|
165
170
|
def normalized_vector_stores(self) -> list[str]:
|
|
166
171
|
"""Return configured system vector stores as a list.
|
|
@@ -201,21 +206,21 @@ class StreamlitAppConfig(BaseModel):
|
|
|
201
206
|
raise ValueError("response must be provided.")
|
|
202
207
|
return self
|
|
203
208
|
|
|
204
|
-
def create_response(self) ->
|
|
209
|
+
def create_response(self) -> ResponseBase[StructureBase]:
|
|
205
210
|
"""Instantiate and return the configured response handler.
|
|
206
211
|
|
|
207
212
|
Converts the response field (whether class, instance, or callable)
|
|
208
|
-
into an active
|
|
213
|
+
into an active ResponseBase instance ready for chat interactions.
|
|
209
214
|
|
|
210
215
|
Returns
|
|
211
216
|
-------
|
|
212
|
-
|
|
217
|
+
ResponseBase[StructureBase]
|
|
213
218
|
Active response instance for handling chat messages.
|
|
214
219
|
|
|
215
220
|
Raises
|
|
216
221
|
------
|
|
217
222
|
TypeError
|
|
218
|
-
If the configured response cannot produce a
|
|
223
|
+
If the configured response cannot produce a ResponseBase.
|
|
219
224
|
|
|
220
225
|
Examples
|
|
221
226
|
--------
|
|
@@ -224,6 +229,39 @@ class StreamlitAppConfig(BaseModel):
|
|
|
224
229
|
"""
|
|
225
230
|
return _instantiate_response(self.response)
|
|
226
231
|
|
|
232
|
+
|
|
233
|
+
class StreamlitAppRegistry(RegistryBase[StreamlitAppConfig]):
|
|
234
|
+
"""Registry for managing StreamlitAppConfig instances.
|
|
235
|
+
|
|
236
|
+
Inherits from RegistryBase to provide centralized storage and retrieval
|
|
237
|
+
of Streamlit app configurations, enabling reuse across applications.
|
|
238
|
+
|
|
239
|
+
Methods
|
|
240
|
+
-------
|
|
241
|
+
register(config)
|
|
242
|
+
Add a configuration to the registry.
|
|
243
|
+
get(name)
|
|
244
|
+
Retrieve a configuration by name.
|
|
245
|
+
list_names()
|
|
246
|
+
Return all registered configuration names.
|
|
247
|
+
clear()
|
|
248
|
+
Remove all registered configurations.
|
|
249
|
+
save_to_directory(path)
|
|
250
|
+
Export all registered configurations to JSON files.
|
|
251
|
+
load_from_directory(path)
|
|
252
|
+
Load configurations from JSON files in a directory.
|
|
253
|
+
load_app_config(config_path)
|
|
254
|
+
Load, validate, and return configuration from a Python module.
|
|
255
|
+
|
|
256
|
+
Examples
|
|
257
|
+
--------
|
|
258
|
+
>>> registry = StreamlitAppRegistry()
|
|
259
|
+
>>> config = StreamlitAppConfig(response=MyResponse)
|
|
260
|
+
>>> registry.register(config)
|
|
261
|
+
>>> registry.get(config.name)
|
|
262
|
+
StreamlitAppConfig(...)
|
|
263
|
+
"""
|
|
264
|
+
|
|
227
265
|
@staticmethod
|
|
228
266
|
def load_app_config(
|
|
229
267
|
config_path: Path,
|
|
@@ -257,7 +295,7 @@ class StreamlitAppConfig(BaseModel):
|
|
|
257
295
|
Examples
|
|
258
296
|
--------
|
|
259
297
|
>>> from pathlib import Path
|
|
260
|
-
>>> config =
|
|
298
|
+
>>> config = StreamlitAppRegistry.load_app_config(
|
|
261
299
|
... Path("./my_config.py")
|
|
262
300
|
... )
|
|
263
301
|
"""
|
|
@@ -311,7 +349,7 @@ def _extract_config(module: ModuleType) -> StreamlitAppConfig:
|
|
|
311
349
|
|
|
312
350
|
Looks for APP_CONFIG in the module and converts it to a validated
|
|
313
351
|
StreamlitAppConfig instance. Supports multiple input formats including
|
|
314
|
-
dictionaries,
|
|
352
|
+
dictionaries, ResponseBase instances, and existing config objects.
|
|
315
353
|
|
|
316
354
|
Parameters
|
|
317
355
|
----------
|
|
@@ -328,7 +366,7 @@ def _extract_config(module: ModuleType) -> StreamlitAppConfig:
|
|
|
328
366
|
ValueError
|
|
329
367
|
If APP_CONFIG is missing from the module.
|
|
330
368
|
TypeError
|
|
331
|
-
If APP_CONFIG is not a valid type (dict,
|
|
369
|
+
If APP_CONFIG is not a valid type (dict, ResponseBase, callable,
|
|
332
370
|
or StreamlitAppConfig).
|
|
333
371
|
|
|
334
372
|
Examples
|
|
@@ -345,20 +383,20 @@ def _extract_config(module: ModuleType) -> StreamlitAppConfig:
|
|
|
345
383
|
return raw_config
|
|
346
384
|
if isinstance(raw_config, dict):
|
|
347
385
|
return _config_from_mapping(raw_config)
|
|
348
|
-
if isinstance(raw_config,
|
|
386
|
+
if isinstance(raw_config, ResponseBase):
|
|
349
387
|
return StreamlitAppConfig(response=raw_config)
|
|
350
|
-
if isinstance(raw_config, type) and issubclass(raw_config,
|
|
388
|
+
if isinstance(raw_config, type) and issubclass(raw_config, ResponseBase):
|
|
351
389
|
return StreamlitAppConfig(response=raw_config)
|
|
352
390
|
if callable(raw_config):
|
|
353
391
|
return StreamlitAppConfig(response=raw_config)
|
|
354
392
|
|
|
355
393
|
raise TypeError(
|
|
356
|
-
"APP_CONFIG must be a dict, callable,
|
|
394
|
+
"APP_CONFIG must be a dict, callable, ResponseBase, or StreamlitAppConfig."
|
|
357
395
|
)
|
|
358
396
|
|
|
359
397
|
|
|
360
|
-
def _instantiate_response(candidate: object) ->
|
|
361
|
-
"""Convert a response candidate into a
|
|
398
|
+
def _instantiate_response(candidate: object) -> ResponseBase[StructureBase]:
|
|
399
|
+
"""Convert a response candidate into a ResponseBase instance.
|
|
362
400
|
|
|
363
401
|
Handles multiple candidate types: existing instances (returned as-is),
|
|
364
402
|
classes (instantiated with no arguments), and callables (invoked to
|
|
@@ -371,31 +409,31 @@ def _instantiate_response(candidate: object) -> BaseResponse[BaseStructure]:
|
|
|
371
409
|
|
|
372
410
|
Returns
|
|
373
411
|
-------
|
|
374
|
-
|
|
412
|
+
ResponseBase[StructureBase]
|
|
375
413
|
Active response instance ready for use.
|
|
376
414
|
|
|
377
415
|
Raises
|
|
378
416
|
------
|
|
379
417
|
TypeError
|
|
380
|
-
If candidate cannot produce a
|
|
418
|
+
If candidate cannot produce a ResponseBase instance.
|
|
381
419
|
|
|
382
420
|
Examples
|
|
383
421
|
--------
|
|
384
422
|
>>> response = _instantiate_response(MyResponse)
|
|
385
|
-
>>> isinstance(response,
|
|
423
|
+
>>> isinstance(response, ResponseBase)
|
|
386
424
|
True
|
|
387
425
|
"""
|
|
388
|
-
if isinstance(candidate,
|
|
426
|
+
if isinstance(candidate, ResponseBase):
|
|
389
427
|
return candidate
|
|
390
|
-
if isinstance(candidate, type) and issubclass(candidate,
|
|
391
|
-
response_cls = cast(type[
|
|
428
|
+
if isinstance(candidate, type) and issubclass(candidate, ResponseBase):
|
|
429
|
+
response_cls = cast(type[ResponseBase[StructureBase]], candidate)
|
|
392
430
|
return response_cls() # type: ignore[call-arg]
|
|
393
431
|
if callable(candidate):
|
|
394
|
-
response_callable = cast(Callable[[],
|
|
432
|
+
response_callable = cast(Callable[[], ResponseBase[StructureBase]], candidate)
|
|
395
433
|
response = response_callable()
|
|
396
|
-
if isinstance(response,
|
|
434
|
+
if isinstance(response, ResponseBase):
|
|
397
435
|
return response
|
|
398
|
-
raise TypeError("response must be a
|
|
436
|
+
raise TypeError("response must be a ResponseBase, subclass, or callable")
|
|
399
437
|
|
|
400
438
|
|
|
401
439
|
def _config_from_mapping(raw_config: dict) -> StreamlitAppConfig:
|
|
@@ -432,36 +470,10 @@ def _config_from_mapping(raw_config: dict) -> StreamlitAppConfig:
|
|
|
432
470
|
return StreamlitAppConfig(**config_kwargs)
|
|
433
471
|
|
|
434
472
|
|
|
435
|
-
def load_app_config(
|
|
436
|
-
config_path: Path,
|
|
437
|
-
) -> StreamlitAppConfig:
|
|
438
|
-
"""Load and validate Streamlit configuration from a Python module.
|
|
439
|
-
|
|
440
|
-
Convenience function that proxies to StreamlitAppConfig.load_app_config
|
|
441
|
-
for backward compatibility.
|
|
442
|
-
|
|
443
|
-
Parameters
|
|
444
|
-
----------
|
|
445
|
-
config_path : Path
|
|
446
|
-
Filesystem path to the configuration module.
|
|
447
|
-
|
|
448
|
-
Returns
|
|
449
|
-
-------
|
|
450
|
-
StreamlitAppConfig
|
|
451
|
-
Validated configuration loaded from the module.
|
|
452
|
-
|
|
453
|
-
Examples
|
|
454
|
-
--------
|
|
455
|
-
>>> from pathlib import Path
|
|
456
|
-
>>> config = load_app_config(Path("./my_config.py"))
|
|
457
|
-
"""
|
|
458
|
-
return StreamlitAppConfig.load_app_config(config_path=config_path)
|
|
459
|
-
|
|
460
|
-
|
|
461
473
|
def _load_configuration(config_path: Path) -> StreamlitAppConfig:
|
|
462
474
|
"""Load configuration with user-friendly error handling for Streamlit.
|
|
463
475
|
|
|
464
|
-
Wraps
|
|
476
|
+
Wraps StreamlitAppRegistry.load_app_config with exception handling that
|
|
465
477
|
displays errors in the Streamlit UI and halts execution gracefully.
|
|
466
478
|
|
|
467
479
|
Parameters
|
|
@@ -486,7 +498,7 @@ def _load_configuration(config_path: Path) -> StreamlitAppConfig:
|
|
|
486
498
|
than raising exceptions that crash the app.
|
|
487
499
|
"""
|
|
488
500
|
try:
|
|
489
|
-
return
|
|
501
|
+
return StreamlitAppRegistry.load_app_config(config_path=config_path)
|
|
490
502
|
except Exception as exc: # pragma: no cover - surfaced in UI
|
|
491
503
|
import streamlit as st # type: ignore[import-not-found]
|
|
492
504
|
|
|
@@ -497,6 +509,6 @@ def _load_configuration(config_path: Path) -> StreamlitAppConfig:
|
|
|
497
509
|
|
|
498
510
|
__all__ = [
|
|
499
511
|
"StreamlitAppConfig",
|
|
500
|
-
"
|
|
512
|
+
"StreamlitAppRegistry",
|
|
501
513
|
"_load_configuration",
|
|
502
514
|
]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from openai_sdk_helpers.agent.search.web import WebAgentSearch
|
|
5
5
|
from openai_sdk_helpers.config import OpenAISettings
|
|
6
|
-
from openai_sdk_helpers.response.base import
|
|
6
|
+
from openai_sdk_helpers.response.base import ResponseBase
|
|
7
7
|
from openai_sdk_helpers.structure.web_search import WebSearchStructure
|
|
8
8
|
from openai_sdk_helpers.structure.prompt import PromptStructure
|
|
9
9
|
from openai_sdk_helpers.tools import ToolSpec, build_tool_definitions
|
|
@@ -11,7 +11,7 @@ from openai_sdk_helpers.utils import coerce_jsonable, customJSONEncoder
|
|
|
11
11
|
from openai_sdk_helpers.environment import DEFAULT_MODEL
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class StreamlitWebSearch(
|
|
14
|
+
class StreamlitWebSearch(ResponseBase[WebSearchStructure]):
|
|
15
15
|
"""Response tuned for a generic chat experience with structured output.
|
|
16
16
|
|
|
17
17
|
Methods
|
|
@@ -7,7 +7,7 @@ generating OpenAI-compatible schema definitions.
|
|
|
7
7
|
|
|
8
8
|
Classes
|
|
9
9
|
-------
|
|
10
|
-
|
|
10
|
+
StructureBase
|
|
11
11
|
Base class for all structured output models with schema generation.
|
|
12
12
|
SchemaOptions
|
|
13
13
|
Configuration options for schema generation behavior.
|
|
@@ -27,6 +27,8 @@ SummaryStructure
|
|
|
27
27
|
Basic summary with topic breakdown.
|
|
28
28
|
ExtendedSummaryStructure
|
|
29
29
|
Enhanced summary with additional metadata.
|
|
30
|
+
TranslationStructure
|
|
31
|
+
Structured translation output.
|
|
30
32
|
WebSearchStructure
|
|
31
33
|
Web search results structure.
|
|
32
34
|
WebSearchPlanStructure
|
|
@@ -74,12 +76,13 @@ from .plan import *
|
|
|
74
76
|
from .prompt import PromptStructure
|
|
75
77
|
from .responses import *
|
|
76
78
|
from .summary import *
|
|
79
|
+
from .translation import TranslationStructure
|
|
77
80
|
from .validation import ValidationResultStructure
|
|
78
81
|
from .vector_search import *
|
|
79
82
|
from .web_search import *
|
|
80
83
|
|
|
81
84
|
__all__ = [
|
|
82
|
-
"
|
|
85
|
+
"StructureBase",
|
|
83
86
|
"SchemaOptions",
|
|
84
87
|
"spec_field",
|
|
85
88
|
"AgentBlueprint",
|
|
@@ -93,6 +96,7 @@ __all__ = [
|
|
|
93
96
|
"SummaryTopic",
|
|
94
97
|
"SummaryStructure",
|
|
95
98
|
"ExtendedSummaryStructure",
|
|
99
|
+
"TranslationStructure",
|
|
96
100
|
"WebSearchStructure",
|
|
97
101
|
"WebSearchPlanStructure",
|
|
98
102
|
"WebSearchItemStructure",
|
|
@@ -6,12 +6,12 @@ converting them into executable plans with validation and deployment steps.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from .base import
|
|
9
|
+
from .base import StructureBase, spec_field
|
|
10
10
|
from .plan import PlanStructure, TaskStructure
|
|
11
11
|
from .plan.enum import AgentEnum
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class AgentBlueprint(
|
|
14
|
+
class AgentBlueprint(StructureBase):
|
|
15
15
|
"""Capture requirements for creating a new agent.
|
|
16
16
|
|
|
17
17
|
Defines the complete specification for an agent including mission,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Base classes for structured output models.
|
|
2
2
|
|
|
3
|
-
This module provides the foundational
|
|
3
|
+
This module provides the foundational StructureBase class and utilities for
|
|
4
4
|
defining Pydantic-based structured output models with OpenAI-compatible schema
|
|
5
5
|
generation, validation, and serialization.
|
|
6
6
|
"""
|
|
@@ -12,7 +12,6 @@ import ast
|
|
|
12
12
|
import inspect
|
|
13
13
|
import json
|
|
14
14
|
import logging
|
|
15
|
-
from collections.abc import Mapping, Sequence
|
|
16
15
|
from dataclasses import dataclass
|
|
17
16
|
from enum import Enum
|
|
18
17
|
from pathlib import Path
|
|
@@ -32,13 +31,13 @@ from openai.types.responses.response_text_config_param import ResponseTextConfig
|
|
|
32
31
|
|
|
33
32
|
# Internal imports
|
|
34
33
|
|
|
35
|
-
from ..utils import check_filepath,
|
|
34
|
+
from ..utils import check_filepath, log, BaseModelJSONSerializable
|
|
36
35
|
|
|
37
|
-
T = TypeVar("T", bound="
|
|
36
|
+
T = TypeVar("T", bound="StructureBase")
|
|
38
37
|
DEFAULT_DATA_PATH: Path | None = None
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
class
|
|
40
|
+
class StructureBase(BaseModelJSONSerializable):
|
|
42
41
|
"""Base class for structured output models with schema generation.
|
|
43
42
|
|
|
44
43
|
Provides Pydantic-based schema definition and serialization utilities
|
|
@@ -93,8 +92,8 @@ class BaseStructure(BaseModel):
|
|
|
93
92
|
--------
|
|
94
93
|
Define a custom structure:
|
|
95
94
|
|
|
96
|
-
>>> from openai_sdk_helpers.structure import
|
|
97
|
-
>>> class MyOutput(
|
|
95
|
+
>>> from openai_sdk_helpers.structure import StructureBase, spec_field
|
|
96
|
+
>>> class MyOutput(StructureBase):
|
|
98
97
|
... title: str = spec_field("title", description="The title")
|
|
99
98
|
... score: float = spec_field("score", description="Quality score")
|
|
100
99
|
|
|
@@ -454,7 +453,7 @@ class BaseStructure(BaseModel):
|
|
|
454
453
|
schema = cls.get_schema()
|
|
455
454
|
if cls.DATA_PATH is None:
|
|
456
455
|
raise RuntimeError(
|
|
457
|
-
"DATA_PATH is not set. Set
|
|
456
|
+
"DATA_PATH is not set. Set StructureBase.DATA_PATH before saving."
|
|
458
457
|
)
|
|
459
458
|
file_path = cls.DATA_PATH / f"{cls.__name__}_schema.json"
|
|
460
459
|
check_filepath(file_path)
|
|
@@ -462,96 +461,6 @@ class BaseStructure(BaseModel):
|
|
|
462
461
|
json.dump(schema, file_handle, indent=2, ensure_ascii=False)
|
|
463
462
|
return file_path
|
|
464
463
|
|
|
465
|
-
def to_json(self) -> dict[str, Any]:
|
|
466
|
-
"""Serialize the instance to a JSON-compatible dictionary.
|
|
467
|
-
|
|
468
|
-
Converts the Pydantic model instance to a dictionary suitable for
|
|
469
|
-
JSON serialization. Enum members are converted to their values,
|
|
470
|
-
and nested structures are recursively processed.
|
|
471
|
-
|
|
472
|
-
Returns
|
|
473
|
-
-------
|
|
474
|
-
dict[str, Any]
|
|
475
|
-
Model instance serialized as a dictionary with JSON-compatible types.
|
|
476
|
-
|
|
477
|
-
Examples
|
|
478
|
-
--------
|
|
479
|
-
>>> instance = MyStructure(title="Test", score=0.95)
|
|
480
|
-
>>> data = instance.to_json()
|
|
481
|
-
>>> print(json.dumps(data))
|
|
482
|
-
"""
|
|
483
|
-
|
|
484
|
-
def convert(obj: Any) -> Any:
|
|
485
|
-
if isinstance(obj, Enum):
|
|
486
|
-
return obj.value
|
|
487
|
-
if isinstance(obj, BaseStructure):
|
|
488
|
-
return obj.to_json()
|
|
489
|
-
if isinstance(obj, Mapping):
|
|
490
|
-
return {str(k): convert(v) for k, v in obj.items()}
|
|
491
|
-
if isinstance(obj, Sequence) and not isinstance(
|
|
492
|
-
obj, (str, bytes, bytearray)
|
|
493
|
-
):
|
|
494
|
-
return [convert(item) for item in obj]
|
|
495
|
-
return obj
|
|
496
|
-
|
|
497
|
-
payload = convert(self.model_dump())
|
|
498
|
-
|
|
499
|
-
def is_list_field(field) -> bool:
|
|
500
|
-
annotation = getattr(field, "annotation", None)
|
|
501
|
-
if annotation is None:
|
|
502
|
-
return False
|
|
503
|
-
|
|
504
|
-
origins_to_match = {list, Sequence, tuple, set}
|
|
505
|
-
|
|
506
|
-
origin = get_origin(annotation)
|
|
507
|
-
if origin in origins_to_match or annotation in origins_to_match:
|
|
508
|
-
return True
|
|
509
|
-
|
|
510
|
-
# Check for Union types (e.g., list[str] | None)
|
|
511
|
-
if origin is not None:
|
|
512
|
-
# Handle Union by checking args
|
|
513
|
-
args = get_args(annotation)
|
|
514
|
-
return any(
|
|
515
|
-
get_origin(arg) in origins_to_match or arg in origins_to_match
|
|
516
|
-
for arg in args
|
|
517
|
-
)
|
|
518
|
-
return False
|
|
519
|
-
|
|
520
|
-
for name, field in self.__class__.model_fields.items():
|
|
521
|
-
if name not in payload:
|
|
522
|
-
continue
|
|
523
|
-
if not is_list_field(field):
|
|
524
|
-
continue
|
|
525
|
-
value = payload[name]
|
|
526
|
-
if value is None:
|
|
527
|
-
continue
|
|
528
|
-
if isinstance(value, (str, bytes, bytearray)):
|
|
529
|
-
payload[name] = [value]
|
|
530
|
-
elif not isinstance(value, list):
|
|
531
|
-
payload[name] = [value]
|
|
532
|
-
|
|
533
|
-
return payload
|
|
534
|
-
|
|
535
|
-
def to_json_file(self, filepath: str) -> str:
|
|
536
|
-
"""Write :meth:`to_json` output to ``filepath``.
|
|
537
|
-
|
|
538
|
-
Parameters
|
|
539
|
-
----------
|
|
540
|
-
filepath : str
|
|
541
|
-
Destination path for the JSON file.
|
|
542
|
-
|
|
543
|
-
Returns
|
|
544
|
-
-------
|
|
545
|
-
str
|
|
546
|
-
Path to the written file.
|
|
547
|
-
"""
|
|
548
|
-
check_filepath(fullfilepath=filepath)
|
|
549
|
-
with open(file=filepath, mode="w", encoding="utf-8") as f:
|
|
550
|
-
json.dump(
|
|
551
|
-
self.to_json(), f, ensure_ascii=False, indent=4, cls=customJSONEncoder
|
|
552
|
-
)
|
|
553
|
-
return filepath
|
|
554
|
-
|
|
555
464
|
@classmethod
|
|
556
465
|
def _extract_enum_class(cls, field_type: Any) -> type[Enum] | None:
|
|
557
466
|
"""Extract an Enum class from a field's type annotation.
|
|
@@ -772,7 +681,7 @@ class BaseStructure(BaseModel):
|
|
|
772
681
|
"""
|
|
773
682
|
return "\n".join(
|
|
774
683
|
[
|
|
775
|
-
|
|
684
|
+
StructureBase.format_output(field, value=value)
|
|
776
685
|
for field, value in self.model_dump().items()
|
|
777
686
|
]
|
|
778
687
|
)
|
|
@@ -14,12 +14,12 @@ from typing import Any, Awaitable, Coroutine, cast
|
|
|
14
14
|
from collections.abc import Mapping
|
|
15
15
|
|
|
16
16
|
from .enum import AgentEnum
|
|
17
|
-
from ..base import
|
|
17
|
+
from ..base import StructureBase, spec_field
|
|
18
18
|
from .task import TaskStructure
|
|
19
19
|
from .types import AgentCallable, AgentRegistry
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class PlanStructure(
|
|
22
|
+
class PlanStructure(StructureBase):
|
|
23
23
|
"""Structured representation of an ordered list of agent tasks.
|
|
24
24
|
|
|
25
25
|
Represents a complete execution plan consisting of multiple agent tasks
|
|
@@ -12,10 +12,10 @@ from typing import Literal
|
|
|
12
12
|
from pydantic import field_validator
|
|
13
13
|
|
|
14
14
|
from .enum import AgentEnum
|
|
15
|
-
from ..base import
|
|
15
|
+
from ..base import StructureBase, spec_field
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class TaskStructure(
|
|
18
|
+
class TaskStructure(StructureBase):
|
|
19
19
|
"""Structured representation of a single agent task.
|
|
20
20
|
|
|
21
21
|
Represents one task in an agent execution plan, including its type,
|
|
@@ -140,13 +140,13 @@ class TaskStructure(BaseStructure):
|
|
|
140
140
|
"""
|
|
141
141
|
return "\n".join(
|
|
142
142
|
[
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
StructureBase.format_output("Task type", value=self.task_type),
|
|
144
|
+
StructureBase.format_output("Prompt", value=self.prompt),
|
|
145
|
+
StructureBase.format_output("Context", value=self.context),
|
|
146
|
+
StructureBase.format_output("Status", value=self.status),
|
|
147
|
+
StructureBase.format_output("Start date", value=self.start_date),
|
|
148
|
+
StructureBase.format_output("End date", value=self.end_date),
|
|
149
|
+
StructureBase.format_output("Results", value=self.results),
|
|
150
150
|
]
|
|
151
151
|
)
|
|
152
152
|
|
|
@@ -6,10 +6,10 @@ used in OpenAI API requests.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from .base import
|
|
9
|
+
from .base import StructureBase, spec_field
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class PromptStructure(
|
|
12
|
+
class PromptStructure(StructureBase):
|
|
13
13
|
"""Structured representation of prompt text for OpenAI API requests.
|
|
14
14
|
|
|
15
15
|
Simple structure containing a single prompt string with examples.
|