openai-sdk-helpers 0.0.5__py3-none-any.whl → 0.0.7__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 +62 -0
- openai_sdk_helpers/agent/__init__.py +31 -0
- openai_sdk_helpers/agent/base.py +330 -0
- openai_sdk_helpers/agent/config.py +66 -0
- openai_sdk_helpers/agent/project_manager.py +511 -0
- openai_sdk_helpers/agent/prompt_utils.py +9 -0
- openai_sdk_helpers/agent/runner.py +215 -0
- openai_sdk_helpers/agent/summarizer.py +85 -0
- openai_sdk_helpers/agent/translator.py +139 -0
- openai_sdk_helpers/agent/utils.py +47 -0
- openai_sdk_helpers/agent/validation.py +97 -0
- openai_sdk_helpers/agent/vector_search.py +462 -0
- openai_sdk_helpers/agent/web_search.py +404 -0
- openai_sdk_helpers/config.py +199 -0
- openai_sdk_helpers/enums/__init__.py +7 -0
- openai_sdk_helpers/enums/base.py +29 -0
- openai_sdk_helpers/environment.py +27 -0
- openai_sdk_helpers/prompt/__init__.py +77 -0
- openai_sdk_helpers/py.typed +0 -0
- openai_sdk_helpers/response/__init__.py +20 -0
- openai_sdk_helpers/response/base.py +505 -0
- openai_sdk_helpers/response/messages.py +211 -0
- openai_sdk_helpers/response/runner.py +104 -0
- openai_sdk_helpers/response/tool_call.py +70 -0
- openai_sdk_helpers/response/vector_store.py +84 -0
- openai_sdk_helpers/structure/__init__.py +43 -0
- openai_sdk_helpers/structure/agent_blueprint.py +224 -0
- openai_sdk_helpers/structure/base.py +713 -0
- openai_sdk_helpers/structure/plan/__init__.py +13 -0
- openai_sdk_helpers/structure/plan/enum.py +64 -0
- openai_sdk_helpers/structure/plan/plan.py +253 -0
- openai_sdk_helpers/structure/plan/task.py +122 -0
- openai_sdk_helpers/structure/prompt.py +24 -0
- openai_sdk_helpers/structure/responses.py +132 -0
- openai_sdk_helpers/structure/summary.py +65 -0
- openai_sdk_helpers/structure/validation.py +47 -0
- openai_sdk_helpers/structure/vector_search.py +86 -0
- openai_sdk_helpers/structure/web_search.py +46 -0
- openai_sdk_helpers/utils/__init__.py +25 -0
- openai_sdk_helpers/utils/core.py +300 -0
- openai_sdk_helpers/vector_storage/__init__.py +15 -0
- openai_sdk_helpers/vector_storage/cleanup.py +91 -0
- openai_sdk_helpers/vector_storage/storage.py +564 -0
- openai_sdk_helpers/vector_storage/types.py +58 -0
- {openai_sdk_helpers-0.0.5.dist-info → openai_sdk_helpers-0.0.7.dist-info}/METADATA +6 -3
- openai_sdk_helpers-0.0.7.dist-info/RECORD +51 -0
- openai_sdk_helpers-0.0.5.dist-info/RECORD +0 -7
- {openai_sdk_helpers-0.0.5.dist-info → openai_sdk_helpers-0.0.7.dist-info}/WHEEL +0 -0
- {openai_sdk_helpers-0.0.5.dist-info → openai_sdk_helpers-0.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""Generic project manager for coordinating agent plans."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from ..structure import TaskStructure, PlanStructure, PromptStructure
|
|
15
|
+
from ..environment import DATETIME_FMT
|
|
16
|
+
from ..utils import JSONSerializable, log
|
|
17
|
+
from .base import AgentBase
|
|
18
|
+
from .config import AgentConfig
|
|
19
|
+
from ..structure.plan.enum import AgentEnum
|
|
20
|
+
|
|
21
|
+
PromptFn = Callable[[str], PromptStructure]
|
|
22
|
+
BuildPlanFn = Callable[[str], PlanStructure]
|
|
23
|
+
ExecutePlanFn = Callable[[PlanStructure], List[str]]
|
|
24
|
+
SummarizeFn = Callable[[List[str]], str]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProjectManager(AgentBase, JSONSerializable):
|
|
28
|
+
"""Coordinate agent plans while persisting project state and outputs.
|
|
29
|
+
|
|
30
|
+
Methods
|
|
31
|
+
-------
|
|
32
|
+
build_prompt(prompt)
|
|
33
|
+
Summarize the prompt into a concise brief.
|
|
34
|
+
build_plan()
|
|
35
|
+
Create a list of ``TaskStructure`` entries for the project.
|
|
36
|
+
execute_plan()
|
|
37
|
+
Run each task sequentially while tracking status and timing.
|
|
38
|
+
summarize_plan(results)
|
|
39
|
+
Summarize a collection of result strings.
|
|
40
|
+
run_plan(prompt)
|
|
41
|
+
Execute the prompt-to-summary workflow end to end.
|
|
42
|
+
file_path
|
|
43
|
+
Path to the JSON artifact for the current run.
|
|
44
|
+
to_dict()
|
|
45
|
+
Return a JSON-serializable snapshot of stored project data.
|
|
46
|
+
save()
|
|
47
|
+
Persist the stored project data to a JSON file.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
prompt_fn: PromptFn,
|
|
53
|
+
build_plan_fn: BuildPlanFn,
|
|
54
|
+
execute_plan_fn: ExecutePlanFn,
|
|
55
|
+
summarize_fn: SummarizeFn,
|
|
56
|
+
module_data_path: Path,
|
|
57
|
+
module_name: str,
|
|
58
|
+
config: Optional[AgentConfig] = None,
|
|
59
|
+
prompt_dir: Optional[Path] = None,
|
|
60
|
+
default_model: Optional[str] = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Initialize the project manager with injected workflow helpers.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
prompt_fn
|
|
67
|
+
Callable that generates a prompt brief from the input string.
|
|
68
|
+
build_plan_fn
|
|
69
|
+
Callable that generates a plan from the prompt brief.
|
|
70
|
+
execute_plan_fn
|
|
71
|
+
Callable that executes a plan and returns results.
|
|
72
|
+
summarize_fn
|
|
73
|
+
Callable that summarizes a list of result strings.
|
|
74
|
+
module_data_path
|
|
75
|
+
Base path for persisting project artifacts.
|
|
76
|
+
module_name
|
|
77
|
+
Name of the parent module for data organization.
|
|
78
|
+
config
|
|
79
|
+
Optional agent configuration describing prompts and metadata.
|
|
80
|
+
prompt_dir
|
|
81
|
+
Optional directory holding prompt templates.
|
|
82
|
+
default_model
|
|
83
|
+
Optional fallback model identifier.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
None
|
|
88
|
+
"""
|
|
89
|
+
if config is None:
|
|
90
|
+
config = AgentConfig(
|
|
91
|
+
name="project_manager",
|
|
92
|
+
description="Coordinates agents for planning and summarization.",
|
|
93
|
+
)
|
|
94
|
+
super().__init__(
|
|
95
|
+
config=config, prompt_dir=prompt_dir, default_model=default_model
|
|
96
|
+
)
|
|
97
|
+
self._prompt_fn = prompt_fn
|
|
98
|
+
self._build_plan_fn = build_plan_fn
|
|
99
|
+
self._execute_plan_fn = execute_plan_fn
|
|
100
|
+
self._summarize_fn = summarize_fn
|
|
101
|
+
self._module_data_path = Path(module_data_path)
|
|
102
|
+
self._module_name = module_name
|
|
103
|
+
|
|
104
|
+
self.prompt: Optional[str] = None
|
|
105
|
+
self.brief: Optional[PromptStructure] = None
|
|
106
|
+
self.plan: PlanStructure = PlanStructure()
|
|
107
|
+
self.summary: Optional[str] = None
|
|
108
|
+
self.start_date: Optional[datetime] = None
|
|
109
|
+
self.end_date: Optional[datetime] = None
|
|
110
|
+
|
|
111
|
+
def build_prompt(self, prompt: str) -> None:
|
|
112
|
+
"""Return a concise brief for the project.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
prompt : str
|
|
117
|
+
The core request or goal for the project.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
None
|
|
122
|
+
"""
|
|
123
|
+
log("build_prompt", level=logging.INFO)
|
|
124
|
+
self.start_date = datetime.now(timezone.utc)
|
|
125
|
+
self.prompt = prompt
|
|
126
|
+
self.brief = self._prompt_fn(prompt)
|
|
127
|
+
self.save()
|
|
128
|
+
|
|
129
|
+
def build_plan(self) -> None:
|
|
130
|
+
"""Generate and store a structured plan based on the current brief.
|
|
131
|
+
|
|
132
|
+
Raises
|
|
133
|
+
------
|
|
134
|
+
ValueError
|
|
135
|
+
If called before :meth:`build_prompt`.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
None
|
|
140
|
+
"""
|
|
141
|
+
log("build_plan", level=logging.INFO)
|
|
142
|
+
if not self.brief:
|
|
143
|
+
raise ValueError("Brief is required before building a plan.")
|
|
144
|
+
|
|
145
|
+
plan = self._build_plan_fn(self.brief.prompt)
|
|
146
|
+
if isinstance(plan, PlanStructure):
|
|
147
|
+
self.plan = plan
|
|
148
|
+
self.save()
|
|
149
|
+
|
|
150
|
+
def execute_plan(self) -> List[str]:
|
|
151
|
+
"""Run each task, updating status, timestamps, and recorded results.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
list[str]
|
|
156
|
+
Flattened list of results from all executed tasks.
|
|
157
|
+
"""
|
|
158
|
+
log("execute_plan", level=logging.INFO)
|
|
159
|
+
if not self.plan:
|
|
160
|
+
log("No tasks to execute.", level=logging.WARNING)
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
compiled_results = self._execute_plan_fn(self.plan)
|
|
164
|
+
self.save()
|
|
165
|
+
return compiled_results
|
|
166
|
+
|
|
167
|
+
def summarize_plan(self, results: Optional[List[str]] = None) -> str:
|
|
168
|
+
"""Summarize a collection of task outputs.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
results : list[str], optional
|
|
173
|
+
List of string outputs gathered from task execution. Defaults to
|
|
174
|
+
``None``, which uses the stored plan task results if available.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
str
|
|
179
|
+
Concise summary derived from the provided results.
|
|
180
|
+
"""
|
|
181
|
+
log("summarize_plan", level=logging.INFO)
|
|
182
|
+
|
|
183
|
+
if results is None:
|
|
184
|
+
results = []
|
|
185
|
+
if self.plan and self.plan.tasks:
|
|
186
|
+
for task in self.plan.tasks:
|
|
187
|
+
results.extend(task.results or [])
|
|
188
|
+
|
|
189
|
+
if not results:
|
|
190
|
+
self.summary = ""
|
|
191
|
+
return self.summary
|
|
192
|
+
|
|
193
|
+
self.summary = self._summarize_fn(results)
|
|
194
|
+
self.end_date = datetime.now(timezone.utc)
|
|
195
|
+
self.save()
|
|
196
|
+
return self.summary
|
|
197
|
+
|
|
198
|
+
def run_plan(self, prompt: str) -> None:
|
|
199
|
+
"""Execute the full workflow for the provided prompt.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
prompt : str
|
|
204
|
+
The request or question to analyze and summarize.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
None
|
|
209
|
+
"""
|
|
210
|
+
self.build_prompt(prompt)
|
|
211
|
+
self.build_plan()
|
|
212
|
+
results = self.execute_plan()
|
|
213
|
+
self.summarize_plan(results)
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def file_path(self) -> Path:
|
|
217
|
+
"""Return the path where the project snapshot will be stored.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
Path
|
|
222
|
+
Location of the JSON artifact for the current run.
|
|
223
|
+
"""
|
|
224
|
+
if not self.start_date:
|
|
225
|
+
self.start_date = datetime.now(timezone.utc)
|
|
226
|
+
start_date_str = self.start_date.strftime(DATETIME_FMT)
|
|
227
|
+
return self._module_data_path / self._module_name / f"{start_date_str}.json"
|
|
228
|
+
|
|
229
|
+
def save(self) -> Path:
|
|
230
|
+
"""Persist the current project state to disk.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
Path
|
|
235
|
+
Path to the saved JSON artifact.
|
|
236
|
+
"""
|
|
237
|
+
self.to_json_file(self.file_path)
|
|
238
|
+
return self.file_path
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def _run_task(
|
|
242
|
+
task: TaskStructure,
|
|
243
|
+
agent_callable: Callable[..., Any],
|
|
244
|
+
aggregated_context: List[str],
|
|
245
|
+
) -> Any:
|
|
246
|
+
"""Execute a single task and return the raw result.
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
task : TaskStructure
|
|
251
|
+
Task definition containing the callable and inputs.
|
|
252
|
+
agent_callable : Callable[..., Any]
|
|
253
|
+
Callable that executes the task prompt and returns a result.
|
|
254
|
+
aggregated_context : list[str]
|
|
255
|
+
Context combined from the task and prior task outputs.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
Any
|
|
260
|
+
Raw output from the underlying callable.
|
|
261
|
+
"""
|
|
262
|
+
task_type = ProjectManager._normalize_task_type(task.task_type)
|
|
263
|
+
prompt_with_context = task.prompt
|
|
264
|
+
if aggregated_context and task_type not in {"WebAgentSearch", "VectorSearch"}:
|
|
265
|
+
context_block = "\n".join(aggregated_context)
|
|
266
|
+
prompt_with_context = f"{task.prompt}\n\nContext:\n{context_block}"
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
if task_type == "summarizer":
|
|
270
|
+
summary_chunks: List[str] = [task.prompt] + aggregated_context
|
|
271
|
+
output = agent_callable(summary_chunks)
|
|
272
|
+
elif task_type in {"WebAgentSearch", "VectorSearch"}:
|
|
273
|
+
output = agent_callable(task.prompt)
|
|
274
|
+
else:
|
|
275
|
+
output = agent_callable(
|
|
276
|
+
prompt_with_context,
|
|
277
|
+
context=aggregated_context,
|
|
278
|
+
)
|
|
279
|
+
except TypeError:
|
|
280
|
+
output = agent_callable(prompt_with_context)
|
|
281
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
282
|
+
log(
|
|
283
|
+
f"Task '{task.task_type}' encountered an error: {exc}",
|
|
284
|
+
level=logging.ERROR,
|
|
285
|
+
)
|
|
286
|
+
return f"Task error: {exc}"
|
|
287
|
+
return ProjectManager._resolve_result(output)
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def _run_task_in_thread(
|
|
291
|
+
task: TaskStructure,
|
|
292
|
+
agent_callable: Callable[..., Any],
|
|
293
|
+
aggregated_context: List[str],
|
|
294
|
+
) -> Any:
|
|
295
|
+
"""Execute a task in a background thread to avoid event-loop conflicts.
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
task : TaskStructure
|
|
300
|
+
Task definition containing the callable and inputs.
|
|
301
|
+
agent_callable : Callable[..., Any]
|
|
302
|
+
Callable that executes the task prompt and returns a result.
|
|
303
|
+
aggregated_context : list[str]
|
|
304
|
+
Context combined from the task and prior task outputs.
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
Any
|
|
309
|
+
Resolved output from the underlying callable.
|
|
310
|
+
"""
|
|
311
|
+
result_container: Dict[str, Any] = {"result": None, "error": None}
|
|
312
|
+
|
|
313
|
+
def _runner() -> None:
|
|
314
|
+
try:
|
|
315
|
+
result_container["result"] = ProjectManager._run_task(
|
|
316
|
+
task,
|
|
317
|
+
agent_callable=agent_callable,
|
|
318
|
+
aggregated_context=aggregated_context,
|
|
319
|
+
)
|
|
320
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
|
321
|
+
result_container["error"] = exc
|
|
322
|
+
|
|
323
|
+
thread = threading.Thread(target=_runner)
|
|
324
|
+
thread.start()
|
|
325
|
+
thread.join()
|
|
326
|
+
if result_container["error"] is not None:
|
|
327
|
+
raise result_container["error"]
|
|
328
|
+
return result_container["result"]
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def _resolve_result(result: Any) -> Any:
|
|
332
|
+
"""Return awaited results when the callable is asynchronous.
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
result : Any
|
|
337
|
+
Potentially awaitable output from a task callable.
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
Any
|
|
342
|
+
Resolved output, awaited when necessary.
|
|
343
|
+
"""
|
|
344
|
+
if not inspect.isawaitable(result):
|
|
345
|
+
return result
|
|
346
|
+
|
|
347
|
+
if isinstance(result, (asyncio.Future, asyncio.Task)):
|
|
348
|
+
if result.done():
|
|
349
|
+
return result.result()
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
owning_loop = result.get_loop()
|
|
353
|
+
except AttributeError: # pragma: no cover - defensive guard
|
|
354
|
+
owning_loop = None
|
|
355
|
+
if owning_loop is not None and owning_loop.is_running():
|
|
356
|
+
try:
|
|
357
|
+
current_loop = asyncio.get_running_loop()
|
|
358
|
+
except RuntimeError:
|
|
359
|
+
current_loop = None
|
|
360
|
+
if current_loop is not None and current_loop is owning_loop:
|
|
361
|
+
raise RuntimeError(
|
|
362
|
+
"Cannot resolve a pending task from its owning running event loop; "
|
|
363
|
+
"await the task instead."
|
|
364
|
+
)
|
|
365
|
+
return asyncio.run_coroutine_threadsafe(
|
|
366
|
+
ProjectManager._await_wrapper(result), owning_loop
|
|
367
|
+
).result()
|
|
368
|
+
|
|
369
|
+
awaitable: asyncio.Future[Any] | asyncio.Task[Any] | Any = result
|
|
370
|
+
coroutine = (
|
|
371
|
+
awaitable
|
|
372
|
+
if inspect.iscoroutine(awaitable)
|
|
373
|
+
else ProjectManager._await_wrapper(awaitable)
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
loop = asyncio.get_running_loop()
|
|
378
|
+
except RuntimeError:
|
|
379
|
+
return asyncio.run(coroutine)
|
|
380
|
+
|
|
381
|
+
if loop.is_running():
|
|
382
|
+
resolved_result: Any = None
|
|
383
|
+
|
|
384
|
+
def _run_in_thread() -> None:
|
|
385
|
+
nonlocal resolved_result
|
|
386
|
+
resolved_result = asyncio.run(coroutine)
|
|
387
|
+
|
|
388
|
+
thread = threading.Thread(target=_run_in_thread, daemon=True)
|
|
389
|
+
thread.start()
|
|
390
|
+
thread.join()
|
|
391
|
+
return resolved_result
|
|
392
|
+
|
|
393
|
+
return loop.run_until_complete(coroutine)
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
async def _await_wrapper(awaitable: Any) -> Any:
|
|
397
|
+
"""Await a generic awaitable and return its result.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
awaitable : Any
|
|
402
|
+
Awaitable object to resolve.
|
|
403
|
+
|
|
404
|
+
Returns
|
|
405
|
+
-------
|
|
406
|
+
Any
|
|
407
|
+
Result of the awaited object.
|
|
408
|
+
"""
|
|
409
|
+
return await awaitable
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def _normalize_results(result: Any) -> List[str]:
|
|
413
|
+
"""Convert agent outputs into a list of strings.
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
result : Any
|
|
418
|
+
Raw output from a task execution.
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
list[str]
|
|
423
|
+
Normalized string values representing the output.
|
|
424
|
+
"""
|
|
425
|
+
if result is None:
|
|
426
|
+
return []
|
|
427
|
+
if isinstance(result, list):
|
|
428
|
+
return [str(item) for item in result]
|
|
429
|
+
return [str(result)]
|
|
430
|
+
|
|
431
|
+
def _persist_task_results(self, task: TaskStructure) -> Path:
|
|
432
|
+
"""Write task context and results to disk for future analysis.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
task : TaskStructure
|
|
437
|
+
Task definition containing the callable and inputs.
|
|
438
|
+
|
|
439
|
+
Returns
|
|
440
|
+
-------
|
|
441
|
+
Path
|
|
442
|
+
Location where the task artifact was saved.
|
|
443
|
+
"""
|
|
444
|
+
run_dir = self._get_run_directory()
|
|
445
|
+
task_label = self._task_label(task)
|
|
446
|
+
file_path = run_dir / f"{task_label}.json"
|
|
447
|
+
task.to_json_file(str(file_path))
|
|
448
|
+
return file_path
|
|
449
|
+
|
|
450
|
+
def _get_run_directory(self) -> Path:
|
|
451
|
+
"""Return (and create) the directory used to persist task artifacts.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
Path
|
|
456
|
+
Directory where task outputs are stored for the run.
|
|
457
|
+
"""
|
|
458
|
+
if not hasattr(self, "_run_directory"):
|
|
459
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
|
460
|
+
self._run_directory = (
|
|
461
|
+
self._module_data_path
|
|
462
|
+
/ Path(self._module_name)
|
|
463
|
+
/ "project_manager"
|
|
464
|
+
/ timestamp
|
|
465
|
+
)
|
|
466
|
+
self._run_directory.mkdir(parents=True, exist_ok=True)
|
|
467
|
+
return self._run_directory
|
|
468
|
+
|
|
469
|
+
@staticmethod
|
|
470
|
+
def _task_label(task: TaskStructure) -> str:
|
|
471
|
+
"""Generate a filesystem-safe label for the task.
|
|
472
|
+
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
task : TaskStructure
|
|
476
|
+
Task definition containing the callable and inputs.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
str
|
|
481
|
+
Lowercase label safe for filesystem usage.
|
|
482
|
+
"""
|
|
483
|
+
task_type = ProjectManager._normalize_task_type(task.task_type)
|
|
484
|
+
base = (task_type or "task").replace(" ", "_").lower()
|
|
485
|
+
return f"{base}_{task_type}"
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def _normalize_task_type(task_type: AgentEnum | str) -> str:
|
|
489
|
+
"""Return the normalized task type string.
|
|
490
|
+
|
|
491
|
+
Parameters
|
|
492
|
+
----------
|
|
493
|
+
task_type : AgentEnum or str
|
|
494
|
+
Task classification to normalize.
|
|
495
|
+
|
|
496
|
+
Returns
|
|
497
|
+
-------
|
|
498
|
+
str
|
|
499
|
+
String representation of the task type.
|
|
500
|
+
"""
|
|
501
|
+
if isinstance(task_type, AgentEnum):
|
|
502
|
+
return task_type.value
|
|
503
|
+
if task_type in AgentEnum.__members__:
|
|
504
|
+
return AgentEnum.__members__[task_type].value
|
|
505
|
+
try:
|
|
506
|
+
return AgentEnum(task_type).value
|
|
507
|
+
except ValueError:
|
|
508
|
+
return str(task_type)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
__all__ = ["ProjectManager"]
|