agent-framework-foundry 1.1.0__tar.gz → 1.2.0__tar.gz
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_foundry-1.1.0 → agent_framework_foundry-1.2.0}/PKG-INFO +2 -2
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/__init__.py +2 -1
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_agent.py +228 -29
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_chat_client.py +37 -6
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_foundry_evals.py +18 -2
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_memory_provider.py +2 -2
- agent_framework_foundry-1.2.0/agent_framework_foundry/_oauth_helpers.py +75 -0
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_tools.py +44 -13
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/pyproject.toml +2 -2
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/LICENSE +0 -0
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/README.md +0 -0
- {agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_embedding_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-foundry
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Microsoft Foundry integrations for Microsoft Agent Framework.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: agent-framework-core>=1.
|
|
19
|
+
Requires-Dist: agent-framework-core>=1.2.0,<2
|
|
20
20
|
Requires-Dist: agent-framework-openai>=1.1.0,<2
|
|
21
21
|
Requires-Dist: azure-ai-inference>=1.0.0b9,<1.0.0b10
|
|
22
22
|
Requires-Dist: azure-ai-projects>=2.1.0,<3.0
|
{agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/__init__.py
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import importlib.metadata
|
|
4
4
|
|
|
5
|
-
from ._agent import FoundryAgent, RawFoundryAgent, RawFoundryAgentChatClient
|
|
5
|
+
from ._agent import FoundryAgent, FoundryAgentOptions, RawFoundryAgent, RawFoundryAgentChatClient
|
|
6
6
|
from ._chat_client import FoundryChatClient, FoundryChatOptions, RawFoundryChatClient
|
|
7
7
|
from ._embedding_client import (
|
|
8
8
|
FoundryEmbeddingClient,
|
|
@@ -25,6 +25,7 @@ except importlib.metadata.PackageNotFoundError:
|
|
|
25
25
|
|
|
26
26
|
__all__ = [
|
|
27
27
|
"FoundryAgent",
|
|
28
|
+
"FoundryAgentOptions",
|
|
28
29
|
"FoundryChatClient",
|
|
29
30
|
"FoundryChatOptions",
|
|
30
31
|
"FoundryEmbeddingClient",
|
{agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_agent.py
RENAMED
|
@@ -15,10 +15,11 @@ from collections.abc import Awaitable, Callable, Mapping, MutableMapping, Sequen
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any, ClassVar, Generic, cast
|
|
16
16
|
|
|
17
17
|
from agent_framework import (
|
|
18
|
-
AGENT_FRAMEWORK_USER_AGENT,
|
|
19
18
|
AgentMiddlewareLayer,
|
|
19
|
+
AgentSession,
|
|
20
20
|
ChatAndFunctionMiddlewareTypes,
|
|
21
21
|
ChatMiddlewareLayer,
|
|
22
|
+
ChatResponseUpdate,
|
|
22
23
|
ContextProvider,
|
|
23
24
|
FunctionInvocationConfiguration,
|
|
24
25
|
FunctionInvocationLayer,
|
|
@@ -28,13 +29,16 @@ from agent_framework import (
|
|
|
28
29
|
load_settings,
|
|
29
30
|
)
|
|
30
31
|
from agent_framework._compaction import CompactionStrategy, TokenizerProtocol
|
|
32
|
+
from agent_framework._telemetry import get_user_agent
|
|
31
33
|
from agent_framework.observability import AgentTelemetryLayer, ChatTelemetryLayer
|
|
32
34
|
from agent_framework_openai._chat_client import OpenAIChatOptions, RawOpenAIChatClient
|
|
33
35
|
from azure.ai.projects.aio import AIProjectClient
|
|
34
36
|
from azure.core.credentials import TokenCredential
|
|
35
37
|
from azure.core.credentials_async import AsyncTokenCredential
|
|
36
38
|
|
|
37
|
-
from .
|
|
39
|
+
from agent_framework_foundry._oauth_helpers import try_parse_oauth_consent_event
|
|
40
|
+
|
|
41
|
+
from ._tools import _sanitize_foundry_response_tool # pyright: ignore[reportPrivateUsage]
|
|
38
42
|
|
|
39
43
|
if sys.version_info >= (3, 13):
|
|
40
44
|
from typing import TypeVar # type: ignore # pragma: no cover
|
|
@@ -52,11 +56,13 @@ else:
|
|
|
52
56
|
if TYPE_CHECKING:
|
|
53
57
|
from agent_framework import (
|
|
54
58
|
Agent,
|
|
59
|
+
AgentRunInputs,
|
|
55
60
|
ChatAndFunctionMiddlewareTypes,
|
|
56
61
|
ContextProvider,
|
|
57
62
|
MiddlewareTypes,
|
|
58
63
|
ToolTypes,
|
|
59
64
|
)
|
|
65
|
+
from agent_framework._agents import _RunContext # pyright: ignore[reportPrivateUsage]
|
|
60
66
|
|
|
61
67
|
logger: logging.Logger = logging.getLogger("agent_framework.foundry")
|
|
62
68
|
|
|
@@ -81,14 +87,54 @@ class FoundryAgentSettings(TypedDict, total=False):
|
|
|
81
87
|
agent_version: str | None
|
|
82
88
|
|
|
83
89
|
|
|
90
|
+
class FoundryAgentOptions(OpenAIChatOptions, total=False):
|
|
91
|
+
"""Microsoft Foundry agent-specific chat options.
|
|
92
|
+
|
|
93
|
+
Extends ``OpenAIChatOptions`` with hosted-agent session configuration used by
|
|
94
|
+
``FoundryAgent`` / ``RawFoundryAgent``.
|
|
95
|
+
|
|
96
|
+
Keyword Args:
|
|
97
|
+
extra_body: Additional request body values sent to the Responses API.
|
|
98
|
+
isolation_key: Isolation key used when lazily creating a hosted-agent
|
|
99
|
+
session through ``project_client.beta.agents.create_session(...)``.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
extra_body: dict[str, Any]
|
|
103
|
+
isolation_key: str
|
|
104
|
+
|
|
105
|
+
|
|
84
106
|
FoundryAgentOptionsT = TypeVar(
|
|
85
107
|
"FoundryAgentOptionsT",
|
|
86
108
|
bound=TypedDict, # type: ignore[valid-type]
|
|
87
|
-
default="
|
|
109
|
+
default="FoundryAgentOptions",
|
|
88
110
|
covariant=True,
|
|
89
111
|
)
|
|
90
112
|
|
|
91
113
|
|
|
114
|
+
def _merge_extra_body(extra_body: Any | None, *, additions: Mapping[str, Any] | None = None) -> dict[str, Any]:
|
|
115
|
+
"""Normalize and merge provider-specific extra_body values."""
|
|
116
|
+
if extra_body is None:
|
|
117
|
+
merged: dict[str, Any] = {}
|
|
118
|
+
elif isinstance(extra_body, Mapping):
|
|
119
|
+
merged = dict(cast(Mapping[str, Any], extra_body))
|
|
120
|
+
else:
|
|
121
|
+
raise TypeError(f"extra_body must be a mapping when provided, got {type(extra_body).__name__}.")
|
|
122
|
+
|
|
123
|
+
if additions:
|
|
124
|
+
merged.update(additions)
|
|
125
|
+
return merged
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _uses_foundry_agent_session(conversation_id: Any) -> bool:
|
|
129
|
+
"""Return whether a conversation_id should be treated as a Foundry agent session id."""
|
|
130
|
+
return (
|
|
131
|
+
isinstance(conversation_id, str)
|
|
132
|
+
and bool(conversation_id)
|
|
133
|
+
and not conversation_id.startswith("resp_")
|
|
134
|
+
and not conversation_id.startswith("conv_")
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
92
138
|
class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
93
139
|
RawOpenAIChatClient[FoundryAgentOptionsT],
|
|
94
140
|
Generic[FoundryAgentOptionsT],
|
|
@@ -167,13 +213,15 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
167
213
|
)
|
|
168
214
|
|
|
169
215
|
resolved_endpoint = settings.get("project_endpoint")
|
|
170
|
-
|
|
171
|
-
self.agent_version = settings.get("agent_version")
|
|
216
|
+
agent_name_setting = settings.get("agent_name")
|
|
217
|
+
self.agent_version: str | None = settings.get("agent_version")
|
|
218
|
+
self.allow_preview = allow_preview or False
|
|
172
219
|
|
|
173
|
-
if not
|
|
220
|
+
if not agent_name_setting:
|
|
174
221
|
raise ValueError(
|
|
175
222
|
"Agent name is required. Set via 'agent_name' parameter or 'FOUNDRY_AGENT_NAME' environment variable."
|
|
176
223
|
)
|
|
224
|
+
self.agent_name = agent_name_setting
|
|
177
225
|
|
|
178
226
|
# Create or use provided project client
|
|
179
227
|
self._should_close_client = False
|
|
@@ -190,18 +238,20 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
190
238
|
project_client_kwargs: dict[str, Any] = {
|
|
191
239
|
"endpoint": resolved_endpoint,
|
|
192
240
|
"credential": credential,
|
|
193
|
-
"user_agent":
|
|
241
|
+
"user_agent": get_user_agent(),
|
|
194
242
|
}
|
|
195
243
|
if allow_preview is not None:
|
|
196
244
|
project_client_kwargs["allow_preview"] = allow_preview
|
|
197
245
|
self.project_client = AIProjectClient(**project_client_kwargs)
|
|
198
246
|
self._should_close_client = True
|
|
199
247
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
248
|
+
openai_client_kwargs: dict[str, Any] = {}
|
|
249
|
+
if default_headers:
|
|
250
|
+
openai_client_kwargs["default_headers"] = dict(default_headers)
|
|
251
|
+
if allow_preview:
|
|
252
|
+
openai_client_kwargs["agent_name"] = self.agent_name
|
|
203
253
|
super().__init__(
|
|
204
|
-
async_client=
|
|
254
|
+
async_client=self.project_client.get_openai_client(**openai_client_kwargs),
|
|
205
255
|
default_headers=default_headers,
|
|
206
256
|
instruction_role=instruction_role,
|
|
207
257
|
compaction_strategy=compaction_strategy,
|
|
@@ -209,13 +259,6 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
209
259
|
additional_properties=additional_properties,
|
|
210
260
|
)
|
|
211
261
|
|
|
212
|
-
def _get_agent_reference(self) -> dict[str, str]:
|
|
213
|
-
"""Build the agent reference dict for the Responses API."""
|
|
214
|
-
ref: dict[str, str] = {"name": self.agent_name, "type": "agent_reference"} # type: ignore[dict-item]
|
|
215
|
-
if self.agent_version:
|
|
216
|
-
ref["version"] = self.agent_version
|
|
217
|
-
return ref
|
|
218
|
-
|
|
219
262
|
@override
|
|
220
263
|
def as_agent(
|
|
221
264
|
self,
|
|
@@ -270,7 +313,7 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
270
313
|
options: Mapping[str, Any],
|
|
271
314
|
**kwargs: Any,
|
|
272
315
|
) -> dict[str, Any]:
|
|
273
|
-
"""Prepare options for the Responses API
|
|
316
|
+
"""Prepare options for the Responses API and validate client-side tools."""
|
|
274
317
|
# Validate tools — only FunctionTool allowed
|
|
275
318
|
tools = options.get("tools", [])
|
|
276
319
|
if tools:
|
|
@@ -292,18 +335,61 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
292
335
|
if "input" in run_options and isinstance(run_options["input"], list):
|
|
293
336
|
run_options["input"] = self._transform_input_for_azure_ai(cast(list[dict[str, Any]], run_options["input"]))
|
|
294
337
|
|
|
295
|
-
#
|
|
296
|
-
|
|
338
|
+
# Merge caller-supplied extra_body with any agent-specific request payload.
|
|
339
|
+
conversation_id = options.get("conversation_id")
|
|
340
|
+
extra_body = _merge_extra_body(run_options.pop("extra_body", None))
|
|
341
|
+
if _uses_foundry_agent_session(conversation_id):
|
|
342
|
+
run_options.pop("previous_response_id", None)
|
|
343
|
+
run_options.pop("conversation", None)
|
|
344
|
+
extra_body["agent_session_id"] = conversation_id
|
|
345
|
+
if extra_body:
|
|
346
|
+
run_options["extra_body"] = extra_body
|
|
347
|
+
|
|
348
|
+
run_options.pop("isolation_key", None)
|
|
297
349
|
|
|
298
350
|
# Strip tools from request body - Foundry API rejects requests with both
|
|
299
|
-
#
|
|
351
|
+
# agent endpoint and tools present. FunctionTools are invoked client-side
|
|
300
352
|
# by the function invocation layer, not sent to the service.
|
|
301
|
-
run_options.pop("
|
|
302
|
-
|
|
303
|
-
|
|
353
|
+
run_options.pop("model", None)
|
|
354
|
+
if not self.allow_preview:
|
|
355
|
+
run_options.pop("tools", None)
|
|
356
|
+
run_options.pop("tool_choice", None)
|
|
357
|
+
run_options.pop("parallel_tool_calls", None)
|
|
304
358
|
|
|
305
359
|
return run_options
|
|
306
360
|
|
|
361
|
+
@override
|
|
362
|
+
def _parse_response_from_openai(
|
|
363
|
+
self,
|
|
364
|
+
response: Any,
|
|
365
|
+
options: dict[str, Any],
|
|
366
|
+
) -> Any:
|
|
367
|
+
parsed_response = super()._parse_response_from_openai(response, options)
|
|
368
|
+
if _uses_foundry_agent_session(options.get("conversation_id")):
|
|
369
|
+
parsed_response.conversation_id = None
|
|
370
|
+
return parsed_response
|
|
371
|
+
|
|
372
|
+
@override
|
|
373
|
+
def _parse_chunk_from_openai(
|
|
374
|
+
self,
|
|
375
|
+
event: Any,
|
|
376
|
+
options: dict[str, Any],
|
|
377
|
+
function_call_ids: dict[int, tuple[str, str]],
|
|
378
|
+
seen_reasoning_delta_item_ids: set[str] | None = None,
|
|
379
|
+
) -> ChatResponseUpdate:
|
|
380
|
+
"""Parse streaming events while preserving hosted-agent session state."""
|
|
381
|
+
update = try_parse_oauth_consent_event(event, self.model)
|
|
382
|
+
if update is None:
|
|
383
|
+
update = super()._parse_chunk_from_openai(
|
|
384
|
+
event,
|
|
385
|
+
options,
|
|
386
|
+
function_call_ids,
|
|
387
|
+
seen_reasoning_delta_item_ids,
|
|
388
|
+
)
|
|
389
|
+
if _uses_foundry_agent_session(options.get("conversation_id")):
|
|
390
|
+
update.conversation_id = None
|
|
391
|
+
return update
|
|
392
|
+
|
|
307
393
|
@override
|
|
308
394
|
def _check_model_presence(self, options: dict[str, Any]) -> None:
|
|
309
395
|
"""Skip model check — model is configured on the Foundry agent."""
|
|
@@ -321,7 +407,7 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
321
407
|
surface.
|
|
322
408
|
"""
|
|
323
409
|
response_tools = super()._prepare_tools_for_openai(tools)
|
|
324
|
-
return [
|
|
410
|
+
return [_sanitize_foundry_response_tool(tool_item) for tool_item in response_tools]
|
|
325
411
|
|
|
326
412
|
def _prepare_messages_for_azure_ai(self, messages: Sequence[Message]) -> tuple[list[Message], str | None]:
|
|
327
413
|
"""Extract system/developer messages as instructions for Azure AI.
|
|
@@ -368,6 +454,26 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
368
454
|
|
|
369
455
|
return transformed
|
|
370
456
|
|
|
457
|
+
async def get_agent_version(self) -> str | None:
|
|
458
|
+
"""Return the agent version if available, else None."""
|
|
459
|
+
if self.agent_version is not None:
|
|
460
|
+
return self.agent_version
|
|
461
|
+
if not self.allow_preview:
|
|
462
|
+
return None
|
|
463
|
+
agent_details = await cast(Any, self.project_client.beta.agents).get( # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType]
|
|
464
|
+
agent_name=self.agent_name
|
|
465
|
+
)
|
|
466
|
+
versions_object = getattr(agent_details, "versions", None)
|
|
467
|
+
if not isinstance(versions_object, Mapping):
|
|
468
|
+
raise TypeError("Foundry agent details did not include a versions mapping.")
|
|
469
|
+
versions = cast(Mapping[str, Any], versions_object)
|
|
470
|
+
latest_version = versions.get("latest")
|
|
471
|
+
agent_version = getattr(cast(Any, latest_version), "version", None)
|
|
472
|
+
if not isinstance(agent_version, str):
|
|
473
|
+
raise TypeError("Foundry agent details did not include a latest version string.")
|
|
474
|
+
self.agent_version = agent_version
|
|
475
|
+
return agent_version
|
|
476
|
+
|
|
371
477
|
async def close(self) -> None:
|
|
372
478
|
"""Close the project client if we created it."""
|
|
373
479
|
if self._should_close_client:
|
|
@@ -395,7 +501,7 @@ class _FoundryAgentChatClient( # type: ignore[misc]
|
|
|
395
501
|
client = FoundryAgentClient(
|
|
396
502
|
project_endpoint="https://your-project.services.ai.azure.com",
|
|
397
503
|
agent_name="my-prompt-agent",
|
|
398
|
-
agent_version="1
|
|
504
|
+
agent_version="1",
|
|
399
505
|
credential=AzureCliCredential(),
|
|
400
506
|
)
|
|
401
507
|
|
|
@@ -477,7 +583,7 @@ class RawFoundryAgent( # type: ignore[misc]
|
|
|
477
583
|
agent = RawFoundryAgent(
|
|
478
584
|
project_endpoint="https://your-project.services.ai.azure.com",
|
|
479
585
|
agent_name="my-prompt-agent",
|
|
480
|
-
agent_version="1
|
|
586
|
+
agent_version="1",
|
|
481
587
|
credential=AzureCliCredential(),
|
|
482
588
|
)
|
|
483
589
|
result = await agent.run("Hello!")
|
|
@@ -570,7 +676,7 @@ class RawFoundryAgent( # type: ignore[misc]
|
|
|
570
676
|
client=client, # type: ignore[arg-type]
|
|
571
677
|
instructions=instructions,
|
|
572
678
|
id=id,
|
|
573
|
-
name=name,
|
|
679
|
+
name=name or agent_name,
|
|
574
680
|
description=description,
|
|
575
681
|
tools=tools, # type: ignore[arg-type]
|
|
576
682
|
default_options=cast(FoundryAgentOptionsT | None, default_options),
|
|
@@ -582,6 +688,81 @@ class RawFoundryAgent( # type: ignore[misc]
|
|
|
582
688
|
additional_properties=dict(additional_properties) if additional_properties is not None else None,
|
|
583
689
|
)
|
|
584
690
|
|
|
691
|
+
def _resolve_service_session_isolation_key(self, isolation_key: str | None = None) -> str:
|
|
692
|
+
"""Resolve the isolation key from an explicit value or default_options."""
|
|
693
|
+
resolved_isolation_key = (
|
|
694
|
+
isolation_key if isolation_key is not None else self.default_options.get("isolation_key")
|
|
695
|
+
)
|
|
696
|
+
if resolved_isolation_key is None:
|
|
697
|
+
raise ValueError("isolation_key is required. Pass it explicitly or set default_options['isolation_key'].")
|
|
698
|
+
return resolved_isolation_key
|
|
699
|
+
|
|
700
|
+
async def _create_service_session_id(
|
|
701
|
+
self,
|
|
702
|
+
*,
|
|
703
|
+
isolation_key: str | None = None,
|
|
704
|
+
) -> str:
|
|
705
|
+
"""Create a hosted Foundry service session and return the service session ID."""
|
|
706
|
+
if not isinstance(self.client, RawFoundryAgentChatClient):
|
|
707
|
+
raise TypeError("_create_service_session_id requires a RawFoundryAgentChatClient-based client.")
|
|
708
|
+
if not self.client.allow_preview:
|
|
709
|
+
raise RuntimeError("Hosted Foundry service sessions require allow_preview=True.")
|
|
710
|
+
|
|
711
|
+
create_session_kwargs: dict[str, Any] = {
|
|
712
|
+
"agent_name": self.client.agent_name,
|
|
713
|
+
"isolation_key": self._resolve_service_session_isolation_key(isolation_key),
|
|
714
|
+
}
|
|
715
|
+
if version := await self.client.get_agent_version():
|
|
716
|
+
from azure.ai.projects.models import VersionRefIndicator
|
|
717
|
+
|
|
718
|
+
create_session_kwargs["version_indicator"] = VersionRefIndicator(agent_version=version) # type: ignore
|
|
719
|
+
|
|
720
|
+
service_session = await self.client.project_client.beta.agents.create_session(**create_session_kwargs)
|
|
721
|
+
agent_session_id = getattr(service_session, "agent_session_id", None)
|
|
722
|
+
if not isinstance(agent_session_id, str) or not agent_session_id:
|
|
723
|
+
raise ValueError("Hosted Foundry session creation did not return a non-empty agent_session_id.")
|
|
724
|
+
|
|
725
|
+
return agent_session_id
|
|
726
|
+
|
|
727
|
+
@override
|
|
728
|
+
async def _prepare_run_context(
|
|
729
|
+
self,
|
|
730
|
+
*,
|
|
731
|
+
messages: AgentRunInputs | None,
|
|
732
|
+
session: AgentSession | None,
|
|
733
|
+
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
|
734
|
+
options: Mapping[str, Any] | None,
|
|
735
|
+
compaction_strategy: CompactionStrategy | None,
|
|
736
|
+
tokenizer: TokenizerProtocol | None,
|
|
737
|
+
function_invocation_kwargs: Mapping[str, Any] | None,
|
|
738
|
+
client_kwargs: Mapping[str, Any] | None,
|
|
739
|
+
) -> _RunContext:
|
|
740
|
+
runtime_options = dict(options) if options else {}
|
|
741
|
+
effective_options = {
|
|
742
|
+
**{key: value for key, value in self.default_options.items() if value is not None},
|
|
743
|
+
**{key: value for key, value in runtime_options.items() if value is not None},
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (
|
|
747
|
+
session is not None
|
|
748
|
+
and session.service_session_id is None
|
|
749
|
+
and effective_options.get("isolation_key") is not None
|
|
750
|
+
):
|
|
751
|
+
session.service_session_id = await self._create_service_session_id(
|
|
752
|
+
isolation_key=cast(str | None, effective_options.get("isolation_key")),
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
return await super()._prepare_run_context(
|
|
756
|
+
messages=messages,
|
|
757
|
+
session=session,
|
|
758
|
+
tools=tools,
|
|
759
|
+
options=runtime_options,
|
|
760
|
+
compaction_strategy=compaction_strategy,
|
|
761
|
+
tokenizer=tokenizer,
|
|
762
|
+
function_invocation_kwargs=function_invocation_kwargs,
|
|
763
|
+
client_kwargs=client_kwargs,
|
|
764
|
+
)
|
|
765
|
+
|
|
585
766
|
async def configure_azure_monitor(
|
|
586
767
|
self,
|
|
587
768
|
enable_sensitive_data: bool = False,
|
|
@@ -708,6 +889,19 @@ class FoundryAgent( # type: ignore[misc]
|
|
|
708
889
|
) -> None:
|
|
709
890
|
"""Initialize a Foundry Agent with full middleware and telemetry.
|
|
710
891
|
|
|
892
|
+
``FoundryAgent`` supports both PromptAgents and HostedAgents. PromptAgents
|
|
893
|
+
typically provide ``agent_version`` directly. HostedAgents can omit
|
|
894
|
+
``agent_version`` and, when they need preview-only session APIs, should
|
|
895
|
+
opt in with ``allow_preview=True`` when this class creates the underlying
|
|
896
|
+
``AIProjectClient``. If you pass ``project_client`` explicitly, it must
|
|
897
|
+
already be configured for preview APIs before being passed to
|
|
898
|
+
``FoundryAgent``.
|
|
899
|
+
|
|
900
|
+
To lazily create HostedAgent service sessions inside the agent, pass an
|
|
901
|
+
``isolation_key`` through ``default_options`` (or per-run options). The
|
|
902
|
+
agent stores the resulting HostedAgent session ID in
|
|
903
|
+
``AgentSession.service_session_id`` and reuses it on subsequent runs.
|
|
904
|
+
|
|
711
905
|
Keyword Args:
|
|
712
906
|
project_endpoint: The Foundry project endpoint URL.
|
|
713
907
|
agent_name: The name of the Foundry agent to connect to.
|
|
@@ -715,6 +909,9 @@ class FoundryAgent( # type: ignore[misc]
|
|
|
715
909
|
credential: Azure credential for authentication.
|
|
716
910
|
project_client: An existing AIProjectClient to use.
|
|
717
911
|
allow_preview: Enables preview opt-in on internally-created AIProjectClient.
|
|
912
|
+
Set this to ``True`` for HostedAgents that need preview-only
|
|
913
|
+
session APIs, including lazy service session creation from
|
|
914
|
+
``isolation_key``.
|
|
718
915
|
tools: Function tools to provide to the agent. Only ``FunctionTool`` objects are accepted.
|
|
719
916
|
context_providers: Optional context providers.
|
|
720
917
|
middleware: Optional agent-level middleware.
|
|
@@ -726,6 +923,8 @@ class FoundryAgent( # type: ignore[misc]
|
|
|
726
923
|
description: Optional local description for the local agent wrapper.
|
|
727
924
|
instructions: Optional instructions for the local agent wrapper.
|
|
728
925
|
default_options: Default chat options for the local agent wrapper.
|
|
926
|
+
``FoundryAgentOptions`` can include ``isolation_key`` and
|
|
927
|
+
``extra_body`` when working with HostedAgents.
|
|
729
928
|
require_per_service_call_history_persistence: Whether to require per-service-call
|
|
730
929
|
chat history persistence when using local history providers.
|
|
731
930
|
function_invocation_configuration: Optional function invocation configuration override.
|
|
@@ -8,8 +8,8 @@ from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal
|
|
9
9
|
|
|
10
10
|
from agent_framework import (
|
|
11
|
-
AGENT_FRAMEWORK_USER_AGENT,
|
|
12
11
|
ChatMiddlewareLayer,
|
|
12
|
+
ChatResponseUpdate,
|
|
13
13
|
Content,
|
|
14
14
|
FunctionInvocationConfiguration,
|
|
15
15
|
FunctionInvocationLayer,
|
|
@@ -17,6 +17,7 @@ from agent_framework import (
|
|
|
17
17
|
)
|
|
18
18
|
from agent_framework._compaction import CompactionStrategy, TokenizerProtocol
|
|
19
19
|
from agent_framework._feature_stage import ExperimentalFeature, experimental
|
|
20
|
+
from agent_framework._telemetry import get_user_agent
|
|
20
21
|
from agent_framework.observability import ChatTelemetryLayer
|
|
21
22
|
from agent_framework_openai._chat_client import OpenAIChatOptions, RawOpenAIChatClient
|
|
22
23
|
from azure.ai.projects.aio import AIProjectClient
|
|
@@ -33,7 +34,9 @@ from azure.ai.projects.models import MCPTool as FoundryMCPTool
|
|
|
33
34
|
from azure.core.credentials import TokenCredential
|
|
34
35
|
from azure.core.credentials_async import AsyncTokenCredential
|
|
35
36
|
|
|
36
|
-
from .
|
|
37
|
+
from agent_framework_foundry._oauth_helpers import try_parse_oauth_consent_event
|
|
38
|
+
|
|
39
|
+
from ._tools import _sanitize_foundry_response_tool, fetch_toolbox # pyright: ignore[reportPrivateUsage]
|
|
37
40
|
|
|
38
41
|
if sys.version_info >= (3, 13):
|
|
39
42
|
from typing import TypeVar # type: ignore # pragma: no cover
|
|
@@ -198,15 +201,19 @@ class RawFoundryChatClient( # type: ignore[misc]
|
|
|
198
201
|
project_client_kwargs: dict[str, Any] = {
|
|
199
202
|
"endpoint": project_endpoint,
|
|
200
203
|
"credential": credential, # type: ignore[arg-type]
|
|
201
|
-
"user_agent":
|
|
204
|
+
"user_agent": get_user_agent(),
|
|
202
205
|
}
|
|
203
206
|
if allow_preview is not None:
|
|
204
207
|
project_client_kwargs["allow_preview"] = allow_preview
|
|
205
208
|
project_client = AIProjectClient(**project_client_kwargs)
|
|
206
209
|
|
|
210
|
+
openai_kwargs: dict[str, Any] = {}
|
|
211
|
+
if default_headers:
|
|
212
|
+
openai_kwargs["default_headers"] = default_headers
|
|
213
|
+
|
|
207
214
|
super().__init__(
|
|
208
215
|
model=resolved_model,
|
|
209
|
-
async_client=project_client.get_openai_client(),
|
|
216
|
+
async_client=project_client.get_openai_client(**openai_kwargs),
|
|
210
217
|
default_headers=default_headers,
|
|
211
218
|
instruction_role=instruction_role,
|
|
212
219
|
compaction_strategy=compaction_strategy,
|
|
@@ -235,7 +242,21 @@ class RawFoundryChatClient( # type: ignore[misc]
|
|
|
235
242
|
them downstream.
|
|
236
243
|
"""
|
|
237
244
|
response_tools = super()._prepare_tools_for_openai(tools)
|
|
238
|
-
return [
|
|
245
|
+
return [_sanitize_foundry_response_tool(tool_item) for tool_item in response_tools]
|
|
246
|
+
|
|
247
|
+
@override
|
|
248
|
+
def _parse_chunk_from_openai(
|
|
249
|
+
self,
|
|
250
|
+
event: Any,
|
|
251
|
+
options: dict[str, Any],
|
|
252
|
+
function_call_ids: dict[int, tuple[str, str]],
|
|
253
|
+
seen_reasoning_delta_item_ids: set[str] | None = None,
|
|
254
|
+
) -> ChatResponseUpdate:
|
|
255
|
+
"""Parse streaming event, intercepting oauth_consent_request items."""
|
|
256
|
+
update = try_parse_oauth_consent_event(event, self.model)
|
|
257
|
+
if update is not None:
|
|
258
|
+
return update
|
|
259
|
+
return super()._parse_chunk_from_openai(event, options, function_call_ids, seen_reasoning_delta_item_ids)
|
|
239
260
|
|
|
240
261
|
async def configure_azure_monitor(
|
|
241
262
|
self,
|
|
@@ -455,8 +476,18 @@ class RawFoundryChatClient( # type: ignore[misc]
|
|
|
455
476
|
|
|
456
477
|
Returns:
|
|
457
478
|
An MCPTool configuration ready to pass to an Agent.
|
|
479
|
+
|
|
480
|
+
Raises:
|
|
481
|
+
ValueError: If neither ``url`` nor ``project_connection_id`` is supplied
|
|
482
|
+
— one is required by the Foundry Responses API.
|
|
458
483
|
"""
|
|
459
|
-
|
|
484
|
+
if not url and not project_connection_id:
|
|
485
|
+
raise ValueError("MCP tool requires either 'url' or 'project_connection_id' to be specified.")
|
|
486
|
+
|
|
487
|
+
mcp_kwargs: dict[str, Any] = {"server_label": name.replace(" ", "_"), **kwargs}
|
|
488
|
+
if url:
|
|
489
|
+
mcp_kwargs["server_url"] = url
|
|
490
|
+
mcp = FoundryMCPTool(**mcp_kwargs)
|
|
460
491
|
|
|
461
492
|
if description:
|
|
462
493
|
mcp["server_description"] = description
|
|
@@ -75,6 +75,11 @@ _TOOL_EVALUATORS: set[str] = {
|
|
|
75
75
|
"builtin.tool_call_success",
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
# Evaluators that require a ground_truth / expected_output field.
|
|
79
|
+
_GROUND_TRUTH_EVALUATORS: set[str] = {
|
|
80
|
+
"builtin.similarity",
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
_BUILTIN_EVALUATORS: dict[str, str] = {
|
|
79
84
|
# Agent behavior
|
|
80
85
|
"intent_resolution": "builtin.intent_resolution",
|
|
@@ -196,6 +201,8 @@ def _build_testing_criteria(
|
|
|
196
201
|
}
|
|
197
202
|
if qualified == "builtin.groundedness":
|
|
198
203
|
mapping["context"] = "{{item.context}}"
|
|
204
|
+
if qualified in _GROUND_TRUTH_EVALUATORS:
|
|
205
|
+
mapping["ground_truth"] = "{{item.ground_truth}}"
|
|
199
206
|
if qualified in _TOOL_EVALUATORS:
|
|
200
207
|
mapping["tool_definitions"] = "{{item.tool_definitions}}"
|
|
201
208
|
entry["data_mapping"] = mapping
|
|
@@ -204,7 +211,9 @@ def _build_testing_criteria(
|
|
|
204
211
|
return criteria
|
|
205
212
|
|
|
206
213
|
|
|
207
|
-
def _build_item_schema(
|
|
214
|
+
def _build_item_schema(
|
|
215
|
+
*, has_context: bool = False, has_tools: bool = False, has_ground_truth: bool = False
|
|
216
|
+
) -> dict[str, Any]:
|
|
208
217
|
"""Build the ``item_schema`` for custom JSONL eval definitions."""
|
|
209
218
|
properties: dict[str, Any] = {
|
|
210
219
|
"query": {"type": "string"},
|
|
@@ -214,6 +223,8 @@ def _build_item_schema(*, has_context: bool = False, has_tools: bool = False) ->
|
|
|
214
223
|
}
|
|
215
224
|
if has_context:
|
|
216
225
|
properties["context"] = {"type": "string"}
|
|
226
|
+
if has_ground_truth:
|
|
227
|
+
properties["ground_truth"] = {"type": "string"}
|
|
217
228
|
if has_tools:
|
|
218
229
|
properties["tool_definitions"] = {"type": "array"}
|
|
219
230
|
return {
|
|
@@ -681,16 +692,21 @@ class FoundryEvals:
|
|
|
681
692
|
]
|
|
682
693
|
if item.context:
|
|
683
694
|
d["context"] = item.context
|
|
695
|
+
if item.expected_output is not None:
|
|
696
|
+
d["ground_truth"] = item.expected_output
|
|
684
697
|
dicts.append(d)
|
|
685
698
|
|
|
686
699
|
has_context = any("context" in d for d in dicts)
|
|
700
|
+
has_ground_truth = any("ground_truth" in d for d in dicts)
|
|
687
701
|
has_tools = any("tool_definitions" in d for d in dicts)
|
|
688
702
|
|
|
689
703
|
eval_obj = await self._client.evals.create(
|
|
690
704
|
name=eval_name,
|
|
691
705
|
data_source_config={ # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
|
|
692
706
|
"type": "custom",
|
|
693
|
-
"item_schema": _build_item_schema(
|
|
707
|
+
"item_schema": _build_item_schema(
|
|
708
|
+
has_context=has_context, has_ground_truth=has_ground_truth, has_tools=has_tools
|
|
709
|
+
),
|
|
694
710
|
"include_sample_schema": True,
|
|
695
711
|
},
|
|
696
712
|
testing_criteria=_build_testing_criteria( # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
|
|
@@ -14,13 +14,13 @@ from contextlib import AbstractAsyncContextManager
|
|
|
14
14
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
15
15
|
|
|
16
16
|
from agent_framework import (
|
|
17
|
-
AGENT_FRAMEWORK_USER_AGENT,
|
|
18
17
|
AgentSession,
|
|
19
18
|
ContextProvider,
|
|
20
19
|
Message,
|
|
21
20
|
SessionContext,
|
|
22
21
|
load_settings,
|
|
23
22
|
)
|
|
23
|
+
from agent_framework._telemetry import get_user_agent
|
|
24
24
|
from azure.ai.projects.aio import AIProjectClient
|
|
25
25
|
from azure.core.credentials import TokenCredential
|
|
26
26
|
from azure.core.credentials_async import AsyncTokenCredential
|
|
@@ -119,7 +119,7 @@ class FoundryMemoryProvider(ContextProvider):
|
|
|
119
119
|
project_client_kwargs: dict[str, Any] = {
|
|
120
120
|
"endpoint": resolved_endpoint,
|
|
121
121
|
"credential": credential, # type: ignore[arg-type]
|
|
122
|
-
"user_agent":
|
|
122
|
+
"user_agent": get_user_agent(),
|
|
123
123
|
}
|
|
124
124
|
if allow_preview is not None:
|
|
125
125
|
project_client_kwargs["allow_preview"] = allow_preview
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from agent_framework import ChatResponseUpdate, Content
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _validate_consent_link(consent_link: str, item_id: str) -> str:
|
|
15
|
+
"""Validate a consent link is HTTPS with a valid netloc.
|
|
16
|
+
|
|
17
|
+
Returns the link unchanged if valid, or an empty string if not.
|
|
18
|
+
"""
|
|
19
|
+
parsed = urlparse(consent_link)
|
|
20
|
+
if parsed.scheme.lower() != "https" or not parsed.netloc:
|
|
21
|
+
logger.warning(
|
|
22
|
+
"Skipping oauth_consent_request with non-HTTPS consent_link (item id=%s)",
|
|
23
|
+
item_id,
|
|
24
|
+
)
|
|
25
|
+
return ""
|
|
26
|
+
return consent_link
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def try_parse_oauth_consent_event(event: Any, model: str) -> ChatResponseUpdate | None:
|
|
30
|
+
"""Parse an oauth_consent_request from a streaming event, if present.
|
|
31
|
+
|
|
32
|
+
Returns a ``ChatResponseUpdate`` when *event* is a
|
|
33
|
+
``response.output_item.added`` carrying an ``oauth_consent_request`` item
|
|
34
|
+
or a top-level ``response.oauth_consent_requested`` event,
|
|
35
|
+
or ``None`` so the caller can fall through to the base implementation.
|
|
36
|
+
"""
|
|
37
|
+
consent_link: str = ""
|
|
38
|
+
raw_item: Any = None
|
|
39
|
+
|
|
40
|
+
event_type = getattr(event, "type", None)
|
|
41
|
+
|
|
42
|
+
if event_type == "response.output_item.added" and getattr(event.item, "type", None) == "oauth_consent_request":
|
|
43
|
+
raw_item = event.item
|
|
44
|
+
consent_link = getattr(raw_item, "consent_link", None) or ""
|
|
45
|
+
elif event_type == "response.oauth_consent_requested":
|
|
46
|
+
raw_item = event
|
|
47
|
+
consent_link = getattr(event, "consent_link", None) or ""
|
|
48
|
+
else:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
item_id = getattr(raw_item, "id", "<unknown>")
|
|
52
|
+
|
|
53
|
+
if consent_link:
|
|
54
|
+
consent_link = _validate_consent_link(consent_link, item_id)
|
|
55
|
+
|
|
56
|
+
contents: list[Content] = []
|
|
57
|
+
if consent_link:
|
|
58
|
+
contents.append(
|
|
59
|
+
Content.from_oauth_consent_request(
|
|
60
|
+
consent_link=consent_link,
|
|
61
|
+
raw_representation=raw_item,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
logger.warning(
|
|
66
|
+
"Received oauth_consent_request output without valid consent_link (item id=%s)",
|
|
67
|
+
item_id,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return ChatResponseUpdate(
|
|
71
|
+
contents=contents,
|
|
72
|
+
role="assistant",
|
|
73
|
+
model=model,
|
|
74
|
+
raw_representation=event,
|
|
75
|
+
)
|
{agent_framework_foundry-1.1.0 → agent_framework_foundry-1.2.0}/agent_framework_foundry/_tools.py
RENAMED
|
@@ -133,26 +133,54 @@ def select_toolbox_tools(
|
|
|
133
133
|
return selected
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"""Return a Responses-API-safe tool payload for Foundry hosted tools.
|
|
139
|
-
|
|
140
|
-
Azure AI Projects toolbox reads can currently return hosted tool objects with
|
|
141
|
-
extra read-model decoration fields such as top-level ``name`` and
|
|
142
|
-
``description``. Azure AI Foundry rejects at least ``name`` on Responses API
|
|
143
|
-
requests with:
|
|
136
|
+
def _validate_hosted_tool_payload(sanitized: Mapping[str, Any]) -> None:
|
|
137
|
+
"""Fail fast on hosted tool payloads that would always be rejected by the Responses API.
|
|
144
138
|
|
|
145
|
-
|
|
139
|
+
These mismatches are not injectable defaults — the caller must supply the
|
|
140
|
+
missing information — so surfacing a clear error here points at the toolbox
|
|
141
|
+
definition instead of letting the API return a generic 400.
|
|
142
|
+
"""
|
|
143
|
+
tool_type = sanitized.get("type")
|
|
144
|
+
if tool_type == "file_search" and not sanitized.get("vector_store_ids"):
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"'file_search' tool is missing required 'vector_store_ids'. "
|
|
147
|
+
"If this came from a Foundry toolbox, update the toolbox definition "
|
|
148
|
+
"to include at least one vector store ID."
|
|
149
|
+
)
|
|
150
|
+
if tool_type == "mcp" and not sanitized.get("server_url") and not sanitized.get("project_connection_id"):
|
|
151
|
+
raise ValueError(
|
|
152
|
+
"'mcp' tool is missing both 'server_url' and 'project_connection_id'. "
|
|
153
|
+
"If this came from a Foundry toolbox, update the toolbox definition "
|
|
154
|
+
"to include one of these."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _sanitize_foundry_response_tool(tool_item: Any) -> Any: # pyright: ignore[reportUnusedFunction]
|
|
159
|
+
"""Return a Responses-API-safe tool payload for Foundry hosted tools.
|
|
146
160
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
Reconciles known mismatches between toolbox reads and the Responses API:
|
|
162
|
+
|
|
163
|
+
1. Toolbox reads can return hosted tool objects decorated with read-model
|
|
164
|
+
fields such as top-level ``name`` and ``description``. The Responses API
|
|
165
|
+
rejects at least ``name`` with ``Unknown parameter: 'tools[0].name'``.
|
|
166
|
+
These fields are stripped from non-function hosted tool payloads.
|
|
167
|
+
2. ``code_interpreter`` tools stored in a toolbox without a ``container``
|
|
168
|
+
field (the Azure SDK treats it as optional) are rejected by the Responses
|
|
169
|
+
API with ``Missing required parameter: 'tools[N].container'``. A default
|
|
170
|
+
``{"type": "auto"}`` container is injected when absent.
|
|
171
|
+
3. Hosted tools that are structurally incomplete in ways that cannot be
|
|
172
|
+
defaulted (``file_search`` without ``vector_store_ids``, ``mcp`` without
|
|
173
|
+
either ``server_url`` or ``project_connection_id``) raise ``ValueError``
|
|
174
|
+
with a message that points at the toolbox definition.
|
|
175
|
+
|
|
176
|
+
These are workarounds until the toolbox/Responses proxy normalizes payloads
|
|
177
|
+
server-side.
|
|
151
178
|
"""
|
|
152
179
|
if isinstance(tool_item, FoundryMCPTool):
|
|
153
180
|
sanitized: dict[str, Any] = dict(cast("Mapping[str, Any]", tool_item))
|
|
154
181
|
sanitized.pop("name", None)
|
|
155
182
|
sanitized.pop("description", None)
|
|
183
|
+
_validate_hosted_tool_payload(sanitized)
|
|
156
184
|
return sanitized
|
|
157
185
|
|
|
158
186
|
if isinstance(tool_item, Mapping):
|
|
@@ -161,6 +189,9 @@ def sanitize_foundry_response_tool(tool_item: Any) -> Any:
|
|
|
161
189
|
sanitized = dict(mapping)
|
|
162
190
|
sanitized.pop("name", None)
|
|
163
191
|
sanitized.pop("description", None)
|
|
192
|
+
if sanitized.get("type") == "code_interpreter" and "container" not in sanitized:
|
|
193
|
+
sanitized["container"] = {"type": "auto"}
|
|
194
|
+
_validate_hosted_tool_payload(sanitized)
|
|
164
195
|
return sanitized
|
|
165
196
|
|
|
166
197
|
return cast(Any, tool_item)
|
|
@@ -4,7 +4,7 @@ description = "Microsoft Foundry integrations for Microsoft Agent Framework."
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
urls.homepage = "https://aka.ms/agent-framework"
|
|
10
10
|
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Typing :: Typed",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"agent-framework-core>=1.
|
|
26
|
+
"agent-framework-core>=1.2.0,<2",
|
|
27
27
|
"agent-framework-openai>=1.1.0,<2",
|
|
28
28
|
"azure-ai-inference>=1.0.0b9,<1.0.0b10",
|
|
29
29
|
"azure-ai-projects>=2.1.0,<3.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|