openai-sdk-helpers 0.0.9__tar.gz → 0.1.0__tar.gz

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 (70) hide show
  1. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/PKG-INFO +1 -1
  2. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/pyproject.toml +1 -1
  3. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/__init__.py +24 -0
  4. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/config.py +27 -29
  5. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/prompt/base.py +55 -5
  6. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/__init__.py +5 -2
  7. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/config.py +142 -0
  8. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/tool_call.py +15 -4
  9. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/__init__.py +3 -0
  10. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/plan/__init__.py +15 -1
  11. openai_sdk_helpers-0.1.0/src/openai_sdk_helpers/structure/plan/helpers.py +172 -0
  12. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/plan/plan.py +13 -9
  13. openai_sdk_helpers-0.1.0/src/openai_sdk_helpers/structure/plan/types.py +15 -0
  14. openai_sdk_helpers-0.1.0/src/openai_sdk_helpers/tools.py +193 -0
  15. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/utils/__init__.py +6 -2
  16. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/utils/core.py +128 -0
  17. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/.gitignore +0 -0
  18. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/LICENSE +0 -0
  19. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/README.md +0 -0
  20. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/__init__.py +0 -0
  21. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/base.py +0 -0
  22. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/config.py +0 -0
  23. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/coordination.py +0 -0
  24. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/prompt_utils.py +0 -0
  25. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/runner.py +0 -0
  26. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/search/__init__.py +0 -0
  27. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/search/base.py +0 -0
  28. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/search/vector.py +0 -0
  29. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/search/web.py +0 -0
  30. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/summarizer.py +0 -0
  31. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/translator.py +0 -0
  32. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/utils.py +0 -0
  33. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/agent/validation.py +0 -0
  34. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/async_utils.py +0 -0
  35. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/context_manager.py +0 -0
  36. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/enums/__init__.py +0 -0
  37. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/enums/base.py +0 -0
  38. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/environment.py +0 -0
  39. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/errors.py +0 -0
  40. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/logging_config.py +0 -0
  41. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/prompt/__init__.py +0 -0
  42. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/prompt/summarizer.jinja +0 -0
  43. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/prompt/translator.jinja +0 -0
  44. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/prompt/validator.jinja +0 -0
  45. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/py.typed +0 -0
  46. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/base.py +0 -0
  47. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/messages.py +0 -0
  48. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/runner.py +0 -0
  49. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/response/vector_store.py +0 -0
  50. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/retry.py +0 -0
  51. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/streamlit_app/__init__.py +0 -0
  52. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/streamlit_app/app.py +0 -0
  53. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/streamlit_app/config.py +0 -0
  54. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/streamlit_app/streamlit_web_search.py +0 -0
  55. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/agent_blueprint.py +0 -0
  56. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/base.py +0 -0
  57. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/plan/enum.py +0 -0
  58. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/plan/task.py +0 -0
  59. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/prompt.py +0 -0
  60. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/responses.py +0 -0
  61. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/summary.py +0 -0
  62. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/validation.py +0 -0
  63. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/vector_search.py +0 -0
  64. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/structure/web_search.py +0 -0
  65. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/types.py +0 -0
  66. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/validation.py +0 -0
  67. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/vector_storage/__init__.py +0 -0
  68. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/vector_storage/cleanup.py +0 -0
  69. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/vector_storage/storage.py +0 -0
  70. {openai_sdk_helpers-0.0.9 → openai_sdk_helpers-0.1.0}/src/openai_sdk_helpers/vector_storage/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openai-sdk-helpers
3
- Version: 0.0.9
3
+ Version: 0.1.0
4
4
  Summary: Composable helpers for OpenAI SDK agents, prompts, and storage
5
5
  Author: openai-sdk-helpers maintainers
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openai-sdk-helpers"
3
- version = "0.0.9"
3
+ version = "0.1.0"
4
4
  requires-python = ">=3.10"
5
5
  readme = "README.md"
6
6
  description = "Composable helpers for OpenAI SDK agents, prompts, and storage"
@@ -46,6 +46,9 @@ from .structure import (
46
46
  ExtendedSummaryStructure,
47
47
  ValidationResultStructure,
48
48
  AgentBlueprint,
49
+ create_plan,
50
+ execute_task,
51
+ execute_plan,
49
52
  )
50
53
  from .prompt import PromptRenderer
51
54
  from .config import OpenAISettings
