openai-sdk-helpers 0.0.9__py3-none-any.whl → 0.1.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.
Files changed (46) hide show
  1. openai_sdk_helpers/__init__.py +63 -5
  2. openai_sdk_helpers/agent/base.py +5 -1
  3. openai_sdk_helpers/agent/coordination.py +4 -5
  4. openai_sdk_helpers/agent/runner.py +4 -1
  5. openai_sdk_helpers/agent/search/base.py +1 -0
  6. openai_sdk_helpers/agent/search/vector.py +2 -0
  7. openai_sdk_helpers/cli.py +265 -0
  8. openai_sdk_helpers/config.py +120 -31
  9. openai_sdk_helpers/context_manager.py +1 -1
  10. openai_sdk_helpers/deprecation.py +167 -0
  11. openai_sdk_helpers/environment.py +3 -2
  12. openai_sdk_helpers/errors.py +0 -12
  13. openai_sdk_helpers/logging_config.py +24 -95
  14. openai_sdk_helpers/prompt/base.py +56 -6
  15. openai_sdk_helpers/response/__init__.py +5 -2
  16. openai_sdk_helpers/response/base.py +84 -115
  17. openai_sdk_helpers/response/config.py +142 -0
  18. openai_sdk_helpers/response/messages.py +1 -0
  19. openai_sdk_helpers/response/tool_call.py +15 -4
  20. openai_sdk_helpers/retry.py +1 -1
  21. openai_sdk_helpers/streamlit_app/app.py +14 -3
  22. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +15 -8
  23. openai_sdk_helpers/structure/__init__.py +3 -0
  24. openai_sdk_helpers/structure/base.py +6 -6
  25. openai_sdk_helpers/structure/plan/__init__.py +15 -1
  26. openai_sdk_helpers/structure/plan/helpers.py +173 -0
  27. openai_sdk_helpers/structure/plan/plan.py +13 -9
  28. openai_sdk_helpers/structure/plan/task.py +7 -7
  29. openai_sdk_helpers/structure/plan/types.py +15 -0
  30. openai_sdk_helpers/tools.py +296 -0
  31. openai_sdk_helpers/utils/__init__.py +82 -31
  32. openai_sdk_helpers/{async_utils.py → utils/async_utils.py} +5 -6
  33. openai_sdk_helpers/utils/coercion.py +138 -0
  34. openai_sdk_helpers/utils/deprecation.py +167 -0
  35. openai_sdk_helpers/utils/json_utils.py +98 -0
  36. openai_sdk_helpers/utils/output_validation.py +448 -0
  37. openai_sdk_helpers/utils/path_utils.py +46 -0
  38. openai_sdk_helpers/{validation.py → utils/validation.py} +7 -3
  39. openai_sdk_helpers/vector_storage/storage.py +9 -6
  40. {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/METADATA +59 -3
  41. openai_sdk_helpers-0.1.1.dist-info/RECORD +76 -0
  42. openai_sdk_helpers-0.1.1.dist-info/entry_points.txt +2 -0
  43. openai_sdk_helpers/utils/core.py +0 -468
  44. openai_sdk_helpers-0.0.9.dist-info/RECORD +0 -66
  45. {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/WHEEL +0 -0
  46. {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,448 @@
1
+ """Output validation framework for agent responses.
2
+
3
+ Provides validators for checking agent outputs against schemas,
4
+ semantic constraints, and content policies.
5
+
6
+ Classes
7
+ -------
8
+ ValidationRule
9
+ Base class for validation rules.
10
+ JSONSchemaValidator
11
+ Validate outputs against JSON schemas.
12
+ SemanticValidator
13
+ Validate outputs semantically (e.g., must reference sources).
14
+ LengthValidator
15
+ Validate output length constraints.
16
+ OutputValidator
17
+ Composite validator for multiple rules.
18
+
19
+ Functions
20
+ ---------
21
+ validate_output
22
+ Convenience function for validating outputs.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import re
29
+ from abc import ABC, abstractmethod
30
+ from typing import Any
31
+
32
+ from pydantic import BaseModel, ValidationError
33
+
34
+
35
+ class ValidationResult(BaseModel):
36
+ """Result of output validation.
37
+
38
+ Attributes
39
+ ----------
40
+ valid : bool
41
+ Whether validation passed.
42
+ errors : list[str]
43
+ List of validation error messages.
44
+ warnings : list[str]
45
+ List of validation warnings.
46
+ """
47
+
48
+ valid: bool
49
+ errors: list[str] = []
50
+ warnings: list[str] = []
51
+
52
+
53
+ class ValidationRule(ABC):
54
+ """Base class for validation rules.
55
+
56
+ Methods
57
+ -------
58
+ validate
59
+ Validate output and return ValidationResult.
60
+ """
61
+
62
+ @abstractmethod
63
+ def validate(self, output: Any) -> ValidationResult:
64
+ """Validate output.
65
+
66
+ Parameters
67
+ ----------
68
+ output : Any
69
+ Output to validate.
70
+
71
+ Returns
72
+ -------
73
+ ValidationResult
74
+ Validation result.
75
+ """
76
+ pass
77
+
78
+
79
+ class JSONSchemaValidator(ValidationRule):
80
+ """Validate outputs against JSON schemas.
81
+
82
+ Parameters
83
+ ----------
84
+ schema : dict
85
+ JSON schema to validate against.
86
+
87
+ Examples
88
+ --------
89
+ >>> schema = {"type": "object", "required": ["name"]}
90
+ >>> validator = JSONSchemaValidator(schema)
91
+ >>> result = validator.validate({"name": "test"})
92
+ >>> result.valid
93
+ True
94
+ """
95
+
96
+ def __init__(self, schema: dict[str, Any]) -> None:
97
+ """Initialize JSON schema validator.
98
+
99
+ Parameters
100
+ ----------
101
+ schema : dict
102
+ JSON schema dictionary.
103
+ """
104
+ self.schema = schema
105
+
106
+ def validate(self, output: Any) -> ValidationResult:
107
+ """Validate output against JSON schema.
108
+
109
+ Parameters
110
+ ----------
111
+ output : Any
112
+ Output to validate.
113
+
114
+ Returns
115
+ -------
116
+ ValidationResult
117
+ Validation result.
118
+ """
119
+ try:
120
+ # Try using jsonschema if available
121
+ import jsonschema
122
+ except ImportError:
123
+ # Fallback to basic type checking
124
+ return self._basic_validate(output)
125
+
126
+ try:
127
+ jsonschema.validate(instance=output, schema=self.schema)
128
+ return ValidationResult(valid=True)
129
+ except jsonschema.ValidationError as e:
130
+ return ValidationResult(valid=False, errors=[str(e)])
131
+
132
+ def _basic_validate(self, output: Any) -> ValidationResult:
133
+ """Perform basic validation without jsonschema library."""
134
+ errors = []
135
+
136
+ # Check type
137
+ expected_type = self.schema.get("type")
138
+ if expected_type == "object" and not isinstance(output, dict):
139
+ errors.append(f"Expected object, got {type(output).__name__}")
140
+ elif expected_type == "array" and not isinstance(output, list):
141
+ errors.append(f"Expected array, got {type(output).__name__}")
142
+
143
+ # Check required fields
144
+ if expected_type == "object" and isinstance(output, dict):
145
+ required = self.schema.get("required", [])
146
+ for field in required:
147
+ if field not in output:
148
+ errors.append(f"Missing required field: {field}")
149
+
150
+ return ValidationResult(valid=len(errors) == 0, errors=errors)
151
+
152
+
153
+ class SemanticValidator(ValidationRule):
154
+ """Validate outputs semantically.
155
+
156
+ Parameters
157
+ ----------
158
+ must_contain : list[str], optional
159
+ Phrases that must appear in output.
160
+ must_not_contain : list[str], optional
161
+ Phrases that must not appear in output.
162
+ must_reference_sources : bool
163
+ Whether output must reference source documents. Default is False.
164
+
165
+ Examples
166
+ --------
167
+ >>> validator = SemanticValidator(must_contain=["summary", "conclusion"])
168
+ >>> result = validator.validate("Here is the summary and conclusion.")
169
+ >>> result.valid
170
+ True
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ must_contain: list[str] | None = None,
176
+ must_not_contain: list[str] | None = None,
177
+ must_reference_sources: bool = False,
178
+ ) -> None:
179
+ """Initialize semantic validator.
180
+
181
+ Parameters
182
+ ----------
183
+ must_contain : list[str], optional
184
+ Phrases that must appear.
185
+ must_not_contain : list[str], optional
186
+ Phrases that must not appear.
187
+ must_reference_sources : bool
188
+ Check for source references.
189
+ """
190
+ self.must_contain = must_contain or []
191
+ self.must_not_contain = must_not_contain or []
192
+ self.must_reference_sources = must_reference_sources
193
+
194
+ def validate(self, output: Any) -> ValidationResult:
195
+ """Validate output semantically.
196
+
197
+ Parameters
198
+ ----------
199
+ output : Any
200
+ Output to validate (converted to string).
201
+
202
+ Returns
203
+ -------
204
+ ValidationResult
205
+ Validation result.
206
+ """
207
+ text = str(output).lower()
208
+ errors = []
209
+ warnings = []
210
+
211
+ # Check must contain
212
+ for phrase in self.must_contain:
213
+ if phrase.lower() not in text:
214
+ errors.append(f"Output must contain: '{phrase}'")
215
+
216
+ # Check must not contain
217
+ for phrase in self.must_not_contain:
218
+ if phrase.lower() in text:
219
+ errors.append(f"Output must not contain: '{phrase}'")
220
+
221
+ # Check for source references
222
+ if self.must_reference_sources:
223
+ # Look for common citation patterns
224
+ citation_patterns = [
225
+ r"\[\d+\]", # [1]
226
+ r"\(\d+\)", # (1)
227
+ r"source:", # source:
228
+ r"according to", # according to
229
+ ]
230
+ has_citations = any(
231
+ re.search(pattern, text, re.IGNORECASE) for pattern in citation_patterns
232
+ )
233
+ if not has_citations:
234
+ warnings.append("Output should reference sources")
235
+
236
+ return ValidationResult(
237
+ valid=len(errors) == 0,
238
+ errors=errors,
239
+ warnings=warnings,
240
+ )
241
+
242
+
243
+ class LengthValidator(ValidationRule):
244
+ """Validate output length constraints.
245
+
246
+ Parameters
247
+ ----------
248
+ min_length : int, optional
249
+ Minimum output length in characters.
250
+ max_length : int, optional
251
+ Maximum output length in characters.
252
+ min_words : int, optional
253
+ Minimum word count.
254
+ max_words : int, optional
255
+ Maximum word count.
256
+
257
+ Examples
258
+ --------
259
+ >>> validator = LengthValidator(min_words=10, max_words=100)
260
+ >>> result = validator.validate("Short text")
261
+ >>> result.valid
262
+ False
263
+ """
264
+
265
+ def __init__(
266
+ self,
267
+ min_length: int | None = None,
268
+ max_length: int | None = None,
269
+ min_words: int | None = None,
270
+ max_words: int | None = None,
271
+ ) -> None:
272
+ """Initialize length validator.
273
+
274
+ Parameters
275
+ ----------
276
+ min_length : int, optional
277
+ Minimum character count.
278
+ max_length : int, optional
279
+ Maximum character count.
280
+ min_words : int, optional
281
+ Minimum word count.
282
+ max_words : int, optional
283
+ Maximum word count.
284
+ """
285
+ self.min_length = min_length
286
+ self.max_length = max_length
287
+ self.min_words = min_words
288
+ self.max_words = max_words
289
+
290
+ def validate(self, output: Any) -> ValidationResult:
291
+ """Validate output length.
292
+
293
+ Parameters
294
+ ----------
295
+ output : Any
296
+ Output to validate (converted to string).
297
+
298
+ Returns
299
+ -------
300
+ ValidationResult
301
+ Validation result.
302
+ """
303
+ text = str(output)
304
+ errors = []
305
+
306
+ # Check character length
307
+ if self.min_length and len(text) < self.min_length:
308
+ errors.append(
309
+ f"Output too short: {len(text)} chars (min: {self.min_length})"
310
+ )
311
+ if self.max_length and len(text) > self.max_length:
312
+ errors.append(
313
+ f"Output too long: {len(text)} chars (max: {self.max_length})"
314
+ )
315
+
316
+ # Check word count
317
+ words = text.split()
318
+ if self.min_words and len(words) < self.min_words:
319
+ errors.append(f"Too few words: {len(words)} (min: {self.min_words})")
320
+ if self.max_words and len(words) > self.max_words:
321
+ errors.append(f"Too many words: {len(words)} (max: {self.max_words})")
322
+
323
+ return ValidationResult(valid=len(errors) == 0, errors=errors)
324
+
325
+
326
+ class OutputValidator:
327
+ """Composite validator for multiple rules.
328
+
329
+ Parameters
330
+ ----------
331
+ rules : list[ValidationRule]
332
+ List of validation rules to apply.
333
+
334
+ Methods
335
+ -------
336
+ validate
337
+ Validate output against all rules.
338
+ add_rule
339
+ Add a validation rule.
340
+
341
+ Examples
342
+ --------
343
+ >>> validator = OutputValidator([
344
+ ... LengthValidator(min_words=10),
345
+ ... SemanticValidator(must_contain=["summary"])
346
+ ... ])
347
+ >>> result = validator.validate("This is a brief summary with enough words.")
348
+ """
349
+
350
+ def __init__(self, rules: list[ValidationRule] | None = None) -> None:
351
+ """Initialize output validator.
352
+
353
+ Parameters
354
+ ----------
355
+ rules : list[ValidationRule], optional
356
+ Initial validation rules.
357
+ """
358
+ self.rules = rules or []
359
+
360
+ def add_rule(self, rule: ValidationRule) -> None:
361
+ """Add a validation rule.
362
+
363
+ Parameters
364
+ ----------
365
+ rule : ValidationRule
366
+ Rule to add.
367
+ """
368
+ self.rules.append(rule)
369
+
370
+ def validate(self, output: Any) -> ValidationResult:
371
+ """Validate output against all rules.
372
+
373
+ Parameters
374
+ ----------
375
+ output : Any
376
+ Output to validate.
377
+
378
+ Returns
379
+ -------
380
+ ValidationResult
381
+ Combined validation result.
382
+ """
383
+ all_errors: list[str] = []
384
+ all_warnings: list[str] = []
385
+
386
+ for rule in self.rules:
387
+ result = rule.validate(output)
388
+ all_errors.extend(result.errors)
389
+ all_warnings.extend(result.warnings)
390
+
391
+ return ValidationResult(
392
+ valid=len(all_errors) == 0,
393
+ errors=all_errors,
394
+ warnings=all_warnings,
395
+ )
396
+
397
+
398
+ def validate_output(
399
+ output: Any,
400
+ schema: dict[str, Any] | None = None,
401
+ min_length: int | None = None,
402
+ max_length: int | None = None,
403
+ must_contain: list[str] | None = None,
404
+ ) -> ValidationResult:
405
+ """Validate outputs with common validation rules.
406
+
407
+ Parameters
408
+ ----------
409
+ output : Any
410
+ Output to validate.
411
+ schema : dict, optional
412
+ JSON schema to validate against.
413
+ min_length : int, optional
414
+ Minimum character length.
415
+ max_length : int, optional
416
+ Maximum character length.
417
+ must_contain : list[str], optional
418
+ Phrases that must appear.
419
+
420
+ Returns
421
+ -------
422
+ ValidationResult
423
+ Validation result.
424
+
425
+ Examples
426
+ --------
427
+ >>> result = validate_output(
428
+ ... "Short",
429
+ ... min_length=10,
430
+ ... must_contain=["summary"]
431
+ ... )
432
+ >>> result.valid
433
+ False
434
+ """
435
+ validator = OutputValidator()
436
+
437
+ if schema:
438
+ validator.add_rule(JSONSchemaValidator(schema))
439
+
440
+ if min_length or max_length:
441
+ validator.add_rule(
442
+ LengthValidator(min_length=min_length, max_length=max_length)
443
+ )
444
+
445
+ if must_contain:
446
+ validator.add_rule(SemanticValidator(must_contain=must_contain))
447
+
448
+ return validator.validate(output)
@@ -0,0 +1,46 @@
1
+ """File and path utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def check_filepath(
9
+ filepath: Path | None = None, *, fullfilepath: str | None = None
10
+ ) -> Path:
11
+ """Ensure the parent directory for a file path exists.
12
+
13
+ Creates parent directories as needed. Exactly one of ``filepath`` or
14
+ ``fullfilepath`` must be provided.
15
+
16
+ Parameters
17
+ ----------
18
+ filepath : Path or None, optional
19
+ Path object to validate. Mutually exclusive with ``fullfilepath``.
20
+ fullfilepath : str or None, optional
21
+ String path to validate. Mutually exclusive with ``filepath``.
22
+
23
+ Returns
24
+ -------
25
+ Path
26
+ Path object representing the validated file path.
27
+ """
28
+ if filepath is None and fullfilepath is None:
29
+ raise ValueError("filepath or fullfilepath is required.")
30
+ if fullfilepath is not None:
31
+ target = Path(fullfilepath)
32
+ elif filepath is not None:
33
+ target = Path(filepath)
34
+ else:
35
+ raise ValueError("filepath or fullfilepath is required.")
36
+ ensure_directory(target.parent)
37
+ return target
38
+
39
+
40
+ def ensure_directory(path: Path) -> Path:
41
+ """Ensure a directory exists and return it."""
42
+ path.mkdir(parents=True, exist_ok=True)
43
+ return path
44
+
45
+
46
+ __all__ = ["check_filepath", "ensure_directory"]
@@ -16,7 +16,7 @@ V = TypeVar("V")
16
16
  U = TypeVar("U")
17
17
 
18
18
 
19
- def validate_non_empty_string(value: str, field_name: str) -> str:
19
+ def validate_non_empty_string(value: str, *, field_name: str) -> str:
20
20
  """Validate that a string is non-empty.
21
21
 
22
22
  Parameters
@@ -46,7 +46,7 @@ def validate_non_empty_string(value: str, field_name: str) -> str:
46
46
  return stripped
47
47
 
48
48
 
49
- def validate_max_length(value: str, max_len: int, field_name: str) -> str:
49
+ def validate_max_length(value: str, max_len: int, *, field_name: str) -> str:
50
50
  """Validate that a string doesn't exceed maximum length.
51
51
 
52
52
  Parameters
@@ -76,7 +76,7 @@ def validate_max_length(value: str, max_len: int, field_name: str) -> str:
76
76
  return value
77
77
 
78
78
 
79
- def validate_url_format(url: str, field_name: str = "URL") -> str:
79
+ def validate_url_format(url: str, *, field_name: str = "URL") -> str:
80
80
  """Validate that a string is a valid URL.
81
81
 
82
82
  Parameters
@@ -106,6 +106,7 @@ def validate_url_format(url: str, field_name: str = "URL") -> str:
106
106
  def validate_dict_mapping(
107
107
  mapping: Mapping[K, V],
108
108
  expected_keys: set[K],
109
+ *,
109
110
  field_name: str,
110
111
  allow_extra: bool = False,
111
112
  ) -> dict[K, V]:
@@ -156,6 +157,7 @@ def validate_dict_mapping(
156
157
  def validate_list_items(
157
158
  items: list[U],
158
159
  item_validator: Callable[[U], T],
160
+ *,
159
161
  field_name: str,
160
162
  allow_empty: bool = False,
161
163
  ) -> list[T]:
@@ -203,6 +205,7 @@ def validate_list_items(
203
205
  def validate_choice(
204
206
  value: U,
205
207
  allowed_values: set[U],
208
+ *,
206
209
  field_name: str,
207
210
  ) -> U:
208
211
  """Validate that a value is one of allowed choices.
@@ -235,6 +238,7 @@ def validate_choice(
235
238
 
236
239
  def validate_safe_path(
237
240
  path: Path | str,
241
+ *,
238
242
  base_dir: Path | None = None,
239
243
  field_name: str = "path",
240
244
  ) -> Path:
@@ -96,6 +96,7 @@ class VectorStorage:
96
96
 
97
97
  def __init__(
98
98
  self,
99
+ *,
99
100
  store_name: str,
100
101
  client: OpenAIClient | None = None,
101
102
  model: str | None = None,
@@ -228,6 +229,7 @@ class VectorStorage:
228
229
  def upload_file(
229
230
  self,
230
231
  file_path: str,
232
+ *,
231
233
  purpose: str = "assistants",
232
234
  attributes: dict[str, str | float | bool] | None = None,
233
235
  overwrite: bool = False,
@@ -316,6 +318,7 @@ class VectorStorage:
316
318
  def upload_files(
317
319
  self,
318
320
  file_patterns: str | list[str],
321
+ *,
319
322
  purpose: str = "assistants",
320
323
  attributes: dict[str, str | float | bool] | None = None,
321
324
  overwrite: bool = False,
@@ -369,10 +372,10 @@ class VectorStorage:
369
372
  executor.submit(
370
373
  self.upload_file,
371
374
  path,
372
- purpose,
373
- attributes,
374
- overwrite,
375
- False,
375
+ purpose=purpose,
376
+ attributes=attributes,
377
+ overwrite=overwrite,
378
+ refresh_cache=False,
376
379
  ): path
377
380
  for path in all_paths
378
381
  }
@@ -552,7 +555,7 @@ class VectorStorage:
552
555
  return stats
553
556
 
554
557
  def search(
555
- self, query: str, top_k: int = 5
558
+ self, query: str, *, top_k: int = 5
556
559
  ) -> SyncPage[VectorStoreSearchResponse] | None:
557
560
  """Perform a semantic search within the vector store.
558
561
 
@@ -583,7 +586,7 @@ class VectorStorage:
583
586
  log(f"Error searching vector store: {str(exc)}", level=logging.ERROR)
584
587
  return None
585
588
 
586
- def summarize(self, query: str, top_k: int = 15) -> str | None:
589
+ def summarize(self, query: str, *, top_k: int = 15) -> str | None:
587
590
  """Perform a semantic search and summarize results by topic.
588
591
 
589
592
  Retrieves top search results and generates a summary. This method
@@ -1,14 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openai-sdk-helpers
3
- Version: 0.0.9
3
+ Version: 0.1.1
4
4
  Summary: Composable helpers for OpenAI SDK agents, prompts, and storage
5
5
  Author: openai-sdk-helpers maintainers
6
6
  License: MIT
7
7
  License-File: LICENSE
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
8
18
  Requires-Python: >=3.10
9
19
  Requires-Dist: jinja2
10
- Requires-Dist: openai
11
- Requires-Dist: openai-agents
20
+ Requires-Dist: openai-agents<1.0.0,>=0.6.4
21
+ Requires-Dist: openai<3.0.0,>=2.14.0
12
22
  Requires-Dist: pydantic<3,>=2.7
13
23
  Requires-Dist: python-dotenv
14
24
  Requires-Dist: streamlit
@@ -90,6 +100,26 @@ The `agent` module provides a higher-level abstraction for building agents, whil
90
100
  - **Tool execution framework** with custom handlers and structured outputs
91
101
  - **Session persistence** for saving and restoring conversation history
92
102
 
103
+ #### Infrastructure & Utilities
104
+ - **Centralized logger factory** for consistent application logging
105
+ - **Retry patterns** with exponential backoff and jitter
106
+ - **Output validation** framework with JSON schema, semantic, and length validators
107
+ - **CLI tool** for testing agents, validating templates, and inspecting registries
108
+ - **Deprecation utilities** for managing API changes
109
+
110
+ #### Shared Components
111
+ - **Typed structures** using Pydantic for prompts, responses, and search workflows
112
+ to ensure predictable inputs and outputs
113
+ - **OpenAI configuration management** with environment variable and `.env` file support
114
+ - **Vector storage abstraction** for seamless integration with OpenAI vector stores
115
+ - **Type-safe interfaces** with full type hints and `py.typed` marker for external projects
116
+ - **ValidatorAgent**: Check inputs and outputs against safety guardrails
117
+
118
+ #### Response Module (Built on `openai` SDK)
119
+ - **Response handling utilities** for direct API control with fine-grained message management
120
+ - **Tool execution framework** with custom handlers and structured outputs
121
+ - **Session persistence** for saving and restoring conversation history
122
+
93
123
  #### Shared Components
94
124
  - **Typed structures** using Pydantic for prompts, responses, and search workflows
95
125
  to ensure predictable inputs and outputs
@@ -502,6 +532,31 @@ See `AGENTS.md` for detailed contributing guidelines and conventions.
502
532
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
503
533
  for details.
504
534
 
535
+ ## CLI Tool
536
+
537
+ The package includes a command-line tool for development and testing:
538
+
539
+ ```bash
540
+ # List all registered response configurations
541
+ openai-helpers registry list
542
+
543
+ # Inspect a specific configuration
544
+ openai-helpers registry inspect my_config
545
+
546
+ # Validate Jinja2 templates
547
+ openai-helpers template validate ./templates
548
+
549
+ # Test an agent (coming soon)
550
+ openai-helpers agent test MyAgent --input "test input"
551
+ ```
552
+
553
+ ### CLI Commands
554
+
555
+ - **registry list** - Show all registered response configurations
556
+ - **registry inspect** - Display details of a configuration
557
+ - **template validate** - Check template syntax and structure
558
+ - **agent test** - Test agents locally with sample inputs
559
+
505
560
  ## Troubleshooting
506
561
 
507
562
  ### Common Issues
@@ -523,6 +578,7 @@ OPENAI_API_KEY=your-api-key-here
523
578
  Vector search workflows require custom prompt templates. Either:
524
579
  1. Create the required `.jinja` files in your `prompt_dir`
525
580
  2. Omit the `prompt_dir` parameter to use built-in defaults (for text agents only)
581
+ 3. Use the CLI to validate templates: `openai-helpers template validate ./templates`
526
582
 
527
583
  **Import errors after installation**
528
584