openai-sdk-helpers 0.0.8__py3-none-any.whl → 0.0.9__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 (64) hide show
  1. openai_sdk_helpers/__init__.py +66 -2
  2. openai_sdk_helpers/agent/__init__.py +8 -4
  3. openai_sdk_helpers/agent/base.py +80 -45
  4. openai_sdk_helpers/agent/config.py +6 -4
  5. openai_sdk_helpers/agent/{project_manager.py → coordination.py} +29 -45
  6. openai_sdk_helpers/agent/prompt_utils.py +7 -1
  7. openai_sdk_helpers/agent/runner.py +67 -141
  8. openai_sdk_helpers/agent/search/__init__.py +33 -0
  9. openai_sdk_helpers/agent/search/base.py +297 -0
  10. openai_sdk_helpers/agent/{vector_search.py → search/vector.py} +89 -157
  11. openai_sdk_helpers/agent/{web_search.py → search/web.py} +77 -156
  12. openai_sdk_helpers/agent/summarizer.py +29 -8
  13. openai_sdk_helpers/agent/translator.py +40 -13
  14. openai_sdk_helpers/agent/validation.py +32 -8
  15. openai_sdk_helpers/async_utils.py +132 -0
  16. openai_sdk_helpers/config.py +74 -36
  17. openai_sdk_helpers/context_manager.py +241 -0
  18. openai_sdk_helpers/enums/__init__.py +9 -1
  19. openai_sdk_helpers/enums/base.py +67 -8
  20. openai_sdk_helpers/environment.py +33 -6
  21. openai_sdk_helpers/errors.py +133 -0
  22. openai_sdk_helpers/logging_config.py +105 -0
  23. openai_sdk_helpers/prompt/__init__.py +10 -71
  24. openai_sdk_helpers/prompt/base.py +172 -0
  25. openai_sdk_helpers/response/__init__.py +35 -3
  26. openai_sdk_helpers/response/base.py +363 -210
  27. openai_sdk_helpers/response/config.py +176 -0
  28. openai_sdk_helpers/response/messages.py +56 -40
  29. openai_sdk_helpers/response/runner.py +77 -33
  30. openai_sdk_helpers/response/tool_call.py +49 -25
  31. openai_sdk_helpers/response/vector_store.py +27 -14
  32. openai_sdk_helpers/retry.py +175 -0
  33. openai_sdk_helpers/streamlit_app/__init__.py +19 -2
  34. openai_sdk_helpers/streamlit_app/app.py +114 -39
  35. openai_sdk_helpers/streamlit_app/config.py +502 -0
  36. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +5 -6
  37. openai_sdk_helpers/structure/__init__.py +69 -3
  38. openai_sdk_helpers/structure/agent_blueprint.py +82 -19
  39. openai_sdk_helpers/structure/base.py +208 -93
  40. openai_sdk_helpers/structure/plan/__init__.py +15 -1
  41. openai_sdk_helpers/structure/plan/enum.py +41 -5
  42. openai_sdk_helpers/structure/plan/plan.py +101 -45
  43. openai_sdk_helpers/structure/plan/task.py +38 -6
  44. openai_sdk_helpers/structure/prompt.py +21 -2
  45. openai_sdk_helpers/structure/responses.py +52 -11
  46. openai_sdk_helpers/structure/summary.py +55 -7
  47. openai_sdk_helpers/structure/validation.py +34 -6
  48. openai_sdk_helpers/structure/vector_search.py +132 -18
  49. openai_sdk_helpers/structure/web_search.py +125 -13
  50. openai_sdk_helpers/types.py +57 -0
  51. openai_sdk_helpers/utils/__init__.py +30 -1
  52. openai_sdk_helpers/utils/core.py +168 -34
  53. openai_sdk_helpers/validation.py +302 -0
  54. openai_sdk_helpers/vector_storage/__init__.py +21 -1
  55. openai_sdk_helpers/vector_storage/cleanup.py +25 -13
  56. openai_sdk_helpers/vector_storage/storage.py +123 -64
  57. openai_sdk_helpers/vector_storage/types.py +20 -19
  58. openai_sdk_helpers-0.0.9.dist-info/METADATA +550 -0
  59. openai_sdk_helpers-0.0.9.dist-info/RECORD +66 -0
  60. openai_sdk_helpers/streamlit_app/configuration.py +0 -324
  61. openai_sdk_helpers-0.0.8.dist-info/METADATA +0 -194
  62. openai_sdk_helpers-0.0.8.dist-info/RECORD +0 -55
  63. {openai_sdk_helpers-0.0.8.dist-info → openai_sdk_helpers-0.0.9.dist-info}/WHEEL +0 -0
  64. {openai_sdk_helpers-0.0.8.dist-info → openai_sdk_helpers-0.0.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,54 @@
1
- """Environment helpers for openai-sdk-helpers."""
1
+ """Environment helpers for openai-sdk-helpers.
2
+
3
+ This module provides utility functions and constants for managing paths
4
+ and environment-specific configuration.
5
+
6
+ Constants
7
+ ---------
8
+ DATETIME_FMT : str
9
+ Standard datetime format string for file naming ("%Y%m%d_%H%M%S").
10
+ DEFAULT_MODEL : str
11
+ Default OpenAI model identifier ("gpt-4o-mini").
12
+
13
+ Functions
14
+ ---------
15
+ get_data_path(name)
16
+ Return a writable data directory for the given module name.
17
+ """
2
18
 
3
19
  from __future__ import annotations
4
20
 
5
21
  from pathlib import Path
6
22
 
7
23
  DATETIME_FMT = "%Y%m%d_%H%M%S"
24
+ DEFAULT_MODEL = "gpt-4o-mini"
8
25
 
9
26
 
10
- def get_data_path(module_name: str) -> Path:
27
+ def get_data_path(name: str) -> Path:
11
28
  """Return a writable data directory for the given module name.
12
29
 
30
+ Creates a module-specific directory under ~/.openai-sdk-helpers/ for
31
+ storing data, logs, or other persistent files.
32
+
13
33
  Parameters
14
34
  ----------
15
- module_name : str
35
+ name : str
16
36
  Name of the module requesting a data directory.
17
37
 
18
38
  Returns
19
39
  -------
20
40
  Path
21
- Directory path under ``~/.openai-sdk-helpers`` specific to ``module_name``. The
22
- directory is created if it does not already exist.
41
+ Directory path under ~/.openai-sdk-helpers specific to name.
42
+ The directory is created if it does not exist.
43
+
44
+ Examples
45
+ --------
46
+ >>> from openai_sdk_helpers.environment import get_data_path
47
+ >>> path = get_data_path("my_module")
48
+ >>> path.exists()
49
+ True
23
50
  """
24
51
  base = Path.home() / ".openai-sdk-helpers"
25
- path = base / module_name
52
+ path = base / name
26
53
  path.mkdir(parents=True, exist_ok=True)
27
54
  return path
@@ -0,0 +1,133 @@
1
+ """Custom exception hierarchy for openai-sdk-helpers.
2
+
3
+ Provides specific exception types for different error scenarios,
4
+ improving error handling and debugging capabilities.
5
+ """
6
+
7
+ import logging
8
+ from collections.abc import Mapping
9
+
10
+ from openai_sdk_helpers.utils.core import log
11
+
12
+
13
+ class OpenAISDKError(Exception):
14
+ """Base exception for openai-sdk-helpers library.
15
+
16
+ All custom exceptions in this library inherit from this class,
17
+ allowing callers to catch all SDK-specific errors.
18
+
19
+ Parameters
20
+ ----------
21
+ message : str
22
+ Human-readable error message
23
+ context : Mapping[str, object] | None
24
+ Additional context information for debugging. Default is None.
25
+
26
+ Examples
27
+ --------
28
+ >>> try:
29
+ ... raise OpenAISDKError("Something went wrong", context={"step": "init"})
30
+ ... except OpenAISDKError as exc:
31
+ ... print(f"SDK Error: {exc}")
32
+ ... print(f"Context: {exc.context}")
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ message: str,
38
+ context: Mapping[str, object] | None = None,
39
+ ) -> None:
40
+ """Initialize the exception with message and optional context."""
41
+ super().__init__(message)
42
+ self.context = dict(context) if context is not None else {}
43
+ self._log_context()
44
+
45
+ def _log_context(self) -> None:
46
+ """Log error with context for debugging."""
47
+ context_str = f"\nContext: {self.context}" if self.context else ""
48
+ log(
49
+ f"{self.__class__.__name__}: {str(self)}{context_str}",
50
+ level=logging.ERROR,
51
+ )
52
+
53
+
54
+ class ConfigurationError(OpenAISDKError):
55
+ """Configuration validation or initialization failed.
56
+
57
+ Raised when configuration is missing, invalid, or inconsistent.
58
+ """
59
+
60
+ pass
61
+
62
+
63
+ class PromptNotFoundError(OpenAISDKError):
64
+ """Prompt template file not found or cannot be read.
65
+
66
+ Raised when a required prompt template file is missing or inaccessible.
67
+ """
68
+
69
+ pass
70
+
71
+
72
+ class AgentExecutionError(OpenAISDKError):
73
+ """Agent execution failed.
74
+
75
+ Raised when an agent encounters an error during execution.
76
+ May wrap underlying exceptions with additional context.
77
+ """
78
+
79
+ pass
80
+
81
+
82
+ class VectorStorageError(OpenAISDKError):
83
+ """Vector storage operation failed.
84
+
85
+ Raised when vector store operations (upload, download, cleanup) fail.
86
+ """
87
+
88
+ pass
89
+
90
+
91
+ class ToolExecutionError(OpenAISDKError):
92
+ """Tool execution failed.
93
+
94
+ Raised when a tool handler encounters an error.
95
+ """
96
+
97
+ pass
98
+
99
+
100
+ class ResponseGenerationError(OpenAISDKError):
101
+ """Response generation failed.
102
+
103
+ Raised when generating responses from structured output fails.
104
+ """
105
+
106
+ pass
107
+
108
+
109
+ class InputValidationError(OpenAISDKError):
110
+ """Input validation failed.
111
+
112
+ Raised when provided input doesn't meet required constraints.
113
+ """
114
+
115
+ pass
116
+
117
+
118
+ class AsyncExecutionError(OpenAISDKError):
119
+ """Asynchronous operation failed.
120
+
121
+ Raised when async/await operations fail or timeout.
122
+ """
123
+
124
+ pass
125
+
126
+
127
+ class ResourceCleanupError(OpenAISDKError):
128
+ """Resource cleanup failed.
129
+
130
+ Raised when cleanup of resources fails, but may not be fatal.
131
+ """
132
+
133
+ pass
@@ -0,0 +1,105 @@
1
+ """Centralized logging configuration for openai-sdk-helpers.
2
+
3
+ Provides a centralized factory for creating and configuring loggers
4
+ with consistent formatting and handler management.
5
+ """
6
+
7
+ import logging
8
+ import threading
9
+ from typing import Any
10
+
11
+
12
+ class LoggerFactory:
13
+ """Centralized logger creation and configuration.
14
+
15
+ Manages logger initialization and configuration to ensure consistent
16
+ logging behavior across the entire SDK. Thread-safe.
17
+
18
+ Examples
19
+ --------
20
+ Configure logging once at application startup:
21
+
22
+ >>> from openai_sdk_helpers.logging_config import LoggerFactory
23
+ >>> import logging
24
+ >>> LoggerFactory.configure(
25
+ ... level=logging.DEBUG,
26
+ ... handlers=[logging.StreamHandler()],
27
+ ... )
28
+
29
+ Get a logger instance in your module:
30
+
31
+ >>> logger = LoggerFactory.get_logger("openai_sdk_helpers.agent")
32
+ >>> logger.debug("Debug message")
33
+ """
34
+
35
+ _initialized = False
36
+ _log_level = logging.INFO
37
+ _handlers: list[logging.Handler] = []
38
+ _lock = threading.Lock()
39
+
40
+ @classmethod
41
+ def configure(
42
+ cls,
43
+ level: int = logging.INFO,
44
+ handlers: list[logging.Handler] | None = None,
45
+ ) -> None:
46
+ """Configure logging globally.
47
+
48
+ Parameters
49
+ ----------
50
+ level : int
51
+ Logging level (e.g., logging.DEBUG, logging.INFO).
52
+ Default is logging.INFO.
53
+ handlers : list[logging.Handler] | None
54
+ List of logging handlers. If None, a default
55
+ StreamHandler is created. Default is None.
56
+
57
+ Notes
58
+ -----
59
+ This method is thread-safe and can be called multiple times.
60
+ """
61
+ with cls._lock:
62
+ cls._log_level = level
63
+ if handlers:
64
+ cls._handlers = handlers
65
+ else:
66
+ handler = logging.StreamHandler()
67
+ handler.setLevel(level)
68
+ formatter = logging.Formatter(
69
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
70
+ )
71
+ handler.setFormatter(formatter)
72
+ cls._handlers = [handler]
73
+ cls._initialized = True
74
+
75
+ @classmethod
76
+ def get_logger(cls, name: str) -> logging.Logger:
77
+ """Get configured logger instance.
78
+
79
+ Parameters
80
+ ----------
81
+ name : str
82
+ Logger name, typically __name__ of calling module.
83
+
84
+ Returns
85
+ -------
86
+ logging.Logger
87
+ Configured logger instance.
88
+ """
89
+ logger = logging.getLogger(name)
90
+
91
+ # Skip configuration if already configured
92
+ if logger.handlers:
93
+ return logger
94
+
95
+ with cls._lock:
96
+ if not cls._initialized:
97
+ cls.configure()
98
+
99
+ for handler in cls._handlers:
100
+ logger.addHandler(handler)
101
+
102
+ logger.setLevel(cls._log_level)
103
+ logger.propagate = False
104
+
105
+ return logger
@@ -1,77 +1,16 @@
1
- """Core prompt rendering utilities."""
1
+ """Prompt rendering utilities for template-based text generation.
2
2
 
3
- from __future__ import annotations
4
-
5
- import warnings
6
- from pathlib import Path
7
- from typing import Any, Mapping, Optional
8
-
9
- from dotenv import load_dotenv
10
- from jinja2 import Environment, FileSystemLoader, Template
11
-
12
- load_dotenv()
13
- warnings.filterwarnings("ignore")
14
-
15
-
16
- class PromptRenderer:
17
- """Render prompts using Jinja2 templates.
18
-
19
- The renderer loads templates from a base directory (defaulting to the
20
- ``prompt`` package directory) and exposes a rendering helper for
21
- injecting context values.
3
+ This module provides Jinja2-based template rendering functionality for
4
+ creating dynamic prompts with variable substitution and template inheritance.
22
5
 
23
- Methods
24
- -------
25
- render(template_path, context)
26
- Render the template at ``template_path`` with the supplied context.
27
- """
6
+ Classes
7
+ -------
8
+ PromptRenderer
9
+ Jinja2-based template renderer for dynamic prompt generation.
10
+ """
28
11
 
29
- def __init__(self, base_dir: Optional[Path] = None) -> None:
30
- """Initialize the renderer with a Jinja2 environment.
31
-
32
- Parameters
33
- ----------
34
- base_dir : Path or None, default=None
35
- Base directory containing Jinja2 templates. Defaults to the
36
- ``prompt`` directory adjacent to this file when ``None``.
37
-
38
- Returns
39
- -------
40
- None
41
- """
42
- if base_dir is None:
43
- # Defaults to the directory containing this file, which also
44
- # contains the builtin prompt templates.
45
- self.base_dir = Path(__file__).resolve().parent
46
- else:
47
- self.base_dir = base_dir
48
-
49
- self._env = Environment(
50
- loader=FileSystemLoader(str(self.base_dir)),
51
- autoescape=False, # Prompts are plain text
52
- )
53
-
54
- def render(
55
- self, template_path: str, context: Optional[Mapping[str, Any]] = None
56
- ) -> str:
57
- """Render a Jinja2 template with the given context.
58
-
59
- Parameters
60
- ----------
61
- template_path : str
62
- Path to the template file, relative to ``base_dir``.
63
- context : Mapping[str, Any] or None, default=None
64
- Context variables passed to the template.
65
-
66
- Returns
67
- -------
68
- str
69
- Rendered prompt as a string.
70
- """
71
- template_path_ = Path(self.base_dir, template_path)
72
- template_path_text = template_path_.read_text()
73
- template = Template(template_path_text)
74
- return template.render(context or {})
12
+ from __future__ import annotations
75
13
 
14
+ from .base import PromptRenderer
76
15
 
77
16
  __all__ = ["PromptRenderer"]
@@ -0,0 +1,172 @@
1
+ """Core prompt rendering implementation.
2
+
3
+ This module provides the PromptRenderer class for loading and rendering
4
+ Jinja2 templates with context variables. Templates can be loaded from a
5
+ specified directory or by absolute path.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import warnings
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from dotenv import load_dotenv
15
+ from jinja2 import Environment, FileSystemLoader, Template
16
+
17
+ load_dotenv()
18
+ warnings.filterwarnings("ignore")
19
+
20
+
21
+ class PromptRenderer:
22
+ """Jinja2-based template renderer for dynamic prompt generation.
23
+
24
+ Loads and renders Jinja2 templates from a base directory or by absolute
25
+ path. The renderer supports variable substitution, template inheritance,
26
+ and all standard Jinja2 features for creating dynamic prompts.
27
+
28
+ Templates are loaded from a base directory (defaulting to the built-in
29
+ prompt package directory) or can be specified with absolute paths.
30
+ Autoescape is disabled by default since prompts are plain text.
31
+
32
+ Attributes
33
+ ----------
34
+ base_dir : Path
35
+ Base directory for template loading.
36
+
37
+ Methods
38
+ -------
39
+ render(template_path, context=None)
40
+ Render a Jinja2 template with the given context variables.
41
+
42
+ Examples
43
+ --------
44
+ Basic template rendering with custom base directory:
45
+
46
+ >>> from pathlib import Path
47
+ >>> from openai_sdk_helpers.prompt import PromptRenderer
48
+ >>> renderer = PromptRenderer(base_dir=Path("./templates"))
49
+ >>> prompt = renderer.render(
50
+ ... "greeting.jinja",
51
+ ... context={"name": "Alice", "language": "English"}
52
+ ... )
53
+ >>> print(prompt)
54
+
55
+ Using absolute path (no base_dir required):
56
+
57
+ >>> renderer = PromptRenderer()
58
+ >>> prompt = renderer.render(
59
+ ... "/absolute/path/to/template.jinja",
60
+ ... context={"name": "Bob"}
61
+ ... )
62
+
63
+ Using built-in templates:
64
+
65
+ >>> renderer = PromptRenderer() # Uses built-in templates
66
+ >>> prompt = renderer.render("summarizer.jinja", context={})
67
+ """
68
+
69
+ def __init__(self, base_dir: Path | None = None) -> None:
70
+ """Initialize the renderer with a Jinja2 environment.
71
+
72
+ Sets up the Jinja2 environment with a FileSystemLoader pointing to
73
+ the specified base directory. If no base directory is provided,
74
+ defaults to the built-in prompt package directory containing
75
+ standard templates.
76
+
77
+ Parameters
78
+ ----------
79
+ base_dir : Path or None, default None
80
+ Base directory containing Jinja2 templates. If None, uses the
81
+ prompt package directory containing built-in templates.
82
+
83
+ Examples
84
+ --------
85
+ >>> from pathlib import Path
86
+ >>> renderer = PromptRenderer(base_dir=Path("./my_templates"))
87
+ >>> renderer.base_dir
88
+ PosixPath('.../my_templates')
89
+
90
+ >>> default_renderer = PromptRenderer()
91
+ >>> default_renderer.base_dir.name
92
+ 'prompt'
93
+ """
94
+ if base_dir is None:
95
+ # Defaults to the directory containing this file, which also
96
+ # contains the builtin prompt templates.
97
+ self.base_dir = Path(__file__).resolve().parent
98
+ else:
99
+ self.base_dir = base_dir
100
+
101
+ self._env = Environment(
102
+ loader=FileSystemLoader(str(self.base_dir)),
103
+ autoescape=False, # Prompts are plain text
104
+ )
105
+
106
+ def render(self, template_path: str, context: dict[str, Any] | None = None) -> str:
107
+ """Render a Jinja2 template with the given context variables.
108
+
109
+ Loads the template from either an absolute path or a path relative
110
+ to the base directory. The template is rendered with the provided
111
+ context dictionary using Jinja2's template engine.
112
+
113
+ For security, relative paths are validated to prevent path traversal
114
+ attacks. Absolute paths are allowed but should be used with caution
115
+ as they bypass base directory restrictions.
116
+
117
+ Parameters
118
+ ----------
119
+ template_path : str
120
+ Path to the template file. Can be an absolute path or relative
121
+ to base_dir.
122
+ context : dict[str, Any] or None, default None
123
+ Context variables to pass to the template. If None, an empty
124
+ dictionary is used.
125
+
126
+ Returns
127
+ -------
128
+ str
129
+ Fully rendered template as a string.
130
+
131
+ Raises
132
+ ------
133
+ FileNotFoundError
134
+ If the template file does not exist at the specified path.
135
+ InputValidationError
136
+ If the path contains suspicious patterns or attempts to escape
137
+ the base directory.
138
+
139
+ Examples
140
+ --------
141
+ >>> renderer = PromptRenderer()
142
+ >>> context = {"name": "Alice", "age": 30}
143
+ >>> result = renderer.render("greeting.jinja", context)
144
+ >>> "Alice" in result
145
+ True
146
+
147
+ With absolute path:
148
+
149
+ >>> result = renderer.render(
150
+ ... "/path/to/template.jinja",
151
+ ... context={"key": "value"}
152
+ ... )
153
+ """
154
+ from openai_sdk_helpers.validation import validate_safe_path
155
+
156
+ path = Path(template_path)
157
+ if path.is_absolute():
158
+ # Absolute paths allowed but not validated against base_dir
159
+ template_path_ = path
160
+ else:
161
+ # Relative paths validated to prevent directory traversal
162
+ template_path_ = validate_safe_path(
163
+ self.base_dir / template_path,
164
+ base_dir=self.base_dir,
165
+ field_name="template_path",
166
+ )
167
+ template_path_text = template_path_.read_text()
168
+ template = Template(template_path_text)
169
+ return template.render(context or {})
170
+
171
+
172
+ __all__ = ["PromptRenderer"]
@@ -1,15 +1,47 @@
1
- """Shared response helpers for OpenAI interactions."""
1
+ """Response handling for OpenAI API interactions.
2
+
3
+ This module provides comprehensive support for managing OpenAI API responses,
4
+ including message handling, tool execution, vector store attachments, and
5
+ structured output parsing. It serves as the foundation for building
6
+ sophisticated AI agents with persistent conversation state.
7
+
8
+ Classes
9
+ -------
10
+ BaseResponse
11
+ Core response manager for OpenAI interactions with structured outputs.
12
+ ResponseConfiguration
13
+ Immutable configuration for defining request/response structures.
14
+ ResponseMessage
15
+ Single message exchanged with the OpenAI client.
16
+ ResponseMessages
17
+ Collection of messages in a response conversation.
18
+ ResponseToolCall
19
+ Container for tool call data and formatting.
20
+
21
+ Functions
22
+ ---------
23
+ run_sync
24
+ Execute a response workflow synchronously with resource cleanup.
25
+ run_async
26
+ Execute a response workflow asynchronously with resource cleanup.
27
+ run_streamed
28
+ Execute a response workflow and return the asynchronous result.
29
+ attach_vector_store
30
+ Attach vector stores to a response's file_search tool.
31
+ """
2
32
 
3
33
  from __future__ import annotations
4
34
 
5
35
  from .base import BaseResponse
36
+ from .config import ResponseConfiguration
6
37
  from .messages import ResponseMessage, ResponseMessages
7
- from .runner import run_sync, run_async, run_streamed
8
- from .vector_store import attach_vector_store
38
+ from .runner import run_async, run_streamed, run_sync
9
39
  from .tool_call import ResponseToolCall
40
+ from .vector_store import attach_vector_store
10
41
 
11
42
  __all__ = [
12
43
  "BaseResponse",
44
+ "ResponseConfiguration",
13
45
  "ResponseMessage",
14
46
  "ResponseMessages",
15
47
  "run_sync",