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.
Files changed (58) hide show
  1. openai_sdk_helpers/__init__.py +6 -6
  2. openai_sdk_helpers/agent/__init__.py +4 -4
  3. openai_sdk_helpers/agent/base.py +254 -113
  4. openai_sdk_helpers/agent/config.py +91 -37
  5. openai_sdk_helpers/agent/coordination.py +64 -28
  6. openai_sdk_helpers/agent/runner.py +16 -15
  7. openai_sdk_helpers/agent/search/base.py +94 -45
  8. openai_sdk_helpers/agent/search/vector.py +86 -58
  9. openai_sdk_helpers/agent/search/web.py +71 -40
  10. openai_sdk_helpers/agent/summarizer.py +32 -7
  11. openai_sdk_helpers/agent/translator.py +57 -24
  12. openai_sdk_helpers/agent/validation.py +34 -4
  13. openai_sdk_helpers/cli.py +42 -0
  14. openai_sdk_helpers/config.py +0 -1
  15. openai_sdk_helpers/environment.py +3 -2
  16. openai_sdk_helpers/files_api.py +35 -3
  17. openai_sdk_helpers/prompt/base.py +6 -0
  18. openai_sdk_helpers/response/__init__.py +3 -3
  19. openai_sdk_helpers/response/base.py +142 -73
  20. openai_sdk_helpers/response/config.py +63 -58
  21. openai_sdk_helpers/response/files.py +5 -5
  22. openai_sdk_helpers/response/messages.py +3 -3
  23. openai_sdk_helpers/response/runner.py +7 -7
  24. openai_sdk_helpers/response/tool_call.py +94 -4
  25. openai_sdk_helpers/response/vector_store.py +3 -3
  26. openai_sdk_helpers/streamlit_app/__init__.py +4 -4
  27. openai_sdk_helpers/streamlit_app/app.py +16 -16
  28. openai_sdk_helpers/streamlit_app/config.py +82 -70
  29. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +2 -2
  30. openai_sdk_helpers/structure/__init__.py +6 -2
  31. openai_sdk_helpers/structure/agent_blueprint.py +2 -2
  32. openai_sdk_helpers/structure/base.py +8 -99
  33. openai_sdk_helpers/structure/plan/plan.py +2 -2
  34. openai_sdk_helpers/structure/plan/task.py +9 -9
  35. openai_sdk_helpers/structure/prompt.py +2 -2
  36. openai_sdk_helpers/structure/responses.py +15 -15
  37. openai_sdk_helpers/structure/summary.py +3 -3
  38. openai_sdk_helpers/structure/translation.py +32 -0
  39. openai_sdk_helpers/structure/validation.py +2 -2
  40. openai_sdk_helpers/structure/vector_search.py +7 -7
  41. openai_sdk_helpers/structure/web_search.py +6 -6
  42. openai_sdk_helpers/tools.py +41 -15
  43. openai_sdk_helpers/utils/__init__.py +19 -5
  44. openai_sdk_helpers/utils/json/__init__.py +55 -0
  45. openai_sdk_helpers/utils/json/base_model.py +181 -0
  46. openai_sdk_helpers/utils/{json_utils.py → json/data_class.py} +33 -68
  47. openai_sdk_helpers/utils/json/ref.py +113 -0
  48. openai_sdk_helpers/utils/json/utils.py +203 -0
  49. openai_sdk_helpers/utils/output_validation.py +21 -1
  50. openai_sdk_helpers/utils/path_utils.py +34 -1
  51. openai_sdk_helpers/utils/registry.py +46 -8
  52. openai_sdk_helpers/vector_storage/storage.py +10 -0
  53. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/METADATA +7 -7
  54. openai_sdk_helpers-0.4.1.dist-info/RECORD +86 -0
  55. openai_sdk_helpers-0.3.0.dist-info/RECORD +0 -81
  56. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/WHEEL +0 -0
  57. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/entry_points.txt +0 -0
  58. {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 BaseModel, ConfigDict, Field, field_validator, model_validator
14
+ from pydantic import ConfigDict, Field, field_validator, model_validator
15
15
 
16
- from openai_sdk_helpers.response.base import BaseResponse
17
- from openai_sdk_helpers.structure.base import BaseStructure
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(BaseModel):
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
- response : BaseResponse, type[BaseResponse], Callable, or None
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 BaseResponse.
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
- response: BaseResponse[BaseStructure] | type[BaseResponse] | Callable | None = (
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 ``BaseResponse`` subclass, instance, or callable that returns"
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: BaseResponse[BaseStructure] | type[BaseResponse] | Callable | None
134
- ) -> BaseResponse[BaseStructure] | type[BaseResponse] | Callable | None:
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 BaseResponse
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 : BaseResponse, type[BaseResponse], Callable, or None
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
- BaseResponse, type[BaseResponse], Callable, or None
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 BaseResponse, subclass, or callable.
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, BaseResponse):
162
+ if isinstance(value, ResponseBase):
158
163
  return value
