agent-framework-declarative 1.0.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.
- agent_framework_declarative/__init__.py +71 -0
- agent_framework_declarative/_loader.py +868 -0
- agent_framework_declarative/_models.py +1154 -0
- agent_framework_declarative/_workflows/__init__.py +167 -0
- agent_framework_declarative/_workflows/_declarative_base.py +1226 -0
- agent_framework_declarative/_workflows/_declarative_builder.py +1057 -0
- agent_framework_declarative/_workflows/_errors.py +38 -0
- agent_framework_declarative/_workflows/_executors_agents.py +1025 -0
- agent_framework_declarative/_workflows/_executors_basic.py +574 -0
- agent_framework_declarative/_workflows/_executors_control_flow.py +461 -0
- agent_framework_declarative/_workflows/_executors_external_input.py +243 -0
- agent_framework_declarative/_workflows/_executors_http.py +417 -0
- agent_framework_declarative/_workflows/_executors_mcp.py +549 -0
- agent_framework_declarative/_workflows/_executors_tools.py +660 -0
- agent_framework_declarative/_workflows/_factory.py +808 -0
- agent_framework_declarative/_workflows/_http_handler.py +237 -0
- agent_framework_declarative/_workflows/_mcp_handler.py +581 -0
- agent_framework_declarative/_workflows/_powerfx_functions.py +498 -0
- agent_framework_declarative/_workflows/_state.py +650 -0
- agent_framework_declarative-1.0.0.dist-info/METADATA +49 -0
- agent_framework_declarative-1.0.0.dist-info/RECORD +23 -0
- agent_framework_declarative-1.0.0.dist-info/WHEEL +4 -0
- agent_framework_declarative-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import MutableMapping
|
|
7
|
+
from contextvars import ContextVar
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast, overload
|
|
9
|
+
|
|
10
|
+
from agent_framework._serialization import SerializationMixin
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from powerfx import Engine
|
|
14
|
+
|
|
15
|
+
_engine_initialized = False
|
|
16
|
+
_engine: Engine | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_engine() -> Engine | None:
|
|
20
|
+
"""Lazily initialize the PowerFx engine on first use."""
|
|
21
|
+
global _engine_initialized, _engine
|
|
22
|
+
if not _engine_initialized:
|
|
23
|
+
_engine_initialized = True
|
|
24
|
+
try:
|
|
25
|
+
from powerfx import Engine
|
|
26
|
+
|
|
27
|
+
_engine = Engine()
|
|
28
|
+
except (ImportError, RuntimeError):
|
|
29
|
+
# ImportError: powerfx package not installed
|
|
30
|
+
# RuntimeError: .NET runtime not available or misconfigured
|
|
31
|
+
pass
|
|
32
|
+
return _engine
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger("agent_framework.declarative")
|
|
36
|
+
|
|
37
|
+
# Context variable for safe_mode setting.
|
|
38
|
+
# When True (default), environment variables are NOT accessible in PowerFx expressions.
|
|
39
|
+
# When False, environment variables CAN be accessed via Env symbol in PowerFx.
|
|
40
|
+
_safe_mode_context: ContextVar[bool] = ContextVar("safe_mode", default=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@overload
|
|
44
|
+
def _try_powerfx_eval(value: None, log_value: bool = True) -> None: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@overload
|
|
48
|
+
def _try_powerfx_eval(value: str, log_value: bool = True) -> str: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _try_powerfx_eval(value: str | None, log_value: bool = True) -> str | None:
|
|
52
|
+
"""Check if a value refers to a environment variable and parse it if so.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
value: The value to check.
|
|
56
|
+
log_value: Whether to log additional context on error.
|
|
57
|
+
"""
|
|
58
|
+
if value is None:
|
|
59
|
+
return value
|
|
60
|
+
if not value.startswith("="):
|
|
61
|
+
return value
|
|
62
|
+
engine = _get_engine()
|
|
63
|
+
if engine is None:
|
|
64
|
+
logger.warning(
|
|
65
|
+
"PowerFx engine not available for evaluating values starting with '='. "
|
|
66
|
+
"Ensure you are on python 3.13 or less and have the powerfx package installed. "
|
|
67
|
+
"Otherwise replace all powerfx statements in your yaml with strings."
|
|
68
|
+
)
|
|
69
|
+
return value
|
|
70
|
+
try:
|
|
71
|
+
safe_mode = _safe_mode_context.get()
|
|
72
|
+
if safe_mode:
|
|
73
|
+
return engine.eval(value[1:])
|
|
74
|
+
return engine.eval(value[1:], symbols={"Env": dict(os.environ)})
|
|
75
|
+
except Exception as exc:
|
|
76
|
+
if log_value:
|
|
77
|
+
logger.debug("PowerFx evaluation failed for a value: %s", exc)
|
|
78
|
+
else:
|
|
79
|
+
logger.debug("PowerFx evaluation failed for a value (details redacted): %s", exc)
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Binding(SerializationMixin):
|
|
84
|
+
"""Object representing a tool argument binding."""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
name: str | None = None,
|
|
89
|
+
input: str | None = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
self.name = _try_powerfx_eval(name)
|
|
92
|
+
self.input = _try_powerfx_eval(input)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Property(SerializationMixin):
|
|
96
|
+
"""Object representing a property in a schema."""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
name: str | None = None,
|
|
101
|
+
kind: str | None = None,
|
|
102
|
+
description: str | None = None,
|
|
103
|
+
required: bool | None = None,
|
|
104
|
+
default: Any | None = None,
|
|
105
|
+
example: Any | None = None,
|
|
106
|
+
enum: list[Any] | None = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
self.name = _try_powerfx_eval(name)
|
|
109
|
+
self.kind = _try_powerfx_eval(kind)
|
|
110
|
+
self.description = _try_powerfx_eval(description)
|
|
111
|
+
self.required = required
|
|
112
|
+
self.default = default
|
|
113
|
+
self.example = example
|
|
114
|
+
self.enum = enum or []
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_dict(
|
|
118
|
+
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
|
|
119
|
+
) -> Property:
|
|
120
|
+
"""Create a Property instance from a dictionary, dispatching to the appropriate subclass."""
|
|
121
|
+
# Only dispatch if we're being called on the base Property class
|
|
122
|
+
if cls is not Property:
|
|
123
|
+
# We're being called on a subclass, use the normal from_dict
|
|
124
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
125
|
+
|
|
126
|
+
# The YAML spec uses 'type' for the data type, but Property stores it as 'kind'
|
|
127
|
+
if "type" in value:
|
|
128
|
+
if "kind" not in value:
|
|
129
|
+
value["kind"] = value.pop("type")
|
|
130
|
+
else:
|
|
131
|
+
value.pop("type")
|
|
132
|
+
kind = value.get("kind", "")
|
|
133
|
+
if kind == "array":
|
|
134
|
+
return ArrayProperty.from_dict(value, dependencies=dependencies)
|
|
135
|
+
if kind == "object":
|
|
136
|
+
return ObjectProperty.from_dict(value, dependencies=dependencies)
|
|
137
|
+
# Default to Property for kind="property" or empty
|
|
138
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ArrayProperty(Property):
|
|
142
|
+
"""Object representing an array property."""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
name: str | None = None,
|
|
147
|
+
kind: str = "array",
|
|
148
|
+
description: str | None = None,
|
|
149
|
+
required: bool | None = None,
|
|
150
|
+
default: Any | None = None,
|
|
151
|
+
example: Any | None = None,
|
|
152
|
+
enum: list[Any] | None = None,
|
|
153
|
+
items: Property | None = None,
|
|
154
|
+
) -> None:
|
|
155
|
+
super().__init__(
|
|
156
|
+
name=name,
|
|
157
|
+
kind=kind,
|
|
158
|
+
description=description,
|
|
159
|
+
required=required,
|
|
160
|
+
default=default,
|
|
161
|
+
example=example,
|
|
162
|
+
enum=enum,
|
|
163
|
+
)
|
|
164
|
+
if not isinstance(items, Property) and items is not None:
|
|
165
|
+
items = Property.from_dict(items)
|
|
166
|
+
self.items = items
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ObjectProperty(Property):
|
|
170
|
+
"""Object representing an object property."""
|
|
171
|
+
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
name: str | None = None,
|
|
175
|
+
kind: str = "object",
|
|
176
|
+
description: str | None = None,
|
|
177
|
+
required: bool | None = None,
|
|
178
|
+
default: Any | None = None,
|
|
179
|
+
example: Any | None = None,
|
|
180
|
+
enum: list[Any] | None = None,
|
|
181
|
+
properties: list[Property] | dict[str, dict[str, Any]] | None = None,
|
|
182
|
+
) -> None:
|
|
183
|
+
super().__init__(
|
|
184
|
+
name=name,
|
|
185
|
+
kind=kind,
|
|
186
|
+
description=description,
|
|
187
|
+
required=required,
|
|
188
|
+
default=default,
|
|
189
|
+
example=example,
|
|
190
|
+
enum=enum,
|
|
191
|
+
)
|
|
192
|
+
converted_properties: list[Property] = []
|
|
193
|
+
if isinstance(properties, list):
|
|
194
|
+
for prop in properties:
|
|
195
|
+
if not isinstance(prop, Property):
|
|
196
|
+
prop = Property.from_dict(prop)
|
|
197
|
+
converted_properties.append(prop)
|
|
198
|
+
elif isinstance(properties, dict):
|
|
199
|
+
for k, v in properties.items():
|
|
200
|
+
temp_prop = {"name": k, **v}
|
|
201
|
+
prop = Property.from_dict(temp_prop)
|
|
202
|
+
converted_properties.append(prop)
|
|
203
|
+
self.properties = converted_properties
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _normalize_nested_schemas(node: dict[str, Any]) -> None:
|
|
207
|
+
"""Recursively convert a node's nested schemas to JSON Schema form.
|
|
208
|
+
|
|
209
|
+
Nested schemas (array ``items``, object ``properties``) keep the declarative
|
|
210
|
+
shape after serialization: ``kind`` instead of ``type``, empty ``enum``
|
|
211
|
+
placeholders, and object properties as a list of ``{"name": ..., ...}``
|
|
212
|
+
entries. OpenAI rejects schemas whose nested nodes lack a ``type`` key, so
|
|
213
|
+
apply the same conversion the top-level properties loop performs.
|
|
214
|
+
"""
|
|
215
|
+
items = node.get("items")
|
|
216
|
+
if isinstance(items, dict):
|
|
217
|
+
_normalize_schema_node(cast("dict[str, Any]", items))
|
|
218
|
+
props = node.get("properties")
|
|
219
|
+
if not isinstance(props, list):
|
|
220
|
+
return
|
|
221
|
+
# Serialized PropertySchema shape: [{"name": ..., "kind": ..., ...}, ...].
|
|
222
|
+
# Validate every element BEFORE mutating any, so an unexpected shape
|
|
223
|
+
# leaves the node fully untouched rather than half-converted.
|
|
224
|
+
if not all(isinstance(prop, dict) and "name" in prop for prop in cast("list[Any]", props)):
|
|
225
|
+
return
|
|
226
|
+
new_props: dict[str, Any] = {}
|
|
227
|
+
required_fields: list[str] = []
|
|
228
|
+
for prop in cast("list[dict[str, Any]]", props):
|
|
229
|
+
prop_name = prop.pop("name")
|
|
230
|
+
if prop.pop("required", False):
|
|
231
|
+
required_fields.append(prop_name)
|
|
232
|
+
_normalize_schema_node(prop)
|
|
233
|
+
new_props[prop_name] = prop
|
|
234
|
+
node["properties"] = new_props
|
|
235
|
+
if required_fields:
|
|
236
|
+
node["required"] = required_fields
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _normalize_schema_node(node: dict[str, Any]) -> None:
|
|
240
|
+
"""Rename ``kind`` -> ``type``, drop empty ``enum``, and recurse into children."""
|
|
241
|
+
if "kind" in node:
|
|
242
|
+
node["type"] = node.pop("kind")
|
|
243
|
+
if not node.get("enum"):
|
|
244
|
+
node.pop("enum", None)
|
|
245
|
+
if node.get("type") == "object":
|
|
246
|
+
# OpenAI strict structured outputs require additionalProperties: false on
|
|
247
|
+
# every object node; chat clients only inject it at the schema root.
|
|
248
|
+
node.setdefault("additionalProperties", False)
|
|
249
|
+
_normalize_nested_schemas(node)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class PropertySchema(SerializationMixin):
|
|
253
|
+
"""Object representing a property schema."""
|
|
254
|
+
|
|
255
|
+
def __init__(
|
|
256
|
+
self,
|
|
257
|
+
examples: list[dict[str, Any]] | None = None,
|
|
258
|
+
strict: bool = False,
|
|
259
|
+
properties: list[Property] | dict[str, dict[str, Any]] | None = None,
|
|
260
|
+
) -> None:
|
|
261
|
+
self.examples = examples or []
|
|
262
|
+
self.strict = strict
|
|
263
|
+
converted_properties: list[Property] = []
|
|
264
|
+
if isinstance(properties, list):
|
|
265
|
+
for prop in properties:
|
|
266
|
+
if not isinstance(prop, Property):
|
|
267
|
+
prop = Property.from_dict(prop)
|
|
268
|
+
converted_properties.append(prop)
|
|
269
|
+
elif isinstance(properties, dict):
|
|
270
|
+
for k, v in properties.items():
|
|
271
|
+
temp_prop = {"name": k, **v}
|
|
272
|
+
prop = Property.from_dict(temp_prop)
|
|
273
|
+
converted_properties.append(prop)
|
|
274
|
+
self.properties = converted_properties
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def from_dict(
|
|
278
|
+
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
|
|
279
|
+
) -> PropertySchema:
|
|
280
|
+
"""Create a PropertySchema instance from a dictionary, filtering out 'kind' field."""
|
|
281
|
+
# Filter out 'kind', 'type', 'name', and 'description' fields that may appear in YAML
|
|
282
|
+
# but aren't PropertySchema params
|
|
283
|
+
kwargs = {k: v for k, v in value.items() if k not in ("type", "kind", "name", "description")}
|
|
284
|
+
return SerializationMixin.from_dict.__func__(cls, kwargs, dependencies=dependencies)
|
|
285
|
+
|
|
286
|
+
def to_json_schema(self) -> dict[str, Any]:
|
|
287
|
+
"""Get a schema out of this PropertySchema to create pydantic models."""
|
|
288
|
+
json_schema = self.to_dict(exclude={"type"}, exclude_none=True)
|
|
289
|
+
new_props = {}
|
|
290
|
+
required_fields: list[str] = []
|
|
291
|
+
for prop in json_schema.get("properties", []):
|
|
292
|
+
prop_name = prop.pop("name")
|
|
293
|
+
# Convert property-level 'required' boolean to a top-level 'required' array
|
|
294
|
+
if prop.pop("required", False):
|
|
295
|
+
required_fields.append(prop_name)
|
|
296
|
+
_normalize_schema_node(prop)
|
|
297
|
+
new_props[prop_name] = prop
|
|
298
|
+
json_schema["type"] = "object"
|
|
299
|
+
json_schema["properties"] = new_props
|
|
300
|
+
if required_fields:
|
|
301
|
+
json_schema["required"] = required_fields
|
|
302
|
+
return json_schema
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
ConnectionT = TypeVar("ConnectionT", bound="Connection")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class Connection(SerializationMixin):
|
|
309
|
+
"""Object representing a connection specification."""
|
|
310
|
+
|
|
311
|
+
def __init__(
|
|
312
|
+
self,
|
|
313
|
+
kind: Literal["reference", "remote", "key", "anonymous"],
|
|
314
|
+
authenticationMode: str | None = None,
|
|
315
|
+
usageDescription: str | None = None,
|
|
316
|
+
) -> None:
|
|
317
|
+
self.kind = kind
|
|
318
|
+
self.authenticationMode = _try_powerfx_eval(authenticationMode)
|
|
319
|
+
self.usageDescription = _try_powerfx_eval(usageDescription)
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def from_dict(
|
|
323
|
+
cls: type[ConnectionT],
|
|
324
|
+
value: MutableMapping[str, Any],
|
|
325
|
+
/,
|
|
326
|
+
*,
|
|
327
|
+
dependencies: MutableMapping[str, Any] | None = None,
|
|
328
|
+
) -> ConnectionT:
|
|
329
|
+
"""Create a Connection instance from a dictionary, dispatching to the appropriate subclass."""
|
|
330
|
+
# Only dispatch if we're being called on the base Connection class
|
|
331
|
+
if cls is not Connection:
|
|
332
|
+
# We're being called on a subclass, use the normal from_dict
|
|
333
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
334
|
+
|
|
335
|
+
kind = value.get("kind", "").lower()
|
|
336
|
+
if kind == "reference":
|
|
337
|
+
return SerializationMixin.from_dict.__func__(ReferenceConnection, value, dependencies=dependencies)
|
|
338
|
+
if kind == "remote":
|
|
339
|
+
return SerializationMixin.from_dict.__func__(RemoteConnection, value, dependencies=dependencies)
|
|
340
|
+
if kind in ("key", "apikey"):
|
|
341
|
+
return SerializationMixin.from_dict.__func__(ApiKeyConnection, value, dependencies=dependencies)
|
|
342
|
+
if kind == "anonymous":
|
|
343
|
+
return SerializationMixin.from_dict.__func__(AnonymousConnection, value, dependencies=dependencies)
|
|
344
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class ReferenceConnection(Connection):
|
|
348
|
+
"""Object representing a reference connection."""
|
|
349
|
+
|
|
350
|
+
def __init__(
|
|
351
|
+
self,
|
|
352
|
+
kind: Literal["reference"] = "reference",
|
|
353
|
+
authenticationMode: str | None = None,
|
|
354
|
+
usageDescription: str | None = None,
|
|
355
|
+
name: str | None = None,
|
|
356
|
+
target: str | None = None,
|
|
357
|
+
) -> None:
|
|
358
|
+
super().__init__(
|
|
359
|
+
kind=kind,
|
|
360
|
+
authenticationMode=authenticationMode,
|
|
361
|
+
usageDescription=usageDescription,
|
|
362
|
+
)
|
|
363
|
+
self.name = _try_powerfx_eval(name)
|
|
364
|
+
self.target = _try_powerfx_eval(target)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class RemoteConnection(Connection):
|
|
368
|
+
"""Object representing a remote connection."""
|
|
369
|
+
|
|
370
|
+
def __init__(
|
|
371
|
+
self,
|
|
372
|
+
kind: Literal["remote"] = "remote",
|
|
373
|
+
authenticationMode: str | None = None,
|
|
374
|
+
usageDescription: str | None = None,
|
|
375
|
+
name: str | None = None,
|
|
376
|
+
endpoint: str | None = None,
|
|
377
|
+
) -> None:
|
|
378
|
+
super().__init__(
|
|
379
|
+
kind=kind,
|
|
380
|
+
authenticationMode=authenticationMode,
|
|
381
|
+
usageDescription=usageDescription,
|
|
382
|
+
)
|
|
383
|
+
self.name = _try_powerfx_eval(name)
|
|
384
|
+
self.endpoint = _try_powerfx_eval(endpoint)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class ApiKeyConnection(Connection):
|
|
388
|
+
"""Object representing an API key connection."""
|
|
389
|
+
|
|
390
|
+
def __init__(
|
|
391
|
+
self,
|
|
392
|
+
kind: Literal["key"] = "key",
|
|
393
|
+
authenticationMode: str | None = None,
|
|
394
|
+
usageDescription: str | None = None,
|
|
395
|
+
endpoint: str | None = None,
|
|
396
|
+
apiKey: str | None = None,
|
|
397
|
+
key: str | None = None,
|
|
398
|
+
) -> None:
|
|
399
|
+
super().__init__(
|
|
400
|
+
kind=kind,
|
|
401
|
+
authenticationMode=authenticationMode,
|
|
402
|
+
usageDescription=usageDescription,
|
|
403
|
+
)
|
|
404
|
+
self.endpoint = _try_powerfx_eval(endpoint)
|
|
405
|
+
# Support both 'apiKey' and 'key' fields, with 'key' taking precedence if both are provided
|
|
406
|
+
self.apiKey = _try_powerfx_eval(key if key else apiKey, False)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class AnonymousConnection(Connection):
|
|
410
|
+
"""Object representing an anonymous connection."""
|
|
411
|
+
|
|
412
|
+
def __init__(
|
|
413
|
+
self,
|
|
414
|
+
kind: Literal["anonymous"] = "anonymous",
|
|
415
|
+
authenticationMode: str | None = None,
|
|
416
|
+
usageDescription: str | None = None,
|
|
417
|
+
endpoint: str | None = None,
|
|
418
|
+
) -> None:
|
|
419
|
+
super().__init__(
|
|
420
|
+
kind=kind,
|
|
421
|
+
authenticationMode=authenticationMode,
|
|
422
|
+
usageDescription=usageDescription,
|
|
423
|
+
)
|
|
424
|
+
self.endpoint = _try_powerfx_eval(endpoint)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
Connections = Union[
|
|
428
|
+
ReferenceConnection,
|
|
429
|
+
RemoteConnection,
|
|
430
|
+
ApiKeyConnection,
|
|
431
|
+
AnonymousConnection,
|
|
432
|
+
]
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class ModelOptions(SerializationMixin):
|
|
436
|
+
"""Object representing model options."""
|
|
437
|
+
|
|
438
|
+
def __init__(
|
|
439
|
+
self,
|
|
440
|
+
frequencyPenalty: float | None = None,
|
|
441
|
+
maxOutputTokens: int | None = None,
|
|
442
|
+
presencePenalty: float | None = None,
|
|
443
|
+
seed: int | None = None,
|
|
444
|
+
temperature: float | None = None,
|
|
445
|
+
topK: int | None = None,
|
|
446
|
+
topP: float | None = None,
|
|
447
|
+
stopSequences: list[str] | None = None,
|
|
448
|
+
allowMultipleToolCalls: bool | None = None,
|
|
449
|
+
additionalProperties: dict[str, Any] | None = None,
|
|
450
|
+
**kwargs: Any,
|
|
451
|
+
) -> None:
|
|
452
|
+
self.frequencyPenalty = frequencyPenalty
|
|
453
|
+
self.maxOutputTokens = maxOutputTokens
|
|
454
|
+
self.presencePenalty = presencePenalty
|
|
455
|
+
self.seed = seed
|
|
456
|
+
self.temperature = temperature
|
|
457
|
+
self.topK = topK
|
|
458
|
+
self.topP = topP
|
|
459
|
+
self.stopSequences = stopSequences or []
|
|
460
|
+
self.allowMultipleToolCalls = allowMultipleToolCalls
|
|
461
|
+
# Merge any additional properties from kwargs into additionalProperties
|
|
462
|
+
self.additionalProperties = additionalProperties or {}
|
|
463
|
+
self.additionalProperties.update(kwargs)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class Model(SerializationMixin):
|
|
467
|
+
"""Object representing a model specification."""
|
|
468
|
+
|
|
469
|
+
def __init__(
|
|
470
|
+
self,
|
|
471
|
+
id: str | None = None,
|
|
472
|
+
provider: str | None = None,
|
|
473
|
+
apiType: str | None = None,
|
|
474
|
+
connection: Connections | None = None,
|
|
475
|
+
options: ModelOptions | None = None,
|
|
476
|
+
) -> None:
|
|
477
|
+
self.id = _try_powerfx_eval(id)
|
|
478
|
+
self.provider = _try_powerfx_eval(provider)
|
|
479
|
+
self.apiType = _try_powerfx_eval(apiType)
|
|
480
|
+
if not isinstance(connection, Connection) and connection is not None:
|
|
481
|
+
connection = Connection.from_dict(connection)
|
|
482
|
+
self.connection = connection
|
|
483
|
+
if not isinstance(options, ModelOptions) and options is not None:
|
|
484
|
+
options = ModelOptions.from_dict(options)
|
|
485
|
+
self.options = options
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class Format(SerializationMixin):
|
|
489
|
+
"""Object representing template format."""
|
|
490
|
+
|
|
491
|
+
def __init__(
|
|
492
|
+
self,
|
|
493
|
+
kind: str | None = None,
|
|
494
|
+
strict: bool = False,
|
|
495
|
+
options: dict[str, Any] | None = None,
|
|
496
|
+
) -> None:
|
|
497
|
+
self.kind = _try_powerfx_eval(kind)
|
|
498
|
+
self.strict = strict
|
|
499
|
+
self.options = options or {}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class Parser(SerializationMixin):
|
|
503
|
+
"""Object representing template parser."""
|
|
504
|
+
|
|
505
|
+
def __init__(
|
|
506
|
+
self,
|
|
507
|
+
kind: str | None = None,
|
|
508
|
+
options: dict[str, Any] | None = None,
|
|
509
|
+
) -> None:
|
|
510
|
+
self.kind = _try_powerfx_eval(kind)
|
|
511
|
+
self.options = options or {}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
class Template(SerializationMixin):
|
|
515
|
+
"""Object representing a template configuration."""
|
|
516
|
+
|
|
517
|
+
def __init__(
|
|
518
|
+
self,
|
|
519
|
+
format: Format | None = None,
|
|
520
|
+
parser: Parser | None = None,
|
|
521
|
+
) -> None:
|
|
522
|
+
if not isinstance(format, Format) and format is not None:
|
|
523
|
+
format = Format.from_dict(format)
|
|
524
|
+
self.format = format
|
|
525
|
+
if not isinstance(parser, Parser) and parser is not None:
|
|
526
|
+
parser = Parser.from_dict(parser)
|
|
527
|
+
self.parser = parser
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
class AgentDefinition(SerializationMixin):
|
|
531
|
+
"""Object representing a prompt specification."""
|
|
532
|
+
|
|
533
|
+
def __init__(
|
|
534
|
+
self,
|
|
535
|
+
kind: str | None = None,
|
|
536
|
+
name: str | None = None,
|
|
537
|
+
displayName: str | None = None,
|
|
538
|
+
description: str | None = None,
|
|
539
|
+
metadata: dict[str, Any] | None = None,
|
|
540
|
+
inputSchema: PropertySchema | None = None,
|
|
541
|
+
outputSchema: PropertySchema | None = None,
|
|
542
|
+
) -> None:
|
|
543
|
+
self.kind = _try_powerfx_eval(kind)
|
|
544
|
+
self.name = _try_powerfx_eval(name)
|
|
545
|
+
self.displayName = _try_powerfx_eval(displayName)
|
|
546
|
+
self.description = _try_powerfx_eval(description)
|
|
547
|
+
self.metadata = metadata
|
|
548
|
+
if not isinstance(inputSchema, PropertySchema) and inputSchema is not None:
|
|
549
|
+
inputSchema = PropertySchema.from_dict(inputSchema)
|
|
550
|
+
self.inputSchema = inputSchema
|
|
551
|
+
if not isinstance(outputSchema, PropertySchema) and outputSchema is not None:
|
|
552
|
+
outputSchema = PropertySchema.from_dict(outputSchema)
|
|
553
|
+
self.outputSchema = outputSchema
|
|
554
|
+
|
|
555
|
+
@classmethod
|
|
556
|
+
def from_dict(
|
|
557
|
+
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
|
|
558
|
+
) -> AgentDefinition:
|
|
559
|
+
"""Create an AgentDefinition instance from a dictionary, dispatching to the appropriate subclass."""
|
|
560
|
+
# Only dispatch if we're being called on the base AgentDefinition class
|
|
561
|
+
if cls is not AgentDefinition:
|
|
562
|
+
# We're being called on a subclass, use the normal from_dict
|
|
563
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
564
|
+
|
|
565
|
+
kind = value.get("kind", "")
|
|
566
|
+
if kind == "Prompt" or kind == "Agent":
|
|
567
|
+
return PromptAgent.from_dict(value, dependencies=dependencies)
|
|
568
|
+
# Default to AgentDefinition
|
|
569
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
ToolT = TypeVar("ToolT", bound="Tool")
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class Tool(SerializationMixin):
|
|
576
|
+
"""Base class for tools."""
|
|
577
|
+
|
|
578
|
+
def __init__(
|
|
579
|
+
self,
|
|
580
|
+
name: str | None = None,
|
|
581
|
+
kind: str | None = None,
|
|
582
|
+
description: str | None = None,
|
|
583
|
+
bindings: list[Binding] | dict[str, Any] | None = None,
|
|
584
|
+
) -> None:
|
|
585
|
+
self.name = _try_powerfx_eval(name)
|
|
586
|
+
self.kind = _try_powerfx_eval(kind)
|
|
587
|
+
self.description = _try_powerfx_eval(description)
|
|
588
|
+
converted_bindings: list[Binding] = []
|
|
589
|
+
if isinstance(bindings, list):
|
|
590
|
+
for binding in bindings:
|
|
591
|
+
if not isinstance(binding, Binding):
|
|
592
|
+
binding = Binding.from_dict(binding)
|
|
593
|
+
converted_bindings.append(binding)
|
|
594
|
+
elif isinstance(bindings, dict):
|
|
595
|
+
for k, v in bindings.items():
|
|
596
|
+
temp_binding = {"name": k, "input": v} if isinstance(v, str) else {"name": k, **v}
|
|
597
|
+
binding = Binding.from_dict(temp_binding)
|
|
598
|
+
converted_bindings.append(binding)
|
|
599
|
+
self.bindings = converted_bindings
|
|
600
|
+
|
|
601
|
+
@classmethod
|
|
602
|
+
def from_dict(
|
|
603
|
+
cls: type[ToolT], value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
|
|
604
|
+
) -> ToolT:
|
|
605
|
+
"""Create a Tool instance from a dictionary, dispatching to the appropriate subclass."""
|
|
606
|
+
# Only dispatch if we're being called on the base Tool class
|
|
607
|
+
if cls is not Tool:
|
|
608
|
+
# We're being called on a subclass, use the normal from_dict
|
|
609
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
610
|
+
|
|
611
|
+
kind = value.get("kind", "")
|
|
612
|
+
if kind == "function":
|
|
613
|
+
return SerializationMixin.from_dict.__func__(FunctionTool, value, dependencies=dependencies)
|
|
614
|
+
if kind == "custom":
|
|
615
|
+
return SerializationMixin.from_dict.__func__(CustomTool, value, dependencies=dependencies)
|
|
616
|
+
if kind == "web_search":
|
|
617
|
+
return SerializationMixin.from_dict.__func__(WebSearchTool, value, dependencies=dependencies)
|
|
618
|
+
if kind == "file_search":
|
|
619
|
+
return SerializationMixin.from_dict.__func__(FileSearchTool, value, dependencies=dependencies)
|
|
620
|
+
if kind == "mcp":
|
|
621
|
+
return SerializationMixin.from_dict.__func__(McpTool, value, dependencies=dependencies)
|
|
622
|
+
if kind == "openapi":
|
|
623
|
+
return SerializationMixin.from_dict.__func__(OpenApiTool, value, dependencies=dependencies)
|
|
624
|
+
if kind == "code_interpreter":
|
|
625
|
+
return SerializationMixin.from_dict.__func__(CodeInterpreterTool, value, dependencies=dependencies)
|
|
626
|
+
# Default to base Tool class
|
|
627
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
class FunctionTool(Tool):
|
|
631
|
+
"""Object representing a function tool."""
|
|
632
|
+
|
|
633
|
+
def __init__(
|
|
634
|
+
self,
|
|
635
|
+
name: str | None = None,
|
|
636
|
+
kind: str = "function",
|
|
637
|
+
description: str | None = None,
|
|
638
|
+
bindings: list[Binding] | None = None,
|
|
639
|
+
parameters: PropertySchema | list[Property] | dict[str, Any] | None = None,
|
|
640
|
+
strict: bool = False,
|
|
641
|
+
) -> None:
|
|
642
|
+
super().__init__(
|
|
643
|
+
name=name,
|
|
644
|
+
kind=kind,
|
|
645
|
+
description=description,
|
|
646
|
+
bindings=bindings,
|
|
647
|
+
)
|
|
648
|
+
if isinstance(parameters, list):
|
|
649
|
+
# If parameters is a list, wrap it in a PropertySchema
|
|
650
|
+
parameters = PropertySchema(properties=parameters)
|
|
651
|
+
elif not isinstance(parameters, PropertySchema) and parameters is not None:
|
|
652
|
+
parameters = PropertySchema.from_dict(parameters)
|
|
653
|
+
self.parameters = parameters
|
|
654
|
+
self.strict = strict
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
class CustomTool(Tool):
|
|
658
|
+
"""Object representing a custom tool."""
|
|
659
|
+
|
|
660
|
+
def __init__(
|
|
661
|
+
self,
|
|
662
|
+
name: str | None = None,
|
|
663
|
+
kind: str = "custom",
|
|
664
|
+
description: str | None = None,
|
|
665
|
+
bindings: list[Binding] | None = None,
|
|
666
|
+
connection: Connection | None = None,
|
|
667
|
+
options: dict[str, Any] | None = None,
|
|
668
|
+
) -> None:
|
|
669
|
+
super().__init__(
|
|
670
|
+
name=name,
|
|
671
|
+
kind=kind,
|
|
672
|
+
description=description,
|
|
673
|
+
bindings=bindings,
|
|
674
|
+
)
|
|
675
|
+
if not isinstance(connection, Connection) and connection is not None:
|
|
676
|
+
connection = Connection.from_dict(connection)
|
|
677
|
+
self.connection = connection
|
|
678
|
+
self.options = options or {}
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
class WebSearchTool(Tool):
|
|
682
|
+
"""Object representing a web search tool."""
|
|
683
|
+
|
|
684
|
+
def __init__(
|
|
685
|
+
self,
|
|
686
|
+
name: str | None = None,
|
|
687
|
+
kind: str = "web_search",
|
|
688
|
+
description: str | None = None,
|
|
689
|
+
bindings: list[Binding] | None = None,
|
|
690
|
+
connection: Connection | None = None,
|
|
691
|
+
options: dict[str, Any] | None = None,
|
|
692
|
+
) -> None:
|
|
693
|
+
super().__init__(
|
|
694
|
+
name=name,
|
|
695
|
+
kind=kind,
|
|
696
|
+
description=description,
|
|
697
|
+
bindings=bindings,
|
|
698
|
+
)
|
|
699
|
+
if not isinstance(connection, Connection) and connection is not None:
|
|
700
|
+
connection = Connection.from_dict(connection)
|
|
701
|
+
self.connection = connection
|
|
702
|
+
self.options = options or {}
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class FileSearchTool(Tool):
|
|
706
|
+
"""Object representing a file search tool."""
|
|
707
|
+
|
|
708
|
+
def __init__(
|
|
709
|
+
self,
|
|
710
|
+
name: str | None = None,
|
|
711
|
+
kind: str = "file_search",
|
|
712
|
+
description: str | None = None,
|
|
713
|
+
bindings: list[Binding] | None = None,
|
|
714
|
+
connection: Connection | None = None,
|
|
715
|
+
vectorStoreIds: list[str] | None = None,
|
|
716
|
+
maximumResultCount: int | None = None,
|
|
717
|
+
ranker: str | None = None,
|
|
718
|
+
scoreThreshold: float | None = None,
|
|
719
|
+
filters: dict[str, Any] | None = None,
|
|
720
|
+
) -> None:
|
|
721
|
+
super().__init__(
|
|
722
|
+
name=name,
|
|
723
|
+
kind=kind,
|
|
724
|
+
description=description,
|
|
725
|
+
bindings=bindings,
|
|
726
|
+
)
|
|
727
|
+
if not isinstance(connection, Connection) and connection is not None:
|
|
728
|
+
connection = Connection.from_dict(connection)
|
|
729
|
+
self.connection = connection
|
|
730
|
+
self.vectorStoreIds = vectorStoreIds or []
|
|
731
|
+
self.maximumResultCount = maximumResultCount
|
|
732
|
+
self.ranker = _try_powerfx_eval(ranker)
|
|
733
|
+
self.scoreThreshold = scoreThreshold
|
|
734
|
+
self.filters = filters or {}
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
class McpServerApprovalMode(SerializationMixin):
|
|
738
|
+
"""Base class for MCP server approval modes."""
|
|
739
|
+
|
|
740
|
+
def __init__(
|
|
741
|
+
self,
|
|
742
|
+
kind: str | None = None,
|
|
743
|
+
) -> None:
|
|
744
|
+
self.kind = _try_powerfx_eval(kind)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
class McpServerToolAlwaysRequireApprovalMode(McpServerApprovalMode):
|
|
748
|
+
"""MCP server tool always require approval mode."""
|
|
749
|
+
|
|
750
|
+
def __init__(
|
|
751
|
+
self,
|
|
752
|
+
kind: str = "always",
|
|
753
|
+
) -> None:
|
|
754
|
+
super().__init__(kind=kind)
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
class McpServerToolNeverRequireApprovalMode(McpServerApprovalMode):
|
|
758
|
+
"""MCP server tool never require approval mode."""
|
|
759
|
+
|
|
760
|
+
def __init__(
|
|
761
|
+
self,
|
|
762
|
+
kind: str = "never",
|
|
763
|
+
) -> None:
|
|
764
|
+
super().__init__(kind=kind)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
class McpServerToolSpecifyApprovalMode(McpServerApprovalMode):
|
|
768
|
+
"""MCP server tool specify approval mode."""
|
|
769
|
+
|
|
770
|
+
def __init__(
|
|
771
|
+
self,
|
|
772
|
+
kind: str = "specify",
|
|
773
|
+
alwaysRequireApprovalTools: list[str] | None = None,
|
|
774
|
+
neverRequireApprovalTools: list[str] | None = None,
|
|
775
|
+
) -> None:
|
|
776
|
+
super().__init__(kind=kind)
|
|
777
|
+
self.alwaysRequireApprovalTools = alwaysRequireApprovalTools
|
|
778
|
+
self.neverRequireApprovalTools = neverRequireApprovalTools
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
class McpTool(Tool):
|
|
782
|
+
"""Object representing an MCP tool."""
|
|
783
|
+
|
|
784
|
+
def __init__(
|
|
785
|
+
self,
|
|
786
|
+
name: str | None = None,
|
|
787
|
+
kind: str = "mcp",
|
|
788
|
+
description: str | None = None,
|
|
789
|
+
bindings: list[Binding] | None = None,
|
|
790
|
+
connection: Connection | None = None,
|
|
791
|
+
serverName: str | None = None,
|
|
792
|
+
serverDescription: str | None = None,
|
|
793
|
+
approvalMode: McpServerApprovalMode | None = None,
|
|
794
|
+
allowedTools: list[str] | None = None,
|
|
795
|
+
url: str | None = None,
|
|
796
|
+
) -> None:
|
|
797
|
+
super().__init__(
|
|
798
|
+
name=name,
|
|
799
|
+
kind=kind,
|
|
800
|
+
description=description,
|
|
801
|
+
bindings=bindings,
|
|
802
|
+
)
|
|
803
|
+
if not isinstance(connection, Connection) and connection is not None:
|
|
804
|
+
connection = Connection.from_dict(connection)
|
|
805
|
+
self.connection = connection
|
|
806
|
+
self.serverName = _try_powerfx_eval(serverName)
|
|
807
|
+
self.serverDescription = _try_powerfx_eval(serverDescription)
|
|
808
|
+
if not isinstance(approvalMode, McpServerApprovalMode) and approvalMode is not None:
|
|
809
|
+
# Handle simplified string format: "always" -> {"kind": "always"}
|
|
810
|
+
if isinstance(approvalMode, str):
|
|
811
|
+
approvalMode = McpServerApprovalMode.from_dict({"kind": approvalMode})
|
|
812
|
+
else:
|
|
813
|
+
approvalMode = McpServerApprovalMode.from_dict(approvalMode)
|
|
814
|
+
self.approvalMode = approvalMode
|
|
815
|
+
self.allowedTools = allowedTools or []
|
|
816
|
+
self.url = _try_powerfx_eval(url)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class OpenApiTool(Tool):
|
|
820
|
+
"""Object representing an OpenAPI tool."""
|
|
821
|
+
|
|
822
|
+
def __init__(
|
|
823
|
+
self,
|
|
824
|
+
name: str | None = None,
|
|
825
|
+
kind: str = "openapi",
|
|
826
|
+
description: str | None = None,
|
|
827
|
+
bindings: list[Binding] | None = None,
|
|
828
|
+
connection: Connection | None = None,
|
|
829
|
+
specification: str | None = None,
|
|
830
|
+
) -> None:
|
|
831
|
+
super().__init__(
|
|
832
|
+
name=name,
|
|
833
|
+
kind=kind,
|
|
834
|
+
description=description,
|
|
835
|
+
bindings=bindings,
|
|
836
|
+
)
|
|
837
|
+
if not isinstance(connection, Connection) and connection is not None:
|
|
838
|
+
connection = Connection.from_dict(connection)
|
|
839
|
+
self.connection = connection
|
|
840
|
+
self.specification = _try_powerfx_eval(specification)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
class CodeInterpreterTool(Tool):
|
|
844
|
+
"""Object representing a code interpreter tool."""
|
|
845
|
+
|
|
846
|
+
def __init__(
|
|
847
|
+
self,
|
|
848
|
+
name: str | None = None,
|
|
849
|
+
kind: str = "code_interpreter",
|
|
850
|
+
description: str | None = None,
|
|
851
|
+
bindings: list[Binding] | None = None,
|
|
852
|
+
fileIds: list[str] | None = None,
|
|
853
|
+
) -> None:
|
|
854
|
+
super().__init__(
|
|
855
|
+
name=name,
|
|
856
|
+
kind=kind,
|
|
857
|
+
description=description,
|
|
858
|
+
bindings=bindings,
|
|
859
|
+
)
|
|
860
|
+
self.fileIds = fileIds or []
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
class PromptAgent(AgentDefinition):
|
|
864
|
+
"""Object representing a prompt agent specification."""
|
|
865
|
+
|
|
866
|
+
def __init__(
|
|
867
|
+
self,
|
|
868
|
+
kind: str = "Prompt",
|
|
869
|
+
name: str | None = None,
|
|
870
|
+
displayName: str | None = None,
|
|
871
|
+
description: str | None = None,
|
|
872
|
+
metadata: dict[str, Any] | None = None,
|
|
873
|
+
inputSchema: PropertySchema | None = None,
|
|
874
|
+
outputSchema: PropertySchema | None = None,
|
|
875
|
+
model: Model | dict[str, Any] | None = None,
|
|
876
|
+
tools: list[Tool] | None = None,
|
|
877
|
+
template: Template | dict[str, Any] | None = None,
|
|
878
|
+
instructions: str | None = None,
|
|
879
|
+
additionalInstructions: str | None = None,
|
|
880
|
+
) -> None:
|
|
881
|
+
super().__init__(
|
|
882
|
+
kind=kind,
|
|
883
|
+
name=name,
|
|
884
|
+
displayName=displayName,
|
|
885
|
+
description=description,
|
|
886
|
+
metadata=metadata,
|
|
887
|
+
inputSchema=inputSchema,
|
|
888
|
+
outputSchema=outputSchema,
|
|
889
|
+
)
|
|
890
|
+
if not isinstance(model, Model) and model is not None:
|
|
891
|
+
model = Model.from_dict(model)
|
|
892
|
+
self.model = model
|
|
893
|
+
converted_tools: list[Tool] = []
|
|
894
|
+
for tool in tools or []:
|
|
895
|
+
if not isinstance(tool, Tool):
|
|
896
|
+
tool = Tool.from_dict(tool)
|
|
897
|
+
converted_tools.append(tool)
|
|
898
|
+
self.tools = converted_tools
|
|
899
|
+
if not isinstance(template, Template) and template is not None:
|
|
900
|
+
template = Template.from_dict(template)
|
|
901
|
+
self.template = template
|
|
902
|
+
self.instructions = _try_powerfx_eval(instructions)
|
|
903
|
+
self.additionalInstructions = _try_powerfx_eval(additionalInstructions)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class Resource(SerializationMixin):
|
|
907
|
+
"""Object representing a resource."""
|
|
908
|
+
|
|
909
|
+
def __init__(
|
|
910
|
+
self,
|
|
911
|
+
name: str | None = None,
|
|
912
|
+
kind: str | None = None,
|
|
913
|
+
) -> None:
|
|
914
|
+
self.name = _try_powerfx_eval(name)
|
|
915
|
+
self.kind = _try_powerfx_eval(kind)
|
|
916
|
+
|
|
917
|
+
@classmethod
|
|
918
|
+
def from_dict(
|
|
919
|
+
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
|
|
920
|
+
) -> Resource:
|
|
921
|
+
"""Create a Resource instance from a dictionary, dispatching to the appropriate subclass."""
|
|
922
|
+
# Only dispatch if we're being called on the base Resource class
|
|
923
|
+
if cls is not Resource:
|
|
924
|
+
# We're being called on a subclass, use the normal from_dict
|
|
925
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
926
|
+
|
|
927
|
+
kind = value.get("kind", "")
|
|
928
|
+
if kind == "model":
|
|
929
|
+
return SerializationMixin.from_dict.__func__(ModelResource, value, dependencies=dependencies)
|
|
930
|
+
if kind == "tool":
|
|
931
|
+
return SerializationMixin.from_dict.__func__(ToolResource, value, dependencies=dependencies)
|
|
932
|
+
return SerializationMixin.from_dict.__func__(cls, value, dependencies=dependencies)
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
class ModelResource(Resource):
|
|
936
|
+
"""Object representing a model resource."""
|
|
937
|
+
|
|
938
|
+
def __init__(
|
|
939
|
+
self,
|
|
940
|
+
kind: str = "model",
|
|
941
|
+
name: str | None = None,
|
|
942
|
+
id: str | None = None,
|
|
943
|
+
) -> None:
|
|
944
|
+
super().__init__(kind=kind, name=name)
|
|
945
|
+
self.id = _try_powerfx_eval(id)
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
class ToolResource(Resource):
|
|
949
|
+
"""Object representing a tool resource."""
|
|
950
|
+
|
|
951
|
+
def __init__(
|
|
952
|
+
self,
|
|
953
|
+
kind: str = "tool",
|
|
954
|
+
name: str | None = None,
|
|
955
|
+
id: str | None = None,
|
|
956
|
+
options: dict[str, Any] | None = None,
|
|
957
|
+
) -> None:
|
|
958
|
+
super().__init__(kind=kind, name=name)
|
|
959
|
+
self.id = _try_powerfx_eval(id)
|
|
960
|
+
self.options = options or {}
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
class ProtocolVersionRecord(SerializationMixin):
|
|
964
|
+
"""Object representing a protocol version record."""
|
|
965
|
+
|
|
966
|
+
def __init__(
|
|
967
|
+
self,
|
|
968
|
+
protocol: str | None = None,
|
|
969
|
+
version: str | None = None,
|
|
970
|
+
) -> None:
|
|
971
|
+
self.protocol = _try_powerfx_eval(protocol)
|
|
972
|
+
self.version = _try_powerfx_eval(version)
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
class EnvironmentVariable(SerializationMixin):
|
|
976
|
+
"""Object representing an environment variable."""
|
|
977
|
+
|
|
978
|
+
def __init__(
|
|
979
|
+
self,
|
|
980
|
+
name: str | None = None,
|
|
981
|
+
value: str | None = None,
|
|
982
|
+
) -> None:
|
|
983
|
+
self.name = _try_powerfx_eval(name)
|
|
984
|
+
self.value = _try_powerfx_eval(value)
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
class AgentManifest(SerializationMixin):
|
|
988
|
+
"""Object representing an agent manifest."""
|
|
989
|
+
|
|
990
|
+
def __init__(
|
|
991
|
+
self,
|
|
992
|
+
name: str | None = None,
|
|
993
|
+
displayName: str | None = None,
|
|
994
|
+
description: str | None = None,
|
|
995
|
+
metadata: dict[str, Any] | None = None,
|
|
996
|
+
template: AgentDefinition | None = None,
|
|
997
|
+
parameters: PropertySchema | None = None,
|
|
998
|
+
resources: list[Resource] | dict[str, Any] | None = None,
|
|
999
|
+
) -> None:
|
|
1000
|
+
self.name = _try_powerfx_eval(name)
|
|
1001
|
+
self.displayName = _try_powerfx_eval(displayName)
|
|
1002
|
+
self.description = _try_powerfx_eval(description)
|
|
1003
|
+
self.metadata = metadata or {}
|
|
1004
|
+
if not isinstance(template, AgentDefinition) and template is not None:
|
|
1005
|
+
template = AgentDefinition.from_dict(template)
|
|
1006
|
+
self.template = template or AgentDefinition()
|
|
1007
|
+
if not isinstance(parameters, PropertySchema) and parameters is not None:
|
|
1008
|
+
parameters = PropertySchema.from_dict(parameters)
|
|
1009
|
+
self.parameters = parameters or PropertySchema()
|
|
1010
|
+
converted_resources: list[Resource] = []
|
|
1011
|
+
if isinstance(resources, list):
|
|
1012
|
+
for resource in resources:
|
|
1013
|
+
if not isinstance(resource, Resource):
|
|
1014
|
+
resource = Resource.from_dict(resource)
|
|
1015
|
+
converted_resources.append(resource)
|
|
1016
|
+
elif isinstance(resources, dict):
|
|
1017
|
+
for k, v in resources.items():
|
|
1018
|
+
temp_resource = {"name": k, **v}
|
|
1019
|
+
resource = Resource.from_dict(temp_resource)
|
|
1020
|
+
converted_resources.append(resource)
|
|
1021
|
+
self.resources = converted_resources
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
AgentSchemaSpec = Union[
|
|
1025
|
+
AgentManifest,
|
|
1026
|
+
AgentDefinition,
|
|
1027
|
+
PromptAgent,
|
|
1028
|
+
Tool,
|
|
1029
|
+
FunctionTool,
|
|
1030
|
+
CustomTool,
|
|
1031
|
+
WebSearchTool,
|
|
1032
|
+
FileSearchTool,
|
|
1033
|
+
McpTool,
|
|
1034
|
+
OpenApiTool,
|
|
1035
|
+
CodeInterpreterTool,
|
|
1036
|
+
Resource,
|
|
1037
|
+
ModelResource,
|
|
1038
|
+
ToolResource,
|
|
1039
|
+
Connection,
|
|
1040
|
+
ReferenceConnection,
|
|
1041
|
+
RemoteConnection,
|
|
1042
|
+
ApiKeyConnection,
|
|
1043
|
+
AnonymousConnection,
|
|
1044
|
+
Property,
|
|
1045
|
+
ArrayProperty,
|
|
1046
|
+
ObjectProperty,
|
|
1047
|
+
PropertySchema,
|
|
1048
|
+
McpServerApprovalMode,
|
|
1049
|
+
McpServerToolAlwaysRequireApprovalMode,
|
|
1050
|
+
McpServerToolNeverRequireApprovalMode,
|
|
1051
|
+
McpServerToolSpecifyApprovalMode,
|
|
1052
|
+
Binding,
|
|
1053
|
+
Format,
|
|
1054
|
+
Parser,
|
|
1055
|
+
Template,
|
|
1056
|
+
Model,
|
|
1057
|
+
ModelOptions,
|
|
1058
|
+
ProtocolVersionRecord,
|
|
1059
|
+
EnvironmentVariable,
|
|
1060
|
+
]
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def agent_schema_dispatch(schema: dict[str, Any]) -> AgentSchemaSpec | None:
|
|
1064
|
+
"""Create a component instance from a dictionary, dispatching to the appropriate class based on 'kind' field."""
|
|
1065
|
+
kind = schema.get("kind")
|
|
1066
|
+
|
|
1067
|
+
# If no kind field, assume it's an AgentManifest
|
|
1068
|
+
if kind is None:
|
|
1069
|
+
return AgentManifest.from_dict(schema)
|
|
1070
|
+
# Match on the kind field to determine which class to instantiate
|
|
1071
|
+
match kind.lower():
|
|
1072
|
+
# Agent types
|
|
1073
|
+
case "prompt":
|
|
1074
|
+
return PromptAgent.from_dict(schema)
|
|
1075
|
+
case "agent":
|
|
1076
|
+
return AgentDefinition.from_dict(schema)
|
|
1077
|
+
|
|
1078
|
+
# Resource types
|
|
1079
|
+
case "tool":
|
|
1080
|
+
return ToolResource.from_dict(schema)
|
|
1081
|
+
case "model":
|
|
1082
|
+
return ModelResource.from_dict(schema)
|
|
1083
|
+
case "resource":
|
|
1084
|
+
return Resource.from_dict(schema)
|
|
1085
|
+
|
|
1086
|
+
# Tool types
|
|
1087
|
+
case "function":
|
|
1088
|
+
return FunctionTool.from_dict(schema)
|
|
1089
|
+
case "custom":
|
|
1090
|
+
return CustomTool.from_dict(schema)
|
|
1091
|
+
case "web_search":
|
|
1092
|
+
return WebSearchTool.from_dict(schema)
|
|
1093
|
+
case "file_search":
|
|
1094
|
+
return FileSearchTool.from_dict(schema)
|
|
1095
|
+
case "mcp":
|
|
1096
|
+
return McpTool.from_dict(schema)
|
|
1097
|
+
case "openapi":
|
|
1098
|
+
return OpenApiTool.from_dict(schema)
|
|
1099
|
+
case "code_interpreter":
|
|
1100
|
+
return CodeInterpreterTool.from_dict(schema)
|
|
1101
|
+
|
|
1102
|
+
# Connection types
|
|
1103
|
+
case "reference":
|
|
1104
|
+
return ReferenceConnection.from_dict(schema)
|
|
1105
|
+
case "remote":
|
|
1106
|
+
return RemoteConnection.from_dict(schema)
|
|
1107
|
+
case "key":
|
|
1108
|
+
return ApiKeyConnection.from_dict(schema)
|
|
1109
|
+
case "anonymous":
|
|
1110
|
+
return AnonymousConnection.from_dict(schema)
|
|
1111
|
+
case "connection":
|
|
1112
|
+
return Connection.from_dict(schema)
|
|
1113
|
+
|
|
1114
|
+
# Property types
|
|
1115
|
+
case "array":
|
|
1116
|
+
return ArrayProperty.from_dict(schema)
|
|
1117
|
+
case "object":
|
|
1118
|
+
return ObjectProperty.from_dict(schema)
|
|
1119
|
+
case "property":
|
|
1120
|
+
return Property.from_dict(schema)
|
|
1121
|
+
|
|
1122
|
+
# MCP Server Approval Mode types
|
|
1123
|
+
case "always":
|
|
1124
|
+
return McpServerToolAlwaysRequireApprovalMode.from_dict(schema)
|
|
1125
|
+
case "never":
|
|
1126
|
+
return McpServerToolNeverRequireApprovalMode.from_dict(schema)
|
|
1127
|
+
case "specify":
|
|
1128
|
+
return McpServerToolSpecifyApprovalMode.from_dict(schema)
|
|
1129
|
+
case "approval_mode":
|
|
1130
|
+
return McpServerApprovalMode.from_dict(schema)
|
|
1131
|
+
|
|
1132
|
+
# Other component types
|
|
1133
|
+
case "binding":
|
|
1134
|
+
return Binding.from_dict(schema)
|
|
1135
|
+
case "format":
|
|
1136
|
+
return Format.from_dict(schema)
|
|
1137
|
+
case "parser":
|
|
1138
|
+
return Parser.from_dict(schema)
|
|
1139
|
+
case "template":
|
|
1140
|
+
return Template.from_dict(schema)
|
|
1141
|
+
case "model":
|
|
1142
|
+
return Model.from_dict(schema)
|
|
1143
|
+
case "model_options":
|
|
1144
|
+
return ModelOptions.from_dict(schema)
|
|
1145
|
+
case "property_schema":
|
|
1146
|
+
return PropertySchema.from_dict(schema)
|
|
1147
|
+
case "protocol_version":
|
|
1148
|
+
return ProtocolVersionRecord.from_dict(schema)
|
|
1149
|
+
case "environment_variable":
|
|
1150
|
+
return EnvironmentVariable.from_dict(schema)
|
|
1151
|
+
|
|
1152
|
+
# Unknown kind
|
|
1153
|
+
case _:
|
|
1154
|
+
return None
|