openai-sdk-helpers 0.4.2__py3-none-any.whl → 0.5.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.
Files changed (68) hide show
  1. openai_sdk_helpers/__init__.py +45 -41
  2. openai_sdk_helpers/agent/__init__.py +4 -6
  3. openai_sdk_helpers/agent/base.py +110 -191
  4. openai_sdk_helpers/agent/{config.py → configuration.py} +24 -32
  5. openai_sdk_helpers/agent/{coordination.py → coordinator.py} +22 -23
  6. openai_sdk_helpers/agent/runner.py +3 -45
  7. openai_sdk_helpers/agent/search/base.py +54 -76
  8. openai_sdk_helpers/agent/search/vector.py +92 -108
  9. openai_sdk_helpers/agent/search/web.py +104 -82
  10. openai_sdk_helpers/agent/summarizer.py +22 -28
  11. openai_sdk_helpers/agent/translator.py +22 -24
  12. openai_sdk_helpers/agent/{validation.py → validator.py} +19 -23
  13. openai_sdk_helpers/cli.py +8 -22
  14. openai_sdk_helpers/environment.py +8 -13
  15. openai_sdk_helpers/errors.py +9 -0
  16. openai_sdk_helpers/extract/__init__.py +23 -0
  17. openai_sdk_helpers/extract/extractor.py +157 -0
  18. openai_sdk_helpers/extract/generator.py +476 -0
  19. openai_sdk_helpers/prompt/extractor_config_agent_instructions.jinja +6 -0
  20. openai_sdk_helpers/prompt/extractor_config_generator.jinja +37 -0
  21. openai_sdk_helpers/prompt/extractor_config_generator_instructions.jinja +9 -0
  22. openai_sdk_helpers/prompt/extractor_prompt_optimizer_agent_instructions.jinja +4 -0
  23. openai_sdk_helpers/prompt/extractor_prompt_optimizer_request.jinja +11 -0
  24. openai_sdk_helpers/prompt/vector_planner.jinja +7 -0
  25. openai_sdk_helpers/prompt/vector_search.jinja +6 -0
  26. openai_sdk_helpers/prompt/vector_writer.jinja +7 -0
  27. openai_sdk_helpers/response/__init__.py +3 -7
  28. openai_sdk_helpers/response/base.py +89 -98
  29. openai_sdk_helpers/response/{config.py → configuration.py} +45 -20
  30. openai_sdk_helpers/response/files.py +2 -0
  31. openai_sdk_helpers/response/planner.py +1 -1
  32. openai_sdk_helpers/response/prompter.py +1 -1
  33. openai_sdk_helpers/response/runner.py +1 -48
  34. openai_sdk_helpers/response/tool_call.py +0 -141
  35. openai_sdk_helpers/response/vector_store.py +8 -5
  36. openai_sdk_helpers/streamlit_app/__init__.py +1 -1
  37. openai_sdk_helpers/streamlit_app/app.py +17 -18
  38. openai_sdk_helpers/streamlit_app/{config.py → configuration.py} +13 -13
  39. openai_sdk_helpers/structure/__init__.py +16 -0
  40. openai_sdk_helpers/structure/base.py +239 -278
  41. openai_sdk_helpers/structure/extraction.py +1228 -0
  42. openai_sdk_helpers/structure/plan/plan.py +0 -20
  43. openai_sdk_helpers/structure/plan/task.py +0 -33
  44. openai_sdk_helpers/structure/prompt.py +16 -0
  45. openai_sdk_helpers/structure/responses.py +2 -2
  46. openai_sdk_helpers/structure/web_search.py +0 -10
  47. openai_sdk_helpers/tools.py +346 -99
  48. openai_sdk_helpers/types.py +3 -3
  49. openai_sdk_helpers/utils/__init__.py +9 -6
  50. openai_sdk_helpers/utils/json/base_model.py +316 -33
  51. openai_sdk_helpers/utils/json/data_class.py +1 -1
  52. openai_sdk_helpers/utils/langextract.py +194 -0
  53. openai_sdk_helpers/utils/registry.py +19 -15
  54. openai_sdk_helpers/vector_storage/storage.py +1 -1
  55. {openai_sdk_helpers-0.4.2.dist-info → openai_sdk_helpers-0.5.0.dist-info}/METADATA +25 -11
  56. openai_sdk_helpers-0.5.0.dist-info/RECORD +95 -0
  57. openai_sdk_helpers/agent/prompt_utils.py +0 -15
  58. openai_sdk_helpers/context_manager.py +0 -241
  59. openai_sdk_helpers/deprecation.py +0 -167
  60. openai_sdk_helpers/retry.py +0 -175
  61. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +0 -75
  62. openai_sdk_helpers/utils/deprecation.py +0 -167
  63. openai_sdk_helpers-0.4.2.dist-info/RECORD +0 -88
  64. /openai_sdk_helpers/{logging_config.py → logging.py} +0 -0
  65. /openai_sdk_helpers/{config.py → settings.py} +0 -0
  66. {openai_sdk_helpers-0.4.2.dist-info → openai_sdk_helpers-0.5.0.dist-info}/WHEEL +0 -0
  67. {openai_sdk_helpers-0.4.2.dist-info → openai_sdk_helpers-0.5.0.dist-info}/entry_points.txt +0 -0
  68. {openai_sdk_helpers-0.4.2.dist-info → openai_sdk_helpers-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,9 +7,6 @@ and robust argument parsing.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import ast
11
- import json
12
- import re
13
10
  from dataclasses import dataclass
14
11
 
15
12
  from openai.types.responses.response_function_tool_call_param import (
@@ -94,141 +91,3 @@ class ResponseToolCall(DataclassJSONSerializable):
94
91
  },
95
92
  )