@@ -66,8 +69,19 @@ from .response import (
66
69
  ResponseMessage,
67
70
  ResponseMessages,
68
71
  ResponseToolCall,
72
+ ResponseConfiguration,
73
+ ResponseRegistry,
74
+ get_default_registry,
75
+ parse_tool_arguments,
69
76
  attach_vector_store,
70
77
  )
78
+ from .tools import (
79
+ serialize_tool_result,
80
+ tool_handler_factory,
81
+ )
82
+ from .utils import (
83
+ build_openai_settings,
84
+ )
71
85
 
72
86
  __all__ = [
73
87
  # Async utilities
@@ -133,5 +147,15 @@ __all__ = [
133
147
  "ResponseMessage",
134
148
  "ResponseMessages",
135
149
  "ResponseToolCall",
150
+ "ResponseConfiguration",
151
+ "ResponseRegistry",
152
+ "get_default_registry",
153
+ "parse_tool_arguments",
136
154
  "attach_vector_store",
155
+ "serialize_tool_result",
156
+ "tool_handler_factory",
157
+ "build_openai_settings",
158
+ "create_plan",
159
+ "execute_task",
160
+ "execute_plan",
137
161
  ]
@@ -141,39 +141,37 @@ class OpenAISettings(BaseModel):
141
141
  ValueError
142
142
  If OPENAI_API_KEY is not found in environment or dotenv file.
143
143
  """
144
- env_file_values: Mapping[str, str | None]
144
+ env_file_values: Mapping[str, str | None] = {}
145
145
  if dotenv_path is not None:
146
146
  env_file_values = dotenv_values(dotenv_path)
147
- else:
148
- env_file_values = dotenv_values()
149
-
150
- timeout_raw = (
151
- overrides.get("timeout")
152
- or env_file_values.get("OPENAI_TIMEOUT")
153
- or os.getenv("OPENAI_TIMEOUT")
154
- )
155
- max_retries_raw = (
156
- overrides.get("max_retries")
157
- or env_file_values.get("OPENAI_MAX_RETRIES")
158
- or os.getenv("OPENAI_MAX_RETRIES")
159
- )
147
+
148
+ def first_non_none(*candidates: Any) -> Any:
149
+ for candidate in candidates:
150
+ if candidate is not None:
151
+ return candidate
152
+ return None
153
+
154
+ def resolve_value(override_key: str, env_var: str) -> Any:
155
+ if dotenv_path is not None:
156
+ return first_non_none(
157
+ overrides.get(override_key),
158
+ env_file_values.get(env_var),
159
+ os.getenv(env_var),
160
+ )
161
+ return first_non_none(
162
+ overrides.get(override_key),
163
+ os.getenv(env_var),
164
+ )
165
+
166
+ timeout_raw = resolve_value("timeout", "OPENAI_TIMEOUT")
167
+ max_retries_raw = resolve_value("max_retries", "OPENAI_MAX_RETRIES")
160
168
 
161
169
  values: dict[str, Any] = {
162
- "api_key": overrides.get("api_key")
163
- or env_file_values.get("OPENAI_API_KEY")
164
- or os.getenv("OPENAI_API_KEY"),
165
- "org_id": overrides.get("org_id")
166
- or env_file_values.get("OPENAI_ORG_ID")
167
- or os.getenv("OPENAI_ORG_ID"),
168
- "project_id": overrides.get("project_id")
169
- or env_file_values.get("OPENAI_PROJECT_ID")
170
- or os.getenv("OPENAI_PROJECT_ID"),
171
- "base_url": overrides.get("base_url")
172
- or env_file_values.get("OPENAI_BASE_URL")
173
- or os.getenv("OPENAI_BASE_URL"),
174
- "default_model": overrides.get("default_model")
175
- or env_file_values.get("OPENAI_MODEL")
176
- or os.getenv("OPENAI_MODEL"),
170
+ "api_key": resolve_value("api_key", "OPENAI_API_KEY"),
171
+ "org_id": resolve_value("org_id", "OPENAI_ORG_ID"),
172
+ "project_id": resolve_value("project_id", "OPENAI_PROJECT_ID"),
173
+ "base_url": resolve_value("base_url", "OPENAI_BASE_URL"),
174
+ "default_model": resolve_value("default_model", "OPENAI_MODEL"),
177
175
  "timeout": coerce_optional_float(timeout_raw),
178
176
  "max_retries": coerce_optional_int(max_retries_raw),
179
177
  "extra_client_kwargs": coerce_dict(overrides.get("extra_client_kwargs")),
@@ -2,12 +2,14 @@
2
2
 
3
3
  This module provides the PromptRenderer class for loading and rendering
4
4
  Jinja2 templates with context variables. Templates can be loaded from a
5
- specified directory or by absolute path.
5
+ specified directory or by absolute path. Includes template caching for
6
+ improved performance.
6
7
  """
7
8
 
8
9
  from __future__ import annotations
9
10
 
10
11
  import warnings
12
+ from functools import lru_cache
11
13
  from pathlib import Path
12
14
  from typing import Any
13
15
 
@@ -23,7 +25,8 @@ class PromptRenderer:
23
25
 
24
26
  Loads and renders Jinja2 templates from a base directory or by absolute
25
27
  path. The renderer supports variable substitution, template inheritance,
26
- and all standard Jinja2 features for creating dynamic prompts.
28
+ and all standard Jinja2 features for creating dynamic prompts. Templates
29
+ are cached using LRU cache for improved performance on repeated renders.
27
30
 
28
31
  Templates are loaded from a base directory (defaulting to the built-in
29
32
  prompt package directory) or can be specified with absolute paths.
@@ -38,6 +41,8 @@ class PromptRenderer:
38
41
  -------
39
42
  render(template_path, context=None)
40
43
  Render a Jinja2 template with the given context variables.
44
+ clear_cache()
45
+ Clear the template compilation cache.
41
46
 
42
47
  Examples
43
48
  --------
@@ -103,12 +108,30 @@ class PromptRenderer:
103
108
  autoescape=False, # Prompts are plain text
104
109
  )
