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,179 +1,101 @@
1
1
  """Convenience wrappers for running OpenAI agents.
2
2
 
3
- These helpers provide a narrow surface around the lower-level functions in
4
- ``openai-sdk-helpers.agent.base`` so that callers can execute agents with consistent
3
+ These helpers provide a consistent interface around the lower-level functions in
4
+ the ``agent.base`` module, allowing callers to execute agents with consistent
5
5
  signatures whether they need asynchronous, synchronous, or streamed results.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
10
  from typing import Any, Dict, Optional
11
- import asyncio
12
- import threading
13
- from agents import Agent, Runner, RunResult, RunResultStreaming
14
11
 
12
+ from agents import Agent, RunResult, RunResultStreaming, Runner
15
13
 
16
- async def _run_async(
14
+ from openai_sdk_helpers.async_utils import run_coroutine_with_fallback
15
+
16
+
17
+ async def run_async(
17
18
  agent: Agent,
18
19
  input: str,
19
20
  context: Optional[Dict[str, Any]] = None,
20
21
  output_type: Optional[Any] = None,
21
22
  ) -> Any:
22
- """Run an ``Agent`` asynchronously.
23
+ """Run an Agent asynchronously.
23
24
 
24
25
  Parameters
25
26
  ----------
26
- agent
27
+ agent : Agent
27
28
  Configured agent instance to execute.
28
- input
29
+ input : str
29
30
  Prompt or query string for the agent.
30
- context
31
- Optional context dictionary passed to the agent. Default ``None``.
32
- output_type
33
- Optional type used to cast the final output. Default ``None``.
31
+ context : dict or None, default=None
32
+ Optional context dictionary passed to the agent.
33
+ output_type : type or None, default=None
34
+ Optional type used to cast the final output.
34
35
 
35
36
  Returns
36
37
  -------
37
38
  Any
38
39
  Agent response, optionally converted to ``output_type``.
40
+
41
+ Examples
42
+ --------
43
+ >>> import asyncio
44
+ >>> from agents import Agent
45
+ >>> async def example():
46
+ ... agent = Agent(name="test", instructions="test", model="gpt-4o-mini")
47
+ ... result = await run_async(agent, "What is 2+2?")
48
+ ... return result
49
+ >>> asyncio.run(example()) # doctest: +SKIP
39
50
  """
40
51
  result = await Runner.run(agent, input, context=context)
41
52
  if output_type is not None:
42
- result = result.final_output_as(output_type)
43
- return result
44
-
45
-
46
- def _run_sync(
47
- agent: Agent,
48
- input: str,
49
- context: Optional[Dict[str, Any]] = None,
50
- ) -> RunResult:
51
- """Run an ``Agent`` synchronously.
52
-
53
- Parameters
54
- ----------
55
- agent
56
- Configured agent instance to execute.
57
- input
58
- Prompt or query string for the agent.
59
- context
60
- Optional context dictionary passed to the agent. Default ``None``.
61
-
62
- Returns
63
- -------
64
- RunResult
65
- Result from the agent execution.
66
- """
67
- coro = Runner.run(agent, input, context=context)
68
- try:
69
- loop = asyncio.get_running_loop()
70
- except RuntimeError:
71
- return asyncio.run(coro)
72
-
73
- if loop.is_running():
74
- result: RunResult | None = None
75
-
76
- def _thread_runner() -> None:
77
- nonlocal result
78
- result = asyncio.run(coro)
79
-
80
- thread = threading.Thread(target=_thread_runner, daemon=True)
81
- thread.start()
82
- thread.join()
83
- if result is None:
84
- raise RuntimeError("Agent execution did not return a result.")
85
- return result
86
-
87
- return loop.run_until_complete(coro)
88
-
89
-
90
- def _run_streamed(
91
- agent: Agent,
92
- input: str,
93
- context: Optional[Dict[str, Any]] = None,
94
- ) -> RunResultStreaming:
95
- """Run an ``Agent`` synchronously and return a streaming result.
96
-
97
- Parameters
98
- ----------
99
- agent
100
- Configured agent to execute.
101
- input
102
- Prompt or query string for the agent.
103
- context
104
- Optional context dictionary passed to the agent. Default ``None``.
105
-
106
- Returns
107
- -------
108
- RunResultStreaming
109
- Instance for streaming outputs.
110
- """
111
- result = Runner.run_streamed(agent, input, context=context)
53
+ return result.final_output_as(output_type)
112
54
  return result
