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,868 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from agent_framework import (
|
|
12
|
+
Agent,
|
|
13
|
+
SupportsChatGetResponse,
|
|
14
|
+
)
|
|
15
|
+
from agent_framework import (
|
|
16
|
+
FunctionTool as AFFunctionTool,
|
|
17
|
+
)
|
|
18
|
+
from agent_framework._feature_stage import (
|
|
19
|
+
ExperimentalFeature,
|
|
20
|
+
experimental,
|
|
21
|
+
)
|
|
22
|
+
from agent_framework.exceptions import AgentException
|
|
23
|
+
from dotenv import load_dotenv
|
|
24
|
+
|
|
25
|
+
from ._models import (
|
|
26
|
+
AnonymousConnection,
|
|
27
|
+
ApiKeyConnection,
|
|
28
|
+
CodeInterpreterTool,
|
|
29
|
+
FileSearchTool,
|
|
30
|
+
FunctionTool,
|
|
31
|
+
McpServerToolSpecifyApprovalMode,
|
|
32
|
+
McpTool,
|
|
33
|
+
Model,
|
|
34
|
+
ModelOptions,
|
|
35
|
+
PromptAgent,
|
|
36
|
+
ReferenceConnection,
|
|
37
|
+
RemoteConnection,
|
|
38
|
+
Tool,
|
|
39
|
+
WebSearchTool,
|
|
40
|
+
_safe_mode_context, # type: ignore[reportPrivateUsage]
|
|
41
|
+
agent_schema_dispatch,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if sys.version_info >= (3, 11):
|
|
45
|
+
from typing import TypedDict # pragma: no cover
|
|
46
|
+
else:
|
|
47
|
+
from typing_extensions import TypedDict # pragma: no cover
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@experimental(feature_id=ExperimentalFeature.DECLARATIVE_AGENTS)
|
|
51
|
+
class ProviderTypeMapping(TypedDict, total=True):
|
|
52
|
+
package: str
|
|
53
|
+
name: str
|
|
54
|
+
model_field: str
|
|
55
|
+
endpoint_field: str | None
|
|
56
|
+
api_key_field: str | None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
PROVIDER_TYPE_OBJECT_MAPPING: dict[str, ProviderTypeMapping] = {
|
|
60
|
+
"AzureOpenAI": {
|
|
61
|
+
"package": "agent_framework.openai",
|
|
62
|
+
"name": "OpenAIChatClient",
|
|
63
|
+
"model_field": "model",
|
|
64
|
+
"endpoint_field": "azure_endpoint",
|
|
65
|
+
"api_key_field": "api_key",
|
|
66
|
+
},
|
|
67
|
+
"AzureOpenAI.Chat": {
|
|
68
|
+
"package": "agent_framework.openai",
|
|
69
|
+
"name": "OpenAIChatCompletionClient",
|
|
70
|
+
"model_field": "model",
|
|
71
|
+
"endpoint_field": "azure_endpoint",
|
|
72
|
+
"api_key_field": "api_key",
|
|
73
|
+
},
|
|
74
|
+
"AzureOpenAI.Responses": {
|
|
75
|
+
"package": "agent_framework.openai",
|
|
76
|
+
"name": "OpenAIChatClient",
|
|
77
|
+
"model_field": "model",
|
|
78
|
+
"endpoint_field": "azure_endpoint",
|
|
79
|
+
"api_key_field": "api_key",
|
|
80
|
+
},
|
|
81
|
+
"Foundry": {
|
|
82
|
+
"package": "agent_framework.foundry",
|
|
83
|
+
"name": "FoundryChatClient",
|
|
84
|
+
"model_field": "model",
|
|
85
|
+
"endpoint_field": "project_endpoint",
|
|
86
|
+
"api_key_field": None,
|
|
87
|
+
},
|
|
88
|
+
"OpenAI.Chat": {
|
|
89
|
+
"package": "agent_framework.openai",
|
|
90
|
+
"name": "OpenAIChatCompletionClient",
|
|
91
|
+
"model_field": "model",
|
|
92
|
+
"endpoint_field": "base_url",
|
|
93
|
+
"api_key_field": "api_key",
|
|
94
|
+
},
|
|
95
|
+
"OpenAI.Responses": {
|
|
96
|
+
"package": "agent_framework.openai",
|
|
97
|
+
"name": "OpenAIChatClient",
|
|
98
|
+
"model_field": "model",
|
|
99
|
+
"endpoint_field": "base_url",
|
|
100
|
+
"api_key_field": "api_key",
|
|
101
|
+
},
|
|
102
|
+
"OpenAI": {
|
|
103
|
+
"package": "agent_framework.openai",
|
|
104
|
+
"name": "OpenAIChatClient",
|
|
105
|
+
"model_field": "model",
|
|
106
|
+
"endpoint_field": "base_url",
|
|
107
|
+
"api_key_field": "api_key",
|
|
108
|
+
},
|
|
109
|
+
"Foundry.Chat": {
|
|
110
|
+
"package": "agent_framework.foundry",
|
|
111
|
+
"name": "FoundryChatClient",
|
|
112
|
+
"model_field": "model",
|
|
113
|
+
"endpoint_field": "project_endpoint",
|
|
114
|
+
"api_key_field": None,
|
|
115
|
+
},
|
|
116
|
+
"Anthropic.Chat": {
|
|
117
|
+
"package": "agent_framework.anthropic",
|
|
118
|
+
"name": "AnthropicChatClient",
|
|
119
|
+
"model_field": "model",
|
|
120
|
+
"endpoint_field": None,
|
|
121
|
+
"api_key_field": "api_key",
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@experimental(feature_id=ExperimentalFeature.DECLARATIVE_AGENTS)
|
|
127
|
+
class DeclarativeLoaderError(AgentException):
|
|
128
|
+
"""Exception raised for errors in the declarative loader."""
|
|
129
|
+
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@experimental(feature_id=ExperimentalFeature.DECLARATIVE_AGENTS)
|
|
134
|
+
class ProviderLookupError(DeclarativeLoaderError):
|
|
135
|
+
"""Exception raised for errors in provider type lookup."""
|
|
136
|
+
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@experimental(feature_id=ExperimentalFeature.DECLARATIVE_AGENTS)
|
|
141
|
+
class AgentFactory:
|
|
142
|
+
"""Factory for creating Agent instances from declarative YAML definitions.
|
|
143
|
+
|
|
144
|
+
AgentFactory parses YAML agent definitions (PromptAgent kind) and creates
|
|
145
|
+
configured Agent instances with the appropriate chat client, tools,
|
|
146
|
+
and response format.
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
.. code-block:: python
|
|
150
|
+
|
|
151
|
+
from agent_framework_declarative import AgentFactory
|
|
152
|
+
|
|
153
|
+
# Create agent from YAML file
|
|
154
|
+
factory = AgentFactory()
|
|
155
|
+
agent = factory.create_agent_from_yaml_path("agent.yaml")
|
|
156
|
+
|
|
157
|
+
# Run the agent
|
|
158
|
+
async for event in agent.run("Hello!", stream=True):
|
|
159
|
+
print(event)
|
|
160
|
+
|
|
161
|
+
.. code-block:: python
|
|
162
|
+
|
|
163
|
+
from agent_framework.openai import OpenAIChatClient
|
|
164
|
+
from agent_framework_declarative import AgentFactory
|
|
165
|
+
|
|
166
|
+
# With pre-configured chat client
|
|
167
|
+
client = OpenAIChatClient()
|
|
168
|
+
factory = AgentFactory(client=client)
|
|
169
|
+
agent = factory.create_agent_from_yaml_path("agent.yaml")
|
|
170
|
+
|
|
171
|
+
.. code-block:: python
|
|
172
|
+
|
|
173
|
+
from agent_framework_declarative import AgentFactory
|
|
174
|
+
|
|
175
|
+
# From inline YAML string
|
|
176
|
+
yaml_content = '''
|
|
177
|
+
kind: Prompt
|
|
178
|
+
name: GreetingAgent
|
|
179
|
+
instructions: You are a friendly assistant.
|
|
180
|
+
model:
|
|
181
|
+
id: gpt-4o
|
|
182
|
+
provider: AzureOpenAI
|
|
183
|
+
'''
|
|
184
|
+
|
|
185
|
+
factory = AgentFactory()
|
|
186
|
+
agent = factory.create_agent_from_yaml(yaml_content)
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(
|
|
190
|
+
self,
|
|
191
|
+
*,
|
|
192
|
+
client: SupportsChatGetResponse | None = None,
|
|
193
|
+
bindings: Mapping[str, Any] | None = None,
|
|
194
|
+
connections: Mapping[str, Any] | None = None,
|
|
195
|
+
client_kwargs: Mapping[str, Any] | None = None,
|
|
196
|
+
additional_mappings: Mapping[str, ProviderTypeMapping] | None = None,
|
|
197
|
+
default_provider: str = "Foundry",
|
|
198
|
+
safe_mode: bool = True,
|
|
199
|
+
env_file_path: str | None = None,
|
|
200
|
+
env_file_encoding: str | None = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Create the agent factory.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
client: An optional SupportsChatGetResponse instance to use as a dependency.
|
|
206
|
+
This will be passed to the Agent that gets created.
|
|
207
|
+
If you need to create multiple agents with different chat clients,
|
|
208
|
+
do not pass this and instead provide the chat client in the YAML definition.
|
|
209
|
+
bindings: An optional dictionary of bindings to use when creating agents.
|
|
210
|
+
connections: An optional dictionary of connections to resolve ReferenceConnections.
|
|
211
|
+
client_kwargs: An optional dictionary of keyword arguments to pass to chat client constructor.
|
|
212
|
+
additional_mappings: An optional dictionary to extend the provider type to object mapping.
|
|
213
|
+
Should have the structure:
|
|
214
|
+
|
|
215
|
+
..code-block:: python
|
|
216
|
+
|
|
217
|
+
additional_mappings = {
|
|
218
|
+
"Provider.ApiType": {
|
|
219
|
+
"package": "package.name",
|
|
220
|
+
"name": "ClassName",
|
|
221
|
+
"model_field": "field_name_in_constructor",
|
|
222
|
+
"endpoint_field": "endpoint_kwarg_name_or_null",
|
|
223
|
+
"api_key_field": "api_key_kwarg_name_or_null",
|
|
224
|
+
},
|
|
225
|
+
...
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Here, "Provider.ApiType" is the lookup key used when both provider and apiType are specified in the
|
|
229
|
+
model, "Provider" is also allowed.
|
|
230
|
+
Package refers to which model needs to be imported, Name is the class name of the
|
|
231
|
+
SupportsChatGetResponse implementation, and model_field is the name of the field in the
|
|
232
|
+
constructor that accepts the model.id value.
|
|
233
|
+
default_provider: The default provider used when model.provider is not specified,
|
|
234
|
+
default is "Foundry", which uses the FoundryChatClient.
|
|
235
|
+
safe_mode: Whether to run in safe mode, default is True.
|
|
236
|
+
When safe_mode is True, environment variables are not accessible in the powerfx expressions.
|
|
237
|
+
You can still use environment variables, but through the constructors of the classes.
|
|
238
|
+
Which means you must make sure you are using the standard env variable names of the classes
|
|
239
|
+
you are using and not custom ones and remove the powerfx statements that start with `=Env.`.
|
|
240
|
+
Only when you trust the source of your yaml files, you can set safe_mode to False
|
|
241
|
+
via the AgentFactory constructor.
|
|
242
|
+
env_file_path: The path to the .env file to load environment variables from.
|
|
243
|
+
env_file_encoding: The encoding of the .env file, defaults to 'utf-8'.
|
|
244
|
+
|
|
245
|
+
Examples:
|
|
246
|
+
.. code-block:: python
|
|
247
|
+
|
|
248
|
+
from agent_framework_declarative import AgentFactory
|
|
249
|
+
|
|
250
|
+
# Minimal initialization
|
|
251
|
+
factory = AgentFactory()
|
|
252
|
+
|
|
253
|
+
.. code-block:: python
|
|
254
|
+
|
|
255
|
+
from agent_framework.openai import OpenAIChatClient
|
|
256
|
+
from agent_framework_declarative import AgentFactory
|
|
257
|
+
|
|
258
|
+
# With shared chat client
|
|
259
|
+
client = OpenAIChatClient()
|
|
260
|
+
factory = AgentFactory(
|
|
261
|
+
client=client,
|
|
262
|
+
env_file_path=".env",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
.. code-block:: python
|
|
266
|
+
|
|
267
|
+
from agent_framework_declarative import AgentFactory
|
|
268
|
+
|
|
269
|
+
# With custom provider mappings
|
|
270
|
+
factory = AgentFactory(
|
|
271
|
+
additional_mappings={
|
|
272
|
+
"CustomProvider.Chat": {
|
|
273
|
+
"package": "my_package.clients",
|
|
274
|
+
"name": "CustomChatClient",
|
|
275
|
+
"model_field": "model",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
"""
|
|
280
|
+
self.client = client
|
|
281
|
+
self.bindings = bindings
|
|
282
|
+
self.connections = connections
|
|
283
|
+
self.client_kwargs = client_kwargs or {}
|
|
284
|
+
self.additional_mappings = additional_mappings or {}
|
|
285
|
+
self.default_provider: str = default_provider
|
|
286
|
+
self.safe_mode = safe_mode
|
|
287
|
+
load_dotenv(dotenv_path=env_file_path, encoding=env_file_encoding)
|
|
288
|
+
|
|
289
|
+
def create_agent_from_yaml_path(self, yaml_path: str | Path) -> Agent:
|
|
290
|
+
"""Create a Agent from a YAML file path.
|
|
291
|
+
|
|
292
|
+
This method does the following things:
|
|
293
|
+
|
|
294
|
+
1. Loads the YAML file into an AgentSchema object.
|
|
295
|
+
2. Validates that the loaded object is a PromptAgent.
|
|
296
|
+
3. Creates the appropriate ChatClient based on the model provider and apiType.
|
|
297
|
+
4. Parses the tools, options, and response format from the PromptAgent.
|
|
298
|
+
5. Creates and returns a Agent instance with the configured properties.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
yaml_path: Path to the YAML file representation of a PromptAgent.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
The ``Agent`` instance created from the YAML file.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
DeclarativeLoaderError: If the YAML does not represent a PromptAgent.
|
|
308
|
+
ProviderLookupError: If the provider type is unknown or unsupported.
|
|
309
|
+
ValueError: If a ReferenceConnection cannot be resolved.
|
|
310
|
+
ModuleNotFoundError: If the required module for the provider type cannot be imported.
|
|
311
|
+
AttributeError: If the required class for the provider type cannot be found in the module.
|
|
312
|
+
|
|
313
|
+
Examples:
|
|
314
|
+
.. code-block:: python
|
|
315
|
+
|
|
316
|
+
from agent_framework_declarative import AgentFactory
|
|
317
|
+
|
|
318
|
+
factory = AgentFactory()
|
|
319
|
+
agent = factory.create_agent_from_yaml_path("agents/support_agent.yaml")
|
|
320
|
+
|
|
321
|
+
# Execute the agent
|
|
322
|
+
async for event in agent.run("Help me with my order", stream=True):
|
|
323
|
+
print(event)
|
|
324
|
+
|
|
325
|
+
.. code-block:: python
|
|
326
|
+
|
|
327
|
+
from pathlib import Path
|
|
328
|
+
from agent_framework_declarative import AgentFactory
|
|
329
|
+
|
|
330
|
+
# Using Path object for cross-platform compatibility
|
|
331
|
+
agent_path = Path(__file__).parent / "agents" / "writer.yaml"
|
|
332
|
+
factory = AgentFactory()
|
|
333
|
+
agent = factory.create_agent_from_yaml_path(agent_path)
|
|
334
|
+
"""
|
|
335
|
+
if not isinstance(yaml_path, Path):
|
|
336
|
+
yaml_path = Path(yaml_path)
|
|
337
|
+
if not yaml_path.exists():
|
|
338
|
+
raise DeclarativeLoaderError(f"YAML file not found at path: {yaml_path}")
|
|
339
|
+
with open(yaml_path) as f:
|
|
340
|
+
yaml_str = f.read()
|
|
341
|
+
return self.create_agent_from_yaml(yaml_str)
|
|
342
|
+
|
|
343
|
+
def create_agent_from_yaml(self, yaml_str: str) -> Agent:
|
|
344
|
+
"""Create a Agent from a YAML string.
|
|
345
|
+
|
|
346
|
+
This method does the following things:
|
|
347
|
+
|
|
348
|
+
1. Loads the YAML string into an AgentSchema object.
|
|
349
|
+
2. Validates that the loaded object is a PromptAgent.
|
|
350
|
+
3. Creates the appropriate ChatClient based on the model provider and apiType.
|
|
351
|
+
4. Parses the tools, options, and response format from the PromptAgent.
|
|
352
|
+
5. Creates and returns a Agent instance with the configured properties.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
yaml_str: YAML string representation of a PromptAgent.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
The ``Agent`` instance created from the YAML string.
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
DeclarativeLoaderError: If the YAML does not represent a PromptAgent.
|
|
362
|
+
ProviderLookupError: If the provider type is unknown or unsupported.
|
|
363
|
+
ValueError: If a ReferenceConnection cannot be resolved.
|
|
364
|
+
ModuleNotFoundError: If the required module for the provider type cannot be imported.
|
|
365
|
+
AttributeError: If the required class for the provider type cannot be found in the module.
|
|
366
|
+
|
|
367
|
+
Examples:
|
|
368
|
+
.. code-block:: python
|
|
369
|
+
|
|
370
|
+
from agent_framework_declarative import AgentFactory
|
|
371
|
+
|
|
372
|
+
yaml_content = '''
|
|
373
|
+
kind: Prompt
|
|
374
|
+
name: TranslationAgent
|
|
375
|
+
description: Translates text between languages
|
|
376
|
+
instructions: |
|
|
377
|
+
You are a translation assistant.
|
|
378
|
+
Translate user input to the requested language.
|
|
379
|
+
model:
|
|
380
|
+
id: gpt-4o
|
|
381
|
+
provider: AzureOpenAI
|
|
382
|
+
options:
|
|
383
|
+
temperature: 0.3
|
|
384
|
+
'''
|
|
385
|
+
|
|
386
|
+
factory = AgentFactory()
|
|
387
|
+
agent = factory.create_agent_from_yaml(yaml_content)
|
|
388
|
+
|
|
389
|
+
.. code-block:: python
|
|
390
|
+
|
|
391
|
+
from agent_framework_declarative import AgentFactory
|
|
392
|
+
from pydantic import BaseModel
|
|
393
|
+
|
|
394
|
+
# Agent with structured output
|
|
395
|
+
yaml_content = '''
|
|
396
|
+
kind: Prompt
|
|
397
|
+
name: SentimentAnalyzer
|
|
398
|
+
instructions: Analyze the sentiment of the input text.
|
|
399
|
+
model:
|
|
400
|
+
id: gpt-4o
|
|
401
|
+
outputSchema:
|
|
402
|
+
type: object
|
|
403
|
+
properties:
|
|
404
|
+
sentiment:
|
|
405
|
+
type: string
|
|
406
|
+
enum: [positive, negative, neutral]
|
|
407
|
+
confidence:
|
|
408
|
+
type: number
|
|
409
|
+
'''
|
|
410
|
+
|
|
411
|
+
factory = AgentFactory()
|
|
412
|
+
agent = factory.create_agent_from_yaml(yaml_content)
|
|
413
|
+
"""
|
|
414
|
+
return self.create_agent_from_dict(yaml.safe_load(yaml_str))
|
|
415
|
+
|
|
416
|
+
def create_agent_from_dict(self, agent_def: dict[str, Any]) -> Agent:
|
|
417
|
+
"""Create a Agent from a dictionary definition.
|
|
418
|
+
|
|
419
|
+
This method does the following things:
|
|
420
|
+
|
|
421
|
+
1. Converts the dictionary into an AgentSchema object.
|
|
422
|
+
2. Validates that the loaded object is a PromptAgent.
|
|
423
|
+
3. Creates the appropriate ChatClient based on the model provider and apiType.
|
|
424
|
+
4. Parses the tools, options, and response format from the PromptAgent.
|
|
425
|
+
5. Creates and returns a Agent instance with the configured properties.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
agent_def: Dictionary representation of a PromptAgent.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
The `Agent` instance created from the dictionary.
|
|
432
|
+
|
|
433
|
+
Raises:
|
|
434
|
+
DeclarativeLoaderError: If the dictionary does not represent a PromptAgent.
|
|
435
|
+
ProviderLookupError: If the provider type is unknown or unsupported.
|
|
436
|
+
ValueError: If a ReferenceConnection cannot be resolved.
|
|
437
|
+
ModuleNotFoundError: If the required module for the provider type cannot be imported.
|
|
438
|
+
AttributeError: If the required class for the provider type cannot be found in the module.
|
|
439
|
+
|
|
440
|
+
Examples:
|
|
441
|
+
.. code-block:: python
|
|
442
|
+
|
|
443
|
+
from agent_framework_declarative import AgentFactory
|
|
444
|
+
|
|
445
|
+
agent_def = {
|
|
446
|
+
"kind": "Prompt",
|
|
447
|
+
"name": "TranslationAgent",
|
|
448
|
+
"description": "Translates text between languages",
|
|
449
|
+
"instructions": "You are a translation assistant.",
|
|
450
|
+
"model": {
|
|
451
|
+
"id": "gpt-4o",
|
|
452
|
+
"provider": "AzureOpenAI",
|
|
453
|
+
},
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
factory = AgentFactory()
|
|
457
|
+
agent = factory.create_agent_from_dict(agent_def)
|
|
458
|
+
"""
|
|
459
|
+
# Set safe_mode context before parsing YAML to control PowerFx environment variable access
|
|
460
|
+
_safe_mode_context.set(self.safe_mode)
|
|
461
|
+
prompt_agent = agent_schema_dispatch(agent_def)
|
|
462
|
+
if not isinstance(prompt_agent, PromptAgent):
|
|
463
|
+
raise DeclarativeLoaderError("Only definitions for a PromptAgent are supported for agent creation.")
|
|
464
|
+
|
|
465
|
+
# Step 1: Create the ChatClient
|
|
466
|
+
client = self._get_client(prompt_agent)
|
|
467
|
+
# Step 2: Get the chat options
|
|
468
|
+
chat_options = self._parse_chat_options(prompt_agent.model)
|
|
469
|
+
if tools := self._parse_tools(prompt_agent.tools):
|
|
470
|
+
chat_options["tools"] = tools
|
|
471
|
+
if output_schema := prompt_agent.outputSchema:
|
|
472
|
+
chat_options["response_format"] = output_schema.to_json_schema()
|
|
473
|
+
# Step 3: Create the agent instance
|
|
474
|
+
return Agent(
|
|
475
|
+
client=client,
|
|
476
|
+
name=prompt_agent.name,
|
|
477
|
+
description=prompt_agent.description,
|
|
478
|
+
instructions=prompt_agent.instructions,
|
|
479
|
+
default_options=chat_options, # type: ignore[arg-type]
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
async def create_agent_from_yaml_path_async(self, yaml_path: str | Path) -> Agent:
|
|
483
|
+
"""Async version: Create a Agent from a YAML file path.
|
|
484
|
+
|
|
485
|
+
This is the async counterpart to ``create_agent_from_dict`` and is useful when
|
|
486
|
+
the rest of your setup is already async.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
yaml_path: Path to the YAML file representation of a PromptAgent.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
The ``Agent`` instance created from the YAML file.
|
|
493
|
+
|
|
494
|
+
Examples:
|
|
495
|
+
.. code-block:: python
|
|
496
|
+
|
|
497
|
+
from agent_framework_declarative import AgentFactory
|
|
498
|
+
|
|
499
|
+
factory = AgentFactory(
|
|
500
|
+
client_kwargs={"credential": credential},
|
|
501
|
+
default_provider="Foundry",
|
|
502
|
+
)
|
|
503
|
+
agent = await factory.create_agent_from_yaml_path_async("agent.yaml")
|
|
504
|
+
"""
|
|
505
|
+
if not isinstance(yaml_path, Path):
|
|
506
|
+
yaml_path = Path(yaml_path)
|
|
507
|
+
if not yaml_path.exists():
|
|
508
|
+
raise DeclarativeLoaderError(f"YAML file not found at path: {yaml_path}")
|
|
509
|
+
yaml_str = yaml_path.read_text()
|
|
510
|
+
return await self.create_agent_from_yaml_async(yaml_str)
|
|
511
|
+
|
|
512
|
+
async def create_agent_from_yaml_async(self, yaml_str: str) -> Agent:
|
|
513
|
+
"""Async version: Create a Agent from a YAML string.
|
|
514
|
+
|
|
515
|
+
Use this method when the surrounding call site is already async and you
|
|
516
|
+
want to build an agent directly from YAML text.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
yaml_str: YAML string representation of a PromptAgent.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
The ``Agent`` instance created from the YAML string.
|
|
523
|
+
|
|
524
|
+
Examples:
|
|
525
|
+
.. code-block:: python
|
|
526
|
+
|
|
527
|
+
from agent_framework_declarative import AgentFactory
|
|
528
|
+
|
|
529
|
+
yaml_content = '''
|
|
530
|
+
kind: Prompt
|
|
531
|
+
name: MyAgent
|
|
532
|
+
instructions: You are a helpful assistant.
|
|
533
|
+
model:
|
|
534
|
+
id: gpt-4o
|
|
535
|
+
provider: Foundry
|
|
536
|
+
'''
|
|
537
|
+
|
|
538
|
+
factory = AgentFactory(client_kwargs={"credential": credential})
|
|
539
|
+
agent = await factory.create_agent_from_yaml_async(yaml_content)
|
|
540
|
+
"""
|
|
541
|
+
return await self.create_agent_from_dict_async(yaml.safe_load(yaml_str))
|
|
542
|
+
|
|
543
|
+
async def create_agent_from_dict_async(self, agent_def: dict[str, Any]) -> Agent:
|
|
544
|
+
"""Async version: Create a Agent from a dictionary definition.
|
|
545
|
+
|
|
546
|
+
This is the async counterpart to ``create_agent_from_dict`` and is useful when
|
|
547
|
+
the rest of your setup is already async.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
agent_def: Dictionary representation of a PromptAgent.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
The ``Agent`` instance created from the dictionary.
|
|
554
|
+
|
|
555
|
+
Examples:
|
|
556
|
+
.. code-block:: python
|
|
557
|
+
|
|
558
|
+
from agent_framework_declarative import AgentFactory
|
|
559
|
+
|
|
560
|
+
agent_def = {
|
|
561
|
+
"kind": "Prompt",
|
|
562
|
+
"name": "MyAgent",
|
|
563
|
+
"instructions": "You are a helpful assistant.",
|
|
564
|
+
"model": {
|
|
565
|
+
"id": "gpt-4o",
|
|
566
|
+
"provider": "Foundry",
|
|
567
|
+
},
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
factory = AgentFactory(client_kwargs={"credential": credential})
|
|
571
|
+
agent = await factory.create_agent_from_dict_async(agent_def)
|
|
572
|
+
"""
|
|
573
|
+
# Set safe_mode context before parsing YAML to control PowerFx environment variable access
|
|
574
|
+
_safe_mode_context.set(self.safe_mode)
|
|
575
|
+
prompt_agent = agent_schema_dispatch(agent_def)
|
|
576
|
+
if not isinstance(prompt_agent, PromptAgent):
|
|
577
|
+
raise DeclarativeLoaderError("Only definitions for a PromptAgent are supported for agent creation.")
|
|
578
|
+
|
|
579
|
+
client = self._get_client(prompt_agent)
|
|
580
|
+
chat_options = self._parse_chat_options(prompt_agent.model)
|
|
581
|
+
if tools := self._parse_tools(prompt_agent.tools):
|
|
582
|
+
chat_options["tools"] = tools
|
|
583
|
+
if output_schema := prompt_agent.outputSchema:
|
|
584
|
+
chat_options["response_format"] = output_schema.to_json_schema()
|
|
585
|
+
return Agent(
|
|
586
|
+
client=client,
|
|
587
|
+
name=prompt_agent.name,
|
|
588
|
+
description=prompt_agent.description,
|
|
589
|
+
instructions=prompt_agent.instructions,
|
|
590
|
+
default_options=chat_options, # type: ignore[arg-type]
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
async def _create_agent_with_provider(self, prompt_agent: PromptAgent, mapping: ProviderTypeMapping) -> Agent:
|
|
594
|
+
"""Create an Agent through a provider object that exposes ``create_agent``.
|
|
595
|
+
|
|
596
|
+
This remains available as an internal escape hatch for provider-style custom mappings
|
|
597
|
+
that return a fully constructed ``Agent`` rather than a chat client.
|
|
598
|
+
"""
|
|
599
|
+
module_name = mapping["package"]
|
|
600
|
+
class_name = mapping["name"]
|
|
601
|
+
module = __import__(module_name, fromlist=[class_name])
|
|
602
|
+
provider_class = getattr(module, class_name)
|
|
603
|
+
|
|
604
|
+
provider_kwargs: dict[str, Any] = {}
|
|
605
|
+
provider_kwargs.update(self.client_kwargs)
|
|
606
|
+
|
|
607
|
+
endpoint_field = mapping.get("endpoint_field")
|
|
608
|
+
api_key_field = mapping.get("api_key_field", "api_key")
|
|
609
|
+
|
|
610
|
+
if prompt_agent.model and prompt_agent.model.connection:
|
|
611
|
+
match prompt_agent.model.connection:
|
|
612
|
+
case ApiKeyConnection():
|
|
613
|
+
if api_key_field:
|
|
614
|
+
provider_kwargs[api_key_field] = prompt_agent.model.connection.apiKey
|
|
615
|
+
if prompt_agent.model.connection.endpoint and endpoint_field:
|
|
616
|
+
provider_kwargs[endpoint_field] = prompt_agent.model.connection.endpoint
|
|
617
|
+
case RemoteConnection() | AnonymousConnection():
|
|
618
|
+
if prompt_agent.model.connection.endpoint and endpoint_field:
|
|
619
|
+
provider_kwargs[endpoint_field] = prompt_agent.model.connection.endpoint
|
|
620
|
+
case ReferenceConnection():
|
|
621
|
+
pass
|
|
622
|
+
|
|
623
|
+
provider = provider_class(**provider_kwargs)
|
|
624
|
+
tools = self._parse_tools(prompt_agent.tools) if prompt_agent.tools else None
|
|
625
|
+
|
|
626
|
+
default_options: dict[str, Any] | None = None
|
|
627
|
+
if prompt_agent.outputSchema:
|
|
628
|
+
default_options = {"response_format": prompt_agent.outputSchema.to_json_schema()}
|
|
629
|
+
|
|
630
|
+
return cast(
|
|
631
|
+
Agent,
|
|
632
|
+
await provider.create_agent(
|
|
633
|
+
name=prompt_agent.name,
|
|
634
|
+
model=prompt_agent.model.id if prompt_agent.model else None,
|
|
635
|
+
instructions=prompt_agent.instructions,
|
|
636
|
+
description=prompt_agent.description,
|
|
637
|
+
tools=tools,
|
|
638
|
+
default_options=default_options,
|
|
639
|
+
),
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
def _get_client(self, prompt_agent: PromptAgent) -> SupportsChatGetResponse:
|
|
643
|
+
"""Create the SupportsChatGetResponse instance based on the PromptAgent model."""
|
|
644
|
+
if not prompt_agent.model:
|
|
645
|
+
# if no model is defined, use the supplied client
|
|
646
|
+
if self.client:
|
|
647
|
+
return self.client
|
|
648
|
+
raise DeclarativeLoaderError(
|
|
649
|
+
"ChatClient must be provided to create agent from PromptAgent, "
|
|
650
|
+
"alternatively define a model in the PromptAgent."
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
mapping = self._retrieve_provider_configuration(prompt_agent.model)
|
|
654
|
+
setup_dict: dict[str, Any] = {}
|
|
655
|
+
setup_dict.update(self.client_kwargs)
|
|
656
|
+
endpoint_field = mapping.get("endpoint_field")
|
|
657
|
+
api_key_field = mapping.get("api_key_field", "api_key")
|
|
658
|
+
|
|
659
|
+
# parse connections
|
|
660
|
+
if prompt_agent.model.connection:
|
|
661
|
+
match prompt_agent.model.connection:
|
|
662
|
+
case ApiKeyConnection():
|
|
663
|
+
if api_key_field:
|
|
664
|
+
setup_dict[api_key_field] = prompt_agent.model.connection.apiKey
|
|
665
|
+
elif prompt_agent.model.connection.apiKey:
|
|
666
|
+
raise DeclarativeLoaderError(
|
|
667
|
+
f"{mapping['name']} does not support API key-based model connections."
|
|
668
|
+
)
|
|
669
|
+
if prompt_agent.model.connection.endpoint:
|
|
670
|
+
if not endpoint_field:
|
|
671
|
+
raise DeclarativeLoaderError(
|
|
672
|
+
f"{mapping['name']} does not support endpoint-based model connections."
|
|
673
|
+
)
|
|
674
|
+
setup_dict[endpoint_field] = prompt_agent.model.connection.endpoint
|
|
675
|
+
case RemoteConnection() | AnonymousConnection():
|
|
676
|
+
if prompt_agent.model.connection.endpoint:
|
|
677
|
+
if not endpoint_field:
|
|
678
|
+
raise DeclarativeLoaderError(
|
|
679
|
+
f"{mapping['name']} does not support endpoint-based model connections."
|
|
680
|
+
)
|
|
681
|
+
setup_dict[endpoint_field] = prompt_agent.model.connection.endpoint
|
|
682
|
+
case ReferenceConnection():
|
|
683
|
+
if not self.connections:
|
|
684
|
+
raise ValueError("Connections must be provided to resolve ReferenceConnection")
|
|
685
|
+
# find the referenced connection
|
|
686
|
+
if prompt_agent.model.connection.name and (
|
|
687
|
+
value := self.connections.get(prompt_agent.model.connection.name)
|
|
688
|
+
):
|
|
689
|
+
setup_dict[prompt_agent.model.connection.name] = value
|
|
690
|
+
else:
|
|
691
|
+
raise ValueError(
|
|
692
|
+
f"ReferenceConnection with name {prompt_agent.model.connection.name} not found in provided "
|
|
693
|
+
"connections."
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
# Any client we create, needs a model.id
|
|
697
|
+
if not prompt_agent.model.id:
|
|
698
|
+
# if prompt_agent.model is defined, but no id, use the supplied client
|
|
699
|
+
if self.client:
|
|
700
|
+
return self.client
|
|
701
|
+
# or raise, since we cannot create a client without a model
|
|
702
|
+
raise DeclarativeLoaderError(
|
|
703
|
+
"ChatClient must be provided to create agent from PromptAgent, or define model.id in the PromptAgent."
|
|
704
|
+
)
|
|
705
|
+
# if provider is defined, use that, if possible with apiType, fallback to default_provider
|
|
706
|
+
module_name = mapping["package"]
|
|
707
|
+
class_name = mapping["name"]
|
|
708
|
+
module = __import__(module_name, fromlist=[class_name])
|
|
709
|
+
agent_class = getattr(module, class_name)
|
|
710
|
+
setup_dict[mapping["model_field"]] = prompt_agent.model.id
|
|
711
|
+
return agent_class(**setup_dict)
|
|
712
|
+
|
|
713
|
+
def _parse_chat_options(self, model: Model | None) -> dict[str, Any]:
|
|
714
|
+
"""Parse ModelOptions into chat options dictionary."""
|
|
715
|
+
chat_options: dict[str, Any] = {}
|
|
716
|
+
if not model or not model.options or not isinstance(model.options, ModelOptions):
|
|
717
|
+
return chat_options
|
|
718
|
+
options = model.options
|
|
719
|
+
if options.frequencyPenalty is not None:
|
|
720
|
+
chat_options["frequency_penalty"] = options.frequencyPenalty
|
|
721
|
+
if options.presencePenalty is not None:
|
|
722
|
+
chat_options["presence_penalty"] = options.presencePenalty
|
|
723
|
+
if options.maxOutputTokens is not None:
|
|
724
|
+
chat_options["max_tokens"] = options.maxOutputTokens
|
|
725
|
+
if options.temperature is not None:
|
|
726
|
+
chat_options["temperature"] = options.temperature
|
|
727
|
+
if options.topP is not None:
|
|
728
|
+
chat_options["top_p"] = options.topP
|
|
729
|
+
if options.seed is not None:
|
|
730
|
+
chat_options["seed"] = options.seed
|
|
731
|
+
if options.stopSequences:
|
|
732
|
+
chat_options["stop"] = options.stopSequences
|
|
733
|
+
if options.allowMultipleToolCalls is not None:
|
|
734
|
+
chat_options["allow_multiple_tool_calls"] = options.allowMultipleToolCalls
|
|
735
|
+
if (chat_tool_mode := options.additionalProperties.pop("chatToolMode", None)) is not None:
|
|
736
|
+
chat_options["tool_choice"] = chat_tool_mode
|
|
737
|
+
if options.additionalProperties:
|
|
738
|
+
chat_options["additional_chat_options"] = options.additionalProperties
|
|
739
|
+
return chat_options
|
|
740
|
+
|
|
741
|
+
def _parse_tools(self, tools: list[Tool] | None) -> list[AFFunctionTool | dict[str, Any]] | None:
|
|
742
|
+
"""Parse tool resources into AFFunctionTool instances or dict-based tools."""
|
|
743
|
+
if not tools:
|
|
744
|
+
return None
|
|
745
|
+
return [self._parse_tool(tool_resource) for tool_resource in tools]
|
|
746
|
+
|
|
747
|
+
def _parse_tool(self, tool_resource: Tool) -> AFFunctionTool | dict[str, Any]:
|
|
748
|
+
"""Parse a single tool resource into an AFFunctionTool instance."""
|
|
749
|
+
match tool_resource:
|
|
750
|
+
case FunctionTool():
|
|
751
|
+
func: Callable[..., Any] | None = None
|
|
752
|
+
if self.bindings and tool_resource.bindings:
|
|
753
|
+
for binding in tool_resource.bindings:
|
|
754
|
+
if binding.name and (func := self.bindings.get(binding.name)):
|
|
755
|
+
break
|
|
756
|
+
return AFFunctionTool(
|
|
757
|
+
name=tool_resource.name, # type: ignore
|
|
758
|
+
description=tool_resource.description, # type: ignore
|
|
759
|
+
input_model=tool_resource.parameters.to_json_schema() if tool_resource.parameters else None,
|
|
760
|
+
func=func,
|
|
761
|
+
)
|
|
762
|
+
case WebSearchTool():
|
|
763
|
+
result: dict[str, Any] = {"type": "web_search_preview"}
|
|
764
|
+
if tool_resource.description:
|
|
765
|
+
result["description"] = tool_resource.description
|
|
766
|
+
if tool_resource.options:
|
|
767
|
+
result.update(tool_resource.options)
|
|
768
|
+
return result
|
|
769
|
+
case FileSearchTool():
|
|
770
|
+
result = {
|
|
771
|
+
"type": "file_search",
|
|
772
|
+
"vector_store_ids": tool_resource.vectorStoreIds or [],
|
|
773
|
+
}
|
|
774
|
+
if tool_resource.maximumResultCount is not None:
|
|
775
|
+
result["max_num_results"] = tool_resource.maximumResultCount
|
|
776
|
+
if tool_resource.description:
|
|
777
|
+
result["description"] = tool_resource.description
|
|
778
|
+
if tool_resource.ranker is not None:
|
|
779
|
+
result["ranker"] = tool_resource.ranker
|
|
780
|
+
if tool_resource.scoreThreshold is not None:
|
|
781
|
+
result["score_threshold"] = tool_resource.scoreThreshold
|
|
782
|
+
if tool_resource.filters:
|
|
783
|
+
result["filters"] = tool_resource.filters
|
|
784
|
+
return result
|
|
785
|
+
case CodeInterpreterTool():
|
|
786
|
+
result = {"type": "code_interpreter"}
|
|
787
|
+
if tool_resource.fileIds:
|
|
788
|
+
result["file_ids"] = tool_resource.fileIds
|
|
789
|
+
if tool_resource.description:
|
|
790
|
+
result["description"] = tool_resource.description
|
|
791
|
+
return result
|
|
792
|
+
case McpTool():
|
|
793
|
+
result = {
|
|
794
|
+
"type": "mcp",
|
|
795
|
+
"server_label": tool_resource.name.replace(" ", "_") if tool_resource.name else "",
|
|
796
|
+
"server_url": str(tool_resource.url) if tool_resource.url else "",
|
|
797
|
+
}
|
|
798
|
+
if tool_resource.description:
|
|
799
|
+
result["server_description"] = tool_resource.description
|
|
800
|
+
if tool_resource.allowedTools:
|
|
801
|
+
result["allowed_tools"] = list(tool_resource.allowedTools)
|
|
802
|
+
|
|
803
|
+
# Handle approval mode
|
|
804
|
+
if tool_resource.approvalMode is not None:
|
|
805
|
+
if tool_resource.approvalMode.kind == "always":
|
|
806
|
+
result["require_approval"] = "always"
|
|
807
|
+
elif tool_resource.approvalMode.kind == "never":
|
|
808
|
+
result["require_approval"] = "never"
|
|
809
|
+
elif isinstance(tool_resource.approvalMode, McpServerToolSpecifyApprovalMode):
|
|
810
|
+
approval_config: dict[str, Any] = {}
|
|
811
|
+
if tool_resource.approvalMode.alwaysRequireApprovalTools:
|
|
812
|
+
approval_config["always"] = {
|
|
813
|
+
"tool_names": list(tool_resource.approvalMode.alwaysRequireApprovalTools)
|
|
814
|
+
}
|
|
815
|
+
if tool_resource.approvalMode.neverRequireApprovalTools:
|
|
816
|
+
approval_config["never"] = {
|
|
817
|
+
"tool_names": list(tool_resource.approvalMode.neverRequireApprovalTools)
|
|
818
|
+
}
|
|
819
|
+
if approval_config:
|
|
820
|
+
result["require_approval"] = approval_config
|
|
821
|
+
|
|
822
|
+
# Handle connection settings
|
|
823
|
+
if tool_resource.connection is not None:
|
|
824
|
+
match tool_resource.connection:
|
|
825
|
+
case ApiKeyConnection():
|
|
826
|
+
if tool_resource.connection.apiKey:
|
|
827
|
+
result["headers"] = {"Authorization": f"Bearer {tool_resource.connection.apiKey}"}
|
|
828
|
+
case RemoteConnection():
|
|
829
|
+
result["project_connection_id"] = tool_resource.connection.name
|
|
830
|
+
case ReferenceConnection():
|
|
831
|
+
result["project_connection_id"] = tool_resource.connection.name
|
|
832
|
+
case AnonymousConnection():
|
|
833
|
+
pass
|
|
834
|
+
case _:
|
|
835
|
+
raise ValueError(f"Unsupported connection kind: {tool_resource.connection.kind}")
|
|
836
|
+
|
|
837
|
+
return result
|
|
838
|
+
case _:
|
|
839
|
+
raise ValueError(f"Unsupported tool kind: {tool_resource.kind}")
|
|
840
|
+
|
|
841
|
+
def _retrieve_provider_configuration(self, model: Model) -> ProviderTypeMapping:
|
|
842
|
+
"""Retrieve the provider configuration based on the model's provider and apiType.
|
|
843
|
+
|
|
844
|
+
If only provider is specified, it will be used.
|
|
845
|
+
If both provider and apiType are specified, both will be used.
|
|
846
|
+
If neither is specified, the default_provider will be used.
|
|
847
|
+
|
|
848
|
+
Args:
|
|
849
|
+
model: The Model instance containing provider and apiType information.
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
A dictionary containing the package, name, and model_field for the provider.
|
|
853
|
+
|
|
854
|
+
Raises:
|
|
855
|
+
ProviderLookupError: If the provider type is not supported or can't be found.
|
|
856
|
+
"""
|
|
857
|
+
class_lookup = (
|
|
858
|
+
f"{model.provider}.{model.apiType}"
|
|
859
|
+
if model.apiType
|
|
860
|
+
else f"{model.provider}"
|
|
861
|
+
if model.provider
|
|
862
|
+
else self.default_provider
|
|
863
|
+
)
|
|
864
|
+
if class_lookup in self.additional_mappings:
|
|
865
|
+
return self.additional_mappings[class_lookup]
|
|
866
|
+
if class_lookup not in PROVIDER_TYPE_OBJECT_MAPPING:
|
|
867
|
+
raise ProviderLookupError(f"Unsupported provider type: {class_lookup}")
|
|
868
|
+
return PROVIDER_TYPE_OBJECT_MAPPING[class_lookup]
|