openai-sdk-helpers 0.2.0__py3-none-any.whl → 0.3.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.
@@ -16,7 +16,7 @@ from ...structure.vector_search import (
16
16
  VectorSearchReportStructure,
17
17
  )
18
18
  from ...vector_storage import VectorStorage
19
- from ..config import AgentConfig
19
+ from ..config import AgentConfiguration
20
20
  from ..utils import run_coroutine_agent_sync
21
21
  from .base import SearchPlanner, SearchToolAgent, SearchWriter
22
22
 
@@ -46,16 +46,17 @@ class VectorSearchPlanner(SearchPlanner[VectorSearchPlanStructure]):
46
46
  """
47
47
  super().__init__(prompt_dir=prompt_dir, default_model=default_model)
48
48
 
49
- def _configure_agent(self) -> AgentConfig:
49
+ def _configure_agent(self) -> AgentConfiguration:
50
50
  """Return configuration for the vector planner agent.
51
51
 
52
52
  Returns
53
53
  -------
54
- AgentConfig
54
+ AgentConfiguration
55
55
  Configuration with name, description, and output type.
56
56
  """
57
- return AgentConfig(
57
+ return AgentConfiguration(
58
58
  name="vector_planner",
59
+ instructions="Agent instructions",
59
60
  description="Plan vector searches based on a user query.",
60
61
  output_type=VectorSearchPlanStructure,
61
62
  )
@@ -117,16 +118,17 @@ class VectorSearchTool(
117
118
  max_concurrent_searches=max_concurrent_searches,
118
119
  )
119
120
 
120
- def _configure_agent(self) -> AgentConfig:
121
+ def _configure_agent(self) -> AgentConfiguration:
121
122
  """Return configuration for the vector search tool agent.
122
123
 
123
124
  Returns
124
125
  -------
125
- AgentConfig
126
+ AgentConfiguration
126
127
  Configuration with name, description, and input type.
127
128
  """
128
- return AgentConfig(
129
+ return AgentConfiguration(
129
130
  name="vector_search",
131
+ instructions="Agent instructions",
130
132
  description="Perform vector searches based on a search plan.",
131
133
  input_type=VectorSearchPlanStructure,
132
134
  output_type=VectorSearchItemResultsStructure,
@@ -199,16 +201,17 @@ class VectorSearchWriter(SearchWriter[VectorSearchReportStructure]):
199
201
  """
200
202
  super().__init__(prompt_dir=prompt_dir, default_model=default_model)
201
203
 
202
- def _configure_agent(self) -> AgentConfig:
204
+ def _configure_agent(self) -> AgentConfiguration:
203
205
  """Return configuration for the vector writer agent.
204
206
 
205
207
  Returns
206
208
  -------
207
- AgentConfig
209
+ AgentConfiguration
208
210
  Configuration with name, description, and output type.
209
211
  """
