openai-sdk-helpers 0.1.0__py3-none-any.whl → 0.1.2__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 (44) hide show
  1. openai_sdk_helpers/__init__.py +44 -7
  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 +93 -2
  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/files_api.py +373 -0
  14. openai_sdk_helpers/logging_config.py +24 -95
  15. openai_sdk_helpers/prompt/base.py +1 -1
  16. openai_sdk_helpers/response/__init__.py +7 -3
  17. openai_sdk_helpers/response/base.py +217 -147
  18. openai_sdk_helpers/response/config.py +16 -1
  19. openai_sdk_helpers/response/files.py +392 -0
  20. openai_sdk_helpers/response/messages.py +1 -0
  21. openai_sdk_helpers/retry.py +1 -1
  22. openai_sdk_helpers/streamlit_app/app.py +97 -7
  23. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +15 -8
  24. openai_sdk_helpers/structure/base.py +6 -6
  25. openai_sdk_helpers/structure/plan/helpers.py +1 -0
  26. openai_sdk_helpers/structure/plan/task.py +7 -7
  27. openai_sdk_helpers/tools.py +116 -13
  28. openai_sdk_helpers/utils/__init__.py +100 -35
  29. openai_sdk_helpers/{async_utils.py → utils/async_utils.py} +5 -6
  30. openai_sdk_helpers/utils/coercion.py +138 -0
  31. openai_sdk_helpers/utils/deprecation.py +167 -0
  32. openai_sdk_helpers/utils/encoding.py +189 -0
  33. openai_sdk_helpers/utils/json_utils.py +98 -0
  34. openai_sdk_helpers/utils/output_validation.py +448 -0
  35. openai_sdk_helpers/utils/path_utils.py +46 -0
  36. openai_sdk_helpers/{validation.py → utils/validation.py} +7 -3
  37. openai_sdk_helpers/vector_storage/storage.py +59 -28
  38. {openai_sdk_helpers-0.1.0.dist-info → openai_sdk_helpers-0.1.2.dist-info}/METADATA +152 -3
  39. openai_sdk_helpers-0.1.2.dist-info/RECORD +79 -0
  40. openai_sdk_helpers-0.1.2.dist-info/entry_points.txt +2 -0
  41. openai_sdk_helpers/utils/core.py +0 -596
  42. openai_sdk_helpers-0.1.0.dist-info/RECORD +0 -69
  43. {openai_sdk_helpers-0.1.0.dist-info → openai_sdk_helpers-0.1.2.dist-info}/WHEEL +0 -0
  44. {openai_sdk_helpers-0.1.0.dist-info → openai_sdk_helpers-0.1.2.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: