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
|
@@ -5,23 +5,22 @@ 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.registry import
|
|
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 RegistryBase
|
|
15
14
|
from ..utils.instructions import resolve_instructions_from_path
|
|
16
15
|
|
|
17
|
-
TIn = TypeVar("TIn", bound="
|
|
18
|
-
TOut = TypeVar("TOut", bound="
|
|
16
|
+
TIn = TypeVar("TIn", bound="StructureBase")
|
|
17
|
+
TOut = TypeVar("TOut", bound="StructureBase")
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
class ResponseRegistry(
|
|
20
|
+
class ResponseRegistry(RegistryBase["ResponseConfiguration"]):
|
|
22
21
|
"""Registry for managing ResponseConfiguration instances.
|
|
23
22
|
|
|
24
|
-
Inherits from
|
|
23
|
+
Inherits from RegistryBase to provide centralized storage and retrieval
|
|
25
24
|
of response configurations, enabling reusable response specs across the application.
|
|
26
25
|
|
|
27
26
|
Examples
|
|
@@ -43,10 +42,6 @@ class ResponseRegistry(BaseRegistry["ResponseConfiguration"]):
|
|
|
43
42
|
pass
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
# Global default registry instance
|
|
47
|
-
_default_registry = ResponseRegistry()
|
|
48
|
-
|
|
49
|
-
|
|
50
45
|
def get_default_registry() -> ResponseRegistry:
|
|
51
46
|
"""Return the global default registry instance.
|
|
52
47
|
|
|
@@ -65,13 +60,13 @@ def get_default_registry() -> ResponseRegistry:
|
|
|
65
60
|
|
|
66
61
|
|
|
67
62
|
@dataclass(frozen=True, slots=True)
|
|
68
|
-
class ResponseConfiguration(
|
|
63
|
+
class ResponseConfiguration(DataclassJSONSerializable, Generic[TIn, TOut]):
|
|
69
64
|
"""
|
|
70
65
|
Represent an immutable configuration describing input and output structures.
|
|
71
66
|
|
|
72
67
|
Encapsulate all metadata required to define how a request is interpreted and
|
|
73
68
|
how a response is structured, while enforcing strict type and runtime safety.
|
|
74
|
-
Inherits from
|
|
69
|
+
Inherits from DataclassJSONSerializable to support serialization to JSON format.
|
|
75
70
|
|
|
76
71
|
Parameters
|
|
77
72
|
----------
|
|
@@ -82,13 +77,13 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
82
77
|
contents are loaded at runtime.
|
|
83
78
|
tools : Sequence[object], optional
|
|
84
79
|
Tool definitions associated with the configuration. Default is None.
|
|
85
|
-
input_structure : Type[
|
|
80
|
+
input_structure : Type[StructureBase], optional
|
|
86
81
|
Structure class used to parse or validate input. Must subclass
|
|
87
|
-
|
|
88
|
-
output_structure : Type[
|
|
82
|
+
StructureBase. Default is None.
|
|
83
|
+
output_structure : Type[StructureBase], optional
|
|
89
84
|
Structure class used to format or validate output. Schema is
|
|
90
85
|
automatically generated from this structure. Must subclass
|
|
91
|
-
|
|
86
|
+
StructureBase. Default is None.
|
|
92
87
|
system_vector_store : list[str], optional
|
|
93
88
|
Optional list of vector store names to attach as system context.
|
|
94
89
|
Default is None.
|
|
@@ -103,7 +98,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
103
98
|
If instructions is not a string or Path.
|
|
104
99
|
If tools is provided and is not a sequence.
|
|
105
100
|
If input_structure or output_structure is not a class.
|
|
106
|
-
If input_structure or output_structure does not subclass
|
|
101
|
+
If input_structure or output_structure does not subclass StructureBase.
|
|
107
102
|
ValueError
|
|
108
103
|
If instructions is a string that is empty or only whitespace.
|
|
109
104
|
FileNotFoundError
|
|
@@ -112,9 +107,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
112
107
|
Methods
|
|
113
108
|
-------
|
|
114
109
|
__post_init__()
|
|
115
|
-
Validate configuration invariants and enforce
|
|
116
|
-
instructions_text
|
|
117
|
-
Return the resolved instruction content as a string.
|
|
110
|
+
Validate configuration invariants and enforce StructureBase subclassing.
|
|
118
111
|
to_json()
|
|
119
112
|
Return a JSON-compatible dict representation (inherited from JSONSerializable).
|
|
120
113
|
to_json_file(filepath)
|
|
@@ -142,14 +135,15 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
142
135
|
input_structure: Optional[Type[TIn]]
|
|
143
136
|
output_structure: Optional[Type[TOut]]
|
|
144
137
|
system_vector_store: Optional[list[str]] = None
|
|
145
|
-
|
|
138
|
+
add_output_instructions: bool = True
|
|
139
|
+
add_web_search_tool: bool = False
|
|
146
140
|
|
|
147
141
|
def __post_init__(self) -> None:
|
|
148
142
|
"""
|
|
149
143
|
Validate configuration invariants after initialization.
|
|
150
144
|
|
|
151
145
|
Enforce non-empty naming, correct typing of structures, and ensure that
|
|
152
|
-
any declared structure subclasses
|
|
146
|
+
any declared structure subclasses StructureBase.
|
|
153
147
|
|
|
154
148
|
Raises
|
|
155
149
|
------
|
|
@@ -157,7 +151,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
157
151
|
If name is not a non-empty string.
|
|
158
152
|
If tools is provided and is not a sequence.
|
|
159
153
|
If input_structure or output_structure is not a class.
|
|
160
|
-
If input_structure or output_structure does not subclass
|
|
154
|
+
If input_structure or output_structure does not subclass StructureBase.
|
|
161
155
|
"""
|
|
162
156
|
if not self.name or not isinstance(self.name, str):
|
|
163
157
|
raise TypeError("Configuration.name must be a non-empty str")
|
|
@@ -181,16 +175,16 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
181
175
|
continue
|
|
182
176
|
if not isinstance(cls, type):
|
|
183
177
|
raise TypeError(
|
|
184
|
-
f"Configuration.{attr} must be a class (Type[
|
|
178
|
+
f"Configuration.{attr} must be a class (Type[StructureBase]) or None"
|
|
185
179
|
)
|
|
186
|
-
if not issubclass(cls,
|
|
187
|
-
raise TypeError(f"Configuration.{attr} must subclass
|
|
180
|
+
if not issubclass(cls, StructureBase):
|
|
181
|
+
raise TypeError(f"Configuration.{attr} must subclass StructureBase")
|
|
188
182
|
|
|
189
183
|
if self.tools is not None and not isinstance(self.tools, Sequence):
|
|
190
184
|
raise TypeError("Configuration.tools must be a Sequence or None")
|
|
191
185
|
|
|
192
186
|
@property
|
|
193
|
-
def
|
|
187
|
+
def get_resolved_instructions(self) -> str:
|
|
194
188
|
"""Return the resolved instruction text.
|
|
195
189
|
|
|
196
190
|
Returns
|
|
@@ -198,54 +192,65 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
|
|
|
198
192
|
str
|
|
199
193
|
Plain-text instructions, loading template files when necessary.
|
|
200
194
|
"""
|
|
201
|
-
|
|
195
|
+
resolved_instructions: str = resolve_instructions_from_path(self.instructions)
|
|
196
|
+
output_instructions = ""
|
|
197
|
+
if self.output_structure is not None and self.add_output_instructions:
|
|
198
|
+
output_instructions = self.output_structure.get_prompt(
|
|
199
|
+
add_enum_values=False
|
|
200
|
+
)
|
|
201
|
+
if output_instructions:
|
|
202
|
+
return f"{resolved_instructions}\n{output_instructions}"
|
|
203
|
+
|
|
204
|
+
return resolved_instructions
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
@property
|
|
207
|
+
def get_resolved_tools(self) -> list:
|
|
208
|
+
"""Return the complete list of tools, including optional web search tool.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
list
|
|
213
|
+
List of tool definitions, including web search tool if enabled.
|
|
214
|
+
"""
|
|
215
|
+
tools = self.tools or []
|
|
216
|
+
if self.add_web_search_tool:
|
|
217
|
+
tools = tools + [{"type": "web_search"}]
|
|
218
|
+
return tools
|
|
205
219
|
|
|
206
220
|
def gen_response(
|
|
207
221
|
self,
|
|
222
|
+
*,
|
|
208
223
|
openai_settings: OpenAISettings,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
) ->
|
|
212
|
-
"""Generate a
|
|
224
|
+
data_path: Optional[Path] = None,
|
|
225
|
+
tool_handlers: dict[str, ToolHandler] | None = None,
|
|
226
|
+
) -> ResponseBase[TOut]:
|
|
227
|
+
"""Generate a ResponseBase instance based on the configuration.
|
|
213
228
|
|
|
214
229
|
Parameters
|
|
215
230
|
----------
|
|
216
231
|
openai_settings : OpenAISettings
|
|
217
232
|
Authentication and model settings applied to the generated
|
|
218
|
-
:class:`
|
|
233
|
+
:class:`ResponseBase`.
|
|
219
234
|
tool_handlers : dict[str, Callable], optional
|
|
220
235
|
Mapping of tool names to handler callables. Defaults to an empty
|
|
221
236
|
dictionary when not provided.
|
|
222
|
-
add_output_instructions : bool, default=True
|
|
223
|
-
Whether to append the structured output prompt to the instructions.
|
|
224
237
|
|
|
225
238
|
Returns
|
|
226
239
|
-------
|
|
227
|
-
|
|
228
|
-
An instance of
|
|
240
|
+
ResponseBase[TOut]
|
|
241
|
+
An instance of ResponseBase configured with ``openai_settings``.
|
|
229
242
|
"""
|
|
230
|
-
|
|
231
|
-
if self.output_structure is not None and add_output_instructions:
|
|
232
|
-
output_instructions = self.output_structure.get_prompt(
|
|
233
|
-
add_enum_values=False
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
instructions = (
|
|
237
|
-
f"{self.instructions_text}\n{output_instructions}"
|
|
238
|
-
if output_instructions
|
|
239
|
-
else self.instructions_text
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
return BaseResponse[TOut](
|
|
243
|
+
return ResponseBase[TOut](
|
|
243
244
|
name=self.name,
|
|
244
|
-
instructions=
|
|
245
|
-
tools=self.
|
|
245
|
+
instructions=self.get_resolved_instructions,
|
|
246
|
+
tools=self.get_resolved_tools,
|
|
246
247
|
output_structure=self.output_structure,
|
|
247
248
|
system_vector_store=self.system_vector_store,
|
|
248
|
-
data_path=
|
|
249
|
+
data_path=data_path,
|
|
249
250
|
tool_handlers=tool_handlers,
|
|
250
251
|
openai_settings=openai_settings,
|
|
251
252
|
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# Global default registry instance
|
|
256
|
+
_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.
|
|
@@ -8,23 +8,23 @@ Classes
|
|
|
8
8
|
-------
|
|
9
9
|
StreamlitAppConfig
|
|
10
10
|
Validated configuration for Streamlit chat applications.
|
|
11
|
+
StreamlitAppRegistry
|
|
12
|
+
Registry for storing Streamlit app configurations.
|
|
11
13
|
|
|
12
14
|
Functions
|
|
13
15
|
---------
|
|
14
|
-
load_app_config
|
|
15
|
-
Load and validate configuration from a Python module.
|
|
16
16
|
_load_configuration
|
|
17
17
|
Load configuration with user-friendly error handling for Streamlit UI.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from .config import (
|
|
21
21
|
StreamlitAppConfig,
|
|
22
|
+
StreamlitAppRegistry,
|
|
22
23
|
_load_configuration,
|
|
23
|
-
load_app_config,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
__all__ = [
|
|
27
27
|
"StreamlitAppConfig",
|
|
28
|
+
"StreamlitAppRegistry",
|
|
28
29
|
"_load_configuration",
|
|
29
|
-
"load_app_config",
|
|
30
30
|
]
|
|
@@ -18,12 +18,12 @@ from dotenv import load_dotenv
|
|
|
18
18
|
|
|
19
19
|
load_dotenv()
|
|
20
20
|
|
|
21
|
-
from openai_sdk_helpers.response import
|
|
21
|
+
from openai_sdk_helpers.response import ResponseBase, attach_vector_store
|
|
22
22
|
from openai_sdk_helpers.streamlit_app import (
|
|
23
23
|
StreamlitAppConfig,
|
|
24
24
|
_load_configuration,
|
|
25
25
|
)
|
|
26
|
-
from openai_sdk_helpers.structure.base import
|
|
26
|
+
from openai_sdk_helpers.structure.base import StructureBase
|
|
27
27
|
from openai_sdk_helpers.utils import (
|
|
28
28
|
coerce_jsonable,
|
|
29
29
|
customJSONEncoder,
|
|
@@ -96,7 +96,7 @@ def _cleanup_temp_files(file_paths: list[str] | None = None) -> None:
|
|
|
96
96
|
st.session_state["temp_file_paths"] = []
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
def _extract_assistant_text(response:
|
|
99
|
+
def _extract_assistant_text(response: ResponseBase[Any]) -> str:
|
|
100
100
|
"""Extract the latest assistant message as readable text.
|
|
101
101
|
|
|
102
102
|
Searches the response's message history for the most recent assistant
|
|
@@ -104,7 +104,7 @@ def _extract_assistant_text(response: BaseResponse[Any]) -> str:
|
|
|
104
104
|
|
|
105
105
|
Parameters
|
|
106
106
|
----------
|
|
107
|
-
response :
|
|
107
|
+
response : ResponseBase[Any]
|
|
108
108
|
Active response session with message history.
|
|
109
109
|
|
|
110
110
|
Returns
|
|
@@ -153,7 +153,7 @@ def _extract_assistant_text(response: BaseResponse[Any]) -> str:
|
|
|
153
153
|
return ""
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
def _render_summary(result: Any, response:
|
|
156
|
+
def _render_summary(result: Any, response: ResponseBase[Any]) -> str:
|
|
157
157
|
"""Generate display text for the chat transcript.
|
|
158
158
|
|
|
159
159
|
Converts the response result into a human-readable format suitable
|
|
@@ -163,8 +163,8 @@ def _render_summary(result: Any, response: BaseResponse[Any]) -> str:
|
|
|
163
163
|
Parameters
|
|
164
164
|
----------
|
|
165
165
|
result : Any
|
|
166
|
-
Parsed result from
|
|
167
|
-
response :
|
|
166
|
+
Parsed result from ResponseBase.run_sync.
|
|
167
|
+
response : ResponseBase[Any]
|
|
168
168
|
Response instance containing message history.
|
|
169
169
|
|
|
170
170
|
Returns
|
|
@@ -177,7 +177,7 @@ def _render_summary(result: Any, response: BaseResponse[Any]) -> str:
|
|
|
177
177
|
Falls back to extracting assistant text from message history if
|
|
178
178
|
the result cannot be formatted directly.
|
|
179
179
|
"""
|
|
180
|
-
if isinstance(result,
|
|
180
|
+
if isinstance(result, StructureBase):
|
|
181
181
|
return result.print()
|
|
182
182
|
if isinstance(result, str):
|
|
183
183
|
return result
|
|
@@ -196,7 +196,7 @@ def _render_summary(result: Any, response: BaseResponse[Any]) -> str:
|
|
|
196
196
|
return "No response returned."
|
|
197
197
|
|
|
198
198
|
|
|
199
|
-
def _build_raw_output(result: Any, response:
|
|
199
|
+
def _build_raw_output(result: Any, response: ResponseBase[Any]) -> dict[str, Any]:
|
|
200
200
|
"""Assemble raw JSON payload for the expandable transcript section.
|
|
201
201
|
|
|
202
202
|
Creates a structured dictionary containing both the parsed result
|
|
@@ -206,7 +206,7 @@ def _build_raw_output(result: Any, response: BaseResponse[Any]) -> dict[str, Any
|
|
|
206
206
|
----------
|
|
207
207
|
result : Any
|
|
208
208
|
Parsed result from the response execution.
|
|
209
|
-
response :
|
|
209
|
+
response : ResponseBase[Any]
|
|
210
210
|
Response session with complete message history.
|
|
211
211
|
|
|
212
212
|
Returns
|
|
@@ -226,8 +226,8 @@ def _build_raw_output(result: Any, response: BaseResponse[Any]) -> dict[str, Any
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
|
|
229
|
-
def _get_response_instance(config: StreamlitAppConfig) ->
|
|
230
|
-
"""Instantiate and cache the configured
|
|
229
|
+
def _get_response_instance(config: StreamlitAppConfig) -> ResponseBase[Any]:
|
|
230
|
+
"""Instantiate and cache the configured ResponseBase.
|
|
231
231
|
|
|
232
232
|
Creates a new response instance from the configuration if not already
|
|
233
233
|
cached in session state. Applies vector store attachments and cleanup
|
|
@@ -240,13 +240,13 @@ def _get_response_instance(config: StreamlitAppConfig) -> BaseResponse[Any]:
|
|
|
240
240
|
|
|
241
241
|
Returns
|
|
242
242
|
-------
|
|
243
|
-
|
|
243
|
+
ResponseBase[Any]
|
|
244
244
|
Active response instance for the current Streamlit session.
|
|
245
245
|
|
|
246
246
|
Raises
|
|
247
247
|
------
|
|
248
248
|
TypeError
|
|
249
|
-
If the configured response cannot produce a
|
|
249
|
+
If the configured response cannot produce a ResponseBase.
|
|
250
250
|
|
|
251
251
|
Notes
|
|
252
252
|
-----
|
|
@@ -255,7 +255,7 @@ def _get_response_instance(config: StreamlitAppConfig) -> BaseResponse[Any]:
|
|
|
255
255
|
"""
|
|
256
256
|
if "response_instance" in st.session_state:
|
|
257
257
|
cached = st.session_state["response_instance"]
|
|
258
|
-
if isinstance(cached,
|
|
258
|
+
if isinstance(cached, ResponseBase):
|
|
259
259
|
return cached
|
|
260
260
|
|
|
261
261
|
response = config.create_response()
|
|
@@ -291,7 +291,7 @@ def _reset_chat(close_response: bool = True) -> None:
|
|
|
291
291
|
chat_history, response_instance, and temp_file_paths keys.
|
|
292
292
|
"""
|
|
293
293
|
response = st.session_state.get("response_instance")
|
|
294
|
-
if close_response and isinstance(response,
|
|
294
|
+
if close_response and isinstance(response, ResponseBase):
|
|
295
295
|
filepath = f"./data/{response.name}.{response.uuid}.json"
|
|
296
296
|
response.save(filepath)
|
|
297
297
|
response.close()
|