105
110
 
111
+ @lru_cache(maxsize=128)
112
+ def _compile_template(self, template_path_str: str) -> Template:
113
+ """Compile a template by path with LRU caching.
114
+
115
+ Parameters
116
+ ----------
117
+ template_path_str : str
118
+ Absolute path to the template file.
119
+
120
+ Returns
121
+ -------
122
+ Template
123
+ Compiled Jinja2 template ready for rendering.
124
+ """
125
+ template_text = Path(template_path_str).read_text()
126
+ return Template(template_text)
127
+
106
128
  def render(self, template_path: str, context: dict[str, Any] | None = None) -> str:
107
129
  """Render a Jinja2 template with the given context variables.
108
130
 
109
131
  Loads the template from either an absolute path or a path relative
110
132
  to the base directory. The template is rendered with the provided
111
- context dictionary using Jinja2's template engine.
133
+ context dictionary using Jinja2's template engine. Templates are
134
+ cached for improved performance on repeated renders.
112
135
 
113
136
  For security, relative paths are validated to prevent path traversal
114
137
  attacks. Absolute paths are allowed but should be used with caution
@@ -135,6 +158,8 @@ class PromptRenderer:
135
158
  InputValidationError
136
159
  If the path contains suspicious patterns or attempts to escape
137
160
  the base directory.
161
+ TemplateNotFound
162
+ If the template cannot be loaded by Jinja2.
138
163
 
139
164
  Examples
140
165
  --------
@@ -164,9 +189,34 @@ class PromptRenderer:
164
189
  base_dir=self.base_dir,
165
190
  field_name="template_path",
166
191
  )
167
- template_path_text = template_path_.read_text()
168
- template = Template(template_path_text)
192
+
193
+ # Check if template exists and provide clear error message
194
+ if not template_path_.exists():
195
+ raise FileNotFoundError(
196
+ f"Template not found: {template_path_}. "
197
+ f"Ensure the template exists in {self.base_dir} or provide an absolute path."
198
+ )
199
+
200
+ # Cache-compile template by path (not by content)
201
+ template = self._compile_template(str(template_path_))
169
202
  return template.render(context or {})
170
203
 
204
+ def clear_cache(self) -> None:
205
+ """Clear the template compilation cache.
206
+
207
+ Useful when templates are modified during runtime and need to be
208
+ reloaded. Call this method to force re-compilation of all templates
209
+ on next render.
210
+
211
+ Examples
212
+ --------
213
+ >>> renderer = PromptRenderer()
214
+ >>> renderer.render("template.jinja", {}) # Compiles and caches
215
+ >>> # ... modify template.jinja ...
216
+ >>> renderer.clear_cache() # Clear cache
217
+ >>> renderer.render("template.jinja", {}) # Re-compiles
218
+ """
219
+ self._compile_template.cache_clear()
220
+
171
221
 
172
222
  __all__ = ["PromptRenderer"]
@@ -33,20 +33,23 @@ attach_vector_store
33
33
  from __future__ import annotations
34
34
 
35
35
  from .base import BaseResponse
36
- from .config import ResponseConfiguration
36
+ from .config import ResponseConfiguration, ResponseRegistry, get_default_registry
37
37
  from .messages import ResponseMessage, ResponseMessages
38
38
  from .runner import run_async, run_streamed, run_sync
39
- from .tool_call import ResponseToolCall
39
+ from .tool_call import ResponseToolCall, parse_tool_arguments
40
40
  from .vector_store import attach_vector_store
41
41
 
42
42
  __all__ = [
43
43
  "BaseResponse",
44
44
  "ResponseConfiguration",
45
+ "ResponseRegistry",
46
+ "get_default_registry",
45
47
  "ResponseMessage",
46
48
  "ResponseMessages",
47
49
  "run_sync",
48
50
  "run_async",
49
51
  "run_streamed",
50
52
  "ResponseToolCall",
53
+ "parse_tool_arguments",
51
54
  "attach_vector_store",
52
55
  ]
@@ -15,6 +15,148 @@ TIn = TypeVar("TIn", bound="BaseStructure")
15
15
  TOut = TypeVar("TOut", bound="BaseStructure")
16
16
 
17
17
 
18
+ class ResponseRegistry:
19
+ """Registry for managing ResponseConfiguration instances.
20
+
21
+ Provides centralized storage and retrieval of response configurations,
22
+ enabling reusable response specs across the application. Configurations
23
+ are stored by name and can be retrieved or listed as needed.
24
+
25
+ Methods
26
+ -------
27
+ register(config)
28
+ Add a ResponseConfiguration to the registry.
29
+ get(name)
30
+ Retrieve a configuration by name.
31
+ list_names()
32
+ Return all registered configuration names.
33
+ clear()
34
+ Remove all registered configurations.
35
+
36
+ Examples
37
+ --------
38
+ >>> registry = ResponseRegistry()
39
+ >>> config = ResponseConfiguration(
40
+ ... name="test",
41
+ ... instructions="Test instructions",
42
+ ... tools=None,
43
+ ... input_structure=None,
44
+ ... output_structure=None
45
+ ... )
46
+ >>> registry.register(config)
47
+ >>> retrieved = registry.get("test")
48
+ >>> retrieved.name
49
+ 'test'
50
+ """
51
+
52
+ def __init__(self) -> None:
53
+ """Initialize an empty registry."""
54
+ self._configs: dict[str, ResponseConfiguration] = {}
55
+
56
+ def register(self, config: ResponseConfiguration) -> None:
57
+ """Add a ResponseConfiguration to the registry.
58
+
59
+ Parameters
60
+ ----------
61
+ config : ResponseConfiguration
62
+ Configuration to register.
63
+
64
+ Raises
65
+ ------
66
+ ValueError
67
+ If a configuration with the same name is already registered.
68
+
69
+ Examples
70
+ --------
71
+ >>> registry = ResponseRegistry()
72
+ >>> config = ResponseConfiguration(...)
73
+ >>> registry.register(config)
74
+ """
75
+ if config.name in self._configs:
76
+ raise ValueError(
77
+ f"Configuration '{config.name}' is already registered. "
78
+ "Use a unique name or clear the registry first."
79
+ )
80
+ self._configs[config.name] = config
81
+
82
+ def get(self, name: str) -> ResponseConfiguration:
83
+ """Retrieve a configuration by name.
84
+
85
+ Parameters
86
+ ----------
87
+ name : str
88
+ Configuration name to look up.
89
+
90
+ Returns
91
+ -------
92
+ ResponseConfiguration
93
+ The registered configuration.
94
+
95
+ Raises
96
+ ------
97
+ KeyError
98
+ If no configuration with the given name exists.
99
+
100
+ Examples
101
+ --------
102
+ >>> registry = ResponseRegistry()
103
+ >>> config = registry.get("test")
104
+ """
105
+ if name not in self._configs:
106
+ raise KeyError(
107
+ f"No configuration named '{name}' found. "
108
+ f"Available: {list(self._configs.keys())}"
109
+ )
110
+ return self._configs[name]
111
+
112
+ def list_names(self) -> list[str]:
113
+ """Return all registered configuration names.
114
+
115
+ Returns
116
+ -------
117
+ list[str]
118
+ Sorted list of configuration names.
119
+
120
+ Examples
121
+ --------
122
+ >>> registry = ResponseRegistry()
123
+ >>> registry.list_names()
124
+ []
125
+ """
126
+ return sorted(self._configs.keys())
127
+
128
+ def clear(self) -> None:
129
+ """Remove all registered configurations.
130
+
131
+ Examples
132
+ --------
133
+ >>> registry = ResponseRegistry()
134
+ >>> registry.clear()
135
+ """
136
+ self._configs.clear()
137
+
138
+
139
+ # Global default registry instance
140
+ _default_registry = ResponseRegistry()
141
+
142
+
143
+ def get_default_registry() -> ResponseRegistry:
144
+ """Return the global default registry instance.
145
+
146
+ Returns
147
+ -------
148
+ ResponseRegistry
149
+ Singleton registry for application-wide configuration storage.
150
+
151
+ Examples
152
+ --------
153
+ >>> registry = get_default_registry()
154
+ >>> config = ResponseConfiguration(...)
155
+ >>> registry.register(config)
156
+ """
157
+ return _default_registry
158
+
159
+
18
160
  @dataclass(frozen=True, slots=True)
19
161
  class ResponseConfiguration(Generic[TIn, TOut]):
20
162
  """
