openai-sdk-helpers 0.1.4__py3-none-any.whl → 0.2.0__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.
@@ -44,6 +44,14 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
44
44
  Return a JSON-serializable snapshot of stored project data.
45
45
  save()
46
46
  Persist the stored project data to a JSON file.
47
+ to_json()
48
+ Return a JSON-compatible dict representation (inherited from JSONSerializable).
49
+ to_json_file(filepath)
50
+ Write serialized JSON data to a file path (inherited from JSONSerializable).
51
+ from_json(data)
52
+ Create an instance from a JSON-compatible dict (class method, inherited from JSONSerializable).
53
+ from_json_file(filepath)
54
+ Load an instance from a JSON file (class method, inherited from JSONSerializable).
47
55
  """
48
56
 
49
57
  def __init__(
@@ -126,10 +126,10 @@ class BaseResponse(Generic[T]):
126
126
  instructions: str,
127
127
  tools: list | None,
128
128
  output_structure: type[T] | None,
129
- tool_handlers: dict[str, ToolHandler],
130
- openai_settings: OpenAISettings,
131
129
  system_vector_store: list[str] | None = None,
132
130
  data_path: Path | str | None = None,
131
+ tool_handlers: dict[str, ToolHandler] | None = None,
132
+ openai_settings: OpenAISettings | None = None,
133
133
  ) -> None:
134
134
  """Initialize a response session with OpenAI configuration.
135
135
 
@@ -150,18 +150,19 @@ class BaseResponse(Generic[T]):
150
150
  Structure class used to parse tool call outputs. When provided,
151
151
  the schema is automatically generated using the structure's
152
152
  response_format() method. Pass None for unstructured responses.
153
- tool_handlers : dict[str, ToolHandler]
154
- Mapping from tool names to callable handlers. Each handler receives
155
- a ResponseFunctionToolCall and returns a string or any serializable
156
- result.
157
- openai_settings : OpenAISettings
158
- Fully configured OpenAI settings with API key and default model.
159
153
  system_vector_store : list[str] or None, default None
160
154
  Optional list of vector store names to attach as system context.
161
155
  data_path : Path, str, or None, default None
162
156
  Optional absolute directory path for storing artifacts. If not provided,
163
157
  defaults to get_data_path(class_name). Session files are saved as
164
158
  data_path / uuid.json.
159
+ tool_handlers : dict[str, ToolHandler] or None, default None
160
+ Mapping from tool names to callable handlers. Each handler receives
161
+ a ResponseFunctionToolCall and returns a string or any serializable
162
+ result. Defaults to an empty dict when not provided.
163
+ openai_settings : OpenAISettings or None, default None
164
+ Fully configured OpenAI settings with API key and default model.
165
+ Required for normal operation.
165
166
 
166
167
  Raises
167
168
  ------
@@ -184,6 +185,11 @@ class BaseResponse(Generic[T]):
184
185
  ... openai_settings=settings,
185
186
  ... )
186
187
  """
188
+ if tool_handlers is None:
189
+ tool_handlers = {}
190
+ if openai_settings is None:
191
+ raise ValueError("openai_settings is required")
192
+
187
193
  self._tool_handlers = tool_handlers
188
194
  self._name = name
189
195
 
@@ -10,6 +10,8 @@ from openai.types.responses.response_text_config_param import ResponseTextConfig
10
10
  from ..config import OpenAISettings
11
11
  from ..structure.base import BaseStructure
12
12
  from ..response.base import BaseResponse, ToolHandler
13
+ from ..utils import JSONSerializable
14
+ from ..utils.path_utils import ensure_directory
13
15
 
14
16
  TIn = TypeVar("TIn", bound="BaseStructure")
15
17
  TOut = TypeVar("TOut", bound="BaseStructure")
@@ -135,6 +137,45 @@ class ResponseRegistry:
135
137
  """
136
138
  self._configs.clear()
137
139
 
140
+ def save_to_directory(self, path: Path | str) -> None:
141
+ """Export all registered configurations to JSON files in a directory.
142
+
143
+ Serializes each registered ResponseConfiguration to an individual JSON file
144
+ named after the configuration. Creates the directory if it does not exist.
145
+
146
+ Parameters
147
+ ----------
148
+ path : Path or str
149
+ Directory path where JSON files will be saved. Will be created if
150
+ it does not already exist.
151
+
152
+ Returns
153
+ -------
154
+ None
155
+
156
+ Raises
157
+ ------
158
+ OSError
159
+ If the directory cannot be created or files cannot be written.
160
+
161
+ Examples
162
+ --------
163
+ >>> registry = ResponseRegistry()
164
+ >>> registry.save_to_directory("./data")
165
+ >>> registry.save_to_directory(Path("exports"))
166
+ """
167
+ dir_path = ensure_directory(Path(path))
168
+ config_names = self.list_names()
169
+
170
+ if not config_names:
171
+ return
172
+
173
+ for config_name in config_names:
174
+ config = self.get(config_name)
175
+ filename = f"{config_name}.json"
176
+ filepath = dir_path / filename
177
+ config.to_json_file(filepath)
178
+
138
179
 
139
180
  # Global default registry instance
140
181
  _default_registry = ResponseRegistry()
@@ -158,12 +199,13 @@ def get_default_registry() -> ResponseRegistry:
158
199
 
159
200
 
160
201
  @dataclass(frozen=True, slots=True)
161
- class ResponseConfiguration(Generic[TIn, TOut]):
202
+ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
162
203
  """
163
204
  Represent an immutable configuration describing input and output structures.
164
205
 
165
206
  Encapsulate all metadata required to define how a request is interpreted and
166
207
  how a response is structured, while enforcing strict type and runtime safety.
208
+ Inherits from JSONSerializable to support serialization to JSON format.
167
209
 
168
210
  Parameters
169
211
  ----------
@@ -181,6 +223,12 @@ class ResponseConfiguration(Generic[TIn, TOut]):
181
223
  Structure class used to format or validate output. Schema is
182
224
  automatically generated from this structure. Must subclass
183
225
  BaseStructure. Default is None.
226
+ system_vector_store : list[str], optional
227
+ Optional list of vector store names to attach as system context.
228
+ Default is None.
229
+ data_path : Path, str, or None, optional
230
+ Optional absolute directory path for storing artifacts. If not provided,
231
+ defaults to get_data_path(class_name). Default is None.
184
232
 
185
233
  Raises
186
234
  ------
@@ -201,6 +249,14 @@ class ResponseConfiguration(Generic[TIn, TOut]):
201
249
  Validate configuration invariants and enforce BaseStructure subclassing.
202
250
  instructions_text
203
251
  Return the resolved instruction content as a string.
252
+ to_json()
253
+ Return a JSON-compatible dict representation (inherited from JSONSerializable).
254
+ to_json_file(filepath)
255
+ Write serialized JSON data to a file path (inherited from JSONSerializable).
256
+ from_json(data)
257
+ Create an instance from a JSON-compatible dict (class method, inherited from JSONSerializable).
258
+ from_json_file(filepath)
259
+ Load an instance from a JSON file (class method, inherited from JSONSerializable).
204
260
 
205
261
  Examples
206
262
  --------
@@ -219,6 +275,8 @@ class ResponseConfiguration(Generic[TIn, TOut]):
219
275
  tools: Optional[list]
220
276
  input_structure: Optional[Type[TIn]]
221
277
  output_structure: Optional[Type[TOut]]
278
+ system_vector_store: Optional[list[str]] = None
279
+ data_path: Optional[Path | str] = None
222
280
 
223
281
  def __post_init__(self) -> None:
224
282
  """
@@ -328,6 +386,8 @@ class ResponseConfiguration(Generic[TIn, TOut]):
328
386
  instructions=instructions,
