openai-sdk-helpers 0.4.3__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 (47) hide show
  1. openai_sdk_helpers/__init__.py +41 -7
  2. openai_sdk_helpers/agent/__init__.py +1 -2
  3. openai_sdk_helpers/agent/base.py +89 -173
  4. openai_sdk_helpers/agent/configuration.py +12 -20
  5. openai_sdk_helpers/agent/coordinator.py +14 -17
  6. openai_sdk_helpers/agent/runner.py +3 -45
  7. openai_sdk_helpers/agent/search/base.py +49 -71
  8. openai_sdk_helpers/agent/search/vector.py +82 -110
  9. openai_sdk_helpers/agent/search/web.py +103 -81
  10. openai_sdk_helpers/agent/summarizer.py +20 -28
  11. openai_sdk_helpers/agent/translator.py +17 -23
  12. openai_sdk_helpers/agent/validator.py +17 -23
  13. openai_sdk_helpers/errors.py +9 -0
  14. openai_sdk_helpers/extract/__init__.py +23 -0
  15. openai_sdk_helpers/extract/extractor.py +157 -0
  16. openai_sdk_helpers/extract/generator.py +476 -0
  17. openai_sdk_helpers/prompt/extractor_config_agent_instructions.jinja +6 -0
  18. openai_sdk_helpers/prompt/extractor_config_generator.jinja +37 -0
  19. openai_sdk_helpers/prompt/extractor_config_generator_instructions.jinja +9 -0
  20. openai_sdk_helpers/prompt/extractor_prompt_optimizer_agent_instructions.jinja +4 -0
  21. openai_sdk_helpers/prompt/extractor_prompt_optimizer_request.jinja +11 -0
  22. openai_sdk_helpers/response/__init__.py +2 -6
  23. openai_sdk_helpers/response/base.py +85 -94
  24. openai_sdk_helpers/response/configuration.py +39 -14
  25. openai_sdk_helpers/response/files.py +2 -0
  26. openai_sdk_helpers/response/runner.py +1 -48
  27. openai_sdk_helpers/response/tool_call.py +0 -141
  28. openai_sdk_helpers/response/vector_store.py +8 -5
  29. openai_sdk_helpers/streamlit_app/app.py +1 -1
  30. openai_sdk_helpers/structure/__init__.py +16 -0
  31. openai_sdk_helpers/structure/base.py +239 -278
  32. openai_sdk_helpers/structure/extraction.py +1228 -0
  33. openai_sdk_helpers/structure/plan/plan.py +0 -20
  34. openai_sdk_helpers/structure/plan/task.py +0 -33
  35. openai_sdk_helpers/structure/prompt.py +16 -0
  36. openai_sdk_helpers/structure/responses.py +2 -2
  37. openai_sdk_helpers/structure/web_search.py +0 -10
  38. openai_sdk_helpers/tools.py +346 -99
  39. openai_sdk_helpers/utils/__init__.py +7 -0
  40. openai_sdk_helpers/utils/json/base_model.py +315 -32
  41. openai_sdk_helpers/utils/langextract.py +194 -0
  42. {openai_sdk_helpers-0.4.3.dist-info → openai_sdk_helpers-0.5.0.dist-info}/METADATA +18 -4
  43. {openai_sdk_helpers-0.4.3.dist-info → openai_sdk_helpers-0.5.0.dist-info}/RECORD +46 -37
  44. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +0 -75
  45. {openai_sdk_helpers-0.4.3.dist-info → openai_sdk_helpers-0.5.0.dist-info}/WHEEL +0 -0
  46. {openai_sdk_helpers-0.4.3.dist-info → openai_sdk_helpers-0.5.0.dist-info}/entry_points.txt +0 -0
  47. {openai_sdk_helpers-0.4.3.dist-info → openai_sdk_helpers-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -10,81 +10,207 @@ definitions from named metadata structures.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import ast
13
14
  import asyncio
14
15
  import inspect
16
+ import json
17
+ import re
15
18
  import threading
16
19
  from dataclasses import dataclass
17
- from typing import Any, Callable, TypeAlias, TypeVar
18
-
19
- from pydantic import BaseModel, ValidationError
20
+ from typing import Any, Callable, TypeAlias, get_type_hints
20
21
 
21
- from openai_sdk_helpers.response.tool_call import parse_tool_arguments
22
22
  from openai_sdk_helpers.structure.base import StructureBase
23
- from openai_sdk_helpers.utils import coerce_jsonable, customJSONEncoder
24
- import json
23
+ from openai_sdk_helpers.utils import customJSONEncoder
25
24
 
26
- T = TypeVar("T", bound=BaseModel)
27
25
  StructureType: TypeAlias = type[StructureBase]
26
+ ToolHandler: TypeAlias = Callable[[Any], str | Any]
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class ToolHandlerRegistration:
31
+ """Bundle a tool handler with optional ToolSpec metadata.
32
+
33
+ Parameters
34
+ ----------
35
+ handler : ToolHandler
36
+ Callable that executes the tool and returns a serializable payload.
37
+ tool_spec : ToolSpec or None, default None
38
+ Optional ToolSpec used to parse tool outputs based on the tool name.
28
39
 
40
+ Attributes
41
+ ----------
42
+ handler : ToolHandler
43
+ Callable that executes the tool and returns a serializable payload.
44
+ tool_spec : ToolSpec
45
+ ToolSpec describing the tool input/output structures.
46
+
47
+ Methods
48
+ -------
49
+ __init__(handler, tool_spec)
50
+ Initialize the registration with a handler and ToolSpec.
51
+ """
29
52
 
30
- def serialize_tool_result(result: Any) -> str:
31
- """Serialize tool results into a standardized JSON string.
53
+ handler: ToolHandler
54
+ tool_spec: ToolSpec
32
55
 
33
- Handles Pydantic models, lists, dicts, and plain strings with consistent
34
- JSON formatting. Pydantic models are serialized using model_dump(),
35
- while other types are converted to JSON or string representation.
56
+
57
+ def _to_snake_case(name: str) -> str:
58
+ """Convert a PascalCase or camelCase string to snake_case.
36
59
 
37
60
  Parameters
38
61
  ----------
39
- result : Any
40
- Tool result to serialize. Can be a Pydantic model, list, dict, str,
41
- or any JSON-serializable type.
62
+ name : str
63
+ The name to convert.
42
64
 
43
65
  Returns
44
66
  -------
45
67
  str
46
- JSON-formatted string representation of the result.
68
+ The snake_case version of the name.
47
69
 
48
70
  Examples
49
71
  --------
50
- >>> from pydantic import BaseModel
51
- >>> class Result(BaseModel):
52
- ... value: int
53
- >>> serialize_tool_result(Result(value=42))
54
- '{"value": 42}'
72
+ >>> _to_snake_case("ExampleStructure")
73
+ 'example_structure'
74
+ >>> _to_snake_case("MyToolName")
75
+ 'my_tool_name'
76
+ """
77
+ # First regex: Insert underscore before uppercase letters followed by
78
+ # lowercase letters (e.g., "Tool" in "ExampleTool" becomes "_Tool")
79
+ s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
80
+ # Second regex: Insert underscore between lowercase/digit and uppercase
81
+ # (e.g., "e3" followed by "T" becomes "e3_T")
82
+ return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
83
+
84
+
85
+ def _unwrap_arguments(parsed: dict, tool_name: str) -> dict:
86
+ """Unwrap arguments if wrapped in a single-key dict.
55
87
 
56
- >>> serialize_tool_result(["item1", "item2"])
57
- '["item1", "item2"]'
88
+ Some responses wrap arguments under a key matching the structure class
89
+ name (e.g., {"ExampleStructure": {...}}) or snake_case variant
90
+ (e.g., {"example_structure": {...}}). This function detects and unwraps
91
+ such wrappers to normalize the payload.
58
92
 
59
- >>> serialize_tool_result("plain text")
60
- '"plain text"'
93
+ Parameters
94
+ ----------
95
+ parsed : dict
96
+ The parsed arguments dictionary.
97
+ tool_name : str
98
+ The tool name, used to match potential wrapper keys.
61
99
 
62
- >>> serialize_tool_result({"key": "value"})
63
- '{"key": "value"}'
100
+ Returns
101
+ -------
102
+ dict
103
+ Unwrapped arguments dictionary, or original if no wrapper detected.
104
+
105
+ Examples
106
+ --------
107
+ >>> _unwrap_arguments({"ExampleTool": {"arg": "value"}}, "ExampleTool")
108
+ {'arg': 'value'}
109
+ >>> _unwrap_arguments({"example_tool": {"arg": "value"}}, "ExampleTool")
110
+ {'arg': 'value'}
111
+ >>> _unwrap_arguments({"arg": "value"}, "ExampleTool")
112
+ {'arg': 'value'}
64
113
  """
65
- if isinstance(result, BaseModel):
66
- return result.model_dump_json()
114
+ # Only unwrap if dict has exactly one key
115
+ if not isinstance(parsed, dict) or len(parsed) != 1:
116
+ return parsed
117
+
118
+ wrapper_key = next(iter(parsed))
119
+ wrapped_value = parsed[wrapper_key]
67
120
 
68
- payload = coerce_jsonable(result)
69
- return json.dumps(payload, cls=customJSONEncoder)
121
+ # Only unwrap if the value is also a dict
122
+ if not isinstance(wrapped_value, dict):
123
+ return parsed
124
+
125
+ # Check if wrapper key matches tool name (case-insensitive or snake_case)
126
+ tool_name_lower = tool_name.lower()
127
+ tool_name_snake = _to_snake_case(tool_name)
128
+ wrapper_key_lower = wrapper_key.lower()
129
+
130
+ if wrapper_key_lower in (tool_name_lower, tool_name_snake):
131
+ return wrapped_value
132
+
133
+ return parsed
134
+
135
+
136
+ def _parse_tool_arguments(arguments: str, tool_name: str) -> dict:
137
+ """Parse tool call arguments with fallback for malformed JSON.
138
+
139
+ Attempts to parse arguments as JSON first, then falls back to
140
+ ast.literal_eval for cases where the OpenAI API returns minor
141
+ formatting issues like single quotes instead of double quotes.
142
+ Provides clear error context including tool name and raw payload.
143
+
144
+ Also handles unwrapping of arguments that are wrapped in a single-key
145
+ dictionary matching the tool name (e.g., {"ExampleStructure": {...}}).
146
+
147
+ Parameters
148
+ ----------
149
+ arguments : str
150
+ Raw argument string from a tool call, expected to be JSON.
151
+ tool_name : str
152
+ Tool name for improved error context (required).
153
+
154
+ Returns
155
+ -------
156
+ dict
157
+ Parsed dictionary of tool arguments, with wrapper unwrapped if present.
158
+
159
+ Raises
160
+ ------
161
+ ValueError
162
+ If the arguments cannot be parsed as valid JSON or Python literal.
163
+ Error message includes tool name and payload excerpt for debugging.
164
+
165
+ Examples
166
+ --------
167
+ >>> _parse_tool_arguments('{"key": "value"}', tool_name="search")
168
+ {'key': 'value'}
169
+
170
+ >>> _parse_tool_arguments("{'key': 'value'}", tool_name="search")
171
+ {'key': 'value'}
172
+
173
+ >>> _parse_tool_arguments('{"ExampleTool": {"arg": "value"}}', "ExampleTool")
174
+ {'arg': 'value'}
175
+ """
176
+ try:
177
+ parsed = json.loads(arguments)
178
+ except json.JSONDecodeError:
179
+ try:
180
+ parsed = ast.literal_eval(arguments)
181
+ except Exception as exc: # noqa: BLE001
182
+ # Build informative error message with context
183
+ payload_preview = (
184
+ arguments[:100] + "..." if len(arguments) > 100 else arguments
185
+ )
186
+ raise ValueError(
187
+ f"Failed to parse tool arguments for tool '{tool_name}'. "
188
+ f"Raw payload: {payload_preview}"
189
+ ) from exc
190
+
191
+ # Unwrap if wrapped in a single-key dict matching tool name
192
+ return _unwrap_arguments(parsed, tool_name)
70
193
 
71
194
 
72
195
  def tool_handler_factory(
73
196
  func: Callable[..., Any],
74
197
  *,
75
- input_model: type[T] | None = None,
198
+ tool_spec: "ToolSpec",
76
199
  ) -> Callable[[Any], str]:
77
200
  """Create a generic tool handler that parses, validates, and serializes.
78
201
 
79
- Wraps a tool function with automatic argument parsing, optional Pydantic
202
+ Wraps a tool function with automatic argument parsing, structured
80
203
  validation, execution, and result serialization. This eliminates
81
204
  repetitive boilerplate for tool implementations.
82
205
 
83
206
  The returned handler:
84
- 1. Parses tool_call.arguments using parse_tool_arguments
85
- 2. Validates arguments with input_model if provided
86
- 3. Calls func with validated/parsed arguments (handles both sync and async)
87
- 4. Serializes the result using serialize_tool_result
207
+ 1. Parses tool_call.arguments using ToolSpec.unserialize_tool_arguments
208
+ 2. Validates arguments with the input structure
209
+ 3. Calls func with structured input (handles both sync and async)
210
+ 4. Serializes the result using ToolSpec.serialize_tool_result
211
+
212
+ Forward-referenced annotations on single-argument tool callables are
213
+ resolved when possible to decide whether to pass the structured input.
88
214
 
89
215
  Parameters
90
216
  ----------
@@ -92,10 +218,10 @@ def tool_handler_factory(
92
218
  The actual tool implementation function. Should accept keyword
93
219
  arguments matching the tool's parameter schema. Can be synchronous
94
220
  or asynchronous.
95
- input_model : type[BaseModel] or None, default None
96
- Optional Pydantic model for input validation. When provided,
97
- arguments are validated and converted to this model before being
98
- passed to func.
221
+ tool_spec : ToolSpec
222
+ Tool specification describing input and output structures. When
223
+ provided, input parsing uses the input structure and output
224
+ serialization uses the output structure.
99
225
 
100
226
  Returns
101
227
  -------
@@ -105,34 +231,42 @@ def tool_handler_factory(
105
231
 
106
232
  Raises
107
233
  ------
108
- ValidationError
109
- If input_model is provided and validation fails.
110
234
  ValueError
111
235
  If argument parsing fails.
236
+ ValidationError
237
+ If input validation fails.
112
238
 
113
239
  Examples
114
240
  --------
115
- Basic usage without validation:
116
-
117
- >>> def search_tool(query: str, limit: int = 10):
118
- ... return {"results": [f"Result for {query}"]}
119
- >>> handler = tool_handler_factory(search_tool)
241
+ Basic usage with ToolSpec:
120
242
 
121
- With Pydantic validation:
122
-
123
- >>> from pydantic import BaseModel
124
- >>> class SearchInput(BaseModel):
125
- ... query: str
126
- ... limit: int = 10
127
- >>> def search_tool(query: str, limit: int = 10):
128
- ... return {"results": [f"Result for {query}"]}
129
- >>> handler = tool_handler_factory(search_tool, input_model=SearchInput)
243
+ >>> from openai_sdk_helpers import ToolSpec
244
+ >>> from openai_sdk_helpers.structure import PromptStructure
245
+ >>> def search_tool(prompt: PromptStructure):
246
+ ... return {"prompt": prompt.prompt}
247
+ >>> handler = tool_handler_factory(
248
+ ... search_tool,
249
+ ... tool_spec=ToolSpec(
250
+ ... tool_name="search",
251
+ ... tool_description="Run a search query",
252
+ ... input_structure=PromptStructure,
253
+ ... output_structure=PromptStructure,
254
+ ... ),
255
+ ... )
130
256
 
131
257
  With async function:
132
258
 
133
- >>> async def async_search_tool(query: str, limit: int = 10):
134
- ... return {"results": [f"Result for {query}"]}
135
- >>> handler = tool_handler_factory(async_search_tool)
259
+ >>> async def async_search_tool(prompt: PromptStructure):
260
+ ... return {"prompt": prompt.prompt}
261
+ >>> handler = tool_handler_factory(
262
+ ... async_search_tool,
263
+ ... tool_spec=ToolSpec(
264
+ ... tool_name="async_search",
265
+ ... tool_description="Run an async search query",
266
+ ... input_structure=PromptStructure,
267
+ ... output_structure=PromptStructure,
268
+ ... ),
269
+ ... )
136
270
 
137
271
  The handler can then be used with OpenAI tool calls:
138
272
 
@@ -145,6 +279,29 @@ def tool_handler_factory(
145
279
  """
146
280
  is_async = inspect.iscoroutinefunction(func)
147
281
 
282
+ def _call_with_input(validated_input: StructureBase) -> Any:
283
+ signature = inspect.signature(func)
284
+ params = list(signature.parameters.values())
285
+ if len(params) == 1:
286
+ param = params[0]
287
+ try:
288
+ type_hints = get_type_hints(func)
289
+ except (NameError, TypeError):
290
+ type_hints = {}
291
+ annotated_type = type_hints.get(param.name, param.annotation)
292
+ if isinstance(annotated_type, str):
293
+ if (
294
+ annotated_type == tool_spec.input_structure.__name__
295
+ or annotated_type.endswith(f".{tool_spec.input_structure.__name__}")
296
+ ):
297
+ return func(validated_input)
298
+ if annotated_type is tool_spec.input_structure or (
299
+ inspect.isclass(annotated_type)
300
+ and issubclass(annotated_type, StructureBase)
301
+ ):
302
+ return func(validated_input)
303
+ return func(**validated_input.model_dump())
304
+
148
305
  def handler(tool_call: Any) -> str:
149
306
  """Handle tool execution with parsing, validation, and serialization.
150
307
 
@@ -163,21 +320,9 @@ def tool_handler_factory(
163
320
  ValueError
164
321
  If argument parsing fails.
165
322
  ValidationError
166
- If Pydantic validation fails (when input_model is provided).
323
+ If input validation fails.
167
324
  """
168
- # Extract tool name for error context (required)
169
- tool_name = getattr(tool_call, "name", "unknown")
170
-
171
- # Parse arguments with error context
172
- parsed_args = parse_tool_arguments(tool_call.arguments, tool_name=tool_name)
173
-
174
- # Validate with Pydantic if model provided
175
- if input_model is not None:
176
- validated_input = input_model(**parsed_args)
177
- # Convert back to dict for function call
178
- call_kwargs = validated_input.model_dump()
179
- else:
180
- call_kwargs = parsed_args
325
+ validated_input = tool_spec.unserialize_arguments(tool_call)
181
326
 
182
327
  # Execute function (sync or async with event loop detection)
183
328
  if is_async:
@@ -189,7 +334,9 @@ def tool_handler_factory(
189
334
 
190
335
  def _thread_func() -> None:
191
336
  try:
192
- result_holder["value"] = asyncio.run(func(**call_kwargs))
337
+ result_holder["value"] = asyncio.run(
338
+ _call_with_input(validated_input)
339
+ )
193
340
  except Exception as exc:
194
341
  result_holder["exception"] = exc
195
342
 
@@ -202,12 +349,12 @@ def tool_handler_factory(
202
349
  result = result_holder["value"]
203
350
  except RuntimeError:
204
351
  # No event loop running, can use asyncio.run directly
205
- result = asyncio.run(func(**call_kwargs))
352
+ result = asyncio.run(_call_with_input(validated_input))
206
353
  else:
207
- result = func(**call_kwargs)
354
+ result = _call_with_input(validated_input)
208
355
 
209
356
  # Serialize result
210
- return serialize_tool_result(result)
357
+ return tool_spec.serialize_tool_results(result)
211
358
 
212
359
  return handler
213
360
 
@@ -225,15 +372,15 @@ class ToolSpec:
225
372
 
226
373
  Attributes
227
374
  ----------
228
- structure : StructureType
229
- The StructureBase class that defines the tool's input parameter schema.
230
- Used to generate the OpenAI tool definition.
231
375
  tool_name : str
232
376
  Name identifier for the tool.
233
377
  tool_description : str
234
378
  Human-readable description of what the tool does.
235
- output_structure : StructureType or None, default=None
236
- Optional StructureBase class that defines the tool's output schema.
379
+ input_structure : StructureType
380
+ The StructureBase class that defines the tool's input parameter schema.
381
+ Used to generate the OpenAI tool definition.
382
+ output_structure : StructureType
383
+ StructureBase class that defines the tool's output schema.
237
384
  This is for documentation/reference only and is not sent to OpenAI.
238
385
  Useful when a tool accepts one type of input but returns a different
239
386
  structured output.
@@ -245,29 +392,126 @@ class ToolSpec:
245
392
  >>> from openai_sdk_helpers import ToolSpec
246
393
  >>> from openai_sdk_helpers.structure import PromptStructure
247
394
  >>> spec = ToolSpec(
248
- ... structure=PromptStructure,
249
395
  ... tool_name="web_agent",
250
- ... tool_description="Run a web research workflow"
396
+ ... tool_description="Run a web research workflow",
397
+ ... input_structure=PromptStructure,
398
+ ... output_structure=PromptStructure
251
399
  ... )
252
400
 
253
401
  Define a tool with different input and output structures:
254
402
 
255
403
  >>> from openai_sdk_helpers.structure import PromptStructure, SummaryStructure
256
404
  >>> spec = ToolSpec(
257
- ... structure=PromptStructure,
258
405
  ... tool_name="summarizer",
259
406
  ... tool_description="Summarize the provided prompt",
407
+ ... input_structure=PromptStructure,
260
408
  ... output_structure=SummaryStructure
261
409
  ... )
262
410
  """
263
411
 
264
- structure: StructureType
265
412
  tool_name: str
266
- tool_description: str
267
- output_structure: StructureType | None = None
413
+ tool_description: str | None
414
+ input_structure: StructureType
415
+ output_structure: StructureType
416
+
417
+ def __post_init__(self) -> None:
418
+ """Validate required ToolSpec fields."""
419
+ if self.output_structure is None:
420
+ raise ValueError("ToolSpec.output_structure must be set.")
421
+
422
+ def serialize_tool_results(self, tool_results: Any) -> str:
423
+ """Serialize tool results into a standardized JSON string.
424
+
425
+ Handles structured outputs with consistent JSON formatting. Outputs are
426
+ validated and serialized through the ToolSpec output structure.
427
+
428
+ Parameters
429
+ ----------
430
+ result : Any
431
+ Tool result to serialize. Can be a structure instance or a compatible
432
+ mapping for validation.
433
+
434
+ Returns
435
+ -------
436
+ str
437
+ JSON-formatted string representation of the result.
438
+
439
+ Examples
440
+ --------
441
+ >>> from openai_sdk_helpers import ToolSpec
442
+ >>> from openai_sdk_helpers.structure import PromptStructure
443
+ >>> spec = ToolSpec(
444
+ ... tool_name="echo",
445
+ ... tool_description="Echo a prompt",
446
+ ... input_structure=PromptStructure,
447
+ ... output_structure=PromptStructure,
448
+ ... )
449
+ >>> spec.serialize_tool_result({"prompt": "hello"})
450
+ '{"prompt": "hello"}'
451
+ """
452
+ output_structure = self.output_structure
453
+ payload = output_structure.model_validate(tool_results).to_json()
454
+ return json.dumps(payload, cls=customJSONEncoder)
455
+
456
+ def unserialize_arguments(self, tool_call: Any) -> StructureBase:
457
+ """Unserialize tool call arguments into a structured input instance.
458
+
459
+ Parameters
460
+ ----------
461
+ tool_call : Any
462
+ Tool call object with 'arguments' and 'name' attributes.
463
+
464
+ Returns
465
+ -------
466
+ StructureBase
467
+ Validated input structure instance.
468
+
469
+ Raises
470
+ ------
471
+ ValueError
472
+ If argument parsing fails.
473
+ ValidationError
474
+ If input validation fails.
475
+ """
476
+ tool_name = getattr(tool_call, "name", self.tool_name)
477
+ parsed_args = _parse_tool_arguments(tool_call.arguments, tool_name=tool_name)
478
+ return self.input_structure.from_json(parsed_args)
479
+
480
+ def as_tool_definition(self) -> dict:
481
+ """Generate OpenAI-compatible tool definition from the ToolSpec.
482
+
483
+ Uses the input structure to create a tool definition dictionary
484
+ suitable for inclusion in OpenAI API calls.
485
+
486
+ Returns
487
+ -------
488
+ dict
489
+ Tool definition dictionary.
490
+
491
+ Examples
492
+ --------
493
+ >>> from openai_sdk_helpers import ToolSpec
494
+ >>> from openai_sdk_helpers.structure import PromptStructure
495
+ >>> spec = ToolSpec(
496
+ ... tool_name="web_agent",
497
+ ... tool_description="Run a web research workflow",
498
+ ... input_structure=PromptStructure,
499
+ ... output_structure=PromptStructure
500
+ ... )
501
+ >>> spec.as_tool_definition()
502
+ {
503
+ "name": "web_agent",
504
+ "description": "Run a web research workflow",
505
+ "parameters": { ... } # Schema from PromptStructure
506
+ }
507
+ """
508
+ return self.input_structure.response_tool_definition(
509
+ tool_name=self.tool_name,
510
+ tool_description=self.tool_description,
511
+ )
268
512
 
269
513
 
270
- def build_tool_definitions(tool_specs: list[ToolSpec]) -> list[dict]:
514
+ def build_tool_definition_list(tool_specs: list[ToolSpec]) -> list[dict]:
271
515
  """Build tool definitions from named tool specs.
272
516
 
273
517
  Converts a list of ToolSpec objects into OpenAI-compatible tool
@@ -289,23 +533,25 @@ def build_tool_definitions(tool_specs: list[ToolSpec]) -> list[dict]:
289
533
  --------
290
534
  Build multiple tool definitions:
291
535
 
292
- >>> from openai_sdk_helpers import ToolSpec, build_tool_definitions
536
+ >>> from openai_sdk_helpers import ToolSpec, build_tool_definition_list
293
537
  >>> from openai_sdk_helpers.structure import PromptStructure
294
- >>> tools = build_tool_definitions([
538
+ >>> tools = build_tool_definition_list([
295
539
  ... ToolSpec(
296
- ... structure=PromptStructure,
297
540
  ... tool_name="web_agent",
298
- ... tool_description="Run a web research workflow"
541
+ ... tool_description="Run a web research workflow",
542
+ ... input_structure=PromptStructure,
543
+ ... output_structure=PromptStructure
299
544
  ... ),
300
545
  ... ToolSpec(
301
- ... structure=PromptStructure,
302
546
  ... tool_name="vector_agent",
303
- ... tool_description="Run a vector search workflow"
547
+ ... tool_description="Run a vector search workflow",
548
+ ... input_structure=PromptStructure,
549
+ ... output_structure=PromptStructure
304
550
  ... ),
305
551
  ... ])
306
552
  """
307
553
  return [
308
- spec.structure.response_tool_definition(
554
+ spec.input_structure.response_tool_definition(
309
555
  tool_name=spec.tool_name,
310
556
  tool_description=spec.tool_description,
311
557
  )
@@ -314,9 +560,10 @@ def build_tool_definitions(tool_specs: list[ToolSpec]) -> list[dict]:
314
560
 
315
561
 
316
562
  __all__ = [
317
- "serialize_tool_result",
318
563
  "tool_handler_factory",
319
564
  "StructureType",
565
+ "ToolHandler",
566
+ "ToolHandlerRegistration",
320
567
  "ToolSpec",
321
- "build_tool_definitions",
568
+ "build_tool_definition_list",
322
569
  ]
@@ -87,6 +87,10 @@ from .encoding import (
87
87
  get_mime_type,
88
88
  is_image_file,
89
89
  )
90
+ from .langextract import (
91
+ LangExtractAdapter,
92
+ build_langextract_adapter,
93
+ )
90
94
 
91
95
  __all__ = [
92
96
  "ensure_list",
@@ -130,6 +134,9 @@ __all__ = [
130
134
  "create_image_data_url",
131
135
  "create_file_data_url",
132
136
  "is_image_file",
137
+ # LangExtract
138
+ "LangExtractAdapter",
139
+ "build_langextract_adapter",
133
140
  # Registry
134
141
  "RegistryBase",
135
142
  ]