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.
@@ -25,8 +25,16 @@ from typing import (
25
25
  cast,
26
26
  )
27
27
 
28
- from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
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 check_filepath, coerce_jsonable, customJSONEncoder, ensure_list, log
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
- attachments: list[str] | None = None,
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
- Uploads any file attachments to vector stores and adds all messages
255
- to the conversation history.
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
- attachments : list[str] or None, default None
262
- Optional list of file paths to upload and attach to all messages.
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
- If attachments are provided and no user vector storage exists, this
267
- method automatically creates one and adds a file_search tool to
268
- the tools list.
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
- # Upload files once and collect their IDs
274
- file_ids: list[str] = []
275
- if all_attachments:
276
- if self._user_vector_storage is None:
277
- from openai_sdk_helpers.vector_storage import VectorStorage
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
- store_name = (
280
- f"{self.__class__.__name__.lower()}_{self._name}_{self.uuid}_user"
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
- user_vector_storage = cast(Any, self._user_vector_storage)
297
- for file_path in all_attachments:
298
- uploaded_file = user_vector_storage.upload_file(file_path)
299
- file_ids.append(uploaded_file.id)
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[ResponseInputTextParam | ResponseInputFileParam] = [
305
- ResponseInputTextParam(type="input_text", text=processed_text)
306
- ]
324
+ input_content: list[
325
+ ResponseInputTextParam
326
+ | ResponseInputFileParam
327
+ | ResponseInputFileContentParam
328
+ | ResponseInputImageContentParam
329
+ ] = [ResponseInputTextParam(type="input_text", text=processed_text)]
307
330
 
308
- for file_id in file_ids:
309
- input_content.append(
310
- ResponseInputFileParam(type="input_file", file_id=file_id)
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
- attachments: str | list[str] | None = None,
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
- attachments : str, list[str], or None, default None
335
- Optional file path or list of file paths to upload and attach.
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
- >>> result = await response.run_async("Analyze this text")
354
- >>> print(result)
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
- attachments=(ensure_list(attachments) if attachments else None),
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
- attachments: str | list[str] | None = None,
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
- attachments : str, list[str], or None, default None
461
- Optional file path or list of file paths to upload and attach.
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
- >>> result = response.run_sync("Summarize this document")
471
- >>> print(result)
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(content=content, attachments=attachments)
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
- attachments: str | list[str] | None = None,
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
- attachments : str, list[str], or None, default None
509
- Optional file path or list of file paths to upload and attach.
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(self.run_async(content=content, attachments=attachments))
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 and deletes managed vector stores.
685
- User vector stores are always cleaned up. System vector store cleanup
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=self.instructions_text,
328
+ instructions=instructions,
314
329
  tools=self.tools,
315
330
  output_structure=self.output_structure,
316
331
  tool_handlers=tool_handlers,