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
|
@@ -25,8 +25,16 @@ from typing import (
|
|
|
25
25
|
cast,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
from openai.types.responses.response_function_tool_call import
|
|
28
|
+
from openai.types.responses.response_function_tool_call import (
|
|
29
|
+
ResponseFunctionToolCall,
|
|
30
|
+
)
|
|
31
|
+
from openai.types.responses.response_input_file_content_param import (
|
|
32
|
+
ResponseInputFileContentParam,
|
|
33
|
+
)
|
|
29
34
|
from openai.types.responses.response_input_file_param import ResponseInputFileParam
|
|
35
|
+
from openai.types.responses.response_input_image_content_param import (
|
|
36
|
+
ResponseInputImageContentParam,
|
|
37
|
+
)
|
|
30
38
|
from openai.types.responses.response_input_message_content_list_param import (
|
|
31
39
|
ResponseInputMessageContentListParam,
|
|
32
40
|
)
|
|
@@ -38,7 +46,13 @@ from .messages import ResponseMessage, ResponseMessages
|
|
|
38
46
|
from ..config import OpenAISettings
|
|
39
47
|
from ..structure import BaseStructure
|
|
40
48
|
from ..types import OpenAIClient
|
|
41
|
-
from ..utils import
|
|
49
|
+
from ..utils import (
|
|
50
|
+
check_filepath,
|
|
51
|
+
coerce_jsonable,
|
|
52
|
+
customJSONEncoder,
|
|
53
|
+
ensure_list,
|
|
54
|
+
log,
|
|
55
|
+
)
|
|
42
56
|
|
|
43
57
|
if TYPE_CHECKING: # pragma: no cover - only for typing hints
|
|
44
58
|
from openai_sdk_helpers.streamlit_app.config import StreamlitAppConfig
|
|
@@ -214,6 +228,11 @@ class BaseResponse(Generic[T]):
|
|
|
214
228
|
|
|
215
229
|
self._user_vector_storage: Any | None = None
|
|
216
230
|
|
|
231
|
+
# Initialize Files API manager for tracking uploaded files
|
|
232
|
+
from ..files_api import FilesAPIManager
|
|
233
|
+
|
|
234
|
+
self._files_manager = FilesAPIManager(self._client, auto_track=True)
|
|
235
|
+
|
|
217
236
|
# New logic: system_vector_store is a list of vector store names to attach
|
|
218
237
|
if system_vector_store:
|
|
219
238
|
from .vector_store import attach_vector_store
|
|
@@ -247,68 +266,76 @@ class BaseResponse(Generic[T]):
|
|
|
247
266
|
def _build_input(
|
|
248
267
|
self,
|
|
249
268
|
content: str | list[str],
|
|
250
|
-
|
|
269
|
+
files: list[str] | None = None,
|
|
270
|
+
use_vector_store: bool = False,
|
|
251
271
|
) -> None:
|
|
252
272
|
"""Construct input messages for the OpenAI API request.
|
|
253
273
|
|
|
254
|
-
|
|
255
|
-
|
|
274
|
+
Automatically detects file types and handles them appropriately:
|
|
275
|
+
- Images (jpg, png, gif, etc.) are sent as base64-encoded images
|
|
276
|
+
- Documents are sent as base64-encoded file data by default
|
|
277
|
+
- Documents can optionally be uploaded to vector stores for RAG
|
|
256
278
|
|
|
257
279
|
Parameters
|
|
258
280
|
----------
|
|
259
281
|
content : str or list[str]
|
|
260
282
|
String or list of strings to include as user messages.
|
|
261
|
-
|
|
262
|
-
Optional list of file paths
|
|
283
|
+
files : list[str] or None, default None
|
|
284
|
+
Optional list of file paths. Each file is automatically processed
|
|
285
|
+
based on its type:
|
|
286
|
+
- Images are base64-encoded as input_image
|
|
287
|
+
- Documents are base64-encoded as input_file (default)
|
|
288
|
+
- Documents can be uploaded to vector stores if use_vector_store=True
|
|
289
|
+
use_vector_store : bool, default False
|
|
290
|
+
If True, non-image files are uploaded to a vector store for
|
|
291
|
+
RAG-enabled file search instead of inline base64 encoding.
|
|
263
292
|
|
|
264
293
|
Notes
|
|
265
294
|
-----
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"""
|
|
270
|
-
contents = ensure_list(content)
|
|
271
|
-
all_attachments = attachments or []
|
|
295
|
+
When use_vector_store is True, this method automatically creates
|
|
296
|
+
a vector store and adds a file_search tool for document retrieval.
|
|
297
|
+
Images are always base64-encoded regardless of this setting.
|
|
272
298
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
299
|
+
Examples
|
|
300
|
+
--------
|
|
301
|
+
>>> # Automatic handling - images as base64, docs inline
|
|
302
|
+
>>> response._build_input("Analyze these", files=["photo.jpg", "doc.pdf"])
|
|
303
|
+
|
|
304
|
+
>>> # Use vector store for documents (RAG)
|
|
305
|
+
>>> response._build_input(
|
|
306
|
+
... "Search these documents",
|
|
307
|
+
... files=["doc1.pdf", "doc2.pdf"],
|
|
308
|
+
... use_vector_store=True
|
|
309
|
+
... )
|
|
310
|
+
"""
|
|
311
|
+
from .files import process_files
|
|
278
312
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
)
|
|
282
|
-
self._user_vector_storage = VectorStorage(
|
|
283
|
-
store_name=store_name,
|
|
284
|
-
client=self._client,
|
|
285
|
-
model=self._model,
|
|
286
|
-
)
|
|
287
|
-
user_vector_storage = cast(Any, self._user_vector_storage)
|
|
288
|
-
if not any(tool.get("type") == "file_search" for tool in self._tools):
|
|
289
|
-
self._tools.append(
|
|
290
|
-
{
|
|
291
|
-
"type": "file_search",
|
|
292
|
-
"vector_store_ids": [user_vector_storage.id],
|
|
293
|
-
}
|
|
294
|
-
)
|
|
313
|
+
contents = ensure_list(content)
|
|
314
|
+
all_files = files or []
|
|
295
315
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
316
|
+
# Process files using the dedicated files module
|
|
317
|
+
vector_file_refs, base64_files, image_contents = process_files(
|
|
318
|
+
self, all_files, use_vector_store
|
|
319
|
+
)
|
|
300
320
|
|
|
301
321
|
# Add each content as a separate message with the same attachments
|
|
302
322
|
for raw_content in contents:
|
|
303
323
|
processed_text = raw_content.strip()
|
|
304
|
-
input_content: list[
|
|
305
|
-
ResponseInputTextParam
|
|
306
|
-
|
|
324
|
+
input_content: list[
|
|
325
|
+
ResponseInputTextParam
|
|
326
|
+
| ResponseInputFileParam
|
|
327
|
+
| ResponseInputFileContentParam
|
|
328
|
+
| ResponseInputImageContentParam
|
|
329
|
+
] = [ResponseInputTextParam(type="input_text", text=processed_text)]
|
|
307
330
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
331
|
+
# Add vector store file references
|
|
332
|
+
input_content.extend(vector_file_refs)
|
|
333
|
+
|
|
334
|
+
# Add base64 files
|
|
335
|
+
input_content.extend(base64_files)
|
|
336
|
+
|
|
337
|
+
# Add images
|
|
338
|
+
input_content.extend(image_contents)
|
|
312
339
|
|
|
313
340
|
message = cast(
|
|
314
341
|
ResponseInputItemParam,
|
|
@@ -319,7 +346,8 @@ class BaseResponse(Generic[T]):
|
|
|
319
346
|
async def run_async(
|
|
320
347
|
self,
|
|
321
348
|
content: str | list[str],
|
|
322
|
-
|
|
349
|
+
files: str | list[str] | None = None,
|
|
350
|
+
use_vector_store: bool = False,
|
|
323
351
|
) -> T | None:
|
|
324
352
|
"""Generate a response asynchronously from the OpenAI API.
|
|
325
353
|
|
|
@@ -327,12 +355,21 @@ class BaseResponse(Generic[T]):
|
|
|
327
355
|
tool calls with registered handlers, and optionally parses the
|
|
328
356
|
result into the configured output_structure.
|
|
329
357
|
|
|
358
|
+
Automatically detects file types:
|
|
359
|
+
- Images are sent as base64-encoded images
|
|
360
|
+
- Documents are sent as base64-encoded files (default)
|
|
361
|
+
- Documents can optionally use vector stores for RAG
|
|
362
|
+
|
|
330
363
|
Parameters
|
|
331
364
|
----------
|
|
332
365
|
content : str or list[str]
|
|
333
366
|
Prompt text or list of prompt texts to send.
|
|
334
|
-
|
|
335
|
-
Optional file path or list of file paths
|
|
367
|
+
files : str, list[str], or None, default None
|
|
368
|
+
Optional file path or list of file paths. Each file is
|
|
369
|
+
automatically processed based on its type.
|
|
370
|
+
use_vector_store : bool, default False
|
|
371
|
+
If True, non-image files are uploaded to a vector store
|
|
372
|
+
for RAG-enabled search instead of inline base64 encoding.
|
|
336
373
|
|
|
337
374
|
Returns
|
|
338
375
|
-------
|
|
@@ -350,15 +387,26 @@ class BaseResponse(Generic[T]):
|
|
|
350
387
|
|
|
351
388
|
Examples
|
|
352
389
|
--------
|
|
353
|
-
>>>
|
|
354
|
-
>>>
|
|
390
|
+
>>> # Automatic type detection
|
|
391
|
+
>>> result = await response.run_async(
|
|
392
|
+
... "Analyze these files",
|
|
393
|
+
... files=["photo.jpg", "document.pdf"]
|
|
394
|
+
... )
|
|
395
|
+
|
|
396
|
+
>>> # Use vector store for documents
|
|
397
|
+
>>> result = await response.run_async(
|
|
398
|
+
... "Search these documents",
|
|
399
|
+
... files=["doc1.pdf", "doc2.pdf"],
|
|
400
|
+
... use_vector_store=True
|
|
401
|
+
... )
|
|
355
402
|
"""
|
|
356
403
|
log(f"{self.__class__.__name__}::run_response")
|
|
357
404
|
parsed_result: T | None = None
|
|
358
405
|
|
|
359
406
|
self._build_input(
|
|
360
407
|
content=content,
|
|
361
|
-
|
|
408
|
+
files=(ensure_list(files) if files else None),
|
|
409
|
+
use_vector_store=use_vector_store,
|
|
362
410
|
)
|
|
363
411
|
|
|
364
412
|
kwargs = {
|
|
@@ -445,7 +493,8 @@ class BaseResponse(Generic[T]):
|
|
|
445
493
|
self,
|
|
446
494
|
content: str | list[str],
|
|
447
495
|
*,
|
|
448
|
-
|
|
496
|
+
files: str | list[str] | None = None,
|
|
497
|
+
use_vector_store: bool = False,
|
|
449
498
|
) -> T | None:
|
|
450
499
|
"""Execute run_async synchronously with proper event loop handling.
|
|
451
500
|
|
|
@@ -453,12 +502,21 @@ class BaseResponse(Generic[T]):
|
|
|
453
502
|
a separate thread if necessary. This enables safe usage in both
|
|
454
503
|
synchronous and asynchronous contexts.
|
|
455
504
|
|
|
505
|
+
Automatically detects file types:
|
|
506
|
+
- Images are sent as base64-encoded images
|
|
507
|
+
- Documents are sent as base64-encoded files (default)
|
|
508
|
+
- Documents can optionally use vector stores for RAG
|
|
509
|
+
|
|
456
510
|
Parameters
|
|
457
511
|
----------
|
|
458
512
|
content : str or list[str]
|
|
459
513
|
Prompt text or list of prompt texts to send.
|
|
460
|
-
|
|
461
|
-
Optional file path or list of file paths
|
|
514
|
+
files : str, list[str], or None, default None
|
|
515
|
+
Optional file path or list of file paths. Each file is
|
|
516
|
+
automatically processed based on its type.
|
|
517
|
+
use_vector_store : bool, default False
|
|
518
|
+
If True, non-image files are uploaded to a vector store
|
|
519
|
+
for RAG-enabled search instead of inline base64 encoding.
|
|
462
520
|
|
|
463
521
|
Returns
|
|
464
522
|
-------
|
|
@@ -467,12 +525,26 @@ class BaseResponse(Generic[T]):
|
|
|
467
525
|
|
|
468
526
|
Examples
|
|
469
527
|
--------
|
|
470
|
-
>>>
|
|
471
|
-
>>>
|
|
528
|
+
>>> # Automatic type detection
|
|
529
|
+
>>> result = response.run_sync(
|
|
530
|
+
... "Analyze these files",
|
|
531
|
+
... files=["photo.jpg", "document.pdf"]
|
|
532
|
+
... )
|
|
533
|
+
|
|
534
|
+
>>> # Use vector store for documents
|
|
535
|
+
>>> result = response.run_sync(
|
|
536
|
+
... "Search these documents",
|
|
537
|
+
... files=["doc1.pdf", "doc2.pdf"],
|
|
538
|
+
... use_vector_store=True
|
|
539
|
+
... )
|
|
472
540
|
"""
|
|
473
541
|
|
|
474
542
|
async def runner() -> T | None:
|
|
475
|
-
return await self.run_async(
|
|
543
|
+
return await self.run_async(
|
|
544
|
+
content=content,
|
|
545
|
+
files=files,
|
|
546
|
+
use_vector_store=use_vector_store,
|
|
547
|
+
)
|
|
476
548
|
|
|
477
549
|
try:
|
|
478
550
|
asyncio.get_running_loop()
|
|
@@ -493,7 +565,8 @@ class BaseResponse(Generic[T]):
|
|
|
493
565
|
self,
|
|
494
566
|
content: str | list[str],
|
|
495
567
|
*,
|
|
496
|
-
|
|
568
|
+
files: str | list[str] | None = None,
|
|
569
|
+
use_vector_store: bool = False,
|
|
497
570
|
) -> T | None:
|
|
498
571
|
"""Execute run_async and await the result.
|
|
499
572
|
|
|
@@ -501,12 +574,21 @@ class BaseResponse(Generic[T]):
|
|
|
501
574
|
simply awaits run_async to provide API compatibility with agent
|
|
502
575
|
interfaces.
|
|
503
576
|
|
|
577
|
+
Automatically detects file types:
|
|
578
|
+
- Images are sent as base64-encoded images
|
|
579
|
+
- Documents are sent as base64-encoded files (default)
|
|
580
|
+
- Documents can optionally use vector stores for RAG
|
|
581
|
+
|
|
504
582
|
Parameters
|
|
505
583
|
----------
|
|
506
584
|
content : str or list[str]
|
|
507
585
|
Prompt text or list of prompt texts to send.
|
|
508
|
-
|
|
509
|
-
Optional file path or list of file paths
|
|
586
|
+
files : str, list[str], or None, default None
|
|
587
|
+
Optional file path or list of file paths. Each file is
|
|
588
|
+
automatically processed based on its type.
|
|
589
|
+
use_vector_store : bool, default False
|
|
590
|
+
If True, non-image files are uploaded to a vector store
|
|
591
|
+
for RAG-enabled search instead of inline base64 encoding.
|
|
510
592
|
|
|
511
593
|
Returns
|
|
512
594
|
-------
|
|
@@ -518,7 +600,13 @@ class BaseResponse(Generic[T]):
|
|
|
518
600
|
This method exists for API consistency but does not currently
|
|
519
601
|
provide true streaming functionality.
|
|
520
602
|
"""
|
|
521
|
-
return asyncio.run(
|
|
603
|
+
return asyncio.run(
|
|
604
|
+
self.run_async(
|
|
605
|
+
content=content,
|
|
606
|
+
files=files,
|
|
607
|
+
use_vector_store=use_vector_store,
|
|
608
|
+
)
|
|
609
|
+
)
|
|
522
610
|
|
|
523
611
|
def get_last_tool_message(self) -> ResponseMessage | None:
|
|
524
612
|
"""Return the most recent tool message from conversation history.
|
|
@@ -679,11 +767,11 @@ class BaseResponse(Generic[T]):
|
|
|
679
767
|
self.close()
|
|
680
768
|
|
|
681
769
|
def close(self) -> None:
|
|
682
|
-
"""Clean up session resources including vector stores.
|
|
770
|
+
"""Clean up session resources including vector stores and uploaded files.
|
|
683
771
|
|
|
684
|
-
Saves the current message history
|
|
685
|
-
|
|
686
|
-
is handled via tool configuration.
|
|
772
|
+
Saves the current message history, deletes managed vector stores, and
|
|
773
|
+
cleans up all tracked Files API uploads. User vector stores are always
|
|
774
|
+
cleaned up. System vector store cleanup is handled via tool configuration.
|
|
687
775
|
|
|
688
776
|
Notes
|
|
689
777
|
-----
|
|
@@ -701,6 +789,19 @@ class BaseResponse(Generic[T]):
|
|
|
701
789
|
"""
|
|
702
790
|
log(f"Closing session {self.uuid} for {self.__class__.__name__}")
|
|
703
791
|
self.save()
|
|
792
|
+
|
|
793
|
+
# Clean up tracked Files API uploads
|
|
794
|
+
try:
|
|
795
|
+
if hasattr(self, "_files_manager") and self._files_manager:
|
|
796
|
+
cleanup_results = self._files_manager.cleanup()
|
|
797
|
+
if cleanup_results:
|
|
798
|
+
successful = sum(cleanup_results.values())
|
|
799
|
+
log(
|
|
800
|
+
f"Files API cleanup: {successful}/{len(cleanup_results)} files deleted"
|
|
801
|
+
)
|
|
802
|
+
except Exception as exc:
|
|
803
|
+
log(f"Error cleaning up Files API uploads: {exc}", level=logging.WARNING)
|
|
804
|
+
|
|
704
805
|
# Always clean user vector storage if it exists
|
|
705
806
|
try:
|
|
706
807
|
if self._user_vector_storage:
|
|
@@ -291,6 +291,7 @@ class ResponseConfiguration(Generic[TIn, TOut]):
|
|
|
291
291
|
self,
|
|
292
292
|
openai_settings: OpenAISettings,
|
|
293
293
|
tool_handlers: dict[str, ToolHandler] = {},
|
|
294
|
+
add_output_instructions: bool = True,
|
|
294
295
|
) -> BaseResponse[TOut]:
|
|
295
296
|
"""Generate a BaseResponse instance based on the configuration.
|
|
296
297
|
|
|
@@ -302,15 +303,29 @@ class ResponseConfiguration(Generic[TIn, TOut]):
|
|
|
302
303
|
tool_handlers : dict[str, Callable], optional
|
|
303
304
|
Mapping of tool names to handler callables. Defaults to an empty
|
|
304
305
|
dictionary when not provided.
|
|
306
|
+
add_output_instructions : bool, default=True
|
|
307
|
+
Whether to append the structured output prompt to the instructions.
|
|
305
308
|
|
|
306
309
|
Returns
|
|
307
310
|
-------
|
|
308
311
|
BaseResponse[TOut]
|
|
309
312
|
An instance of BaseResponse configured with ``openai_settings``.
|
|
310
313
|
"""
|
|
314
|
+
output_instructions = ""
|
|
315
|
+
if self.output_structure is not None and add_output_instructions:
|
|
316
|
+
output_instructions = self.output_structure.get_prompt(
|
|
317
|
+
add_enum_values=False
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
instructions = (
|
|
321
|
+
f"{self.instructions_text}\n{output_instructions}"
|
|
322
|
+
if output_instructions
|
|
323
|
+
else self.instructions_text
|
|
324
|
+
)
|
|
325
|
+
|
|
311
326
|
return BaseResponse[TOut](
|
|
312
327
|
name=self.name,
|
|
313
|
-
instructions=
|
|
328
|
+
instructions=instructions,
|
|
314
329
|
tools=self.tools,
|
|
315
330
|
output_structure=self.output_structure,
|
|
316
331
|
tool_handlers=tool_handlers,
|