openai-sdk-helpers 0.2.0__py3-none-any.whl → 0.4.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.
- openai_sdk_helpers/__init__.py +6 -6
- openai_sdk_helpers/agent/__init__.py +4 -2
- openai_sdk_helpers/agent/base.py +391 -106
- openai_sdk_helpers/agent/config.py +405 -44
- openai_sdk_helpers/agent/coordination.py +68 -31
- openai_sdk_helpers/agent/runner.py +29 -19
- openai_sdk_helpers/agent/search/base.py +103 -54
- openai_sdk_helpers/agent/search/vector.py +99 -68
- openai_sdk_helpers/agent/search/web.py +84 -50
- openai_sdk_helpers/agent/summarizer.py +33 -7
- openai_sdk_helpers/agent/translator.py +58 -24
- openai_sdk_helpers/agent/validation.py +35 -4
- openai_sdk_helpers/cli.py +42 -0
- openai_sdk_helpers/config.py +0 -1
- openai_sdk_helpers/environment.py +3 -2
- openai_sdk_helpers/files_api.py +35 -3
- openai_sdk_helpers/prompt/base.py +6 -0
- openai_sdk_helpers/response/__init__.py +3 -3
- openai_sdk_helpers/response/base.py +161 -22
- openai_sdk_helpers/response/config.py +50 -200
- openai_sdk_helpers/response/files.py +5 -5
- openai_sdk_helpers/response/messages.py +3 -3
- openai_sdk_helpers/response/runner.py +7 -7
- openai_sdk_helpers/response/tool_call.py +94 -4
- openai_sdk_helpers/response/vector_store.py +3 -3
- openai_sdk_helpers/streamlit_app/app.py +16 -16
- openai_sdk_helpers/streamlit_app/config.py +38 -37
- openai_sdk_helpers/streamlit_app/streamlit_web_search.py +2 -2
- openai_sdk_helpers/structure/__init__.py +6 -2
- openai_sdk_helpers/structure/agent_blueprint.py +2 -2
- openai_sdk_helpers/structure/base.py +8 -99
- openai_sdk_helpers/structure/plan/plan.py +2 -2
- openai_sdk_helpers/structure/plan/task.py +9 -9
- openai_sdk_helpers/structure/prompt.py +2 -2
- openai_sdk_helpers/structure/responses.py +15 -15
- openai_sdk_helpers/structure/summary.py +3 -3
- openai_sdk_helpers/structure/translation.py +32 -0
- openai_sdk_helpers/structure/validation.py +2 -2
- openai_sdk_helpers/structure/vector_search.py +7 -7
- openai_sdk_helpers/structure/web_search.py +6 -6
- openai_sdk_helpers/tools.py +41 -15
- openai_sdk_helpers/utils/__init__.py +19 -5
- openai_sdk_helpers/utils/instructions.py +35 -0
- openai_sdk_helpers/utils/json/__init__.py +55 -0
- openai_sdk_helpers/utils/json/base_model.py +181 -0
- openai_sdk_helpers/utils/{json_utils.py → json/data_class.py} +43 -70
- openai_sdk_helpers/utils/json/ref.py +113 -0
- openai_sdk_helpers/utils/json/utils.py +203 -0
- openai_sdk_helpers/utils/output_validation.py +21 -1
- openai_sdk_helpers/utils/path_utils.py +34 -1
- openai_sdk_helpers/utils/registry.py +194 -0
- openai_sdk_helpers/vector_storage/storage.py +10 -0
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/METADATA +7 -7
- openai_sdk_helpers-0.4.0.dist-info/RECORD +86 -0
- openai_sdk_helpers-0.2.0.dist-info/RECORD +0 -79
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/entry_points.txt +0 -0
- {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,67 +2,428 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Optional, Type
|
|
6
8
|
|
|
9
|
+
from agents import Agent, Handoff, InputGuardrail, OutputGuardrail, Session
|
|
7
10
|
from agents.model_settings import ModelSettings
|
|
8
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
9
11
|
|
|
10
|
-
from ..
|
|
12
|
+
from ..utils.json.data_class import DataclassJSONSerializable
|
|
13
|
+
from ..utils.registry import BaseRegistry
|
|
14
|
+
from ..utils.instructions import resolve_instructions_from_path
|
|
15
|
+
from ..structure.base import StructureBase
|
|
11
16
|
|
|
12
17
|
|
|
13
|
-
class
|
|
14
|
-
"""
|
|
18
|
+
class AgentConfigurationRegistry(BaseRegistry["AgentConfiguration"]):
|
|
19
|
+
"""Registry for managing AgentConfiguration instances.
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
Inherits from BaseRegistry to provide centralized storage and retrieval
|
|
22
|
+
of agent configurations, enabling reusable agent specs across the application.
|
|
23
|
+
|
|
24
|
+
Examples
|
|
25
|
+
--------
|
|
26
|
+
>>> registry = AgentConfigurationRegistry()
|
|
27
|
+
>>> config = AgentConfiguration(
|
|
28
|
+
... name="test_agent",
|
|
29
|
+
... model="gpt-4o-mini",
|
|
30
|
+
... instructions="Test instructions"
|
|
31
|
+
... )
|
|
32
|
+
>>> registry.register(config)
|
|
33
|
+
>>> retrieved = registry.get("test_agent")
|
|
34
|
+
>>> retrieved.name
|
|
35
|
+
'test_agent'
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def load_from_directory(
|
|
39
|
+
self,
|
|
40
|
+
path: Path | str,
|
|
41
|
+
*,
|
|
42
|
+
config_class: type["AgentConfiguration"] | None = None,
|
|
43
|
+
) -> int:
|
|
44
|
+
"""Load all agent configurations from JSON files in a directory.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
path : Path or str
|
|
49
|
+
Directory path containing JSON configuration files.
|
|
50
|
+
config_class : type[AgentConfiguration], optional
|
|
51
|
+
The configuration class to use for deserialization.
|
|
52
|
+
Defaults to AgentConfiguration.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
int
|
|
57
|
+
Number of configurations successfully loaded and registered.
|
|
58
|
+
|
|
59
|
+
Raises
|
|
60
|
+
------
|
|
61
|
+
FileNotFoundError
|
|
62
|
+
If the directory does not exist.
|
|
63
|
+
NotADirectoryError
|
|
64
|
+
If the path is not a directory.
|
|
65
|
+
|
|
66
|
+
Examples
|
|
67
|
+
--------
|
|
68
|
+
>>> registry = AgentConfigurationRegistry()
|
|
69
|
+
>>> count = registry.load_from_directory("./agents")
|
|
70
|
+
>>> print(f"Loaded {count} configurations")
|
|
71
|
+
"""
|
|
72
|
+
if config_class is None:
|
|
73
|
+
config_class = AgentConfiguration
|
|
74
|
+
return super().load_from_directory(path, config_class=config_class)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_default_registry() -> AgentConfigurationRegistry:
|
|
78
|
+
"""Return the global default registry instance.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
17
81
|
-------
|
|
18
|
-
|
|
19
|
-
|
|
82
|
+
AgentConfigurationRegistry
|
|
83
|
+
Singleton registry for application-wide configuration storage.
|
|
84
|
+
|
|
85
|
+
Examples
|
|
86
|
+
--------
|
|
87
|
+
>>> registry = get_default_registry()
|
|
88
|
+
>>> config = AgentConfiguration(
|
|
89
|
+
... name="test", model="gpt-4o-mini", instructions="Test instructions"
|
|
90
|
+
... )
|
|
91
|
+
>>> registry.register(config)
|
|
20
92
|
"""
|
|
93
|
+
return _default_registry
|
|
21
94
|
|
|
22
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
23
95
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class AgentConfiguration(DataclassJSONSerializable):
|
|
98
|
+
"""Immutable configuration for building a AgentBase.
|
|
99
|
+
|
|
100
|
+
Encapsulates all metadata required to define an agent including its
|
|
101
|
+
instructions, tools, model settings, handoffs, guardrails, and session
|
|
102
|
+
management. Inherits from DataclassJSONSerializable to support serialization.
|
|
103
|
+
|
|
104
|
+
This dataclass is frozen (immutable) to ensure thread-safety and
|
|
105
|
+
enable use as dictionary keys. All list-type fields use None as the
|
|
106
|
+
default value rather than mutable defaults like [] to avoid issues
|
|
107
|
+
with shared state across instances.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
name : str
|
|
112
|
+
Unique identifier for the agent. Must be a non-empty string.
|
|
113
|
+
instructions : str or Path
|
|
114
|
+
Plain text instructions or a path to a Jinja template file whose
|
|
115
|
+
contents are loaded at runtime. Required field.
|
|
116
|
+
description : str, optional
|
|
117
|
+
Short description of the agent's purpose. Default is None.
|
|
118
|
+
model : str, optional
|
|
119
|
+
Model identifier to use (e.g., "gpt-4o-mini"). Default is None.
|
|
120
|
+
template_path : str or Path, optional
|
|
121
|
+
Path to the Jinja template (absolute or relative to prompt_dir).
|
|
122
|
+
This takes precedence over instructions if both are provided.
|
|
123
|
+
Default is None.
|
|
124
|
+
input_structure : type[StructureBase], optional
|
|
125
|
+
Structure class describing the agent input. Default is None.
|
|
126
|
+
output_structure : type[StructureBase], optional
|
|
127
|
+
Structure class describing the agent output. Default is None.
|
|
128
|
+
tools : list, optional
|
|
129
|
+
Tool definitions available to the agent. Default is None.
|
|
130
|
+
model_settings : ModelSettings, optional
|
|
131
|
+
Additional model configuration settings. Default is None.
|
|
132
|
+
handoffs : list[Agent or Handoff], optional
|
|
133
|
+
List of agents or handoff configurations that this agent can
|
|
134
|
+
delegate to for specific tasks. Default is None.
|
|
135
|
+
input_guardrails : list[InputGuardrail], optional
|
|
136
|
+
List of guardrails to validate agent inputs before processing.
|
|
137
|
+
Default is None.
|
|
138
|
+
output_guardrails : list[OutputGuardrail], optional
|
|
139
|
+
List of guardrails to validate agent outputs before returning.
|
|
140
|
+
Default is None.
|
|
141
|
+
session : Session, optional
|
|
142
|
+
Session configuration for automatically maintaining conversation
|
|
143
|
+
history across agent runs. Default is None.
|
|
144
|
+
|
|
145
|
+
Methods
|
|
146
|
+
-------
|
|
147
|
+
__post_init__()
|
|
148
|
+
Validate configuration invariants after initialization.
|
|
149
|
+
instructions_text
|
|
150
|
+
Return the resolved instruction content as a string.
|
|
151
|
+
resolve_prompt_path(prompt_dir)
|
|
152
|
+
Resolve the prompt template path for this configuration.
|
|
153
|
+
gen_agent(run_context_wrapper, prompt_dir, default_model)
|
|
154
|
+
Create a AgentBase instance from this configuration.
|
|
155
|
+
replace(**changes)
|
|
156
|
+
Create a new AgentConfiguration with specified fields replaced.
|
|
157
|
+
to_json()
|
|
158
|
+
Return a JSON-compatible dict (inherited from JSONSerializable).
|
|
159
|
+
to_json_file(filepath)
|
|
160
|
+
Write serialized JSON data to a file (inherited from JSONSerializable).
|
|
161
|
+
from_json(data)
|
|
162
|
+
Create an instance from a JSON-compatible dict (inherited from JSONSerializable).
|
|
163
|
+
from_json_file(filepath)
|
|
164
|
+
Load an instance from a JSON file (inherited from JSONSerializable).
|
|
165
|
+
|
|
166
|
+
Examples
|
|
167
|
+
--------
|
|
168
|
+
>>> config = AgentConfiguration(
|
|
169
|
+
... name="summarizer",
|
|
170
|
+
... description="Summarizes text",
|
|
171
|
+
... model="gpt-4o-mini"
|
|
172
|
+
... )
|
|
173
|
+
>>> config.name
|
|
174
|
+
'summarizer'
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
name: str
|
|
178
|
+
instructions: str | Path
|
|
179
|
+
description: Optional[str] = None
|
|
180
|
+
model: Optional[str] = None
|
|
181
|
+
template_path: Optional[str | Path] = None
|
|
182
|
+
input_structure: Optional[Type[StructureBase]] = None
|
|
183
|
+
output_structure: Optional[Type[StructureBase]] = None
|
|
184
|
+
tools: Optional[list] = None
|
|
185
|
+
model_settings: Optional[ModelSettings] = None
|
|
186
|
+
handoffs: Optional[list[Agent | Handoff]] = None
|
|
187
|
+
input_guardrails: Optional[list[InputGuardrail]] = None
|
|
188
|
+
output_guardrails: Optional[list[OutputGuardrail]] = None
|
|
189
|
+
session: Optional[Session] = None
|
|
190
|
+
_instructions_cache: Optional[str] = field(
|
|
191
|
+
default=None, init=False, repr=False, compare=False
|
|
53
192
|
)
|
|
54
193
|
|
|
55
|
-
def
|
|
56
|
-
"""
|
|
194
|
+
def __post_init__(self) -> None:
|
|
195
|
+
"""Validate configuration invariants after initialization.
|
|
196
|
+
|
|
197
|
+
Ensures that the name is a non-empty string and that instructions
|
|
198
|
+
are properly formatted.
|
|
199
|
+
|
|
200
|
+
Raises
|
|
201
|
+
------
|
|
202
|
+
TypeError
|
|
203
|
+
If name is not a non-empty string.
|
|
204
|
+
If instructions is not a string or Path.
|
|
205
|
+
If input_structure or output_structure is not a class.
|
|
206
|
+
If input_structure or output_structure does not subclass StructureBase.
|
|
207
|
+
ValueError
|
|
208
|
+
If instructions is an empty string.
|
|
209
|
+
FileNotFoundError
|
|
210
|
+
If instructions is a Path that doesn't point to a readable file.
|
|
211
|
+
"""
|
|
212
|
+
if not self.name or not isinstance(self.name, str):
|
|
213
|
+
raise TypeError("AgentConfiguration.name must be a non-empty str")
|
|
214
|
+
|
|
215
|
+
# Validate instructions (required field, like in Response module)
|
|
216
|
+
instructions_value = self.instructions
|
|
217
|
+
if isinstance(instructions_value, str):
|
|
218
|
+
if not instructions_value.strip():
|
|
219
|
+
raise ValueError(
|
|
220
|
+
"AgentConfiguration.instructions must be a non-empty str"
|
|
221
|
+
)
|
|
222
|
+
elif isinstance(instructions_value, Path):
|
|
223
|
+
instruction_path = instructions_value.expanduser()
|
|
224
|
+
if not instruction_path.is_file():
|
|
225
|
+
raise FileNotFoundError(
|
|
226
|
+
f"Instruction template not found: {instruction_path}"
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
raise TypeError("AgentConfiguration.instructions must be a str or Path")
|
|
230
|
+
|
|
231
|
+
for attr in ("input_structure", "output_structure"):
|
|
232
|
+
cls = getattr(self, attr)
|
|
233
|
+
if cls is None:
|
|
234
|
+
continue
|
|
235
|
+
if not isinstance(cls, type):
|
|
236
|
+
raise TypeError(
|
|
237
|
+
f"AgentConfiguration.{attr} must be a class (Type[StructureBase]) or None"
|
|
238
|
+
)
|
|
239
|
+
if not issubclass(cls, StructureBase):
|
|
240
|
+
raise TypeError(
|
|
241
|
+
f"AgentConfiguration.{attr} must subclass StructureBase"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if self.template_path is not None and isinstance(self.template_path, Path):
|
|
245
|
+
# Validate template_path if it's a Path object
|
|
246
|
+
template = self.template_path.expanduser()
|
|
247
|
+
if not template.exists():
|
|
248
|
+
# We don't raise here because template_path might be relative
|
|
249
|
+
# and resolved later with prompt_dir
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def instructions_text(self) -> str:
|
|
254
|
+
"""Return the resolved instruction text.
|
|
57
255
|
|
|
58
256
|
Returns
|
|
59
257
|
-------
|
|
60
258
|
str
|
|
61
|
-
|
|
259
|
+
Plain-text instructions, loading template files when necessary.
|
|
62
260
|
"""
|
|
63
|
-
|
|
261
|
+
cached = self._instructions_cache
|
|
262
|
+
if cached is None:
|
|
263
|
+
cached = self._resolve_instructions()
|
|
264
|
+
object.__setattr__(self, "_instructions_cache", cached)
|
|
265
|
+
return cached
|
|
266
|
+
|
|
267
|
+
def _resolve_instructions(self) -> str:
|
|
268
|
+
"""Resolve instructions from string or file path."""
|
|
269
|
+
return resolve_instructions_from_path(self.instructions)
|
|
270
|
+
|
|
271
|
+
def resolve_prompt_path(self, prompt_dir: Path | None = None) -> Path | None:
|
|
272
|
+
"""Resolve the prompt template path for this configuration.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
prompt_dir : Path or None, default=None
|
|
277
|
+
Directory holding prompt templates when a relative path is needed.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
Path or None
|
|
282
|
+
Resolved prompt path if a template is configured or discovered.
|
|
283
|
+
"""
|
|
284
|
+
if self.template_path:
|
|
285
|
+
return Path(self.template_path)
|
|
286
|
+
if prompt_dir is not None:
|
|
287
|
+
return prompt_dir / f"{self.name}.jinja"
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
def gen_agent(
|
|
291
|
+
self,
|
|
292
|
+
run_context_wrapper: Any = None,
|
|
293
|
+
prompt_dir: Path | None = None,
|
|
294
|
+
default_model: str | None = None,
|
|
295
|
+
) -> Any:
|
|
296
|
+
"""Create a AgentBase instance from this configuration.
|
|
297
|
+
|
|
298
|
+
This is a convenience method that instantiates ``AgentBase`` directly.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
run_context_wrapper : RunContextWrapper or None, default=None
|
|
303
|
+
Optional wrapper providing runtime context for prompt rendering.
|
|
304
|
+
prompt_dir : Path or None, default=None
|
|
305
|
+
Optional directory holding prompt templates.
|
|
306
|
+
default_model : str or None, default=None
|
|
307
|
+
Optional fallback model identifier if config doesn't specify one.
|
|
308
|
+
|
|
309
|
+
Returns
|
|
310
|
+
-------
|
|
311
|
+
AgentBase
|
|
312
|
+
Configured agent instance ready for execution.
|
|
313
|
+
|
|
314
|
+
Examples
|
|
315
|
+
--------
|
|
316
|
+
>>> config = AgentConfiguration(
|
|
317
|
+
... name="helper", model="gpt-4o-mini", instructions="Help the user"
|
|
318
|
+
... )
|
|
319
|
+
>>> agent = config.gen_agent()
|
|
320
|
+
>>> result = agent.run_sync("Hello!")
|
|
321
|
+
"""
|
|
322
|
+
# Import here to avoid circular dependency
|
|
323
|
+
from .base import AgentBase
|
|
324
|
+
|
|
325
|
+
return AgentBase(
|
|
326
|
+
config=self,
|
|
327
|
+
run_context_wrapper=run_context_wrapper,
|
|
328
|
+
prompt_dir=prompt_dir,
|
|
329
|
+
default_model=default_model,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def replace(self, **changes: Any) -> AgentConfiguration:
|
|
333
|
+
"""Create a new AgentConfiguration with specified fields replaced.
|
|
334
|
+
|
|
335
|
+
Since AgentConfiguration is frozen (immutable), this method creates a new
|
|
336
|
+
instance with the specified changes applied. This is useful for
|
|
337
|
+
creating variations of a configuration.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
**changes : Any
|
|
342
|
+
Keyword arguments specifying fields to change and their new values.
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
AgentConfiguration
|
|
347
|
+
New configuration instance with changes applied.
|
|
348
|
+
|
|
349
|
+
Examples
|
|
350
|
+
--------
|
|
351
|
+
>>> config = AgentConfiguration(
|
|
352
|
+
... name="agent1", model="gpt-4o-mini", instructions="Agent instructions"
|
|
353
|
+
... )
|
|
354
|
+
>>> config2 = config.replace(name="agent2", description="Modified")
|
|
355
|
+
>>> config2.name
|
|
356
|
+
'agent2'
|
|
357
|
+
>>> config2.model
|
|
358
|
+
'gpt-4o-mini'
|
|
359
|
+
"""
|
|
360
|
+
from dataclasses import replace
|
|
361
|
+
|
|
362
|
+
return replace(self, **changes)
|
|
363
|
+
|
|
364
|
+
def to_json(self) -> dict[str, Any]:
|
|
365
|
+
"""Return a JSON-compatible dict representation.
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
dict[str, Any]
|
|
370
|
+
Serialized configuration data without cached fields.
|
|
371
|
+
"""
|
|
372
|
+
data = DataclassJSONSerializable.to_json(self)
|
|
373
|
+
data.pop("_instructions_cache", None)
|
|
374
|
+
return data
|
|
375
|
+
|
|
376
|
+
@classmethod
|
|
377
|
+
def from_json(cls, data: dict[str, Any]) -> AgentConfiguration:
|
|
378
|
+
"""Create an AgentConfiguration from JSON data.
|
|
379
|
+
|
|
380
|
+
Overrides the default JSONSerializable.from_json to properly handle
|
|
381
|
+
the instructions field, converting string paths that look like file
|
|
382
|
+
paths back to Path objects for proper file loading.
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
data : dict[str, Any]
|
|
387
|
+
Dictionary containing the configuration data.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
AgentConfiguration
|
|
392
|
+
New configuration instance.
|
|
393
|
+
|
|
394
|
+
Notes
|
|
395
|
+
-----
|
|
396
|
+
This method attempts to preserve the original type of the instructions
|
|
397
|
+
field. If instructions is a string that represents an existing file path,
|
|
398
|
+
it will be converted to a Path object to ensure proper file loading
|
|
399
|
+
behavior is maintained across JSON round-trips.
|
|
400
|
+
"""
|
|
401
|
+
# Make a copy to avoid modifying the input
|
|
402
|
+
data = data.copy()
|
|
403
|
+
data.pop("_instructions_cache", None)
|
|
404
|
+
|
|
405
|
+
# Handle instructions field: if it's a string path to an existing file,
|
|
406
|
+
# convert it back to Path for proper file loading
|
|
407
|
+
if "instructions" in data and data["instructions"] is not None:
|
|
408
|
+
instructions_value = data["instructions"]
|
|
409
|
+
if isinstance(instructions_value, str):
|
|
410
|
+
# Check if it looks like a file path and the file exists
|
|
411
|
+
# This preserves the intended behavior for file-based instructions
|
|
412
|
+
try:
|
|
413
|
+
potential_path = Path(instructions_value)
|
|
414
|
+
# Only convert to Path if it's an existing file
|
|
415
|
+
# This way, plain text instructions stay as strings
|
|
416
|
+
if potential_path.exists() and potential_path.is_file():
|
|
417
|
+
data["instructions"] = potential_path
|
|
418
|
+
except (OSError, ValueError):
|
|
419
|
+
# If path parsing fails, keep it as a string (likely plain text)
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
# Use the parent class method for the rest
|
|
423
|
+
return super(AgentConfiguration, cls).from_json(data)
|
|
64
424
|
|
|
65
425
|
|
|
66
|
-
|
|
426
|
+
# Global default registry instance
|
|
427
|
+
_default_registry = AgentConfigurationRegistry()
|
|
67
428
|
|
|
68
|
-
|
|
429
|
+
__all__ = ["AgentConfiguration", "AgentConfigurationRegistry", "get_default_registry"]
|
|
@@ -12,9 +12,9 @@ from typing import Any, Callable, Dict, List, Optional
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
from ..structure import TaskStructure, PlanStructure, PromptStructure
|
|
15
|
-
from ..utils import
|
|
15
|
+
from ..utils import DataclassJSONSerializable, ensure_directory, log
|
|
16
16
|
from .base import AgentBase
|
|
17
|
-
from .config import
|
|
17
|
+
from .config import AgentConfiguration
|
|
18
18
|
from ..structure.plan.enum import AgentEnum
|
|
19
19
|
|
|
20
20
|
PromptFn = Callable[[str], PromptStructure]
|
|
@@ -23,9 +23,30 @@ ExecutePlanFn = Callable[[PlanStructure], List[str]]
|
|
|
23
23
|
SummarizeFn = Callable[[List[str]], str]
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class CoordinatorAgent(AgentBase
|
|
26
|
+
class CoordinatorAgent(AgentBase):
|
|
27
27
|
"""Coordinate agent plans while persisting project state and outputs.
|
|
28
28
|
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
prompt_fn : PromptFn
|
|
32
|
+
Callable that generates a prompt brief from the input string.
|
|
33
|
+
build_plan_fn : BuildPlanFn
|
|
34
|
+
Callable that generates a plan from the prompt brief.
|
|
35
|
+
execute_plan_fn : ExecutePlanFn
|
|
36
|
+
Callable that executes a plan and returns results.
|
|
37
|
+
summarize_fn : SummarizeFn
|
|
38
|
+
Callable that summarizes a list of result strings.
|
|
39
|
+
module_data_path : Path
|
|
40
|
+
Base path for persisting project artifacts.
|
|
41
|
+
name : str
|
|
42
|
+
Name of the parent module for data organization.
|
|
43
|
+
config : AgentConfiguration or None, default=None
|
|
44
|
+
Optional agent configuration describing prompts and metadata.
|
|
45
|
+
prompt_dir : Path or None, default=None
|
|
46
|
+
Optional directory holding prompt templates.
|
|
47
|
+
default_model : str or None, default=None
|
|
48
|
+
Optional fallback model identifier.
|
|
49
|
+
|
|
29
50
|
Methods
|
|
30
51
|
-------
|
|
31
52
|
build_prompt(prompt)
|
|
@@ -63,7 +84,7 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
63
84
|
summarize_fn: SummarizeFn,
|
|
64
85
|
module_data_path: Path,
|
|
65
86
|
name: str,
|
|
66
|
-
config: Optional[
|
|
87
|
+
config: Optional[AgentConfiguration] = None,
|
|
67
88
|
prompt_dir: Optional[Path] = None,
|
|
68
89
|
default_model: Optional[str] = None,
|
|
69
90
|
) -> None:
|
|
@@ -83,16 +104,33 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
83
104
|
Base path for persisting project artifacts.
|
|
84
105
|
name : str
|
|
85
106
|
Name of the parent module for data organization.
|
|
86
|
-
config :
|
|
107
|
+
config : AgentConfiguration or None, default=None
|
|
87
108
|
Optional agent configuration describing prompts and metadata.
|
|
88
109
|
prompt_dir : Path or None, default=None
|
|
89
110
|
Optional directory holding prompt templates.
|
|
90
111
|
default_model : str or None, default=None
|
|
91
112
|
Optional fallback model identifier.
|
|
113
|
+
|
|
114
|
+
Raises
|
|
115
|
+
------
|
|
116
|
+
ValueError
|
|
117
|
+
If the provided configuration is invalid.
|
|
118
|
+
|
|
119
|
+
Examples
|
|
120
|
+
--------
|
|
121
|
+
>>> coordinator = CoordinatorAgent(
|
|
122
|
+
... prompt_fn=lambda p: PromptStructure(prompt=p),
|
|
123
|
+
... build_plan_fn=lambda p: PlanStructure(),
|
|
124
|
+
... execute_plan_fn=lambda p: [],
|
|
125
|
+
... summarize_fn=lambda r: "summary",
|
|
126
|
+
... module_data_path=Path("."),
|
|
127
|
+
... name="test",
|
|
128
|
+
... )
|
|
92
129
|
"""
|
|
93
130
|
if config is None:
|
|
94
|
-
config =
|
|
131
|
+
config = AgentConfiguration(
|
|
95
132
|
name="coordinator_agent",
|
|
133
|
+
instructions="Coordinate agents for planning and summarization.",
|
|
96
134
|
description="Coordinates agents for planning and summarization.",
|
|
97
135
|
)
|
|
98
136
|
super().__init__(
|
|
@@ -119,6 +157,10 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
119
157
|
----------
|
|
120
158
|
prompt : str
|
|
121
159
|
The core request or goal for the project.
|
|
160
|
+
|
|
161
|
+
Examples
|
|
162
|
+
--------
|
|
163
|
+
>>> coordinator.build_prompt("Analyze the impact of AI on healthcare.")
|
|
122
164
|
"""
|
|
123
165
|
log("build_prompt", level=logging.INFO)
|
|
124
166
|
self.start_date = datetime.now(timezone.utc)
|
|
@@ -133,6 +175,11 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
133
175
|
------
|
|
134
176
|
ValueError
|
|
135
177
|
If called before :meth:`build_prompt`.
|
|
178
|
+
|
|
179
|
+
Examples
|
|
180
|
+
--------
|
|
181
|
+
>>> coordinator.build_prompt("Analyze AI in healthcare.")
|
|
182
|
+
>>> coordinator.build_plan()
|
|
136
183
|
"""
|
|
137
184
|
log("build_plan", level=logging.INFO)
|
|
138
185
|
if not self.brief:
|
|
@@ -150,6 +197,12 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
150
197
|
-------
|
|
151
198
|
list[str]
|
|
152
199
|
Flattened list of results from all executed tasks.
|
|
200
|
+
|
|
201
|
+
Examples
|
|
202
|
+
--------
|
|
203
|
+
>>> coordinator.build_prompt("Analyze AI.")
|
|
204
|
+
>>> coordinator.build_plan()
|
|
205
|
+
>>> results = coordinator.execute_plan()
|
|
153
206
|
"""
|
|
154
207
|
log("execute_plan", level=logging.INFO)
|
|
155
208
|
if not self.plan:
|
|
@@ -173,6 +226,11 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
173
226
|
-------
|
|
174
227
|
str
|
|
175
228
|
Concise summary derived from the provided results.
|
|
229
|
+
|
|
230
|
+
Examples
|
|
231
|
+
--------
|
|
232
|
+
>>> results = ["AI is impacting healthcare.", "New models are faster."]
|
|
233
|
+
>>> summary = coordinator.summarize_plan(results)
|
|
176
234
|
"""
|
|
177
235
|
log("summarize_plan", level=logging.INFO)
|
|
178
236
|
|
|
@@ -198,37 +256,16 @@ class CoordinatorAgent(AgentBase, JSONSerializable):
|
|
|
198
256
|
----------
|
|
199
257
|
prompt : str
|
|
200
258
|
The request or question to analyze and summarize.
|
|
259
|
+
|
|
260
|
+
Examples
|
|
261
|
+
--------
|
|
262
|
+
>>> coordinator.run_plan("Analyze the future of AI.")
|
|
201
263
|
"""
|
|
202
264
|
self.build_prompt(prompt)
|
|
203
265
|
self.build_plan()
|
|
204
266
|
results = self.execute_plan()
|
|
205
267
|
self.summarize_plan(results)
|
|
206
268
|
|
|
207
|
-
@property
|
|
208
|
-
def file_path(self) -> Path:
|
|
209
|
-
"""Return the path where the project snapshot will be stored.
|
|
210
|
-
|
|
211
|
-
Returns
|
|
212
|
-
-------
|
|
213
|
-
Path
|
|
214
|
-
Location of the JSON artifact for the current run.
|
|
215
|
-
"""
|
|
216
|
-
if not self.start_date:
|
|
217
|
-
self.start_date = datetime.now(timezone.utc)
|
|
218
|
-
start_date_str = self.start_date.strftime("%Y%m%d_%H%M%S")
|
|
219
|
-
return self._module_data_path / self._name / f"{start_date_str}.json"
|
|
220
|
-
|
|
221
|
-
def save(self) -> Path:
|
|
222
|
-
"""Persist the current project state to disk.
|
|
223
|
-
|
|
224
|
-
Returns
|
|
225
|
-
-------
|
|
226
|
-
Path
|
|
227
|
-
Path to the saved JSON artifact.
|
|
228
|
-
"""
|
|
229
|
-
self.to_json_file(self.file_path)
|
|
230
|
-
return self.file_path
|
|
231
|
-
|
|
232
269
|
@staticmethod
|
|
233
270
|
def _run_task(
|
|
234
271
|
task: TaskStructure,
|