329
387
  tools=self.tools,
330
388
  output_structure=self.output_structure,
389
+ system_vector_store=self.system_vector_store,
390
+ data_path=self.data_path,
331
391
  tool_handlers=tool_handlers,
332
392
  openai_settings=openai_settings,
333
393
  )
@@ -50,6 +50,14 @@ class ResponseMessage(JSONSerializable):
50
50
  -------
51
51
  to_openai_format()
52
52
  Return the message content in OpenAI API format.
53
+ to_json()
54
+ Return a JSON-compatible dict representation (inherited from JSONSerializable).
55
+ to_json_file(filepath)
56
+ Write serialized JSON data to a file path (inherited from JSONSerializable).
57
+ from_json(data)
58
+ Create an instance from a JSON-compatible dict (class method, inherited from JSONSerializable).
59
+ from_json_file(filepath)
60
+ Load an instance from a JSON file (class method, inherited from JSONSerializable).
53
61
  """
54
62
 
55
63
  role: str # "user", "assistant", "tool", etc.
@@ -113,6 +121,14 @@ class ResponseMessages(JSONSerializable):
113
121
  Return the most recent tool message or None.
114
122
  get_last_user_message()
115
123
  Return the most recent user message or None.
124
+ to_json()
125
+ Return a JSON-compatible dict representation (inherited from JSONSerializable).
126
+ to_json_file(filepath)
127
+ Write serialized JSON data to a file path (inherited from JSONSerializable).
128
+ from_json(data)
129
+ Create an instance from a JSON-compatible dict (class method, inherited from JSONSerializable).
130
+ from_json_file(filepath)
131
+ Load an instance from a JSON file (class method, inherited from JSONSerializable).
116
132
  """
117
133
 
118
134
  messages: list[ResponseMessage] = field(default_factory=list)
@@ -3,14 +3,16 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- from dataclasses import asdict, is_dataclass
6
+ from dataclasses import asdict, fields, is_dataclass
7
7
  from datetime import datetime
8
8
  from enum import Enum
9
9
  from pathlib import Path
10
- from typing import Any
10
+ from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
11
11
 
12
12
  from .path_utils import check_filepath
13
13
 
14
+ T = TypeVar("T", bound="JSONSerializable")
15
+
14
16
 
15
17
  def _to_jsonable(value: Any) -> Any:
16
18
  """Convert common helper types to JSON-serializable forms."""
@@ -65,7 +67,19 @@ class customJSONEncoder(json.JSONEncoder):
65
67
 
66
68
 
67
69
  class JSONSerializable:
68
- """Mixin for classes that can be serialized to JSON."""
70
+ """Mixin for classes that can be serialized to and from JSON.
71
+
72
+ Methods
73
+ -------
74
+ to_json()
75
+ Return a JSON-compatible dict representation.
76
+ to_json_file(filepath)
77
+ Write serialized JSON data to a file path.
78
+ from_json(data)
79
+ Create an instance from a JSON-compatible dict (class method).
80
+ from_json_file(filepath)
81
+ Load an instance from a JSON file (class method).
82
+ """
69
83
 
70
84
  def to_json(self) -> dict[str, Any]:
71
85
  """Return a JSON-compatible dict representation."""
@@ -77,7 +91,18 @@ class JSONSerializable:
77
91
  return _to_jsonable(self.__dict__)
78
92
 
79
93
  def to_json_file(self, filepath: str | Path) -> str:
80
- """Write serialized JSON data to a file path."""
94
+ """Write serialized JSON data to a file path.
95
+
96
+ Parameters
97
+ ----------
98
+ filepath : str or Path
99
+ Path where the JSON file will be written.
100
+
101
+ Returns
102
+ -------
103
+ str
104
+ Absolute path to the written file.
105
+ """
81
106
  target = Path(filepath)
82
107
  check_filepath(fullfilepath=str(target))
