openai-sdk-helpers 0.0.8__py3-none-any.whl → 0.1.0__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 (67) hide show
  1. openai_sdk_helpers/__init__.py +90 -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 +101 -65
  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 +222 -0
  25. openai_sdk_helpers/response/__init__.py +38 -3
  26. openai_sdk_helpers/response/base.py +363 -210
  27. openai_sdk_helpers/response/config.py +318 -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 +62 -27
  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 +72 -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 +29 -1
  41. openai_sdk_helpers/structure/plan/enum.py +41 -5
  42. openai_sdk_helpers/structure/plan/helpers.py +172 -0
  43. openai_sdk_helpers/structure/plan/plan.py +109 -49
  44. openai_sdk_helpers/structure/plan/task.py +38 -6
  45. openai_sdk_helpers/structure/plan/types.py +15 -0
  46. openai_sdk_helpers/structure/prompt.py +21 -2
  47. openai_sdk_helpers/structure/responses.py +52 -11
  48. openai_sdk_helpers/structure/summary.py +55 -7
  49. openai_sdk_helpers/structure/validation.py +34 -6
  50. openai_sdk_helpers/structure/vector_search.py +132 -18
  51. openai_sdk_helpers/structure/web_search.py +125 -13
  52. openai_sdk_helpers/tools.py +193 -0
  53. openai_sdk_helpers/types.py +57 -0
  54. openai_sdk_helpers/utils/__init__.py +34 -1
  55. openai_sdk_helpers/utils/core.py +296 -34
  56. openai_sdk_helpers/validation.py +302 -0
  57. openai_sdk_helpers/vector_storage/__init__.py +21 -1
  58. openai_sdk_helpers/vector_storage/cleanup.py +25 -13
  59. openai_sdk_helpers/vector_storage/storage.py +123 -64
  60. openai_sdk_helpers/vector_storage/types.py +20 -19
  61. openai_sdk_helpers-0.1.0.dist-info/METADATA +550 -0
  62. openai_sdk_helpers-0.1.0.dist-info/RECORD +69 -0
  63. openai_sdk_helpers/streamlit_app/configuration.py +0 -324
  64. openai_sdk_helpers-0.0.8.dist-info/METADATA +0 -194
  65. openai_sdk_helpers-0.0.8.dist-info/RECORD +0 -55
  66. {openai_sdk_helpers-0.0.8.dist-info → openai_sdk_helpers-0.1.0.dist-info}/WHEEL +0 -0
  67. {openai_sdk_helpers-0.0.8.dist-info → openai_sdk_helpers-0.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,15 @@
1
- """Tool call representation for shared responses."""
1
+ """Tool call representation and argument parsing.
2
+
3
+ This module provides data structures and utilities for managing tool calls
4
+ in OpenAI response conversations, including conversion to OpenAI API formats
5
+ and robust argument parsing.
6
+ """
2
7
 
3
8
  from __future__ import annotations
4
9
 
5
- from dataclasses import dataclass
6
- from typing import Tuple
7
- import json
8
10
  import ast
11
+ import json
12
+ from dataclasses import dataclass
9
13
 
10
14
  from openai.types.responses.response_function_tool_call_param import (
11
15
  ResponseFunctionToolCallParam,
@@ -15,23 +19,27 @@ from openai.types.responses.response_input_param import FunctionCallOutput
15
19
 
16
20
  @dataclass
17
21
  class ResponseToolCall:
18
- """Container for tool call data used in a conversation.
22
+ """Container for tool call data in a conversation.
23
+
24
+ Stores the complete information about a tool invocation including
25
+ the call identifier, tool name, input arguments, and execution output.
26
+ Can convert to OpenAI API format for use in subsequent requests.
19
27
 
20
28
  Attributes
21
29
  ----------
22
30
  call_id : str
23
- Identifier of the tool call.
31
+ Unique identifier for this tool call.
24
32
  name : str
25
- Name of the tool invoked.
33
+ Name of the tool that was invoked.
26
34
  arguments : str
27
- JSON string with the arguments passed to the tool.
35
+ JSON string containing the arguments passed to the tool.
28
36
  output : str
29
- JSON string representing the result produced by the tool.
37
+ JSON string representing the result produced by the tool handler.
30
38
 
31
39
  Methods
32
40
  -------
33
41
  to_response_input_item_param()
34
- Convert stored data into OpenAI tool call objects.
42
+ Convert to OpenAI API tool call format.
35
43
  """
36
44
 
37
45
  call_id: str
@@ -41,14 +49,28 @@ class ResponseToolCall:
41
49
 
42
50
  def to_response_input_item_param(
43
51
  self,
44
- ) -> Tuple[ResponseFunctionToolCallParam, FunctionCallOutput]:
45
- """Convert stored data into OpenAI tool call objects.
52
+ ) -> tuple[ResponseFunctionToolCallParam, FunctionCallOutput]:
53
+ """Convert stored data into OpenAI API tool call objects.
54
+
55
+ Creates the function call parameter and corresponding output object
56
+ required by the OpenAI API for tool interaction.
46
57
 
47
58
  Returns
48
59
  -------
49
60
  tuple[ResponseFunctionToolCallParam, FunctionCallOutput]
50
- The function call object and the corresponding output object
51
- suitable for inclusion in an OpenAI request.
61
+ A two-element tuple containing:
62
+ - ResponseFunctionToolCallParam: The function call representation
63
+ - FunctionCallOutput: The function output representation
64
+
65
+ Examples
66
+ --------
67
+ >>> tool_call = ResponseToolCall(
68
+ ... call_id="call_123",
69
+ ... name="search",
70
+ ... arguments='{"query": "test"}',
71
+ ... output='{"results": []}'
72
+ ... )
73
+ >>> func_call, func_output = tool_call.to_response_input_item_param()
52
74
  """
53
75
  from typing import cast
54
76
 
@@ -72,33 +94,39 @@ class ResponseToolCall:
72
94
  return function_call, function_call_output
73
95
 
74
96
 
75
- def parse_tool_arguments(arguments: str) -> dict:
76
- """Parse tool call arguments which may not be valid JSON.
97
+ def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
98
+ """Parse tool call arguments with fallback for malformed JSON.
77
99
 
78
- The OpenAI API is expected to return well-formed JSON for tool arguments,
79
- but minor formatting issues (such as the use of single quotes) can occur.
80
- This helper first tries ``json.loads`` and falls back to
81
- ``ast.literal_eval`` for simple cases.
100
+ Attempts to parse arguments as JSON first, then falls back to
101
+ ast.literal_eval for cases where the OpenAI API returns minor
102
+ formatting issues like single quotes instead of double quotes.
103
+ Provides clear error context including tool name and raw payload.
82
104
 
83
105
  Parameters
84
106
  ----------
85
- arguments
86
- Raw argument string from the tool call.
107
+ arguments : str
108
+ Raw argument string from a tool call, expected to be JSON.
109
+ tool_name : str
110
+ Tool name for improved error context (required).
87
111
 
88
112
  Returns
89
113
  -------
90
114
  dict
91
- Parsed dictionary of arguments.
115
+ Parsed dictionary of tool arguments.
92
116
 
93
117
  Raises
94
118
  ------
95
119
  ValueError
96
- If the arguments cannot be parsed as JSON.
120
+ If the arguments cannot be parsed as valid JSON or Python literal.
121
+ Error message includes tool name and payload excerpt for debugging.
97
122
 
98
123
  Examples
99
124
  --------
100
- >>> parse_tool_arguments('{"key": "value"}')["key"]
101
- 'value'
125
+ >>> parse_tool_arguments('{"key": "value"}', tool_name="search")
126
+ {'key': 'value'}
127
+
128
+ >>> parse_tool_arguments("{'key': 'value'}", tool_name="search")
129
+ {'key': 'value'}
102
130
  """
103
131
  try:
104
132
  return json.loads(arguments)
@@ -106,4 +134,11 @@ def parse_tool_arguments(arguments: str) -> dict:
106
134
  try:
107
135
  return ast.literal_eval(arguments)
108
136
  except Exception as exc: # noqa: BLE001
109
- raise ValueError(f"Invalid JSON arguments: {arguments}") from exc
137
+ # Build informative error message with context
138
+ payload_preview = (
139
+ arguments[:100] + "..." if len(arguments) > 100 else arguments
140
+ )
141
+ raise ValueError(
142
+ f"Failed to parse tool arguments for tool '{tool_name}'. "
143
+ f"Raw payload: {payload_preview}"
144
+ ) from exc
@@ -1,8 +1,12 @@
1
- """Helpers for attaching vector stores to responses."""
1
+ """Vector store attachment utilities for responses.
2
+
3
+ This module provides functions for attaching named vector stores to response
4
+ instances, enabling file search capabilities through the OpenAI API.
5
+ """
2
6
 
3
7
  from __future__ import annotations
4
8
 
5
- from typing import Any, Optional, Sequence
9
+ from typing import Any, Sequence
6
10
 
7
11
  from openai import OpenAI
8
12
 
@@ -13,30 +17,39 @@ from .base import BaseResponse
13
17
  def attach_vector_store(
14
18
  response: BaseResponse[Any],
15
19
  vector_stores: str | Sequence[str],
16
- api_key: Optional[str] = None,
20
+ api_key: str | None = None,
17
21
  ) -> list[str]:
18
- """Attach vector stores to a response ``file_search`` tool.
22
+ """Attach named vector stores to a response's file_search tool.
23
+
24
+ Resolves vector store names to IDs via the OpenAI API and configures
25
+ the response's file_search tool to use them. Creates the file_search
26
+ tool if it doesn't exist, or updates it to include additional stores.
19
27
 
20
28
  Parameters
21
29
  ----------
22
- response
23
- Response instance whose tool configuration is updated.
24
- vector_stores
25
- Single vector store name or a sequence of names to attach.
26
- api_key : str, optional
27
- API key used when the response does not already have a client. Default
28
- ``None``.
30
+ response : BaseResponse[Any]
31
+ Response instance whose tool configuration will be updated.
32
+ vector_stores : str or Sequence[str]
33
+ Single vector store name or sequence of names to attach.
34
+ api_key : str or None, default None
35
+ API key for OpenAI client. If None, uses the response's client.
29
36
 
30
37
  Returns
31
38
  -------
32
39
  list[str]
33
- Ordered list of vector store IDs applied to the ``file_search`` tool.
40
+ Ordered list of vector store IDs attached to the file_search tool.
34
41
 
35
42
  Raises
36
43
  ------
37
44
  ValueError
38
- If a vector store cannot be resolved or no API key is available when
39
- required.
45
+ If a vector store name cannot be resolved to an ID.
46
+ If no API key is available and the response has no client.
47
+
48
+ Examples
49
+ --------
50
+ >>> from openai_sdk_helpers.response import attach_vector_store
51
+ >>> ids = attach_vector_store(response, "knowledge_base")
52
+ >>> ids = attach_vector_store(response, ["docs", "kb"], api_key="sk-...")
40
53
  """
41
54
  requested_stores = ensure_list(vector_stores)
42
55
 
@@ -0,0 +1,175 @@
1
+ """Retry decorators with exponential backoff for API operations.
2
+
3
+ Provides decorators for retrying async and sync functions with
4
+ exponential backoff and jitter when rate limiting or transient
5
+ errors occur.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import random
11
+ import time
12
+ from functools import wraps
13
+ from typing import Any, Callable, ParamSpec, TypeVar
14
+
15
+ from openai import APIError, RateLimitError
16
+
17
+ from openai_sdk_helpers.errors import AsyncExecutionError
18
+ from openai_sdk_helpers.utils.core import log
19
+
20
+ P = ParamSpec("P")
21
+ T = TypeVar("T")
22
+
23
+ # Default retry configuration constants
24
+ DEFAULT_MAX_RETRIES = 3
25
+ DEFAULT_BASE_DELAY = 1.0
26
+ DEFAULT_MAX_DELAY = 60.0
27
+
28
+ # HTTP status codes for transient errors
29
+ TRANSIENT_HTTP_STATUS_CODES = frozenset({408, 429, 500, 502, 503})
30
+
31
+
32
+ def with_exponential_backoff(
33
+ max_retries: int = DEFAULT_MAX_RETRIES,
34
+ base_delay: float = DEFAULT_BASE_DELAY,
35
+ max_delay: float = DEFAULT_MAX_DELAY,
36
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
37
+ """Decorate functions with exponential backoff on transient errors.
38
+
39
+ Retries on RateLimitError or transient API errors (5xx, 408, 429).
40
+ Uses exponential backoff with jitter to avoid thundering herd.
41
+
42
+ Parameters
43
+ ----------
44
+ max_retries : int
45
+ Maximum number of retry attempts (total attempts = max_retries + 1).
46
+ Default is 3.
47
+ base_delay : float
48
+ Initial delay in seconds before first retry. Default is 1.0.
49
+ max_delay : float
50
+ Maximum delay in seconds between retries. Default is 60.0.
51
+
52
+ Returns
53
+ -------
54
+ Callable
55
+ Decorator function.
56
+
57
+ Examples
58
+ --------
59
+ >>> @with_exponential_backoff(max_retries=3, base_delay=1.0)
60
+ ... def call_api(query: str) -> str:
61
+ ... # API call that may fail with rate limiting
62
+ ... return client.call(query)
63
+ """
64
+
65
+ def decorator(func: Callable[P, T]) -> Callable[P, T]:
66
+ """Apply retry logic to function."""
67
+ if asyncio.iscoroutinefunction(func):
68
+
69
+ @wraps(func)
70
+ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
71
+ """Async wrapper with retry logic."""
72
+ last_exc: Exception | None = None
73
+ for attempt in range(max_retries + 1):
74
+ try:
75
+ return await func(*args, **kwargs)
76
+ except RateLimitError as exc:
77
+ last_exc = exc
78
+ if attempt >= max_retries:
79
+ raise
80
+ delay = min(
81
+ base_delay * (2**attempt) + random.uniform(0, 1),
82
+ max_delay,
83
+ )
84
+ log(
85
+ f"Rate limited on {func.__name__}, retrying in "
86
+ f"{delay:.2f}s (attempt {attempt + 1}/{max_retries + 1})",
87
+ level=logging.WARNING,
88
+ )
89
+ await asyncio.sleep(delay)
90
+ except APIError as exc:
91
+ last_exc = exc
92
+ status_code: int | None = getattr(exc, "status_code", None)
93
+ # Only retry on transient errors
94
+ if (
95
+ not status_code
96
+ or status_code not in TRANSIENT_HTTP_STATUS_CODES
97
+ ):
98
+ raise
99
+ if attempt >= max_retries:
100
+ raise
101
+ delay = min(
102
+ base_delay * (2**attempt),
103
+ max_delay,
104
+ )
105
+ log(
106
+ f"Transient API error on {func.__name__}: "
107
+ f"{status_code}, retrying in {delay:.2f}s "
108
+ f"(attempt {attempt + 1}/{max_retries + 1})",
109
+ level=logging.WARNING,
110
+ )
111
+ await asyncio.sleep(delay)
112
+
113
+ # Should never reach here, but handle edge case
114
+ if last_exc:
115
+ raise last_exc
116
+ raise AsyncExecutionError(
117
+ f"Unexpected state in {func.__name__} after retries"
118
+ )
119
+
120
+ return async_wrapper # type: ignore
121
+
122
+ @wraps(func)
123
+ def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
124
+ """Sync wrapper with retry logic."""
125
+ last_exc: Exception | None = None
126
+ for attempt in range(max_retries + 1):
127
+ try:
128
+ return func(*args, **kwargs)
129
+ except RateLimitError as exc:
130
+ last_exc = exc
131
+ if attempt >= max_retries:
132
+ raise
133
+ delay = min(
134
+ base_delay * (2**attempt) + random.uniform(0, 1),
135
+ max_delay,
136
+ )
137
+ log(
138
+ f"Rate limited on {func.__name__}, retrying in "
139
+ f"{delay:.2f}s (attempt {attempt + 1}/{max_retries + 1})",
140
+ level=logging.WARNING,
141
+ )
142
+ time.sleep(delay)
143
+ except APIError as exc:
144
+ last_exc = exc
145
+ status_code: int | None = getattr(exc, "status_code", None)
146
+ # Only retry on transient errors
147
+ if (
148
+ not status_code
149
+ or status_code not in TRANSIENT_HTTP_STATUS_CODES
150
+ ):
151
+ raise
152
+ if attempt >= max_retries:
153
+ raise
154
+ delay = min(
155
+ base_delay * (2**attempt),
156
+ max_delay,
157
+ )
158
+ log(
159
+ f"Transient API error on {func.__name__}: "
160
+ f"{status_code}, retrying in {delay:.2f}s "
161
+ f"(attempt {attempt + 1}/{max_retries + 1})",
162
+ level=logging.WARNING,
163
+ )
164
+ time.sleep(delay)
165
+
166
+ # Should never reach here, but handle edge case
167
+ if last_exc:
168
+ raise last_exc
169
+ raise AsyncExecutionError(
170
+ f"Unexpected state in {func.__name__} after retries"
171
+ )
172
+
173
+ return sync_wrapper # type: ignore
174
+
175
+ return decorator
@@ -1,6 +1,23 @@
1
- """Streamlit app utilities for the config-driven chat interface."""
1
+ """Streamlit application utilities for configuration-driven chat interfaces.
2
2
 
3
- from .configuration import (
3
+ This module provides configuration management and loading utilities for building
4
+ Streamlit-based chat applications powered by OpenAI response handlers. It enables
5
+ rapid deployment of conversational AI interfaces with minimal boilerplate.
6
+
7
+ Classes
8
+ -------
9
+ StreamlitAppConfig
10
+ Validated configuration for Streamlit chat applications.
11
+
12
+ Functions
13
+ ---------
14
+ load_app_config
15
+ Load and validate configuration from a Python module.
16
+ _load_configuration
17
+ Load configuration with user-friendly error handling for Streamlit UI.
18
+ """
19
+
20
+ from .config import (
4
21
  StreamlitAppConfig,
5
22
  _load_configuration,
6
23
  load_app_config,