openai-sdk-helpers 0.0.2__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 +34 -0
- openai_sdk_helpers/agent/__init__.py +23 -0
- openai_sdk_helpers/agent/base.py +432 -0
- openai_sdk_helpers/agent/config.py +66 -0
- openai_sdk_helpers/agent/project_manager.py +416 -0
- openai_sdk_helpers/agent/runner.py +117 -0
- openai_sdk_helpers/agent/utils.py +47 -0
- openai_sdk_helpers/agent/vector_search.py +418 -0
- openai_sdk_helpers/agent/web_search.py +404 -0
- openai_sdk_helpers/config.py +141 -0
- openai_sdk_helpers/enums/__init__.py +7 -0
- openai_sdk_helpers/enums/base.py +17 -0
- openai_sdk_helpers/environment.py +27 -0
- openai_sdk_helpers/prompt/__init__.py +77 -0
- openai_sdk_helpers/response/__init__.py +16 -0
- openai_sdk_helpers/response/base.py +477 -0
- openai_sdk_helpers/response/messages.py +211 -0
- openai_sdk_helpers/response/runner.py +42 -0
- openai_sdk_helpers/response/tool_call.py +70 -0
- openai_sdk_helpers/structure/__init__.py +57 -0
- openai_sdk_helpers/structure/base.py +591 -0
- openai_sdk_helpers/structure/plan/__init__.py +13 -0
- openai_sdk_helpers/structure/plan/enum.py +48 -0
- openai_sdk_helpers/structure/plan/plan.py +104 -0
- openai_sdk_helpers/structure/plan/task.py +122 -0
- openai_sdk_helpers/structure/prompt.py +24 -0
- openai_sdk_helpers/structure/responses.py +148 -0
- openai_sdk_helpers/structure/summary.py +65 -0
- openai_sdk_helpers/structure/vector_search.py +82 -0
- openai_sdk_helpers/structure/web_search.py +46 -0
- openai_sdk_helpers/utils/__init__.py +13 -0
- openai_sdk_helpers/utils/core.py +208 -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 +501 -0
- openai_sdk_helpers/vector_storage/types.py +58 -0
- openai_sdk_helpers-0.0.2.dist-info/METADATA +137 -0
- openai_sdk_helpers-0.0.2.dist-info/RECORD +40 -0
- openai_sdk_helpers-0.0.2.dist-info/WHEEL +4 -0
- openai_sdk_helpers-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
"""Base structure definitions for shared agent structures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# Standard library imports
|
|
6
|
+
import inspect
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
from typing import (
|
|
14
|
+
Any,
|
|
15
|
+
ClassVar,
|
|
16
|
+
Dict,
|
|
17
|
+
List,
|
|
18
|
+
Optional,
|
|
19
|
+
Type,
|
|
20
|
+
TypeVar,
|
|
21
|
+
Union,
|
|
22
|
+
get_args,
|
|
23
|
+
get_origin,
|
|
24
|
+
cast,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Third-party imports
|
|
28
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
29
|
+
|
|
30
|
+
# Internal imports
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from ..utils import check_filepath, customJSONEncoder, log
|
|
33
|
+
|
|
34
|
+
T = TypeVar("T", bound="BaseStructure")
|
|
35
|
+
DEFAULT_DATA_PATH: Path | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BaseStructure(BaseModel):
|
|
39
|
+
"""Base class for defining structured output formats for OpenAI Assistants.
|
|
40
|
+
|
|
41
|
+
This class provides Pydantic-based schema definition and serialization
|
|
42
|
+
helpers that support structured output formatting.
|
|
43
|
+
|
|
44
|
+
Methods
|
|
45
|
+
-------
|
|
46
|
+
get_prompt(add_enum_values)
|
|
47
|
+
Format structured prompt lines into a single output string.
|
|
48
|
+
get_input_prompt_list(add_enum_values)
|
|
49
|
+
Build a structured prompt including inherited fields.
|
|
50
|
+
get_schema(force_required)
|
|
51
|
+
Generate a JSON schema for the structure.
|
|
52
|
+
save_schema_to_file(force_required)
|
|
53
|
+
Persist the schema to disk within the application data path.
|
|
54
|
+
to_json()
|
|
55
|
+
Serialize the structure to a JSON-compatible dictionary.
|
|
56
|
+
to_json_file(filepath)
|
|
57
|
+
Write the serialized payload to ``filepath``.
|
|
58
|
+
from_raw_input(data)
|
|
59
|
+
Construct an instance from raw assistant tool-call arguments.
|
|
60
|
+
format_output(label, value)
|
|
61
|
+
Format a label/value pair for console output.
|
|
62
|
+
schema_overrides()
|
|
63
|
+
Produce ``Field`` overrides for dynamic schema customisation.
|
|
64
|
+
print()
|
|
65
|
+
Return a string representation of the structure.
|
|
66
|
+
console_print()
|
|
67
|
+
Print the string representation to stdout.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
model_config = ConfigDict(
|
|
71
|
+
title="OutputStructure", use_enum_values=False, strict=True, extra="forbid"
|
|
72
|
+
)
|
|
73
|
+
DATA_PATH: ClassVar[Path | None] = DEFAULT_DATA_PATH
|
|
74
|
+
"""Optional location for saving schema files."""
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def get_prompt(cls, add_enum_values: bool = True) -> str:
|
|
78
|
+
"""Format structured prompt lines into a single output string.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
add_enum_values : bool, default=True
|
|
83
|
+
Whether enum choices should be included in the prompt lines.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
str
|
|
88
|
+
Formatted prompt ready for display.
|
|
89
|
+
"""
|
|
90
|
+
prompt_lines = cls.get_input_prompt_list(add_enum_values)
|
|
91
|
+
if not prompt_lines:
|
|
92
|
+
return "No structured prompt available."
|
|
93
|
+
return "# Output Format\n" + "\n".join(prompt_lines)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def _get_all_fields(cls) -> dict[Any, Any]:
|
|
97
|
+
"""Collect all fields, including inherited ones, from the class hierarchy.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
dict[Any, Any]
|
|
102
|
+
Mapping of field names to model fields.
|
|
103
|
+
"""
|
|
104
|
+
fields = {}
|
|
105
|
+
for base in reversed(cls.__mro__): # Traverse inheritance tree
|
|
106
|
+
if issubclass(base, BaseModel) and hasattr(base, "model_fields"):
|
|
107
|
+
fields.update(base.model_fields) # Merge fields from parent
|
|
108
|
+
return fields
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def _get_field_prompt(
|
|
112
|
+
cls, field_name: str, field, add_enum_values: bool = True
|
|
113
|
+
) -> str:
|
|
114
|
+
"""Return a formatted prompt line for a single field.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
field_name : str
|
|
119
|
+
Name of the field being processed.
|
|
120
|
+
field
|
|
121
|
+
Pydantic ``ModelField`` instance.
|
|
122
|
+
add_enum_values : bool, default=True
|
|
123
|
+
Whether enum choices should be included.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
str
|
|
128
|
+
Single line describing the field for inclusion in the prompt.
|
|
129
|
+
"""
|
|
130
|
+
title = field.title or field_name.capitalize()
|
|
131
|
+
description = field.description or f"Provide relevant {field_name}."
|
|
132
|
+
type_hint = field.annotation
|
|
133
|
+
|
|
134
|
+
# Check for enums or list of enums
|
|
135
|
+
enum_cls = cls._extract_enum_class(type_hint)
|
|
136
|
+
if enum_cls:
|
|
137
|
+
enum_choices_str = "\n\t\t• ".join(f"{e.name}: {e.value}" for e in enum_cls)
|
|
138
|
+
if add_enum_values:
|
|
139
|
+
enum_prompt = f" \n\t Choose from: \n\t\t• {enum_choices_str}"
|
|
140
|
+
else:
|
|
141
|
+
enum_prompt = ""
|
|
142
|
+
|
|
143
|
+
return f"- **{title}**: {description}{enum_prompt}"
|
|
144
|
+
|
|
145
|
+
# Otherwise check normal types
|
|
146
|
+
type_mapping = {
|
|
147
|
+
str: f"- **{title}**: {description}",
|
|
148
|
+
bool: f"- **{title}**: {description} Specify if the {title} is true or false.",
|
|
149
|
+
int: f"- **{title}**: {description} Provide the relevant integer value for {title}.",
|
|
150
|
+
float: f"- **{title}**: {description} Provide the relevant float value for {title}.",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return type_mapping.get(
|
|
154
|
+
type_hint, f"- **{title}**: Provide the relevant {title}."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def get_input_prompt_list(cls, add_enum_values: bool = True) -> list[str]:
|
|
159
|
+
"""Dynamically build a structured prompt including inherited fields.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
add_enum_values : bool, default=True
|
|
164
|
+
Whether enumeration values should be included.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
list[str]
|
|
169
|
+
Prompt lines describing each field.
|
|
170
|
+
"""
|
|
171
|
+
prompt_lines = []
|
|
172
|
+
all_fields = cls._get_all_fields()
|
|
173
|
+
for field_name, field in all_fields.items():
|
|
174
|
+
prompt_lines.append(
|
|
175
|
+
cls._get_field_prompt(field_name, field, add_enum_values)
|
|
176
|
+
)
|
|
177
|
+
return prompt_lines
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def get_schema(cls, force_required: bool = False) -> dict[str, Any]:
|
|
181
|
+
"""Generate a JSON schema for the class.
|
|
182
|
+
|
|
183
|
+
Required fields remain driven by Pydantic's schema generation defaults.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
force_required : bool, default=False
|
|
188
|
+
When ``True``, mark all object properties as required.
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
dict[str, Any]
|
|
193
|
+
JSON schema describing the structure.
|
|
194
|
+
"""
|
|
195
|
+
schema = cls.model_json_schema()
|
|
196
|
+
|
|
197
|
+
def clean_refs(obj):
|
|
198
|
+
if isinstance(obj, dict):
|
|
199
|
+
if "$ref" in obj:
|
|
200
|
+
for key in list(obj.keys()):
|
|
201
|
+
if key != "$ref":
|
|
202
|
+
obj.pop(key, None)
|
|
203
|
+
for v in obj.values():
|
|
204
|
+
clean_refs(v)
|
|
205
|
+
elif isinstance(obj, list):
|
|
206
|
+
for item in obj:
|
|
207
|
+
clean_refs(item)
|
|
208
|
+
return obj
|
|
209
|
+
|
|
210
|
+
cleaned_schema = cast(Dict[str, Any], clean_refs(schema))
|
|
211
|
+
|
|
212
|
+
if force_required:
|
|
213
|
+
return cls.apply_required_fields(cleaned_schema)
|
|
214
|
+
|
|
215
|
+
return cleaned_schema
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def apply_required_fields(schema: dict[str, Any]) -> dict[str, Any]:
|
|
219
|
+
"""Force all object properties to be marked as required.
|
|
220
|
+
|
|
221
|
+
This mirrors the previous behavior that treated every field as required
|
|
222
|
+
in downstream JSON schemas.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
schema : dict[str, Any]
|
|
227
|
+
JSON schema to update in place.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
dict[str, Any]
|
|
232
|
+
Updated schema with required fields set for all objects.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def add_required_fields(target: dict[str, Any]) -> None:
|
|
236
|
+
"""Ensure every object declares its required properties."""
|
|
237
|
+
properties = target.get("properties")
|
|
238
|
+
if isinstance(properties, dict) and properties:
|
|
239
|
+
target["required"] = sorted(properties.keys())
|
|
240
|
+
for value in target.values():
|
|
241
|
+
if isinstance(value, dict):
|
|
242
|
+
add_required_fields(value)
|
|
243
|
+
elif isinstance(value, list):
|
|
244
|
+
for item in value:
|
|
245
|
+
if isinstance(item, dict):
|
|
246
|
+
add_required_fields(item)
|
|
247
|
+
|
|
248
|
+
add_required_fields(schema)
|
|
249
|
+
return schema
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def save_schema_to_file(cls, force_required: bool = False) -> Path:
|
|
253
|
+
"""
|
|
254
|
+
Save the generated JSON schema to a file.
|
|
255
|
+
|
|
256
|
+
The schema is generated using :meth:`get_schema` and saved in the
|
|
257
|
+
application's data path.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
force_required : bool, default=False
|
|
262
|
+
When ``True``, mark all object properties as required.
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
Path
|
|
267
|
+
Path to the saved schema file.
|
|
268
|
+
"""
|
|
269
|
+
schema = cls.get_schema(force_required=force_required)
|
|
270
|
+
if cls.DATA_PATH is None:
|
|
271
|
+
raise RuntimeError(
|
|
272
|
+
"DATA_PATH is not set. Set BaseStructure.DATA_PATH before saving."
|
|
273
|
+
)
|
|
274
|
+
file_path = cls.DATA_PATH / f"{cls.__name__}_schema.json"
|
|
275
|
+
check_filepath(file_path)
|
|
276
|
+
with file_path.open("w", encoding="utf-8") as file_handle:
|
|
277
|
+
json.dump(schema, file_handle, indent=2, ensure_ascii=False)
|
|
278
|
+
return file_path
|
|
279
|
+
|
|
280
|
+
def to_json(self) -> Dict[str, Any]:
|
|
281
|
+
"""
|
|
282
|
+
Serialize the Pydantic model instance to a JSON-compatible dictionary.
|
|
283
|
+
|
|
284
|
+
Enum members are converted to their values. Lists and nested dictionaries
|
|
285
|
+
are recursively processed.
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
dict[str, Any]
|
|
290
|
+
Model instance serialized as a dictionary.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def convert(obj: Any) -> Any:
|
|
294
|
+
if isinstance(obj, Enum):
|
|
295
|
+
return obj.value
|
|
296
|
+
if isinstance(obj, BaseStructure):
|
|
297
|
+
return obj.to_json()
|
|
298
|
+
if isinstance(obj, Mapping):
|
|
299
|
+
return {str(k): convert(v) for k, v in obj.items()}
|
|
300
|
+
if isinstance(obj, Sequence) and not isinstance(
|
|
301
|
+
obj, (str, bytes, bytearray)
|
|
302
|
+
):
|
|
303
|
+
return [convert(item) for item in obj]
|
|
304
|
+
return obj
|
|
305
|
+
|
|
306
|
+
payload = convert(self.model_dump())
|
|
307
|
+
|
|
308
|
+
def is_list_field(field) -> bool:
|
|
309
|
+
annotation = getattr(field, "annotation", None)
|
|
310
|
+
if annotation is None:
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
origins_to_match = {list, List, Sequence, tuple, set}
|
|
314
|
+
|
|
315
|
+
origin = get_origin(annotation)
|
|
316
|
+
if origin in origins_to_match or annotation in origins_to_match:
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
if origin is Union:
|
|
320
|
+
return any(
|
|
321
|
+
get_origin(arg) in origins_to_match or arg in origins_to_match
|
|
322
|
+
for arg in get_args(annotation)
|
|
323
|
+
)
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
for name, field in self.model_fields.items():
|
|
327
|
+
if name not in payload:
|
|
328
|
+
continue
|
|
329
|
+
if not is_list_field(field):
|
|
330
|
+
continue
|
|
331
|
+
value = payload[name]
|
|
332
|
+
if value is None:
|
|
333
|
+
continue
|
|
334
|
+
if isinstance(value, (str, bytes, bytearray)):
|
|
335
|
+
payload[name] = [value]
|
|
336
|
+
elif not isinstance(value, list):
|
|
337
|
+
payload[name] = [value]
|
|
338
|
+
|
|
339
|
+
return payload
|
|
340
|
+
|
|
341
|
+
def to_json_file(self, filepath: str) -> str:
|
|
342
|
+
"""Write :meth:`to_json` output to ``filepath``.
|
|
343
|
+
|
|
344
|
+
Parameters
|
|
345
|
+
----------
|
|
346
|
+
filepath : str
|
|
347
|
+
Destination path for the JSON file.
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
str
|
|
352
|
+
Path to the written file.
|
|
353
|
+
"""
|
|
354
|
+
check_filepath(fullfilepath=filepath)
|
|
355
|
+
with open(file=filepath, mode="w", encoding="utf-8") as f:
|
|
356
|
+
json.dump(
|
|
357
|
+
self.to_json(), f, ensure_ascii=False, indent=4, cls=customJSONEncoder
|
|
358
|
+
)
|
|
359
|
+
return filepath
|
|
360
|
+
|
|
361
|
+
@classmethod
|
|
362
|
+
def _extract_enum_class(cls, field_type: Any) -> Optional[Type[Enum]]:
|
|
363
|
+
"""
|
|
364
|
+
Extract an Enum class from a field's type annotation.
|
|
365
|
+
|
|
366
|
+
Handles direct Enum types, List[Enum], and Optional[Enum] (via Union).
|
|
367
|
+
|
|
368
|
+
Parameters
|
|
369
|
+
----------
|
|
370
|
+
field_type
|
|
371
|
+
Type annotation of a field.
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
type[Enum] or None
|
|
376
|
+
Enum class if found, otherwise ``None``.
|
|
377
|
+
"""
|
|
378
|
+
origin = get_origin(field_type)
|
|
379
|
+
args = get_args(field_type)
|
|
380
|
+
|
|
381
|
+
if inspect.isclass(field_type) and issubclass(field_type, Enum):
|
|
382
|
+
return field_type
|
|
383
|
+
elif (
|
|
384
|
+
origin in {list, List}
|
|
385
|
+
and args
|
|
386
|
+
and inspect.isclass(args[0])
|
|
387
|
+
and issubclass(args[0], Enum)
|
|
388
|
+
):
|
|
389
|
+
return args[0]
|
|
390
|
+
elif origin is Union:
|
|
391
|
+
for arg in args:
|
|
392
|
+
enum_cls = cls._extract_enum_class(arg)
|
|
393
|
+
if enum_cls:
|
|
394
|
+
return enum_cls
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
@classmethod
|
|
398
|
+
def _build_enum_field_mapping(cls) -> dict[str, Type[Enum]]:
|
|
399
|
+
"""
|
|
400
|
+
Build a mapping from field names to their Enum classes.
|
|
401
|
+
|
|
402
|
+
This is used by `from_raw_input` to correctly process enum values.
|
|
403
|
+
|
|
404
|
+
Returns
|
|
405
|
+
-------
|
|
406
|
+
dict[str, type[Enum]]
|
|
407
|
+
Mapping of field names to Enum types.
|
|
408
|
+
"""
|
|
409
|
+
mapping: dict[str, Type[Enum]] = {}
|
|
410
|
+
|
|
411
|
+
for name, model_field in cls.model_fields.items():
|
|
412
|
+
field_type = model_field.annotation
|
|
413
|
+
enum_cls = cls._extract_enum_class(field_type)
|
|
414
|
+
|
|
415
|
+
if enum_cls is not None:
|
|
416
|
+
mapping[name] = enum_cls
|
|
417
|
+
|
|
418
|
+
return mapping
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
def from_raw_input(cls: Type[T], data: dict) -> T:
|
|
422
|
+
"""
|
|
423
|
+
Construct an instance of the class from a dictionary of raw input data.
|
|
424
|
+
|
|
425
|
+
This method is particularly useful for converting data received from an
|
|
426
|
+
OpenAI Assistant (e.g., tool call arguments) into a Pydantic model.
|
|
427
|
+
It handles the conversion of string values to Enum members for fields
|
|
428
|
+
typed as Enum or List[Enum]. Warnings are logged for invalid enum values.
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
data : dict
|
|
433
|
+
Raw input data payload.
|
|
434
|
+
|
|
435
|
+
Returns
|
|
436
|
+
-------
|
|
437
|
+
T
|
|
438
|
+
Instance populated with the processed data.
|
|
439
|
+
"""
|
|
440
|
+
mapping = cls._build_enum_field_mapping()
|
|
441
|
+
clean_data = data.copy()
|
|
442
|
+
|
|
443
|
+
for field, enum_cls in mapping.items():
|
|
444
|
+
raw_value = clean_data.get(field)
|
|
445
|
+
|
|
446
|
+
if raw_value is None:
|
|
447
|
+
continue
|
|
448
|
+
|
|
449
|
+
# List of enum values
|
|
450
|
+
if isinstance(raw_value, list):
|
|
451
|
+
converted = []
|
|
452
|
+
for v in raw_value:
|
|
453
|
+
if isinstance(v, enum_cls):
|
|
454
|
+
converted.append(v)
|
|
455
|
+
elif isinstance(v, str):
|
|
456
|
+
# Check if it's a valid value
|
|
457
|
+
if v in enum_cls._value2member_map_:
|
|
458
|
+
converted.append(enum_cls(v))
|
|
459
|
+
# Check if it's a valid name
|
|
460
|
+
elif v in enum_cls.__members__:
|
|
461
|
+
converted.append(enum_cls.__members__[v])
|
|
462
|
+
else:
|
|
463
|
+
log(
|
|
464
|
+
f"[{cls.__name__}] Skipping invalid value for '{field}': '{v}' not in {enum_cls.__name__}",
|
|
465
|
+
level=logging.WARNING,
|
|
466
|
+
)
|
|
467
|
+
clean_data[field] = converted
|
|
468
|
+
|
|
469
|
+
# Single enum value
|
|
470
|
+
elif (
|
|
471
|
+
isinstance(raw_value, str) and raw_value in enum_cls._value2member_map_
|
|
472
|
+
):
|
|
473
|
+
clean_data[field] = enum_cls(raw_value)
|
|
474
|
+
|
|
475
|
+
elif isinstance(raw_value, enum_cls):
|
|
476
|
+
# already the correct type
|
|
477
|
+
continue
|
|
478
|
+
|
|
479
|
+
else:
|
|
480
|
+
log(
|
|
481
|
+
message=f"[{cls.__name__}] Invalid value for '{field}': '{raw_value}' not in {enum_cls.__name__}",
|
|
482
|
+
level=logging.WARNING,
|
|
483
|
+
)
|
|
484
|
+
clean_data[field] = None
|
|
485
|
+
|
|
486
|
+
return cls(**clean_data)
|
|
487
|
+
|
|
488
|
+
@staticmethod
|
|
489
|
+
def format_output(label: str, value: Any) -> str:
|
|
490
|
+
"""
|
|
491
|
+
Format a label and value for string output.
|
|
492
|
+
|
|
493
|
+
Handles None values and lists appropriately.
|
|
494
|
+
|
|
495
|
+
Parameters
|
|
496
|
+
----------
|
|
497
|
+
label : str
|
|
498
|
+
Label describing the value.
|
|
499
|
+
value : Any
|
|
500
|
+
Value to format for display.
|
|
501
|
+
|
|
502
|
+
Returns
|
|
503
|
+
-------
|
|
504
|
+
str
|
|
505
|
+
Formatted string (for example ``"- Label: Value"``).
|
|
506
|
+
"""
|
|
507
|
+
if not value:
|
|
508
|
+
return f"- {label}: None"
|
|
509
|
+
if isinstance(value, list):
|
|
510
|
+
return f"- {label}: {', '.join(str(v) for v in value)}"
|
|
511
|
+
return f"- {label}: {str(value)}"
|
|
512
|
+
|
|
513
|
+
@classmethod
|
|
514
|
+
def schema_overrides(cls) -> Dict[str, Any]:
|
|
515
|
+
"""
|
|
516
|
+
Generate Pydantic ``Field`` overrides.
|
|
517
|
+
|
|
518
|
+
Returns
|
|
519
|
+
-------
|
|
520
|
+
dict[str, Any]
|
|
521
|
+
Mapping of field names to ``Field`` overrides.
|
|
522
|
+
"""
|
|
523
|
+
return {}
|
|
524
|
+
|
|
525
|
+
def print(self) -> str:
|
|
526
|
+
"""
|
|
527
|
+
Generate a string representation of the structure.
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
-------
|
|
531
|
+
str
|
|
532
|
+
Formatted string for the ``logic`` field.
|
|
533
|
+
"""
|
|
534
|
+
return "\n".join(
|
|
535
|
+
[
|
|
536
|
+
BaseStructure.format_output(field, value)
|
|
537
|
+
for field, value in self.model_dump().items()
|
|
538
|
+
]
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
def console_print(self) -> None:
|
|
542
|
+
"""Output the result of :meth:`print` to stdout.
|
|
543
|
+
|
|
544
|
+
Returns
|
|
545
|
+
-------
|
|
546
|
+
None
|
|
547
|
+
"""
|
|
548
|
+
print(self.print())
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@dataclass(frozen=True)
|
|
552
|
+
class SchemaOptions:
|
|
553
|
+
"""Options for schema generation helpers.
|
|
554
|
+
|
|
555
|
+
Parameters
|
|
556
|
+
----------
|
|
557
|
+
force_required : bool, default=False
|
|
558
|
+
When ``True``, mark all object properties as required.
|
|
559
|
+
"""
|
|
560
|
+
|
|
561
|
+
force_required: bool = False
|
|
562
|
+
|
|
563
|
+
def to_kwargs(self) -> dict[str, Any]:
|
|
564
|
+
"""Return keyword arguments for schema helper calls.
|
|
565
|
+
|
|
566
|
+
Returns
|
|
567
|
+
-------
|
|
568
|
+
dict[str, Any]
|
|
569
|
+
Keyword arguments for schema helper methods.
|
|
570
|
+
"""
|
|
571
|
+
return {"force_required": self.force_required}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def spec_field(name: str, **overrides: Any) -> Any:
|
|
575
|
+
"""Return a Pydantic ``Field`` configured with a default title.
|
|
576
|
+
|
|
577
|
+
Parameters
|
|
578
|
+
----------
|
|
579
|
+
name : str
|
|
580
|
+
Name of the field to use as the default title.
|
|
581
|
+
**overrides
|
|
582
|
+
Keyword arguments forwarded to ``pydantic.Field``.
|
|
583
|
+
|
|
584
|
+
Returns
|
|
585
|
+
-------
|
|
586
|
+
Any
|
|
587
|
+
Pydantic ``Field`` configured with a default title.
|
|
588
|
+
"""
|
|
589
|
+
field_kwargs: Dict[str, Any] = {"title": name.replace("_", " ").title()}
|
|
590
|
+
field_kwargs.update(overrides)
|
|
591
|
+
return Field(**field_kwargs)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Structured output models for agent tasks and plans."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .enum import AgentEnum
|
|
6
|
+
from .plan import PlanStructure
|
|
7
|
+
from .task import AgentTaskStructure
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AgentEnum",
|
|
11
|
+
"PlanStructure",
|
|
12
|
+
"AgentTaskStructure",
|
|
13
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Agent task enumeration definitions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ...enums.base import CrosswalkJSONEnum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentEnum(CrosswalkJSONEnum):
|
|
11
|
+
"""Auto-generated enumeration for AgentEnum.
|
|
12
|
+
|
|
13
|
+
Methods
|
|
14
|
+
-------
|
|
15
|
+
CROSSWALK()
|
|
16
|
+
Return the raw crosswalk data for this enum.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
WEB_SEARCH = "WebAgentSearch"
|
|
20
|
+
VECTOR_SEARCH = "VectorSearch"
|
|
21
|
+
DATA_ANALYST = "DataAnalyst"
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def CROSSWALK(cls) -> dict[str, dict[str, Any]]:
|
|
25
|
+
"""Return the raw crosswalk data for this enum.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
dict[str, dict[str, Any]]
|
|
30
|
+
Crosswalk mapping keyed by enum member.
|
|
31
|
+
|
|
32
|
+
Raises
|
|
33
|
+
------
|
|
34
|
+
None
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> AgentEnum.CROSSWALK()["WEB_SEARCH"]["value"]
|
|
39
|
+
'WebAgentSearch'
|
|
40
|
+
"""
|
|
41
|
+
return {
|
|
42
|
+
"WEB_SEARCH": {"value": "WebAgentSearch"},
|
|
43
|
+
"VECTOR_SEARCH": {"value": "VectorSearch"},
|
|
44
|
+
"DATA_ANALYST": {"value": "DataAnalyst"},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = ["AgentEnum"]
|