83
108
  with open(target, "w", encoding="utf-8") as handle:
@@ -90,6 +115,105 @@ class JSONSerializable:
90
115
  )
91
116
  return str(target)
92
117
 
118
+ @classmethod
119
+ def from_json(cls: type[T], data: dict[str, Any]) -> T:
120
+ """Create an instance from a JSON-compatible dict.
121
+
122
+ For dataclasses, this reconstructs Path objects and passes the
123
+ dict keys directly as constructor arguments.
124
+
125
+ Parameters
126
+ ----------
127
+ data : dict[str, Any]
128
+ JSON-compatible dictionary containing the instance data.
129
+
130
+ Returns
131
+ -------
132
+ T
133
+ New instance of the class.
134
+
135
+ Examples
136
+ --------
137
+ >>> json_data = {"name": "test", "path": "/tmp/data"}
138
+ >>> instance = MyClass.from_json(json_data)
139
+ """
140
+ if is_dataclass(cls):
141
+ # Get resolved field types using get_type_hints
142
+ try:
143
+ field_types = get_type_hints(cls)
144
+ except Exception:
145
+ # Fallback to raw annotations if get_type_hints fails
146
+ field_types = {f.name: f.type for f in fields(cls)}
147
+
148
+ converted_data = {}
149
+
150
+ for key, value in data.items():
151
+ if key in field_types:
152
+ field_type = field_types[key]
153
+
154
+ # Check if this field should be converted to Path
155
+ should_convert_to_path = False
156
+
157
+ if field_type is Path:
158
+ should_convert_to_path = True
159
+ else:
160
+ # Handle Union/Optional types
161
+ origin = get_origin(field_type)
162
+ if origin is Union:
163
+ type_args = get_args(field_type)
164
+ # Check if Path is one of the union types
165
+ if Path in type_args:
166
+ should_convert_to_path = True
167
+
168
+ # Convert string to Path if needed
169
+ if (
170
+ should_convert_to_path
171
+ and value is not None
172
+ and isinstance(value, str)
173
+ ):
174
+ converted_data[key] = Path(value)
175
+ else:
176
+ converted_data[key] = value
177
+ else:
178
+ converted_data[key] = value
179
+
180
+ return cls(**converted_data) # type: ignore[return-value]
181
+
182
+ # For non-dataclass types, try to instantiate with data as kwargs
183
+ return cls(**data) # type: ignore[return-value]
184
+
185
+ @classmethod
186
+ def from_json_file(cls: type[T], filepath: str | Path) -> T:
187
+ """Load an instance from a JSON file.
188
+
189
+ Parameters
190
+ ----------
191
+ filepath : str or Path
192
+ Path to the JSON file to load.
193
+
194
+ Returns
195
+ -------
196
+ T
197
+ New instance of the class loaded from the file.
198
+
199
+ Raises
200
+ ------
201
+ FileNotFoundError
202
+ If the file does not exist.
203
+
204
+ Examples
205
+ --------
206
+ >>> instance = MyClass.from_json_file("config.json")
207
+ """
208
+ target = Path(filepath)
209
+ if not target.exists():
210
+ raise FileNotFoundError(f"JSON file not found: {target}")
211
+
212
+ with open(target, "r", encoding="utf-8") as handle:
213
+ data = json.load(handle)
214
+
215
+ return cls.from_json(data)
216
+
93
217
 
