openai-sdk-helpers 0.1.1__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.
- openai_sdk_helpers/__init__.py +3 -0
- openai_sdk_helpers/files_api.py +373 -0
- openai_sdk_helpers/response/__init__.py +7 -3
- openai_sdk_helpers/response/base.py +166 -65
- openai_sdk_helpers/response/config.py +16 -1
- openai_sdk_helpers/response/files.py +392 -0
- openai_sdk_helpers/streamlit_app/app.py +83 -4
- openai_sdk_helpers/utils/__init__.py +18 -0
- openai_sdk_helpers/utils/encoding.py +189 -0
- openai_sdk_helpers/vector_storage/storage.py +50 -22
- {openai_sdk_helpers-0.1.1.dist-info → openai_sdk_helpers-0.1.2.dist-info}/METADATA +94 -1
- {openai_sdk_helpers-0.1.1.dist-info → openai_sdk_helpers-0.1.2.dist-info}/RECORD +15 -12
- {openai_sdk_helpers-0.1.1.dist-info → openai_sdk_helpers-0.1.2.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.1.1.dist-info → openai_sdk_helpers-0.1.2.dist-info}/entry_points.txt +0 -0
- {openai_sdk_helpers-0.1.1.dist-info → openai_sdk_helpers-0.1.2.dist-info}/licenses/LICENSE +0 -0
openai_sdk_helpers/__init__.py
CHANGED
|
@@ -51,6 +51,7 @@ from .structure import (
|
|
|
51
51
|
)
|
|
52
52
|
from .prompt import PromptRenderer
|
|
53
53
|
from .config import OpenAISettings
|
|
54
|
+
from .files_api import FilesAPIManager, FilePurpose
|
|
54
55
|
from .vector_storage import VectorStorage, VectorStorageFileInfo, VectorStorageFileStats
|
|
55
56
|
from .agent import (
|
|
56
57
|
AgentBase,
|
|
@@ -138,6 +139,8 @@ __all__ = [
|
|
|
138
139
|
"spec_field",
|
|
139
140
|
"PromptRenderer",
|
|
140
141
|
"OpenAISettings",
|
|
142
|
+
"FilesAPIManager",
|
|
143
|
+
"FilePurpose",
|
|
141
144
|
"VectorStorage",
|
|
142
145
|
"VectorStorageFileInfo",
|
|
143
146
|
"VectorStorageFileStats",
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""Comprehensive OpenAI Files API wrapper.
|
|
2
|
+
|
|
3
|
+
This module provides a complete, professional implementation of the OpenAI Files API
|
|
4
|
+
with automatic file tracking, lifecycle management, and cleanup capabilities.
|
|
5
|
+
|
|
6
|
+
References
|
|
7
|
+
----------
|
|
8
|
+
OpenAI Files API: https://platform.openai.com/docs/api-reference/files
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, BinaryIO, Literal, cast
|
|
16
|
+
|
|
17
|
+
from openai import OpenAI, NOT_GIVEN
|
|
18
|
+
from openai.types import FileDeleted, FileObject
|
|
19
|
+
from openai.pagination import SyncCursorPage
|
|
20
|
+
|
|
21
|
+
from .utils import log
|
|
22
|
+
|
|
23
|
+
# Valid purposes for file uploads
|
|
24
|
+
FilePurpose = Literal[
|
|
25
|
+
"assistants",
|
|
26
|
+
"batch",
|
|
27
|
+
"fine-tune",
|
|
28
|
+
"user_data",
|
|
29
|
+
"vision",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FilesAPIManager:
|
|
34
|
+
"""Comprehensive manager for OpenAI Files API operations.
|
|
35
|
+
|
|
36
|
+
Provides full access to the OpenAI Files API with automatic file tracking,
|
|
37
|
+
lifecycle management, and cleanup capabilities. Tracks all uploaded files
|
|
38
|
+
and ensures proper deletion on cleanup.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
client : OpenAI
|
|
43
|
+
OpenAI client instance for API calls.
|
|
44
|
+
auto_track : bool, default True
|
|
45
|
+
Automatically track uploaded files for cleanup.
|
|
46
|
+
|
|
47
|
+
Attributes
|
|
48
|
+
----------
|
|
49
|
+
tracked_files : dict[str, FileObject]
|
|
50
|
+
Dictionary of tracked file IDs to FileObject instances.
|
|
51
|
+
|
|
52
|
+
Methods
|
|
53
|
+
-------
|
|
54
|
+
create(file, purpose)
|
|
55
|
+
Upload a file to OpenAI Files API.
|
|
56
|
+
retrieve(file_id)
|
|
57
|
+
Retrieve information about a specific file.
|
|
58
|
+
list(purpose, limit)
|
|
59
|
+
List files, optionally filtered by purpose.
|
|
60
|
+
delete(file_id)
|
|
61
|
+
Delete a specific file.
|
|
62
|
+
retrieve_content(file_id)
|
|
63
|
+
Download file content.
|
|
64
|
+
cleanup()
|
|
65
|
+
Delete all tracked files.
|
|
66
|
+
|
|
67
|
+
Examples
|
|
68
|
+
--------
|
|
69
|
+
>>> from openai import OpenAI
|
|
70
|
+
>>> from openai_sdk_helpers.files_api import FilesAPIManager
|
|
71
|
+
>>>
|
|
72
|
+
>>> client = OpenAI()
|
|
73
|
+
>>> files_manager = FilesAPIManager(client)
|
|
74
|
+
>>>
|
|
75
|
+
>>> # Upload a file
|
|
76
|
+
>>> with open("document.pdf", "rb") as f:
|
|
77
|
+
... file_obj = files_manager.create(f, purpose="user_data")
|
|
78
|
+
>>>
|
|
79
|
+
>>> # List all user data files
|
|
80
|
+
>>> user_files = files_manager.list(purpose="user_data")
|
|
81
|
+
>>>
|
|
82
|
+
>>> # Retrieve file content
|
|
83
|
+
>>> content = files_manager.retrieve_content(file_obj.id)
|
|
84
|
+
>>>
|
|
85
|
+
>>> # Clean up all tracked files
|
|
86
|
+
>>> files_manager.cleanup()
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, client: OpenAI, auto_track: bool = True):
|
|
90
|
+
"""Initialize the Files API manager.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
client : OpenAI
|
|
95
|
+
OpenAI client instance.
|
|
96
|
+
auto_track : bool, default True
|
|
97
|
+
Automatically track uploaded files for cleanup.
|
|
98
|
+
"""
|
|
99
|
+
self._client = client
|
|
100
|
+
self._auto_track = auto_track
|
|
101
|
+
self.tracked_files: dict[str, FileObject] = {}
|
|
102
|
+
|
|
103
|
+
def create(
|
|
104
|
+
self,
|
|
105
|
+
file: BinaryIO | Path | str,
|
|
106
|
+
purpose: FilePurpose,
|
|
107
|
+
track: bool | None = None,
|
|
108
|
+
expires_after: int | None = None,
|
|
109
|
+
) -> FileObject:
|
|
110
|
+
"""Upload a file to the OpenAI Files API.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
file : BinaryIO, Path, or str
|
|
115
|
+
File-like object, path to file, or file path string.
|
|
116
|
+
purpose : FilePurpose
|
|
117
|
+
The intended purpose of the uploaded file.
|
|
118
|
+
Options: "assistants", "batch", "fine-tune", "user_data", "vision"
|
|
119
|
+
track : bool or None, default None
|
|
120
|
+
Override auto_track for this file. If None, uses instance setting.
|
|
121
|
+
expires_after : int or None, default None
|
|
122
|
+
Number of seconds after which the file expires and is deleted.
|
|
123
|
+
If None and purpose is "user_data", defaults to 86400 (24 hours).
|
|
124
|
+
For other purposes, files don't expire unless explicitly set.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
FileObject
|
|
129
|
+
Information about the uploaded file.
|
|
130
|
+
|
|
131
|
+
Raises
|
|
132
|
+
------
|
|
133
|
+
FileNotFoundError
|
|
134
|
+
If file path doesn't exist.
|
|
135
|
+
ValueError
|
|
136
|
+
If purpose is invalid.
|
|
137
|
+
|
|
138
|
+
Examples
|
|
139
|
+
--------
|
|
140
|
+
>>> # Upload from file path (user_data expires in 24h by default)
|
|
141
|
+
>>> file_obj = manager.create("data.jsonl", purpose="user_data")
|
|
142
|
+
>>>
|
|
143
|
+
>>> # Upload with custom expiration (1 hour)
|
|
144
|
+
>>> file_obj = manager.create("temp.txt", purpose="user_data", expires_after=3600)
|
|
145
|
+
>>>
|
|
146
|
+
>>> # Upload from file handle
|
|
147
|
+
>>> with open("image.png", "rb") as f:
|
|
148
|
+
... file_obj = manager.create(f, purpose="vision")
|
|
149
|
+
>>>
|
|
150
|
+
>>> # Upload without tracking
|
|
151
|
+
>>> file_obj = manager.create("temp.txt", purpose="user_data", track=False)
|
|
152
|
+
"""
|
|
153
|
+
should_track = track if track is not None else self._auto_track
|
|
154
|
+
|
|
155
|
+
# Default to 24 hours expiration for user_data files
|
|
156
|
+
if expires_after is None and purpose == "user_data":
|
|
157
|
+
expires_after = 86400 # 24 hours in seconds
|
|
158
|
+
|
|
159
|
+
# Handle different file input types
|
|
160
|
+
# Prepare expires_after in OpenAI API format if provided
|
|
161
|
+
expires_after_param = None
|
|
162
|
+
if expires_after is not None:
|
|
163
|
+
expires_after_param = cast(
|
|
164
|
+
Any, {"anchor": "created_at", "seconds": expires_after}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if isinstance(file, (Path, str)):
|
|
168
|
+
file_path = Path(file).resolve()
|
|
169
|
+
if not file_path.exists():
|
|
170
|
+
raise FileNotFoundError(f"File not found: {file}")
|
|
171
|
+
|
|
172
|
+
# Use only the basename as filename (remove path)
|
|
173
|
+
filename = file_path.name
|
|
174
|
+
with open(file_path, "rb") as f:
|
|
175
|
+
# Pass tuple (filename, file_data) to set custom filename
|
|
176
|
+
if expires_after_param is not None:
|
|
177
|
+
file_obj = self._client.files.create(
|
|
178
|
+
file=(filename, f),
|
|
179
|
+
purpose=purpose,
|
|
180
|
+
expires_after=expires_after_param,
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
file_obj = self._client.files.create(
|
|
184
|
+
file=(filename, f), purpose=purpose
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
# Assume it's a BinaryIO
|
|
188
|
+
if expires_after_param is not None:
|
|
189
|
+
file_obj = self._client.files.create(
|
|
190
|
+
file=file,
|
|
191
|
+
purpose=purpose,
|
|
192
|
+
expires_after=expires_after_param,
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
file_obj = self._client.files.create(file=file, purpose=purpose)
|
|
196
|
+
|
|
197
|
+
if should_track:
|
|
198
|
+
self.tracked_files[file_obj.id] = file_obj
|
|
199
|
+
expiry_msg = f" (expires in {expires_after}s)" if expires_after else ""
|
|
200
|
+
log(
|
|
201
|
+
f"Uploaded and tracking file {file_obj.id} ({file_obj.filename}) "
|
|
202
|
+
f"with purpose '{purpose}'{expiry_msg}"
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
log(
|
|
206
|
+
f"Uploaded file {file_obj.id} ({file_obj.filename}) "
|
|
207
|
+
f"with purpose '{purpose}' (not tracked)"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return file_obj
|
|
211
|
+
|
|
212
|
+
def retrieve(self, file_id: str) -> FileObject:
|
|
213
|
+
"""Retrieve information about a specific file.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
file_id : str
|
|
218
|
+
The ID of the file to retrieve.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
FileObject
|
|
223
|
+
Information about the file.
|
|
224
|
+
|
|
225
|
+
Examples
|
|
226
|
+
--------
|
|
227
|
+
>>> file_info = manager.retrieve("file-abc123")
|
|
228
|
+
>>> print(f"Filename: {file_info.filename}")
|
|
229
|
+
>>> print(f"Size: {file_info.bytes} bytes")
|
|
230
|
+
"""
|
|
231
|
+
return self._client.files.retrieve(file_id)
|
|
232
|
+
|
|
233
|
+
def list(
|
|
234
|
+
self,
|
|
235
|
+
purpose: FilePurpose | None = None,
|
|
236
|
+
limit: int | None = None,
|
|
237
|
+
) -> SyncCursorPage[FileObject]:
|
|
238
|
+
"""List files, optionally filtered by purpose.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
purpose : FilePurpose or None, default None
|
|
243
|
+
Filter files by purpose. If None, returns all files.
|
|
244
|
+
limit : int or None, default None
|
|
245
|
+
Maximum number of files to return. If None, returns all.
|
|
246
|
+
|
|
247
|
+
Returns
|
|
248
|
+
-------
|
|
249
|
+
SyncCursorPage[FileObject]
|
|
250
|
+
Page of file objects matching the criteria.
|
|
251
|
+
|
|
252
|
+
Examples
|
|
253
|
+
--------
|
|
254
|
+
>>> # List all files
|
|
255
|
+
>>> all_files = manager.list()
|
|
256
|
+
>>>
|
|
257
|
+
>>> # List user data files
|
|
258
|
+
>>> user_files = manager.list(purpose="user_data")
|
|
259
|
+
>>>
|
|
260
|
+
>>> # List up to 10 files
|
|
261
|
+
>>> recent_files = manager.list(limit=10)
|
|
262
|
+
"""
|
|
263
|
+
limit_param = NOT_GIVEN if limit is None else limit
|
|
264
|
+
if purpose is not None:
|
|
265
|
+
return self._client.files.list(
|
|
266
|
+
purpose=purpose, limit=cast(Any, limit_param)
|
|
267
|
+
)
|
|
268
|
+
return self._client.files.list(limit=cast(Any, limit_param))
|
|
269
|
+
|
|
270
|
+
def delete(self, file_id: str, untrack: bool = True) -> FileDeleted:
|
|
271
|
+
"""Delete a specific file from OpenAI Files API.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
file_id : str
|
|
276
|
+
The ID of the file to delete.
|
|
277
|
+
untrack : bool, default True
|
|
278
|
+
Remove from tracked files after deletion.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
FileDeleted
|
|
283
|
+
Confirmation of file deletion.
|
|
284
|
+
|
|
285
|
+
Examples
|
|
286
|
+
--------
|
|
287
|
+
>>> result = manager.delete("file-abc123")
|
|
288
|
+
>>> print(f"Deleted: {result.deleted}")
|
|
289
|
+
"""
|
|
290
|
+
result = self._client.files.delete(file_id)
|
|
291
|
+
|
|
292
|
+
if untrack and file_id in self.tracked_files:
|
|
293
|
+
del self.tracked_files[file_id]
|
|
294
|
+
log(f"Deleted and untracked file {file_id}")
|
|
295
|
+
else:
|
|
296
|
+
log(f"Deleted file {file_id}")
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
|
|
300
|
+
def retrieve_content(self, file_id: str) -> bytes:
|
|
301
|
+
"""Download and retrieve the content of a file.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
file_id : str
|
|
306
|
+
The ID of the file to download.
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
bytes
|
|
311
|
+
The raw bytes of the file content.
|
|
312
|
+
|
|
313
|
+
Examples
|
|
314
|
+
--------
|
|
315
|
+
>>> content = manager.retrieve_content("file-abc123")
|
|
316
|
+
>>> with open("downloaded.pdf", "wb") as f:
|
|
317
|
+
... f.write(content)
|
|
318
|
+
"""
|
|
319
|
+
return self._client.files.content(file_id).read()
|
|
320
|
+
|
|
321
|
+
def cleanup(self) -> dict[str, bool]:
|
|
322
|
+
"""Delete all tracked files.
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
dict[str, bool]
|
|
327
|
+
Dictionary mapping file IDs to deletion success status.
|
|
328
|
+
|
|
329
|
+
Examples
|
|
330
|
+
--------
|
|
331
|
+
>>> results = manager.cleanup()
|
|
332
|
+
>>> print(f"Deleted {sum(results.values())} files")
|
|
333
|
+
"""
|
|
334
|
+
results = {}
|
|
335
|
+
file_ids = list(self.tracked_files.keys())
|
|
336
|
+
|
|
337
|
+
for file_id in file_ids:
|
|
338
|
+
try:
|
|
339
|
+
self.delete(file_id, untrack=True)
|
|
340
|
+
results[file_id] = True
|
|
341
|
+
except Exception as exc:
|
|
342
|
+
log(
|
|
343
|
+
f"Error deleting tracked file {file_id}: {exc}",
|
|
344
|
+
level=logging.WARNING,
|
|
345
|
+
)
|
|
346
|
+
results[file_id] = False
|
|
347
|
+
|
|
348
|
+
if results:
|
|
349
|
+
successful = sum(results.values())
|
|
350
|
+
log(f"Cleanup complete: {successful}/{len(results)} files deleted")
|
|
351
|
+
else:
|
|
352
|
+
log("No tracked files to clean up")
|
|
353
|
+
|
|
354
|
+
return results
|
|
355
|
+
|
|
356
|
+
def __enter__(self) -> FilesAPIManager:
|
|
357
|
+
"""Context manager entry."""
|
|
358
|
+
return self
|
|
359
|
+
|
|
360
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
361
|
+
"""Context manager exit with automatic cleanup."""
|
|
362
|
+
self.cleanup()
|
|
363
|
+
|
|
364
|
+
def __len__(self) -> int:
|
|
365
|
+
"""Return number of tracked files."""
|
|
366
|
+
return len(self.tracked_files)
|
|
367
|
+
|
|
368
|
+
def __repr__(self) -> str:
|
|
369
|
+
"""Return string representation of the manager."""
|
|
370
|
+
return f"FilesAPIManager(tracked_files={len(self.tracked_files)})"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
__all__ = ["FilesAPIManager", "FilePurpose"]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Response handling for OpenAI API interactions.
|
|
2
2
|
|
|
3
3
|
This module provides comprehensive support for managing OpenAI API responses,
|
|
4
|
-
including message handling, tool execution, vector store attachments,
|
|
5
|
-
structured output parsing. It serves as the foundation for
|
|
6
|
-
sophisticated AI agents with persistent conversation state.
|
|
4
|
+
including message handling, tool execution, vector store attachments, file
|
|
5
|
+
processing, and structured output parsing. It serves as the foundation for
|
|
6
|
+
building sophisticated AI agents with persistent conversation state.
|
|
7
7
|
|
|
8
8
|
Classes
|
|
9
9
|
-------
|
|
@@ -28,12 +28,15 @@ run_streamed
|
|
|
28
28
|
Execute a response workflow and return the asynchronous result.
|
|
29
29
|
attach_vector_store
|
|
30
30
|
Attach vector stores to a response's file_search tool.
|
|
31
|
+
process_files
|
|
32
|
+
Process file attachments with automatic type detection.
|
|
31
33
|
"""
|
|
32
34
|
|
|
33
35
|
from __future__ import annotations
|
|
34
36
|
|
|
35
37
|
from .base import BaseResponse
|
|
36
38
|
from .config import ResponseConfiguration, ResponseRegistry, get_default_registry
|
|
39
|
+
from .files import process_files
|
|
37
40
|
from .messages import ResponseMessage, ResponseMessages
|
|
38
41
|
from .runner import run_async, run_streamed, run_sync
|
|
39
42
|
from .tool_call import ResponseToolCall, parse_tool_arguments
|
|
@@ -52,4 +55,5 @@ __all__ = [
|
|
|
52
55
|
"ResponseToolCall",
|
|
53
56
|
"parse_tool_arguments",
|
|
54
57
|
"attach_vector_store",
|
|
58
|
+
"process_files",
|
|
55
59
|
]
|