159
- if isinstance(value, type) and issubclass(value, BaseResponse):
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 BaseResponse, subclass, or callable")
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) -> BaseResponse[BaseStructure]:
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 BaseResponse instance ready for chat interactions.
213
+ into an active ResponseBase instance ready for chat interactions.
209
214
 
210
215
  Returns
211
216
  -------
212
- BaseResponse[BaseStructure]
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 BaseResponse.
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 = StreamlitAppConfig.load_app_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, BaseResponse instances, and existing config objects.
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, BaseResponse, callable,
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, BaseResponse):
386
+ if isinstance(raw_config, ResponseBase):
349
387
  return StreamlitAppConfig(response=raw_config)
350
- if isinstance(raw_config, type) and issubclass(raw_config, BaseResponse):
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, BaseResponse, or StreamlitAppConfig."
394
+ "APP_CONFIG must be a dict, callable, ResponseBase, or StreamlitAppConfig."
357
395
  )
358
396
 
359
397
 
360
- def _instantiate_response(candidate: object) -> BaseResponse[BaseStructure]:
361
- """Convert a response candidate into a BaseResponse instance.
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
- BaseResponse[BaseStructure]
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 BaseResponse instance.
418
+ If candidate cannot produce a ResponseBase instance.
381
419
 
382
420
  Examples
383
421
  --------
384
422
  >>> response = _instantiate_response(MyResponse)
385
- >>> isinstance(response, BaseResponse)
423
+ >>> isinstance(response, ResponseBase)
386
424
  True
387
425
  """
388
- if isinstance(candidate, BaseResponse):
426
+ if isinstance(candidate, ResponseBase):
389
427
  return candidate
390
- if isinstance(candidate, type) and issubclass(candidate, BaseResponse):
391
- response_cls = cast(type[BaseResponse[BaseStructure]], candidate)
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[[], BaseResponse[BaseStructure]], candidate)
432
+ response_callable = cast(Callable[[], ResponseBase[StructureBase]], candidate)
395
433
  response = response_callable()
396
- if isinstance(response, BaseResponse):
434
+ if isinstance(response, ResponseBase):
397
435
  return response
398
- raise TypeError("response must be a BaseResponse, subclass, or callable")
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 StreamlitAppConfig.load_app_config with exception handling that
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 StreamlitAppConfig.load_app_config(config_path=config_path)
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
- "load_app_config",
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 BaseResponse
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(BaseResponse[WebSearchStructure]):
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
- BaseStructure
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
- "BaseStructure",
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 BaseStructure, spec_field
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(BaseStructure):
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 BaseStructure class and utilities for
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, customJSONEncoder, log
34
+ from ..utils import check_filepath, log, BaseModelJSONSerializable
36
35
 
37
- T = TypeVar("T", bound="BaseStructure")
36
+ T = TypeVar("T", bound="StructureBase")
38
37
  DEFAULT_DATA_PATH: Path | None = None
39
38
 
40
39
 
41
- class BaseStructure(BaseModel):
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 BaseStructure, spec_field
97
- >>> class MyOutput(BaseStructure):
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 BaseStructure.DATA_PATH before saving."
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
- BaseStructure.format_output(field, value=value)
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 BaseStructure, spec_field
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(BaseStructure):
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 BaseStructure, spec_field
15
+ from ..base import StructureBase, spec_field
16
16
 
17
17
 
18
- class TaskStructure(BaseStructure):
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
- BaseStructure.format_output("Task type", value=self.task_type),
144
- BaseStructure.format_output("Prompt", value=self.prompt),
145
- BaseStructure.format_output("Context", value=self.context),
146
- BaseStructure.format_output("Status", value=self.status),
147
- BaseStructure.format_output("Start date", value=self.start_date),
148
- BaseStructure.format_output("End date", value=self.end_date),
149
- BaseStructure.format_output("Results", value=self.results),
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 BaseStructure, spec_field
9
+ from .base import StructureBase, spec_field
10
10
 
11
11
 
12
- class PromptStructure(BaseStructure):
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.