94
218
  __all__ = [
95
219
  "coerce_jsonable",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openai-sdk-helpers
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: Composable helpers for OpenAI SDK agents, prompts, and storage
5
5
  Author: openai-sdk-helpers maintainers
6
6
  License: MIT
@@ -14,7 +14,7 @@ openai_sdk_helpers/types.py,sha256=xzldCRfwCZ3rZl18IBmfgA-PVdoZKSWNrlSIhirumSo,1
14
14
  openai_sdk_helpers/agent/__init__.py,sha256=giowU8jke0z0h7FFUG9V6Vssja8AYwvJMQbiMb3s64k,960
15
15
  openai_sdk_helpers/agent/base.py,sha256=8ZkW57vL8_fYzuLr6f9kMvBChYq5lN5vQ8MMJtcWD9s,11784
16
16
  openai_sdk_helpers/agent/config.py,sha256=htqy5bcrJeMf3rIpRdL9CKlYwyQI4po420rcgR3i8XI,1971
17
- openai_sdk_helpers/agent/coordination.py,sha256=knhQtOyQGGkqKb6Q-pWSRwrVW0qJcVvxelinIctNs-A,16152
17
+ openai_sdk_helpers/agent/coordination.py,sha256=mAIEjWJ7xnagKssLCrOEn1oOp-XoqYpQOdRlZpUyw90,16610
18
18
  openai_sdk_helpers/agent/prompt_utils.py,sha256=-1M66tqQxh9wWCFg6X-K7cCcqauca3yA04ZjvOpN3bA,337
19
19
  openai_sdk_helpers/agent/runner.py,sha256=1_azIWx1Bcy7RRlEbizTD0LXBdYgof_tYMpDUcnJJuM,4164
20
20
  openai_sdk_helpers/agent/summarizer.py,sha256=fH8AnYK_68ERf2U7mv0nwXL8KyhrluE-TDY_M5TCdD0,3266
@@ -33,10 +33,10 @@ openai_sdk_helpers/prompt/summarizer.jinja,sha256=jliSetWDISbql1EkWi1RB8-L_BXUg8
33
33
  openai_sdk_helpers/prompt/translator.jinja,sha256=SZhW8ipEzM-9IA4wyS_r2wIMTAclWrilmk1s46njoL0,291
34
34
  openai_sdk_helpers/prompt/validator.jinja,sha256=6t8q_IdxFd3mVBGX6SFKNOert1Wo3YpTOji2SNEbbtE,547
35
35
  openai_sdk_helpers/response/__init__.py,sha256=td-HTSPLtl1d5AkUFZ0rrUBUfsacM_CGtZQNj1_GWB8,1886
36
- openai_sdk_helpers/response/base.py,sha256=rDzFBLl8hU1Rho7SfLUcd0eQLRpn1ZRAvvzXDqdWf0c,29854
37
- openai_sdk_helpers/response/config.py,sha256=zZ4w2-GTSKg-7XgK6iUb5pDK-5aBFd2WkvbIjvegyHU,10871
36
+ openai_sdk_helpers/response/base.py,sha256=Y77LbnNB50-CG_KPYxM6XU0lIj9pYvAQt7FzekeHhF4,30176
37
+ openai_sdk_helpers/response/config.py,sha256=pZgjh5GOb4juy1Afnyn28QS3yIArByU2MAJIc1_i3WM,13237
38
38
  openai_sdk_helpers/response/files.py,sha256=ANCoedNHXmpTXSaaGUvesAGq2DIUXT7SKZDCIJlXOv8,13226
39
- openai_sdk_helpers/response/messages.py,sha256=G4V8a9gis4b8wFW5XnvBE0AHMX0FrkOzqiQ6FxigpnU,9145
39
+ openai_sdk_helpers/response/messages.py,sha256=AbxLy2Q3sDHFLhhhoKfCcrRVlA8M7Ts9SuYx0PODi54,10061
40
40
  openai_sdk_helpers/response/runner.py,sha256=Rf13cQGsR7sN9gA81Y5th1tfH2DCCAwQ6RMs3bVgjnk,4269
41
41
  openai_sdk_helpers/response/tool_call.py,sha256=VYPvKUR-Ren0Y_nYS4jUSinhTyXKzFwQLxu-d3r_YuM,4506
42
42
  openai_sdk_helpers/response/vector_store.py,sha256=MyHUu6P9ueNsd9erbBkyVqq3stLK6qVuehdvmFAHq9E,3074
@@ -64,7 +64,7 @@ openai_sdk_helpers/utils/async_utils.py,sha256=9KbPEVfi6IXdbwkTUE0h5DleK8TI7I6P_
64
64
  openai_sdk_helpers/utils/coercion.py,sha256=Pq1u7tAbD7kTZ84lK-7Fb9CyYKKKQt4fypG5BlSI6oQ,3774
65
65
  openai_sdk_helpers/utils/deprecation.py,sha256=VF0VDDegawYhsu5f-vE6dop9ob-jv8egxsm0KsPvP9E,4753
66
66
  openai_sdk_helpers/utils/encoding.py,sha256=oDtlNGZ5p-edXiHW76REs-0-8NXkQNReKJdj6sHLkt8,4615
67
- openai_sdk_helpers/utils/json_utils.py,sha256=dpv0IPZp4WKL7HsDAQY2za820ukn0cre6kAv-pYw7P0,3141
67
+ openai_sdk_helpers/utils/json_utils.py,sha256=Z-9AugR0CbNMLXcaJk1IJ-SwGNdo_d0a3r6MqP8rbYs,7089
68
68
  openai_sdk_helpers/utils/output_validation.py,sha256=O9Adt-fxL5DtnMd1GuZ9E2YxX3yj4uzSZuBNKVH2GkI,12152
69
69
  openai_sdk_helpers/utils/path_utils.py,sha256=qGGDpuDnY5EODOACzH23MYECQOE2rKhrQ3sbDvefwEg,1307
70
70
  openai_sdk_helpers/utils/validation.py,sha256=ZjnZNOy5AoFlszRxarNol6YZwfgw6LnwPtkCekZmwAU,7826
@@ -72,8 +72,8 @@ openai_sdk_helpers/vector_storage/__init__.py,sha256=L5LxO09puh9_yBB9IDTvc1CvVkA
72
72
  openai_sdk_helpers/vector_storage/cleanup.py,sha256=ImWIE-9lli-odD8qIARvmeaa0y8ZD4pYYP-kT0O3178,3552
73
73
  openai_sdk_helpers/vector_storage/storage.py,sha256=1juu3Qq6hy33afvVfQeI5A35fQzIPjVZumZ-aP_MxhU,23305
74
74
  openai_sdk_helpers/vector_storage/types.py,sha256=jTCcOYMeOpZWvcse0z4T3MVs-RBOPC-fqWTBeQrgafU,1639
75
- openai_sdk_helpers-0.1.4.dist-info/METADATA,sha256=xYqGRG7IMVPkN-THuqKYxN8wwZVrDlldfXV0MDwIb38,23557
76
- openai_sdk_helpers-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
77
- openai_sdk_helpers-0.1.4.dist-info/entry_points.txt,sha256=gEOD1ZeXe8d2OP-KzUlG-b_9D9yUZTCt-GFW3EDbIIY,63
78
- openai_sdk_helpers-0.1.4.dist-info/licenses/LICENSE,sha256=CUhc1NrE50bs45tcXF7OcTQBKEvkUuLqeOHgrWQ5jaA,1067
79
- openai_sdk_helpers-0.1.4.dist-info/RECORD,,
75
+ openai_sdk_helpers-0.2.0.dist-info/METADATA,sha256=4uIwpDW0oqwdAqsf5uhb_GmutPvrlek0pbO6G4zYVXU,23557
76
+ openai_sdk_helpers-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
77
+ openai_sdk_helpers-0.2.0.dist-info/entry_points.txt,sha256=gEOD1ZeXe8d2OP-KzUlG-b_9D9yUZTCt-GFW3EDbIIY,63
78
+ openai_sdk_helpers-0.2.0.dist-info/licenses/LICENSE,sha256=CUhc1NrE50bs45tcXF7OcTQBKEvkUuLqeOHgrWQ5jaA,1067
79
+ openai_sdk_helpers-0.2.0.dist-info/RECORD,,