openai-sdk-helpers 0.2.0__py3-none-any.whl → 0.4.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.
- openai_sdk_helpers/__init__.py +6 -6
- openai_sdk_helpers/agent/__init__.py +4 -2
- openai_sdk_helpers/agent/base.py +391 -106
- openai_sdk_helpers/agent/config.py +405 -44
- openai_sdk_helpers/agent/coordination.py +68 -31
- openai_sdk_helpers/agent/runner.py +29 -19
- openai_sdk_helpers/agent/search/base.py +103 -54
- openai_sdk_helpers/agent/search/vector.py +99 -68
- openai_sdk_helpers/agent/search/web.py +84 -50
- openai_sdk_helpers/agent/summarizer.py +33 -7
- openai_sdk_helpers/agent/translator.py +58 -24
- openai_sdk_helpers/agent/validation.py +35 -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 +161 -22
- openai_sdk_helpers/response/config.py +50 -200
- 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/app.py +16 -16
- openai_sdk_helpers/streamlit_app/config.py +38 -37
- 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/instructions.py +35 -0
- 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} +43 -70
- 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 +194 -0
- openai_sdk_helpers/vector_storage/storage.py +10 -0
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/METADATA +7 -7
- openai_sdk_helpers-0.4.0.dist-info/RECORD +86 -0
- openai_sdk_helpers-0.2.0.dist-info/RECORD +0 -79
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/entry_points.txt +0 -0
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,35 +5,23 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Generic, Optional, Sequence, Type, TypeVar
|
|
8
|
-
from openai.types.responses.response_text_config_param import ResponseTextConfigParam
|
|
9
8
|
|
|
10
9
|
from ..config import OpenAISettings
|
|
11
|
-
from ..structure.base import
|
|
12
|
-
from ..response.base import
|
|
13
|
-
from ..utils import
|
|
14
|
-
from ..utils.
|
|
10
|
+
from ..structure.base import StructureBase
|
|
11
|
+
from ..response.base import ResponseBase, ToolHandler
|
|
12
|
+
from ..utils.json.data_class import DataclassJSONSerializable
|
|
13
|
+
from ..utils.registry import BaseRegistry
|
|
14
|
+
from ..utils.instructions import resolve_instructions_from_path
|
|
15
15
|
|
|
16
|
-
TIn = TypeVar("TIn", bound="
|
|
17
|
-
TOut = TypeVar("TOut", bound="
|
|
16
|
+
TIn = TypeVar("TIn", bound="StructureBase")
|
|
17
|
+
TOut = TypeVar("TOut", bound="StructureBase")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class ResponseRegistry:
|
|
20
|
+
class ResponseRegistry(BaseRegistry["ResponseConfiguration"]):
|
|
21
21
|
"""Registry for managing ResponseConfiguration instances.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
enabling reusable response specs across the application.
|
|
25
|
-
are stored by name and can be retrieved or listed as needed.
|
|
26
|
-
|
|
27
|
-
Methods
|
|
28
|
-
-------
|
|
29
|
-
register(config)
|
|
30
|
-
Add a ResponseConfiguration to the registry.
|
|
31
|
-
get(name)
|
|
32
|
-
Retrieve a configuration by name.
|
|
33
|
-
list_names()
|
|
34
|
-
Return all registered configuration names.
|
|
35
|
-
clear()
|
|
36
|
-
Remove all registered configurations.
|
|
23
|
+
Inherits from BaseRegistry to provide centralized storage and retrieval
|
|
24
|
+
of response configurations, enabling reusable response specs across the application.
|
|
37
25
|
|
|
38
26
|
Examples
|
|
39
27
|
--------
|
|
@@ -51,134 +39,7 @@ class ResponseRegistry:
|
|
|
51
39
|
'test'
|
|
52
40
|
"""
|
|
53
41
|
|
|
54
|
-
|
|
55
|
-
"""Initialize an empty registry."""
|
|
56
|
-
self._configs: dict[str, ResponseConfiguration] = {}
|
|
57
|
-
|
|
58
|
-
def register(self, config: ResponseConfiguration) -> None:
|
|
59
|
-
"""Add a ResponseConfiguration to the registry.
|
|
60
|
-
|
|
61
|
-
Parameters
|
|
62
|
-
----------
|
|
63
|
-
config : ResponseConfiguration
|
|
64
|
-
Configuration to register.
|
|
65
|
-
|
|
66
|
-
Raises
|
|
67
|
-
------
|
|
68
|
-
ValueError
|
|
69
|
-
If a configuration with the same name is already registered.
|
|
70
|
-
|
|
71
|
-
Examples
|
|
72
|
-
--------
|
|
73
|
-
>>> registry = ResponseRegistry()
|
|
74
|
-
>>> config = ResponseConfiguration(...)
|
|
75
|
-
>>> registry.register(config)
|
|
76
|
-
"""
|
|
77
|
-
if config.name in self._configs:
|
|
78
|
-
raise ValueError(
|
|
79
|
-
f"Configuration '{config.name}' is already registered. "
|
|
80
|
-
"Use a unique name or clear the registry first."
|
|
81
|
-
)
|
|
82
|
-
self._configs[config.name] = config
|
|
83
|
-
|
|
84
|
-
def get(self, name: str) -> ResponseConfiguration:
|
|
85
|
-
"""Retrieve a configuration by name.
|
|
86
|
-
|
|
87
|
-
Parameters
|
|
88
|
-
----------
|
|
89
|
-
name : str
|
|
90
|
-
Configuration name to look up.
|
|
91
|
-
|
|
92
|
-
Returns
|
|
93
|
-
-------
|
|
94
|
-
ResponseConfiguration
|
|
95
|
-
The registered configuration.
|
|
96
|
-
|
|
97
|
-
Raises
|
|
98
|
-
------
|
|
99
|
-
KeyError
|
|
100
|
-
If no configuration with the given name exists.
|
|
101
|
-
|
|
102
|
-
Examples
|
|
103
|
-
--------
|
|
104
|
-
>>> registry = ResponseRegistry()
|
|
105
|
-
>>> config = registry.get("test")
|
|
106
|
-
"""
|
|
107
|
-
if name not in self._configs:
|
|
108
|
-
raise KeyError(
|
|
109
|
-
f"No configuration named '{name}' found. "
|
|
110
|
-
f"Available: {list(self._configs.keys())}"
|
|
111
|
-
)
|
|
112
|
-
return self._configs[name]
|
|
113
|
-
|
|
114
|
-
def list_names(self) -> list[str]:
|
|
115
|
-
"""Return all registered configuration names.
|
|
116
|
-
|
|
117
|
-
Returns
|
|
118
|
-
-------
|
|
119
|
-
list[str]
|
|
120
|
-
Sorted list of configuration names.
|
|
121
|
-
|
|
122
|
-
Examples
|
|
123
|
-
--------
|
|
124
|
-
>>> registry = ResponseRegistry()
|
|
125
|
-
>>> registry.list_names()
|
|
126
|
-
[]
|
|
127
|
-
"""
|
|
128
|
-
return sorted(self._configs.keys())
|
|
129
|
-
|
|
130
|
-
def clear(self) -> None:
|
|
131
|
-
"""Remove all registered configurations.
|
|
132
|
-
|
|
133
|
-
Examples
|
|
134
|
-
--------
|
|
135
|
-
>>> registry = ResponseRegistry()
|
|
136
|
-
>>> registry.clear()
|
|
137
|
-
"""
|
|
138
|
-
self._configs.clear()
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
# Global default registry instance
|
|
181
|
-
_default_registry = ResponseRegistry()
|
|
42
|
+
pass
|
|
182
43
|
|
|
183
44
|
|
|
184
45
|
def get_default_registry() -> ResponseRegistry:
|
|
@@ -199,13 +60,13 @@ def get_default_registry() -> ResponseRegistry:
|
|
|
199
60
|
|
|
200
61
|
|
|
201
62
|
@dataclass(frozen=True, slots=True)
|
|
202
|
-
class ResponseConfiguration(
|
|
63
|
+
class ResponseConfiguration(DataclassJSONSerializable, Generic[TIn, TOut]):
|
|
203
64
|
"""
|
|
204
65
|
Represent an immutable configuration describing input and output structures.
|
|
205
66
|
|
|
206
67
|
Encapsulate all metadata required to define how a request is interpreted and
|
|
207
68
|
how a response is structured, while enforcing strict type and runtime safety.
|
|
208
|
-
Inherits from
|
|
69
|
+
Inherits from DataclassJSONSerializable to support serialization to JSON format.
|
|
209
70
|
|
|
210
71
|
Parameters
|
|
211
72
|
----------
|
|
@@ -216,13 +77,13 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
216
77
|
contents are loaded at runtime.
|
|
217
78
|
tools : Sequence[object], optional
|
|
218
79
|
Tool definitions associated with the configuration. Default is None.
|
|
219
|
-
input_structure : Type[
|
|
80
|
+
input_structure : Type[StructureBase], optional
|
|
220
81
|
Structure class used to parse or validate input. Must subclass
|
|
221
|
-
|
|
222
|
-
output_structure : Type[
|
|
82
|
+
StructureBase. Default is None.
|
|
83
|
+
output_structure : Type[StructureBase], optional
|
|
223
84
|
Structure class used to format or validate output. Schema is
|
|
224
85
|
automatically generated from this structure. Must subclass
|
|
225
|
-
|
|
86
|
+
StructureBase. Default is None.
|
|
226
87
|
system_vector_store : list[str], optional
|
|
227
88
|
Optional list of vector store names to attach as system context.
|
|
228
89
|
Default is None.
|
|
@@ -237,7 +98,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
237
98
|
If instructions is not a string or Path.
|
|
238
99
|
If tools is provided and is not a sequence.
|
|
239
100
|
If input_structure or output_structure is not a class.
|
|
240
|
-
If input_structure or output_structure does not subclass
|
|
101
|
+
If input_structure or output_structure does not subclass StructureBase.
|
|
241
102
|
ValueError
|
|
242
103
|
If instructions is a string that is empty or only whitespace.
|
|
243
104
|
FileNotFoundError
|
|
@@ -246,7 +107,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
246
107
|
Methods
|
|
247
108
|
-------
|
|
248
109
|
__post_init__()
|
|
249
|
-
Validate configuration invariants and enforce
|
|
110
|
+
Validate configuration invariants and enforce StructureBase subclassing.
|
|
250
111
|
instructions_text
|
|
251
112
|
Return the resolved instruction content as a string.
|
|
252
113
|
to_json()
|
|
@@ -276,14 +137,14 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
276
137
|
input_structure: Optional[Type[TIn]]
|
|
277
138
|
output_structure: Optional[Type[TOut]]
|
|
278
139
|
system_vector_store: Optional[list[str]] = None
|
|
279
|
-
|
|
140
|
+
add_output_instructions: bool = True
|
|
280
141
|
|
|
281
142
|
def __post_init__(self) -> None:
|
|
282
143
|
"""
|
|
283
144
|
Validate configuration invariants after initialization.
|
|
284
145
|
|
|
285
146
|
Enforce non-empty naming, correct typing of structures, and ensure that
|
|
286
|
-
any declared structure subclasses
|
|
147
|
+
any declared structure subclasses StructureBase.
|
|
287
148
|
|
|
288
149
|
Raises
|
|
289
150
|
------
|
|
@@ -291,7 +152,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
291
152
|
If name is not a non-empty string.
|
|
292
153
|
If tools is provided and is not a sequence.
|
|
293
154
|
If input_structure or output_structure is not a class.
|
|
294
|
-
If input_structure or output_structure does not subclass
|
|
155
|
+
If input_structure or output_structure does not subclass StructureBase.
|
|
295
156
|
"""
|
|
296
157
|
if not self.name or not isinstance(self.name, str):
|
|
297
158
|
raise TypeError("Configuration.name must be a non-empty str")
|
|
@@ -315,10 +176,10 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
315
176
|
continue
|
|
316
177
|
if not isinstance(cls, type):
|
|
317
178
|
raise TypeError(
|
|
318
|
-
f"Configuration.{attr} must be a class (Type[
|
|
179
|
+
f"Configuration.{attr} must be a class (Type[StructureBase]) or None"
|
|
319
180
|
)
|
|
320
|
-
if not issubclass(cls,
|
|
321
|
-
raise TypeError(f"Configuration.{attr} must subclass
|
|
181
|
+
if not issubclass(cls, StructureBase):
|
|
182
|
+
raise TypeError(f"Configuration.{attr} must subclass StructureBase")
|
|
322
183
|
|
|
323
184
|
if self.tools is not None and not isinstance(self.tools, Sequence):
|
|
324
185
|
raise TypeError("Configuration.tools must be a Sequence or None")
|
|
@@ -332,62 +193,51 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
332
193
|
str
|
|
333
194
|
Plain-text instructions, loading template files when necessary.
|
|
334
195
|
"""
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
) from exc
|
|
346
|
-
return self.instructions
|
|
196
|
+
resolved_instructions: str = resolve_instructions_from_path(self.instructions)
|
|
197
|
+
output_instructions = ""
|
|
198
|
+
if self.output_structure is not None and self.add_output_instructions:
|
|
199
|
+
output_instructions = self.output_structure.get_prompt(
|
|
200
|
+
add_enum_values=False
|
|
201
|
+
)
|
|
202
|
+
if output_instructions:
|
|
203
|
+
return f"{resolved_instructions}\n{output_instructions}"
|
|
204
|
+
|
|
205
|
+
return resolved_instructions
|
|
347
206
|
|
|
348
207
|
def gen_response(
|
|
349
208
|
self,
|
|
209
|
+
*,
|
|
350
210
|
openai_settings: OpenAISettings,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
) ->
|
|
354
|
-
"""Generate a
|
|
211
|
+
data_path: Optional[Path] = None,
|
|
212
|
+
tool_handlers: dict[str, ToolHandler] | None = None,
|
|
213
|
+
) -> ResponseBase[TOut]:
|
|
214
|
+
"""Generate a ResponseBase instance based on the configuration.
|
|
355
215
|
|
|
356
216
|
Parameters
|
|
357
217
|
----------
|
|
358
218
|
openai_settings : OpenAISettings
|
|
359
219
|
Authentication and model settings applied to the generated
|
|
360
|
-
:class:`
|
|
220
|
+
:class:`ResponseBase`.
|
|
361
221
|
tool_handlers : dict[str, Callable], optional
|
|
362
222
|
Mapping of tool names to handler callables. Defaults to an empty
|
|
363
223
|
dictionary when not provided.
|
|
364
|
-
add_output_instructions : bool, default=True
|
|
365
|
-
Whether to append the structured output prompt to the instructions.
|
|
366
224
|
|
|
367
225
|
Returns
|
|
368
226
|
-------
|
|
369
|
-
|
|
370
|
-
An instance of
|
|
227
|
+
ResponseBase[TOut]
|
|
228
|
+
An instance of ResponseBase configured with ``openai_settings``.
|
|
371
229
|
"""
|
|
372
|
-
|
|
373
|
-
if self.output_structure is not None and add_output_instructions:
|
|
374
|
-
output_instructions = self.output_structure.get_prompt(
|
|
375
|
-
add_enum_values=False
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
instructions = (
|
|
379
|
-
f"{self.instructions_text}\n{output_instructions}"
|
|
380
|
-
if output_instructions
|
|
381
|
-
else self.instructions_text
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
return BaseResponse[TOut](
|
|
230
|
+
return ResponseBase[TOut](
|
|
385
231
|
name=self.name,
|
|
386
|
-
instructions=
|
|
232
|
+
instructions=self.instructions_text,
|
|
387
233
|
tools=self.tools,
|
|
388
234
|
output_structure=self.output_structure,
|
|
389
235
|
system_vector_store=self.system_vector_store,
|
|
390
|
-
data_path=
|
|
236
|
+
data_path=data_path,
|
|
391
237
|
tool_handlers=tool_handlers,
|
|
392
238
|
openai_settings=openai_settings,
|
|
393
239
|
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Global default registry instance
|
|
243
|
+
_default_registry = ResponseRegistry()
|
|
@@ -23,11 +23,11 @@ from openai.types.responses.response_input_image_content_param import (
|
|
|
23
23
|
from ..utils import create_file_data_url, create_image_data_url, is_image_file, log
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING: # pragma: no cover
|
|
26
|
-
from .base import
|
|
26
|
+
from .base import ResponseBase
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def process_files(
|
|
30
|
-
response:
|
|
30
|
+
response: ResponseBase[Any],
|
|
31
31
|
files: list[str],
|
|
32
32
|
use_vector_store: bool = False,
|
|
33
33
|
batch_size: int = 10,
|
|
@@ -45,7 +45,7 @@ def process_files(
|
|
|
45
45
|
|
|
46
46
|
Parameters
|
|
47
47
|
----------
|
|
48
|
-
response :
|
|
48
|
+
response : ResponseBase[Any]
|
|
49
49
|
Response instance that will use the processed files.
|
|
50
50
|
files : list[str]
|
|
51
51
|
List of file paths to process.
|
|
@@ -114,7 +114,7 @@ def process_files(
|
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def _upload_to_vector_store(
|
|
117
|
-
response:
|
|
117
|
+
response: ResponseBase[Any], document_files: list[str]
|
|
118
118
|
) -> list[ResponseInputFileParam]:
|
|
119
119
|
"""Upload documents to vector store and return file references.
|
|
120
120
|
|
|
@@ -123,7 +123,7 @@ def _upload_to_vector_store(
|
|
|
123
123
|
|
|
124
124
|
Parameters
|
|
125
125
|
----------
|
|
126
|
-
response :
|
|
126
|
+
response : ResponseBase[Any]
|
|
127
127
|
Response instance with vector storage.
|
|
128
128
|
document_files : list[str]
|
|
129
129
|
List of document file paths to upload.
|
|
@@ -24,12 +24,12 @@ from openai.types.responses.response_input_param import (
|
|
|
24
24
|
)
|
|
25
25
|
from openai.types.responses.response_output_message import ResponseOutputMessage
|
|
26
26
|
|
|
27
|
-
from ..utils import
|
|
27
|
+
from ..utils.json.data_class import DataclassJSONSerializable
|
|
28
28
|
from .tool_call import ResponseToolCall
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@dataclass
|
|
32
|
-
class ResponseMessage(
|
|
32
|
+
class ResponseMessage(DataclassJSONSerializable):
|
|
33
33
|
"""Single message exchanged with the OpenAI API.
|
|
34
34
|
|
|
35
35
|
Represents a complete message with role, content, timestamp, and
|
|
@@ -91,7 +91,7 @@ class ResponseMessage(JSONSerializable):
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
@dataclass
|
|
94
|
-
class ResponseMessages(
|
|
94
|
+
class ResponseMessages(DataclassJSONSerializable):
|
|
95
95
|
"""Collection of messages in a conversation.
|
|
96
96
|
|
|
97
97
|
Manages the complete history of messages exchanged during an OpenAI
|
|
@@ -10,10 +10,10 @@ from __future__ import annotations
|
|
|
10
10
|
import asyncio
|
|
11
11
|
from typing import Any, TypeVar
|
|
12
12
|
|
|
13
|
-
from .base import
|
|
13
|
+
from .base import ResponseBase
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
R = TypeVar("R", bound=
|
|
16
|
+
R = TypeVar("R", bound=ResponseBase[Any])
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def run_sync(
|
|
@@ -29,7 +29,7 @@ def run_sync(
|
|
|
29
29
|
|
|
30
30
|
Parameters
|
|
31
31
|
----------
|
|
32
|
-
response_cls : type[
|
|
32
|
+
response_cls : type[ResponseBase]
|
|
33
33
|
Response class to instantiate for the workflow.
|
|
34
34
|
content : str
|
|
35
35
|
Prompt text to send to the OpenAI API.
|
|
@@ -39,7 +39,7 @@ def run_sync(
|
|
|
39
39
|
Returns
|
|
40
40
|
-------
|
|
41
41
|
Any
|
|
42
|
-
Parsed response from
|
|
42
|
+
Parsed response from ResponseBase.run_sync, typically a structured
|
|
43
43
|
output or None.
|
|
44
44
|
|
|
45
45
|
Examples
|
|
@@ -71,7 +71,7 @@ async def run_async(
|
|
|
71
71
|
|
|
72
72
|
Parameters
|
|
73
73
|
----------
|
|
74
|
-
response_cls : type[
|
|
74
|
+
response_cls : type[ResponseBase]
|
|
75
75
|
Response class to instantiate for the workflow.
|
|
76
76
|
content : str
|
|
77
77
|
Prompt text to send to the OpenAI API.
|
|
@@ -81,7 +81,7 @@ async def run_async(
|
|
|
81
81
|
Returns
|
|
82
82
|
-------
|
|
83
83
|
Any
|
|
84
|
-
Parsed response from
|
|
84
|
+
Parsed response from ResponseBase.run_async, typically a structured
|
|
85
85
|
output or None.
|
|
86
86
|
|
|
87
87
|
Examples
|
|
@@ -114,7 +114,7 @@ def run_streamed(
|
|
|
114
114
|
|
|
115
115
|
Parameters
|
|
116
116
|
----------
|
|
117
|
-
response_cls : type[
|
|
117
|
+
response_cls : type[ResponseBase]
|
|
118
118
|
Response class to instantiate for the workflow.
|
|
119
119
|
content : str
|
|
120
120
|
Prompt text to send to the OpenAI API.
|
|
@@ -9,16 +9,18 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import ast
|
|
11
11
|
import json
|
|
12
|
+
import re
|
|
12
13
|
from dataclasses import dataclass
|
|
13
14
|
|
|
14
15
|
from openai.types.responses.response_function_tool_call_param import (
|
|
15
16
|
ResponseFunctionToolCallParam,
|
|
16
17
|
)
|
|
17
18
|
from openai.types.responses.response_input_param import FunctionCallOutput
|
|
19
|
+
from ..utils.json.data_class import DataclassJSONSerializable
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
@dataclass
|
|
21
|
-
class ResponseToolCall:
|
|
23
|
+
class ResponseToolCall(DataclassJSONSerializable):
|
|
22
24
|
"""Container for tool call data in a conversation.
|
|
23
25
|
|
|
24
26
|
Stores the complete information about a tool invocation including
|
|
@@ -94,6 +96,85 @@ class ResponseToolCall:
|
|
|
94
96
|
return function_call, function_call_output
|
|
95
97
|
|
|
96
98
|
|
|
99
|
+
def _to_snake_case(name: str) -> str:
|
|
100
|
+
"""Convert a PascalCase or camelCase string to snake_case.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
name : str
|
|
105
|
+
The name to convert.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
str
|
|
110
|
+
The snake_case version of the name.
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
>>> _to_snake_case("ExampleStructure")
|
|
115
|
+
'example_structure'
|
|
116
|
+
>>> _to_snake_case("MyToolName")
|
|
117
|
+
'my_tool_name'
|
|
118
|
+
"""
|
|
119
|
+
# First regex: Insert underscore before uppercase letters followed by
|
|
120
|
+
# lowercase letters (e.g., "Tool" in "ExampleTool" becomes "_Tool")
|
|
121
|
+
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
122
|
+
# Second regex: Insert underscore between lowercase/digit and uppercase
|
|
123
|
+
# (e.g., "e3" followed by "T" becomes "e3_T")
|
|
124
|
+
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _unwrap_arguments(parsed: dict, tool_name: str) -> dict:
|
|
128
|
+
"""Unwrap arguments if wrapped in a single-key dict.
|
|
129
|
+
|
|
130
|
+
Some responses wrap arguments under a key matching the structure class
|
|
131
|
+
name (e.g., {"ExampleStructure": {...}}) or snake_case variant
|
|
132
|
+
(e.g., {"example_structure": {...}}). This function detects and unwraps
|
|
133
|
+
such wrappers to normalize the payload.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
parsed : dict
|
|
138
|
+
The parsed arguments dictionary.
|
|
139
|
+
tool_name : str
|
|
140
|
+
The tool name, used to match potential wrapper keys.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
dict
|
|
145
|
+
Unwrapped arguments dictionary, or original if no wrapper detected.
|
|
146
|
+
|
|
147
|
+
Examples
|
|
148
|
+
--------
|
|
149
|
+
>>> _unwrap_arguments({"ExampleTool": {"arg": "value"}}, "ExampleTool")
|
|
150
|
+
{'arg': 'value'}
|
|
151
|
+
>>> _unwrap_arguments({"example_tool": {"arg": "value"}}, "ExampleTool")
|
|
152
|
+
{'arg': 'value'}
|
|
153
|
+
>>> _unwrap_arguments({"arg": "value"}, "ExampleTool")
|
|
154
|
+
{'arg': 'value'}
|
|
155
|
+
"""
|
|
156
|
+
# Only unwrap if dict has exactly one key
|
|
157
|
+
if not isinstance(parsed, dict) or len(parsed) != 1:
|
|
158
|
+
return parsed
|
|
159
|
+
|
|
160
|
+
wrapper_key = next(iter(parsed))
|
|
161
|
+
wrapped_value = parsed[wrapper_key]
|
|
162
|
+
|
|
163
|
+
# Only unwrap if the value is also a dict
|
|
164
|
+
if not isinstance(wrapped_value, dict):
|
|
165
|
+
return parsed
|
|
166
|
+
|
|
167
|
+
# Check if wrapper key matches tool name (case-insensitive or snake_case)
|
|
168
|
+
tool_name_lower = tool_name.lower()
|
|
169
|
+
tool_name_snake = _to_snake_case(tool_name)
|
|
170
|
+
wrapper_key_lower = wrapper_key.lower()
|
|
171
|
+
|
|
172
|
+
if wrapper_key_lower in (tool_name_lower, tool_name_snake):
|
|
173
|
+
return wrapped_value
|
|
174
|
+
|
|
175
|
+
return parsed
|
|
176
|
+
|
|
177
|
+
|
|
97
178
|
def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
|
|
98
179
|
"""Parse tool call arguments with fallback for malformed JSON.
|
|
99
180
|
|
|
@@ -102,6 +183,9 @@ def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
|
|
|
102
183
|
formatting issues like single quotes instead of double quotes.
|
|
103
184
|
Provides clear error context including tool name and raw payload.
|
|
104
185
|
|
|
186
|
+
Also handles unwrapping of arguments that are wrapped in a single-key
|
|
187
|
+
dictionary matching the tool name (e.g., {"ExampleStructure": {...}}).
|
|
188
|
+
|
|
105
189
|
Parameters
|
|
106
190
|
----------
|
|
107
191
|
arguments : str
|
|
@@ -112,7 +196,7 @@ def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
|
|
|
112
196
|
Returns
|
|
113
197
|
-------
|
|
114
198
|
dict
|
|
115
|
-
Parsed dictionary of tool arguments.
|
|
199
|
+
Parsed dictionary of tool arguments, with wrapper unwrapped if present.
|
|
116
200
|
|
|
117
201
|
Raises
|
|
118
202
|
------
|
|
@@ -127,12 +211,15 @@ def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
|
|
|
127
211
|
|
|
128
212
|
>>> parse_tool_arguments("{'key': 'value'}", tool_name="search")
|
|
129
213
|
{'key': 'value'}
|
|
214
|
+
|
|
215
|
+
>>> parse_tool_arguments('{"ExampleTool": {"arg": "value"}}', "ExampleTool")
|
|
216
|
+
{'arg': 'value'}
|
|
130
217
|
"""
|
|
131
218
|
try:
|
|
132
|
-
|
|
219
|
+
parsed = json.loads(arguments)
|
|
133
220
|
except json.JSONDecodeError:
|
|
134
221
|
try:
|
|
135
|
-
|
|
222
|
+
parsed = ast.literal_eval(arguments)
|
|
136
223
|
except Exception as exc: # noqa: BLE001
|
|
137
224
|
# Build informative error message with context
|
|
138
225
|
payload_preview = (
|
|
@@ -142,3 +229,6 @@ def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
|
|
|
142
229
|
f"Failed to parse tool arguments for tool '{tool_name}'. "
|
|
143
230
|
f"Raw payload: {payload_preview}"
|
|
144
231
|
) from exc
|
|
232
|
+
|
|
233
|
+
# Unwrap if wrapped in a single-key dict matching tool name
|
|
234
|
+
return _unwrap_arguments(parsed, tool_name)
|
|
@@ -11,11 +11,11 @@ from typing import Any, Sequence
|
|
|
11
11
|
from openai import OpenAI
|
|
12
12
|
|
|
13
13
|
from ..utils import ensure_list
|
|
14
|
-
from .base import
|
|
14
|
+
from .base import ResponseBase
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def attach_vector_store(
|
|
18
|
-
response:
|
|
18
|
+
response: ResponseBase[Any],
|
|
19
19
|
vector_stores: str | Sequence[str],
|
|
20
20
|
api_key: str | None = None,
|
|
21
21
|
) -> list[str]:
|
|
@@ -27,7 +27,7 @@ def attach_vector_store(
|
|
|
27
27
|
|
|
28
28
|
Parameters
|
|
29
29
|
----------
|
|
30
|
-
response :
|
|
30
|
+
response : ResponseBase[Any]
|
|
31
31
|
Response instance whose tool configuration will be updated.
|
|
32
32
|
vector_stores : str or Sequence[str]
|
|
33
33
|
Single vector store name or sequence of names to attach.
|