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.
- openai_sdk_helpers/__init__.py +63 -5
- openai_sdk_helpers/agent/base.py +5 -1
- openai_sdk_helpers/agent/coordination.py +4 -5
- openai_sdk_helpers/agent/runner.py +4 -1
- openai_sdk_helpers/agent/search/base.py +1 -0
- openai_sdk_helpers/agent/search/vector.py +2 -0
- openai_sdk_helpers/cli.py +265 -0
- openai_sdk_helpers/config.py +120 -31
- openai_sdk_helpers/context_manager.py +1 -1
- openai_sdk_helpers/deprecation.py +167 -0
- openai_sdk_helpers/environment.py +3 -2
- openai_sdk_helpers/errors.py +0 -12
- openai_sdk_helpers/logging_config.py +24 -95
- openai_sdk_helpers/prompt/base.py +56 -6
- openai_sdk_helpers/response/__init__.py +5 -2
- openai_sdk_helpers/response/base.py +84 -115
- openai_sdk_helpers/response/config.py +142 -0
- openai_sdk_helpers/response/messages.py +1 -0
- openai_sdk_helpers/response/tool_call.py +15 -4
- openai_sdk_helpers/retry.py +1 -1
- openai_sdk_helpers/streamlit_app/app.py +14 -3
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +15 -8
- openai_sdk_helpers/structure/__init__.py +3 -0
- openai_sdk_helpers/structure/base.py +6 -6
- openai_sdk_helpers/structure/plan/__init__.py +15 -1
- openai_sdk_helpers/structure/plan/helpers.py +173 -0
- openai_sdk_helpers/structure/plan/plan.py +13 -9
- openai_sdk_helpers/structure/plan/task.py +7 -7
- openai_sdk_helpers/structure/plan/types.py +15 -0
- openai_sdk_helpers/tools.py +296 -0
- openai_sdk_helpers/utils/__init__.py +82 -31
- openai_sdk_helpers/{async_utils.py → utils/async_utils.py} +5 -6
- openai_sdk_helpers/utils/coercion.py +138 -0
- openai_sdk_helpers/utils/deprecation.py +167 -0
- openai_sdk_helpers/utils/json_utils.py +98 -0
- openai_sdk_helpers/utils/output_validation.py +448 -0
- openai_sdk_helpers/utils/path_utils.py +46 -0
- openai_sdk_helpers/{validation.py → utils/validation.py} +7 -3
- openai_sdk_helpers/vector_storage/storage.py +9 -6
- {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/METADATA +59 -3
- openai_sdk_helpers-0.1.1.dist-info/RECORD +76 -0
- openai_sdk_helpers-0.1.1.dist-info/entry_points.txt +2 -0
- openai_sdk_helpers/utils/core.py +0 -468
- openai_sdk_helpers-0.0.9.dist-info/RECORD +0 -66
- {openai_sdk_helpers-0.0.9.dist-info → openai_sdk_helpers-0.1.1.dist-info}/WHEEL +0 -0
- {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.
|
|
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
|
|
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
|
|