210
- return AgentConfig(
212
+ return AgentConfiguration(
211
213
  name="vector_writer",
214
+ instructions="Agent instructions",
212
215
  description="Write a report based on search results.",
213
216
  output_type=VectorSearchReportStructure,
214
217
  )
@@ -16,7 +16,7 @@ from ...structure.web_search import (
16
16
  WebSearchPlanStructure,
17
17
  WebSearchReportStructure,
18
18
  )
19
- from ..config import AgentConfig
19
+ from ..config import AgentConfiguration
20
20
  from ..utils import run_coroutine_agent_sync
21
21
  from .base import SearchPlanner, SearchToolAgent, SearchWriter
22
22
 
@@ -46,16 +46,17 @@ class WebAgentPlanner(SearchPlanner[WebSearchPlanStructure]):
46
46
  """
47
47
  super().__init__(prompt_dir=prompt_dir, default_model=default_model)
48
48
 
49
- def _configure_agent(self) -> AgentConfig:
49
+ def _configure_agent(self) -> AgentConfiguration:
50
50
  """Return configuration for the web planner agent.
51
51
 
52
52
  Returns
53
53
  -------
54
- AgentConfig
54
+ AgentConfiguration
55
55
  Configuration with name, description, and output type.
56
56
  """
57
- return AgentConfig(
57
+ return AgentConfiguration(
58
58
  name="web_planner",
59
+ instructions="Agent instructions",
59
60
  description="Agent that plans web searches based on a user query.",
60
61
  output_type=WebSearchPlanStructure,
61
62
  )
@@ -94,16 +95,17 @@ class WebSearchToolAgent(
94
95
  max_concurrent_searches=MAX_CONCURRENT_SEARCHES,
95
96
  )
96
97
 
97
- def _configure_agent(self) -> AgentConfig:
98
+ def _configure_agent(self) -> AgentConfiguration:
98
99
  """Return configuration for the web search tool agent.
99
100
 
100
101
  Returns
101
102
  -------
102
- AgentConfig
103
+ AgentConfiguration
103
104
  Configuration with name, description, input type, and tools.
104
105
  """
105
- return AgentConfig(
106
+ return AgentConfiguration(
106
107
  name="web_search",
108
+ instructions="Agent instructions",
107
109
  description="Agent that performs web searches and summarizes results.",
108
110
  input_type=WebSearchPlanStructure,
109
111
  tools=[WebSearchTool()],
@@ -185,16 +187,17 @@ class WebAgentWriter(SearchWriter[WebSearchReportStructure]):
185
187
  """
186
188
  super().__init__(prompt_dir=prompt_dir, default_model=default_model)
187
189
 
188
- def _configure_agent(self) -> AgentConfig:
190
+ def _configure_agent(self) -> AgentConfiguration:
189
191
  """Return configuration for the web writer agent.
190
192
 
191
193
  Returns
192
194
  -------
193
- AgentConfig
195
+ AgentConfiguration
194
196
  Configuration with name, description, and output type.
195
197
  """
196
- return AgentConfig(
198
+ return AgentConfiguration(
197
199
  name="web_writer",
200
+ instructions="Agent instructions",
198
201
  description="Agent that writes a report based on web search results.",
199
202
  output_type=WebSearchReportStructure,
200
203
  )
@@ -6,12 +6,12 @@ from pathlib import Path
6
6
  from typing import Any, Dict, Optional
7
7
 
8
8
  from ..structure import SummaryStructure
9
- from .base import AgentBase
10
- from .config import AgentConfig
9
+ from .base import BaseAgent
10
+ from .config import AgentConfiguration
11
11
  from .prompt_utils import DEFAULT_PROMPT_DIR
12
12
 
13
13
 
14
- class SummarizerAgent(AgentBase):
14
+ class SummarizerAgent(BaseAgent):
15
15
  """Generate concise summaries from provided text.
16
16
 
17
17
  This agent uses OpenAI models to create structured summaries from longer-form
@@ -64,8 +64,9 @@ class SummarizerAgent(AgentBase):
64
64
  output_type : type, default=SummaryStructure
65
65
  Type describing the expected summary output.
66
66
  """
67
- config = AgentConfig(
67
+ config = AgentConfiguration(
68
68
  name="summarizer",
69
+ instructions="Agent instructions",
69
70
  description="Summarize passages into concise findings.",
70
71
  output_type=output_type,
71
72
  )
@@ -5,12 +5,12 @@ from __future__ import annotations
5
5
  from pathlib import Path
6
6
  from typing import Any, Dict, Optional
7
7
 
8
- from .base import AgentBase
9
- from .config import AgentConfig
8
+ from .base import BaseAgent
9
+ from .config import AgentConfiguration
10
10
  from .prompt_utils import DEFAULT_PROMPT_DIR
11
11
 
12
12
 
13
- class TranslatorAgent(AgentBase):
13
+ class TranslatorAgent(BaseAgent):
14
14
  """Translate text into a target language.
15
15
 
16
16
  This agent provides language translation services using OpenAI models,
@@ -63,8 +63,9 @@ class TranslatorAgent(AgentBase):
63
63
  default_model : str or None, default=None
64
64
  Fallback model identifier when not specified elsewhere.
65
65
  """
66
- config = AgentConfig(
66
+ config = AgentConfiguration(
67
67
  name="translator",
68
+ instructions="Agent instructions",
68
69
  description="Translate text into the requested language.",
69
70
  output_type=str,
70
71
  )
@@ -6,12 +6,12 @@ from pathlib import Path
6
6
  from typing import Any, Dict, Optional
7
7
 
8
8
  from ..structure.validation import ValidationResultStructure
9
- from .base import AgentBase
10
- from .config import AgentConfig
9
+ from .base import BaseAgent
10
+ from .config import AgentConfiguration
11
11
  from .prompt_utils import DEFAULT_PROMPT_DIR
12
12
 
13
13
 
14
- class ValidatorAgent(AgentBase):
14
+ class ValidatorAgent(BaseAgent):
15
15
  """Check user prompts and agent responses against safety guardrails.
16
16
 
17
17
  This agent validates inputs and outputs to ensure they comply with safety
@@ -64,8 +64,9 @@ class ValidatorAgent(AgentBase):
64
64
  default_model : str or None, default=None
65
65
  Fallback model identifier when not specified elsewhere.
66
66
  """
67
- config = AgentConfig(
67
+ config = AgentConfiguration(
68
68
  name="validator",
69
+ instructions="Agent instructions",
69
70
  description="Validate user input and agent output against guardrails.",
70
71
  output_type=ValidationResultStructure,
71
72
  )
@@ -56,6 +56,7 @@ from ..utils import (
56
56
 
57
57
  if TYPE_CHECKING: # pragma: no cover - only for typing hints
58
58
  from openai_sdk_helpers.streamlit_app.config import StreamlitAppConfig
59
+ from .config import ResponseConfiguration
59
60
 
60
61
  T = TypeVar("T", bound=BaseStructure)
61
62
  ToolHandler = Callable[[ResponseFunctionToolCall], str | Any]
@@ -253,11 +254,80 @@ class BaseResponse(Generic[T]):
253
254
  ),
254
255
  )
255
256
 
257
+ # Add retrieval guidance to system instructions to encourage RAG usage
258
+ try:
259
+ store_names = ", ".join(system_vector_store)
260
+ except Exception:
261
+ store_names = "attached vector stores"
262
+ guidance_text = (
263
+ "Retrieval guidance: You have access to a file_search tool "
264
+ f"connected to vector store(s) {store_names}. When relevant, "
265
+ "use file_search to retrieve supporting passages before answering. "
266
+ "Cite or reference retrieved content when helpful."
267
+ )
268
+ system_content.append(
269
+ ResponseInputTextParam(type="input_text", text=guidance_text)
270
+ )
271
+
256
272
  self.messages = ResponseMessages()
257
273
  self.messages.add_system_message(content=system_content)
258
274
  if self._data_path is not None:
259
275
  self.save()
260
276
 
277
+ @classmethod
278
+ def from_configuration(
279
+ cls: type[RB],
280
+ config: "ResponseConfiguration[Any, T]",
281
+ *,
282
+ openai_settings: OpenAISettings,
283
+ tool_handlers: dict[str, ToolHandler] | None = None,
284
+ add_output_instructions: bool = True,
285
+ ) -> RB:
286
+ """Construct a response instance from a configuration object.
287
+
288
+ Parameters
289
+ ----------
290
+ config : ResponseConfiguration
291
+ Configuration describing the response inputs, outputs, and tools.
292
+ openai_settings : OpenAISettings
293
+ OpenAI authentication and model configuration used for the response.
294
+ tool_handlers : dict[str, ToolHandler] or None, default None
295
+ Mapping of tool names to callable handlers. Defaults to an empty
296
+ dictionary when not provided.
297
+ add_output_instructions : bool, default True
298
+ Append structured output instructions when an output structure is
299
+ present.
300
+
301
+ Returns
302
+ -------
303
+ BaseResponse
304
+ Instance of ``cls`` configured from ``config``.
305
+ """
306
+ handlers = tool_handlers or {}
307
+
308
+ output_instructions = ""
309
+ if config.output_structure is not None and add_output_instructions:
310
+ output_instructions = config.output_structure.get_prompt(
311
+ add_enum_values=False
312
+ )
313
+
314
+ instructions = (
315
+ f"{config.instructions_text}\n{output_instructions}"
316
+ if output_instructions
317
+ else config.instructions_text
318
+ )
319
+
320
+ return cls(
321
+ name=config.name,
322
+ instructions=instructions,
323
+ tools=config.tools,
324
+ output_structure=config.output_structure,
325
+ system_vector_store=config.system_vector_store,
326
+ data_path=config.data_path,
327
+ tool_handlers=handlers,
328
+ openai_settings=openai_settings,
329
+ )
330
+
261
331
  @property
262
332
  def name(self) -> str:
263
333
  """Return the name of this response session.
@@ -11,29 +11,18 @@ from ..config import OpenAISettings
11
11
  from ..structure.base import BaseStructure
12
12
  from ..response.base import BaseResponse, ToolHandler
13
13
  from ..utils import JSONSerializable
14
- from ..utils.path_utils import ensure_directory
14
+ from ..utils.registry import BaseRegistry
15
+ from ..utils.instructions import resolve_instructions_from_path
15
16
 
16
17
  TIn = TypeVar("TIn", bound="BaseStructure")
17
18
  TOut = TypeVar("TOut", bound="BaseStructure")
18
19
 
19
20
 
20
- class ResponseRegistry:
21
+ class ResponseRegistry(BaseRegistry["ResponseConfiguration"]):
21
22
  """Registry for managing ResponseConfiguration instances.
22
23
 
23
- Provides centralized storage and retrieval of response configurations,
24
- enabling reusable response specs across the application. Configurations
25
- are stored by name and can be retrieved or listed as needed.
26
-
27
- Methods
28
- -------
29
- register(config)
30
- Add a ResponseConfiguration to the registry.
31
- get(name)
32
- Retrieve a configuration by name.
33
- list_names()
34
- Return all registered configuration names.
35
- clear()
36
- Remove all registered configurations.
24
+ Inherits from BaseRegistry to provide centralized storage and retrieval
25
+ of response configurations, enabling reusable response specs across the application.
37
26
 
38
27
  Examples
39
28
  --------
@@ -51,130 +40,7 @@ class ResponseRegistry:
51
40
  'test'
52
41
  """
53
42
 
54
- def __init__(self) -> None:
55
- """Initialize an empty registry."""
56
- self._configs: dict[str, ResponseConfiguration] = {}
57
-
58
- def register(self, config: ResponseConfiguration) -> None:
59
- """Add a ResponseConfiguration to the registry.
60
-
61
- Parameters
62
- ----------
63
- config : ResponseConfiguration
64
- Configuration to register.
65
-
66
- Raises
67
- ------
68
- ValueError
69
- If a configuration with the same name is already registered.
70
-
71
- Examples
72
- --------
73
- >>> registry = ResponseRegistry()
74
- >>> config = ResponseConfiguration(...)
75
- >>> registry.register(config)
76
- """
77
- if config.name in self._configs:
78
- raise ValueError(
79
- f"Configuration '{config.name}' is already registered. "
80
- "Use a unique name or clear the registry first."
81
- )
82
- self._configs[config.name] = config
83
-
84
- def get(self, name: str) -> ResponseConfiguration:
85
- """Retrieve a configuration by name.
86
-
87
- Parameters
88
- ----------
89
- name : str
90
- Configuration name to look up.
91
-
92
- Returns
93
- -------
94
- ResponseConfiguration
95
- The registered configuration.
96
-
97
- Raises
98
- ------
99
- KeyError
100
- If no configuration with the given name exists.
101
-
102
- Examples
103
- --------
104
- >>> registry = ResponseRegistry()
105
- >>> config = registry.get("test")
106
- """
107
- if name not in self._configs:
108
- raise KeyError(
109
- f"No configuration named '{name}' found. "
110
- f"Available: {list(self._configs.keys())}"
111
- )
112
- return self._configs[name]
113
-
114
- def list_names(self) -> list[str]:
115
- """Return all registered configuration names.
116
-
117
- Returns
118
- -------
119
- list[str]
120
- Sorted list of configuration names.
121
-
122
- Examples
123
- --------
124
- >>> registry = ResponseRegistry()
125
- >>> registry.list_names()
126
- []
127
- """
128
- return sorted(self._configs.keys())
129
-
130
- def clear(self) -> None:
131
- """Remove all registered configurations.
132
-
133
- Examples
134
- --------
135
- >>> registry = ResponseRegistry()
136
- >>> registry.clear()
137
- """
138
- self._configs.clear()
139
-
140
- def save_to_directory(self, path: Path | str) -> None:
141
- """Export all registered configurations to JSON files in a directory.
142
-
143
- Serializes each registered ResponseConfiguration to an individual JSON file
144
- named after the configuration. Creates the directory if it does not exist.
145
-
146
- Parameters
147
- ----------
148
- path : Path or str
149
- Directory path where JSON files will be saved. Will be created if
150
- it does not already exist.
151
-
152
- Returns
153
- -------
154
- None
155
-
156
- Raises
157
- ------
158
- OSError
159
- If the directory cannot be created or files cannot be written.
160
-
161
- Examples
162
- --------
163
- >>> registry = ResponseRegistry()
164
- >>> registry.save_to_directory("./data")
165
- >>> registry.save_to_directory(Path("exports"))
166
- """
167
- dir_path = ensure_directory(Path(path))
168
- config_names = self.list_names()
169
-
170
- if not config_names:
171
- return
172
-
173
- for config_name in config_names:
174
- config = self.get(config_name)
175
- filename = f"{config_name}.json"
176
- filepath = dir_path / filename
177
- config.to_json_file(filepath)
43
+ pass
178
44
 
179
45
 
180
46
  # Global default registry instance
@@ -335,15 +201,7 @@ class ResponseConfiguration(JSONSerializable, Generic[TIn, TOut]):
335
201
  return self._resolve_instructions()
336
202
 
337
203
  def _resolve_instructions(self) -> str:
338
- if isinstance(self.instructions, Path):
339
- instruction_path = self.instructions.expanduser()
340
- try:
341
- return instruction_path.read_text(encoding="utf-8")
342
- except OSError as exc:
343
- raise ValueError(
344
- f"Unable to read instructions at '{instruction_path}': {exc}"
345
- ) from exc
346
- return self.instructions
204
+ return resolve_instructions_from_path(self.instructions)
347
205
 
348
206
  def gen_response(
349
207
  self,
@@ -0,0 +1,35 @@
1
+ """Utilities for resolving instructions from strings or file paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ def resolve_instructions_from_path(instructions: str | Path) -> str:
9
+ """Resolve instructions from a string or file path.
10
+
11
+ Parameters
12
+ ----------
13
+ instructions : str or Path
14
+ Either plain-text instructions or a path to a file containing
15
+ instructions.
16
+
17
+ Returns
18
+ -------
19
+ str
20
+ The resolved instruction text.
21
+
22
+ Raises
23
+ ------
24
+ ValueError
25
+ If instructions is a Path that cannot be read.
26
+ """
27
+ if isinstance(instructions, Path):
28
+ instruction_path = instructions.expanduser()
29
+ try:
30
+ return instruction_path.read_text(encoding="utf-8")
31
+ except OSError as exc:
32
+ raise ValueError(
33
+ f"Unable to read instructions at '{instruction_path}': {exc}"
34
+ ) from exc
35
+ return instructions
@@ -161,9 +161,17 @@ class JSONSerializable:
161
161
  origin = get_origin(field_type)
162
162
  if origin is Union:
163
163
  type_args = get_args(field_type)
164
- # Check if Path is one of the union types
164
+ # Only convert to Path if:
165
+ # 1. Path is in the union AND
166
+ # 2. str is NOT in the union (to avoid converting string fields)
167
+ # OR the field name suggests it's a path (contains "path")
165
168
  if Path in type_args:
166
- should_convert_to_path = True
169
+ if str not in type_args:
170
+ # Path-only union (e.g., Union[Path, None])
171
+ should_convert_to_path = True
172
+ elif "path" in key.lower():
173
+ # Field name contains "path", likely meant to be a path
174
+ should_convert_to_path = True
167
175
 
168
176
  # Convert string to Path if needed
169
177
  if (