@@ -94,17 +94,20 @@ class ResponseToolCall:
94
94
  return function_call, function_call_output
95
95
 
96
96
 
97
- def parse_tool_arguments(arguments: str) -> dict:
97
+ def parse_tool_arguments(arguments: str, tool_name: str) -> dict:
98
98
  """Parse tool call arguments with fallback for malformed JSON.
99
99
 
100
100
  Attempts to parse arguments as JSON first, then falls back to
101
101
  ast.literal_eval for cases where the OpenAI API returns minor
102
102
  formatting issues like single quotes instead of double quotes.
103
+ Provides clear error context including tool name and raw payload.
103
104
 
104
105
  Parameters
105
106
  ----------
106
107
  arguments : str
107
108
  Raw argument string from a tool call, expected to be JSON.
109
+ tool_name : str
110
+ Tool name for improved error context (required).
108
111
 
109
112
  Returns
110
113
  -------
@@ -115,13 +118,14 @@ def parse_tool_arguments(arguments: str) -> dict:
115
118
  ------
116
119
  ValueError
117
120
  If the arguments cannot be parsed as valid JSON or Python literal.
121
+ Error message includes tool name and payload excerpt for debugging.
118
122
 
119
123
  Examples
120
124
  --------
121
- >>> parse_tool_arguments('{"key": "value"}')
125
+ >>> parse_tool_arguments('{"key": "value"}', tool_name="search")
122
126
  {'key': 'value'}
123
127
 
124
- >>> parse_tool_arguments("{'key': 'value'}")
128
+ >>> parse_tool_arguments("{'key': 'value'}", tool_name="search")
125
129
  {'key': 'value'}
126
130
  """