113
55
 
114
56
 
115
- async def run_async(
57
+ def run_sync(
116
58
  agent: Agent,
117
59
  input: str,
118
60
  context: Optional[Dict[str, Any]] = None,
119
61
  output_type: Optional[Any] = None,
120
62
  ) -> Any:
121
- """Run an ``Agent`` asynchronously.
63
+ """Run an Agent synchronously.
64
+
65
+ Internally uses async execution with proper event loop handling.
66
+ If an event loop is already running, creates a new thread to avoid
67
+ nested event loop errors.
122
68
 
123
69
  Parameters
124
70
  ----------
125
- agent
71
+ agent : Agent
126
72
  Configured agent instance to execute.
127
- input
73
+ input : str
128
74
  Prompt or query string for the agent.
129
- context
130
- Optional context dictionary passed to the agent. Default ``None``.
131
- output_type
132
- Optional type used to cast the final output. Default ``None``.
75
+ context : dict or None, default=None
76
+ Optional context dictionary passed to the agent.
77
+ output_type : type or None, default=None
78
+ Optional type used to cast the final output.
133
79
 
134
80
  Returns
135
81
  -------
136
82
  Any
137
83
  Agent response, optionally converted to ``output_type``.
138
- """
139
- return await _run_async(
140
- agent=agent,
141
- input=input,
142
- context=context,
143
- output_type=output_type,
144
- )
145
-
146
-
147
- def run_sync(
148
- agent: Agent,
149
- input: str,
150
- context: Optional[Dict[str, Any]] = None,
151
- output_type: Optional[Any] = None,
152
- ) -> Any:
153
- """Run an ``Agent`` synchronously.
154
84
 
155
- Parameters
156
- ----------
157
- agent
158
- Configured agent instance to execute.
159
- input
160
- Prompt or query string for the agent.
161
- context
162
- Optional context dictionary passed to the agent. Default ``None``.
163
- output_type
164
- Optional type used to cast the final output. Default ``None``.
85
+ Raises
86
+ ------
87
+ AsyncExecutionError
88
+ If execution fails or times out.
165
89
 
166
- Returns
167
- -------
168
- Any
169
- Agent response, optionally converted to ``output_type``.
90
+ Examples
91
+ --------
92
+ >>> from agents import Agent
93
+ >>> agent = Agent(name="test", instructions="test", model="gpt-4o-mini")
94
+ >>> result = run_sync(agent, "What is 2+2?") # doctest: +SKIP
170
95
  """
171
- result: RunResult = _run_sync(
172
- agent=agent,
173
- input=input,
174
- context=context,
175
- )
176
- if output_type:
96
+ coro = Runner.run(agent, input, context=context)
97
+ result: RunResult = run_coroutine_with_fallback(coro)
98
+ if output_type is not None:
177
99
  return result.final_output_as(output_type)
178
100
  return result
179
101
 
@@ -184,30 +106,34 @@ def run_streamed(
184
106
  context: Optional[Dict[str, Any]] = None,
185
107
  output_type: Optional[Any] = None,
186
108
  ) -> RunResultStreaming:
187
- """Run an ``Agent`` and return a streaming result.
109
+ """Stream agent execution results.
188
110
 
189
111
  Parameters
190
112
  ----------
191
- agent
192
- Configured agent instance to execute.
193
- input
113
+ agent : Agent
114
+ Configured agent to execute.
115
+ input : str
194
116
  Prompt or query string for the agent.
195
- context
196
- Optional context dictionary passed to the agent. Default ``None``.
197
- output_type
198
- Optional type used to cast the final output. Default ``None``.
117
+ context : dict or None, default=None
118
+ Optional context dictionary passed to the agent.
119
+ output_type : type or None, default=None
120
+ Optional type used to cast the final output.
199
121
 
200
122
  Returns
201
123
  -------
202
124
  RunResultStreaming
203
125
  Streaming output wrapper from the agent execution.
126
+
127
+ Examples
128
+ --------
129
+ >>> from agents import Agent
130
+ >>> agent = Agent(name="test", instructions="test", model="gpt-4o-mini")
131
+ >>> result = run_streamed(agent, "Explain AI") # doctest: +SKIP
132
+ >>> for chunk in result.stream_text(): # doctest: +SKIP
133
+ ... print(chunk, end="")
204
134
  """
205
- result = _run_streamed(
206
- agent=agent,
207
- input=input,
208
- context=context,
209
- )
210
- if output_type:
135
+ result = Runner.run_streamed(agent, input, context=context)
136
+ if output_type is not None:
211
137
  return result.final_output_as(output_type)
212
138
  return result
213
139
 
@@ -0,0 +1,33 @@
1
+ """Search-related agent workflows and helpers."""
2
+
3
+ from .base import SearchPlanner, SearchToolAgent, SearchWriter
4
+ from .web import (
5
+ MAX_CONCURRENT_SEARCHES as WEB_MAX_CONCURRENT_SEARCHES,
6
+ WebAgentPlanner,
7
+ WebSearchToolAgent,
8
+ WebAgentWriter,
9
+ WebAgentSearch,
10
+ )
11
+ from .vector import (
12
+ MAX_CONCURRENT_SEARCHES as VECTOR_MAX_CONCURRENT_SEARCHES,
13
+ VectorSearchPlanner,
14
+ VectorSearchTool,
15
+ VectorSearchWriter,
16
+ VectorSearch,
17
+ )
18
+
19
+ __all__ = [
20
+ "SearchPlanner",
21
+ "SearchToolAgent",
22
+ "SearchWriter",
23
+ "WEB_MAX_CONCURRENT_SEARCHES",
24
+ "WebAgentPlanner",
25
+ "WebSearchToolAgent",
26
+ "WebAgentWriter",
27
+ "WebAgentSearch",
28
+ "VECTOR_MAX_CONCURRENT_SEARCHES",
29
+ "VectorSearchPlanner",
30
+ "VectorSearchTool",
31
+ "VectorSearchWriter",
32
+ "VectorSearch",
33
+ ]
@@ -0,0 +1,297 @@
1
+ """Generic base classes for search agent workflows.
2
+
3
+ This module provides abstract base classes that extract common patterns from
4
+ web search and vector search implementations, eliminating code duplication
5
+ and providing a consistent interface for new search types.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from abc import ABC, abstractmethod
12
+ from pathlib import Path
13
+ from typing import Generic, List, Optional, TypeVar, Union
14
+
15
+ from ..base import AgentBase
16
+ from ..config import AgentConfig
17
+
18
+ # Type variables for search workflow components
19
+ ItemType = TypeVar("ItemType") # Search item structure (e.g., WebSearchItemStructure)
20
+ ResultType = TypeVar("ResultType") # Individual search result
21
+ PlanType = TypeVar("PlanType") # Complete search plan structure
22
+ ReportType = TypeVar("ReportType") # Final report structure
23
+ OutputType = TypeVar("OutputType") # Generic output type
24
+
25
+
26
+ class SearchPlanner(AgentBase, Generic[PlanType]):
27
+ """Generic planner agent for search workflows.
28
+
29
+ Subclasses implement specific planner logic by overriding the
30
+ `_configure_agent` method and specifying the output type.
31
+
32
+ Methods
33
+ -------
34
+ run_agent(query)
35
+ Generate a search plan for the provided query.
36
+ _configure_agent()
37
+ Return AgentConfig for this planner instance.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ prompt_dir: Optional[Path] = None,
43
+ default_model: Optional[str] = None,
44
+ ) -> None:
45
+ """Initialize the planner agent.
46
+
47
+ Parameters
48
+ ----------
49
+ prompt_dir : Path, optional
50
+ Directory containing prompt templates.
51
+ default_model : str, optional
52
+ Default model identifier to use when not defined in config.
53
+ """
54
+ config = self._configure_agent()
55
+ super().__init__(
56
+ config=config,
57
+ prompt_dir=prompt_dir,
58
+ default_model=default_model,
59
+ )
60
+
61
+ @abstractmethod
62
+ def _configure_agent(self) -> AgentConfig:
63
+ """Return configuration for this planner.
64
+
65
+ Returns
66
+ -------
67
+ AgentConfig
68
+ Configuration with name, description, and output_type set.
69
+
70
+ Examples
71
+ --------
72
+ >>> config = AgentConfig(
73
+ ... name="web_planner",
74
+ ... description="Plan web searches",
75
+ ... output_type=WebSearchPlanStructure,
76
+ ... )
77
+ >>> return config
78
+ """
79
+ pass
80
+
81
+ async def run_agent(self, query: str) -> PlanType:
82
+ """Generate a search plan for the query.
83
+
84
+ Parameters
85
+ ----------
86
+ query : str
87
+ User search query.
88
+
89
+ Returns
90
+ -------
91
+ PlanType
92
+ Generated search plan of the configured output type.
93
+ """
94
+ result: PlanType = await self.run_async(
95
+ input=query,
96
+ output_type=self._output_type,
97
+ )
98
+ return result
99
+
100
+
101
+ class SearchToolAgent(AgentBase, Generic[ItemType, ResultType, PlanType]):
102
+ """Generic tool agent for executing search workflows.
103
+
104
+ Executes individual searches in a plan with concurrency control.
105
+ Subclasses implement search execution logic by overriding the
106
+ `_configure_agent` and `run_search` methods.
107
+
108
+ Methods
109
+ -------
110
+ run_agent(search_plan)
111
+ Execute all searches in the plan.
112
+ run_search(item)
113
+ Execute a single search item.
114
+ _configure_agent()
115
+ Return AgentConfig for this tool agent.
116
+ """
117
+
118
+ def __init__(
119
+ self,
120
+ prompt_dir: Optional[Path] = None,
121
+ default_model: Optional[str] = None,
122
+ max_concurrent_searches: int = 10,
123
+ ) -> None:
124
+ """Initialize the search tool agent.
125
+
126
+ Parameters
127
+ ----------
128
+ prompt_dir : Path, optional
129
+ Directory containing prompt templates.
130
+ default_model : str, optional
131
+ Default model identifier to use when not defined in config.
132
+ max_concurrent_searches : int, default=10
133
+ Maximum number of concurrent search operations.
134
+ """
135
+ self._max_concurrent_searches = max_concurrent_searches
136
+ config = self._configure_agent()
137
+ super().__init__(
138
+ config=config,
139
+ prompt_dir=prompt_dir,
140
+ default_model=default_model,
141
+ )
142
+
143
+ @abstractmethod
144
+ def _configure_agent(self) -> AgentConfig:
145
+ """Return configuration for this tool agent.
146
+
147
+ Returns
148
+ -------
149
+ AgentConfig
150
+ Configuration with name, description, input_type, and tools set.
151
+
152
+ Examples
153
+ --------
154
+ >>> config = AgentConfig(
155
+ ... name="web_search",
156
+ ... description="Perform web searches",
157
+ ... input_type=WebSearchPlanStructure,
158
+ ... tools=[WebSearchTool()],
159
+ ... )
160
+ >>> return config
161
+ """
162
+ pass
163
+
164
+ @abstractmethod
165
+ async def run_search(self, item: ItemType) -> ResultType:
166
+ """Execute a single search item.
167
+
168
+ Parameters
169
+ ----------
170
+ item : ItemType
171
+ Individual search item from the plan.
172
+
173
+ Returns
174
+ -------
175
+ ResultType
176
+ Result of executing the search item.
177
+ """
178
+ pass
179
+
180
+ async def run_agent(self, search_plan: PlanType) -> List[ResultType]:
181
+ """Execute all searches in the plan with concurrency control.
182
+
183
+ Parameters
184
+ ----------
185
+ search_plan : PlanType
186
+ Plan structure containing search items.
187
+
188
+ Returns
189
+ -------
190
+ list[ResultType]
191
+ Completed search results from executing the plan.
192
+ """
193
+ semaphore = asyncio.Semaphore(self._max_concurrent_searches)
194
+
195
+ async def _bounded_search(item: ItemType) -> Optional[ResultType]:
196
+ """Execute search within concurrency limit."""
197
+ async with semaphore:
198
+ return await self.run_search(item)
199
+
200
+ items = getattr(search_plan, "searches", [])
201
+ tasks = [asyncio.create_task(_bounded_search(item)) for item in items]
202
+ results = await asyncio.gather(*tasks)
203
+
204
+ return [result for result in results if result is not None]
205
+
206
+
207
+ class SearchWriter(AgentBase, Generic[ReportType]):
208
+ """Generic writer agent for search workflow reports.
209
+
210
+ Synthesizes search results into a final report. Subclasses implement
211
+ specific report generation logic by overriding the `_configure_agent` method.
212
+
213
+ Methods
214
+ -------
215
+ run_agent(query, search_results)
216
+ Generate a report from search results.
217
+ _configure_agent()
218
+ Return AgentConfig for this writer instance.
219
+ """
220
+
221
+ def __init__(
222
+ self,
223
+ prompt_dir: Optional[Path] = None,
224
+ default_model: Optional[str] = None,
225
+ ) -> None:
226
+ """Initialize the writer agent.
227
+
228
+ Parameters
229
+ ----------
230
+ prompt_dir : Path, optional
231
+ Directory containing prompt templates.
232
+ default_model : str, optional
233
+ Default model identifier to use when not defined in config.
234
+ """
235
+ config = self._configure_agent()
236
+ super().__init__(
237
+ config=config,
238
+ prompt_dir=prompt_dir,
239
+ default_model=default_model,
240
+ )
241
+
242
+ @abstractmethod
243
+ def _configure_agent(self) -> AgentConfig:
244
+ """Return configuration for this writer.
245
+
246
+ Returns
247
+ -------
248
+ AgentConfig
249
+ Configuration with name, description, and output_type set.
250
+
251
+ Examples
252
+ --------
253
+ >>> config = AgentConfig(
254
+ ... name="web_writer",
255
+ ... description="Write web search report",
256
+ ... output_type=WebSearchReportStructure,
257
+ ... )
258
+ >>> return config
259
+ """
260
+ pass
261
+
262
+ async def run_agent(
263
+ self,
264
+ query: str,
265
+ search_results: List[ResultType],
266
+ ) -> ReportType:
267
+ """Generate a report from search results.
268
+
269
+ Parameters
270
+ ----------
271
+ query : str
272
+ Original search query.
273
+ search_results : list[ResultType]
274
+ Results from the search execution phase.
275
+
276
+ Returns
277
+ -------
278
+ ReportType
279
+ Final report structure of the configured output type.
280
+ """
281
+ template_context = {
282
+ "original_query": query,
283
+ "search_results": search_results,
284
+ }
285
+ result: ReportType = await self.run_async(
286
+ input=query,
287
+ context=template_context,
288
+ output_type=self._output_type,
289
+ )
290
+ return result
291
+
292
+
293
+ __all__ = [
294
+ "SearchPlanner",
295
+ "SearchToolAgent",
296
+ "SearchWriter",
297
+ ]