96
93
  return function_call, function_call_output
97
-
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
-
178
- def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
179
- """Parse tool call arguments with fallback for malformed JSON.
180
-
181
- Attempts to parse arguments as JSON first, then falls back to
182
- ast.literal_eval for cases where the OpenAI API returns minor
183
- formatting issues like single quotes instead of double quotes.
184
- Provides clear error context including tool name and raw payload.
185
-
186
- Also handles unwrapping of arguments that are wrapped in a single-key
187
- dictionary matching the tool name (e.g., {"ExampleStructure": {...}}).
188
-
189
- Parameters
190
- ----------
191
- arguments : str
192
- Raw argument string from a tool call, expected to be JSON.
193
- tool_name : str
194
- Tool name for improved error context (required).
195
-
196
- Returns
197
- -------
198
- dict
199
- Parsed dictionary of tool arguments, with wrapper unwrapped if present.
200
-
201
- Raises
202
- ------
203
- ValueError
204
- If the arguments cannot be parsed as valid JSON or Python literal.
205
- Error message includes tool name and payload excerpt for debugging.
206
-
207
- Examples
208
- --------
209
- >>> parse_tool_arguments('{"key": "value"}', tool_name="search")
210
- {'key': 'value'}
211
-
212
- >>> parse_tool_arguments("{'key': 'value'}", tool_name="search")
213
- {'key': 'value'}
214
-
215
- >>> parse_tool_arguments('{"ExampleTool": {"arg": "value"}}', "ExampleTool")
216
- {'arg': 'value'}
217
- """
218
- try:
219
- parsed = json.loads(arguments)
220
- except json.JSONDecodeError:
221
- try:
222
- parsed = ast.literal_eval(arguments)
223
- except Exception as exc: # noqa: BLE001
224
- # Build informative error message with context
225
- payload_preview = (
226
- arguments[:100] + "..." if len(arguments) > 100 else arguments
227
- )
228
- raise ValueError(
229
- f"Failed to parse tool arguments for tool '{tool_name}'. "
230
- f"Raw payload: {payload_preview}"
231
- ) from exc
232
-
233
- # Unwrap if wrapped in a single-key dict matching tool name
234
- return _unwrap_arguments(parsed, tool_name)
@@ -73,13 +73,16 @@ def attach_vector_store(
73
73
  raise ValueError(f"Vector store '{store}' not found.")
74
74
  if match not in resolved_ids:
75
75
  resolved_ids.append(match)
76
-
77
- file_search_tool = next(
78
- (tool for tool in response._tools if tool.get("type") == "file_search"),
79
- None,
80
- )
76
+ file_search_tool = None
77
+ if response._tools is not None:
78
+ file_search_tool = next(
79
+ (tool for tool in response._tools if tool.get("type") == "file_search"),
80
+ None,
81
+ )
81
82
 
82
83
  if file_search_tool is None:
84
+ if response._tools is None:
85
+ response._tools = []
83
86
  response._tools.append(
84
87
  {"type": "file_search", "vector_store_ids": resolved_ids}
85
88
  )
@@ -17,7 +17,7 @@ _load_configuration
17
17
  Load configuration with user-friendly error handling for Streamlit UI.
18
18
  """
19
19
 
20
- from .config import (
20
+ from .configuration import (
21
21
  StreamlitAppConfig,
22
22
  StreamlitAppRegistry,
23
23
  _load_configuration,
@@ -28,7 +28,6 @@ from openai_sdk_helpers.utils import (
28
28
  coerce_jsonable,
29
29
  customJSONEncoder,
30
30
  ensure_list,
31
- log,
32
31
  )
33
32
 
34
33
  # Supported file extensions for OpenAI Assistants file search and vision
@@ -178,7 +177,7 @@ def _render_summary(result: Any, response: ResponseBase[Any]) -> str:
178
177
  the result cannot be formatted directly.
179
178
  """
180
179
  if isinstance(result, StructureBase):
181
- return result.print()
180
+ return str(result)
182
181
  if isinstance(result, str):
183
182
  return result
184
183
  if isinstance(result, dict):
@@ -226,7 +225,7 @@ def _build_raw_output(result: Any, response: ResponseBase[Any]) -> dict[str, Any
226
225
  }
227
226
 
228
227
 
229
- def _get_response_instance(config: StreamlitAppConfig) -> ResponseBase[Any]:
228
+ def _get_response_instance(configuration: StreamlitAppConfig) -> ResponseBase[Any]:
230
229
  """Instantiate and cache the configured ResponseBase.
231
230
 
232
231
  Creates a new response instance from the configuration if not already
@@ -235,7 +234,7 @@ def _get_response_instance(config: StreamlitAppConfig) -> ResponseBase[Any]:
235
234
 
236
235
  Parameters
237
236
  ----------
238
- config : StreamlitAppConfig
237
+ configuration : StreamlitAppConfig
239
238
  Loaded configuration with response handler definition.
240
239
 
241
240
  Returns
@@ -258,13 +257,13 @@ def _get_response_instance(config: StreamlitAppConfig) -> ResponseBase[Any]:
258
257
  if isinstance(cached, ResponseBase):
259
258
  return cached
260
259
 
261
- response = config.create_response()
260
+ response = configuration.create_response()
262
261
 
263
- if config.preserve_vector_stores:
262
+ if configuration.preserve_vector_stores:
264
263
  setattr(response, "_cleanup_system_vector_storage", False)
265
264
  setattr(response, "_cleanup_user_vector_storage", False)
266
265
 
267
- vector_stores = config.normalized_vector_stores()
266
+ vector_stores = configuration.normalized_vector_stores()
268
267
  if vector_stores:
269
268
  attach_vector_store(response=response, vector_stores=vector_stores)
270
269
 
@@ -357,7 +356,7 @@ def _render_chat_history() -> None:
357
356
 
358
357
  def _handle_user_message(
359
358
  prompt: str,
360
- config: StreamlitAppConfig,
359
+ configuration: StreamlitAppConfig,
361
360
  attachment_paths: list[str] | None = None,
362
361
  attachment_names: list[str] | None = None,
363
362
  ) -> None:
@@ -371,7 +370,7 @@ def _handle_user_message(
371
370
  ----------
372
371
  prompt : str
373
372
  User-entered text to send to the assistant.
374
- config : StreamlitAppConfig
373
+ configuration : StreamlitAppConfig
375
374
  Loaded configuration with response handler definition.
376
375
  attachment_paths : list[str] or None, default None
377
376
  Optional list of file paths to attach to the message.
@@ -395,7 +394,7 @@ def _handle_user_message(
395
394
  {"role": "user", "content": prompt, "attachments": display_names}
396
395
  )
397
396
  try:
398
- response = _get_response_instance(config)
397
+ response = _get_response_instance(configuration)
399
398
  except Exception as exc: # pragma: no cover - surfaced in UI
400
399
  st.error(f"Failed to start response session: {exc}")
401
400
  return
@@ -442,15 +441,15 @@ def main(config_path: Path) -> None:
442
441
  >>> from pathlib import Path
443
442
  >>> main(Path("./my_config.py"))
444
443
  """
445
- config = _load_configuration(config_path)
446
- st.set_page_config(page_title=config.display_title, layout="wide")
444
+ configuration = _load_configuration(config_path)
445
+ st.set_page_config(page_title=configuration.display_title, layout="wide")
447
446
  _init_session_state()
448
447
 
449
- st.title(config.display_title)
450
- if config.description:
451
- st.caption(config.description)
452
- if config.model:
453
- st.caption(f"Model: {config.model}")
448
+ st.title(configuration.display_title)
449
+ if configuration.description:
450
+ st.caption(configuration.description)
451
+ if configuration.model:
452
+ st.caption(f"Model: {configuration.model}")
454
453
 
455
454
  close_col, _ = st.columns([1, 5])
456
455
  with close_col:
@@ -514,7 +513,7 @@ def main(config_path: Path) -> None:
514
513
  st.session_state["attachment_names"] = []
515
514
  _handle_user_message(
516
515
  prompt,
517
- config,
516
+ configuration,
518
517
  attachment_paths or None,
519
518
  attachment_display_names or None,
520
519
  )
@@ -54,7 +54,7 @@ class StreamlitAppConfig(BaseModelJSONSerializable):
54
54
  Examples
55
55
  --------
56
56
  >>> from openai_sdk_helpers.streamlit_app import StreamlitAppConfig
57
- >>> config = StreamlitAppConfig(
57
+ >>> configuration = StreamlitAppConfig(
58
58
  ... response=MyResponse,
59
59
  ... display_title="My Assistant",
60
60
  ... description="A helpful AI assistant"
@@ -180,7 +180,7 @@ class StreamlitAppConfig(BaseModelJSONSerializable):
180
180
 
181
181
  Examples
182
182
  --------
183
- >>> config.normalized_vector_stores()
183
+ >>> configuration.normalized_vector_stores()
184
184
  ['docs', 'knowledge_base']
185
185
  """
186
186
  return list(self.system_vector_store or [])
@@ -224,7 +224,7 @@ class StreamlitAppConfig(BaseModelJSONSerializable):
224
224
 
225
225
  Examples
226
226
  --------
227
- >>> response = config.create_response()
227
+ >>> response = configuration.create_response()
228
228
  >>> result = response.run_sync("Hello")
229
229
  """
230
230
  return _instantiate_response(self.response)
@@ -238,7 +238,7 @@ class StreamlitAppRegistry(RegistryBase[StreamlitAppConfig]):
238
238
 
239
239
  Methods
240
240
  -------
241
- register(config)
241
+ register(configuration)
242
242
  Add a configuration to the registry.
243
243
  get(name)
244
244
  Retrieve a configuration by name.
@@ -256,9 +256,9 @@ class StreamlitAppRegistry(RegistryBase[StreamlitAppConfig]):
256
256
  Examples
257
257
  --------
258
258
  >>> registry = StreamlitAppRegistry()
259
- >>> config = StreamlitAppConfig(response=MyResponse)
260
- >>> registry.register(config)
261
- >>> registry.get(config.name)
259
+ >>> configuration = StreamlitAppConfig(response=MyResponse)
260
+ >>> registry.register(configuration)
261
+ >>> registry.get(configuration.name)
262
262
  StreamlitAppConfig(...)
263
263
  """
264
264
 
@@ -295,7 +295,7 @@ class StreamlitAppRegistry(RegistryBase[StreamlitAppConfig]):
295
295
  Examples
296
296
  --------
297
297
  >>> from pathlib import Path
298
- >>> config = StreamlitAppRegistry.load_app_config(
298
+ >>> configuration = StreamlitAppRegistry.load_app_config(
299
299
  ... Path("./my_config.py")
300
300
  ... )
301
301
  """
@@ -328,7 +328,7 @@ def _import_config_module(config_path: Path) -> ModuleType:
328
328
 
329
329
  Examples
330
330
  --------
331
- >>> module = _import_config_module(Path("./config.py"))
331
+ >>> module = _import_config_module(Path("./configuration.py"))
332
332
  >>> hasattr(module, 'APP_CONFIG')
333
333
  True
334
334
  """
@@ -349,7 +349,7 @@ def _extract_config(module: ModuleType) -> StreamlitAppConfig:
349
349
 
350
350
  Looks for APP_CONFIG in the module and converts it to a validated
351
351
  StreamlitAppConfig instance. Supports multiple input formats including
352
- dictionaries, ResponseBase instances, and existing config objects.
352
+ dictionaries, ResponseBase instances, and existing configuration objects.
353
353
 
354
354
  Parameters
355
355
  ----------
@@ -371,8 +371,8 @@ def _extract_config(module: ModuleType) -> StreamlitAppConfig:
371
371
 
372
372
  Examples
373
373
  --------
374
- >>> config = _extract_config(module)
375
- >>> isinstance(config, StreamlitAppConfig)
374
+ >>> configuration = _extract_config(module)
375
+ >>> isinstance(configuration, StreamlitAppConfig)
376
376
  True
377
377
  """
378
378
  if not hasattr(module, "APP_CONFIG"):
@@ -455,7 +455,7 @@ def _config_from_mapping(raw_config: dict) -> StreamlitAppConfig:
455
455
 
456
456
  Examples
457
457
  --------
458
- >>> config = _config_from_mapping({
458
+ >>> configuration = _config_from_mapping({
459
459
  ... 'response': MyResponse,
460
460
  ... 'display_title': 'My App'
461
461
  ... })
@@ -53,6 +53,10 @@ VectorSearchReportStructure
53
53
  Complete vector search report.
54
54
  ValidationResultStructure
55
55
  Validation results with pass/fail status.
56
+ ExtractionItem
57
+ Extracted item with source span data.
58
+ ExtractionResult
59
+ Structured extraction results for a document.
56
60
 
57
61
  Functions
58
62
  ---------
@@ -72,6 +76,13 @@ from __future__ import annotations
72
76
 
73
77
  from .agent_blueprint import AgentBlueprint
74
78
  from .base import *
79
+ from .extraction import (
80
+ AnnotatedDocumentStructure,
81
+ AttributeStructure,
82
+ DocumentStructure,
83
+ ExampleDataStructure,
84
+ ExtractionStructure,
85
+ )
75
86
  from .plan import *
76
87
  from .prompt import PromptStructure
77
88
  from .responses import *
@@ -109,6 +120,11 @@ __all__ = [
109
120
  "VectorSearchPlanStructure",
110
121
  "VectorSearchStructure",
111
122
  "ValidationResultStructure",
123
+ "AnnotatedDocumentStructure",
124
+ "AttributeStructure",
125
+ "DocumentStructure",
126
+ "ExampleDataStructure",
127
+ "ExtractionStructure",
112
128
  "assistant_tool_definition",
113
129
  "assistant_format",
114
130
  "response_tool_definition",