127
131
  try:
@@ -130,4 +134,11 @@ def parse_tool_arguments(arguments: str) -> dict:
130
134
  try:
131
135
  return ast.literal_eval(arguments)
132
136
  except Exception as exc: # noqa: BLE001
133
- 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
@@ -86,6 +86,9 @@ __all__ = [
86
86
  "AgentEnum",
87
87
  "TaskStructure",
88
88
  "PlanStructure",
89
+ "create_plan",
90
+ "execute_task",
91
+ "execute_plan",
89
92
  "PromptStructure",
90
93
  "SummaryTopic",
91
94
  "SummaryStructure",
@@ -2,7 +2,8 @@
2
2
 
3
3
  This package provides Pydantic models for representing agent execution plans,
4
4
  including task definitions, agent type enumerations, and plan structures with
5
- sequential execution support.
5
+ sequential execution support. Also includes helper functions for creating and
6
+ executing plans.
6
7
 
7
8
  Classes
8
9
  -------
@@ -12,6 +13,15 @@ TaskStructure
12
13
  Individual agent task with status tracking and results.
13
14
  AgentEnum
14
15
  Enumeration of available agent types.
16
+
17
+ Functions
18
+ ---------
19
+ create_plan
20
+ Create a PlanStructure from a sequence of tasks.
21
+ execute_task
22
+ Execute a single task with an agent callable.
23
+ execute_plan
24
+ Execute a complete plan using registered agent callables.
15
25
  """
16
26
 
17
27
  from __future__ import annotations
@@ -19,9 +29,13 @@ from __future__ import annotations
19
29
  from .plan import PlanStructure
20
30
  from .task import TaskStructure
21
31
  from .enum import AgentEnum
32
+ from .helpers import create_plan, execute_task, execute_plan
22
33
 
23
34
  __all__ = [
24
35
  "PlanStructure",
25
36
  "TaskStructure",
26
37
  "AgentEnum",
38
+ "create_plan",
39
+ "execute_task",
40
+ "execute_plan",
27
41
  ]