Python: [Breaking] Upgrade to azure-ai-projects 2.0+ (#4536)

* Prepare azure-ai-projects 2.0 GA compatibility

Add allow_preview support for internal AIProjectClient creation, keep backward compatibility for renamed SDK model classes, and align Azure AI/core paths and tests for GA validation workflows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* upgrade to ai-project==2.0.0

* Python: remove azure-ai-projects keyword-guard paths

Assume azure-ai-projects 2.0+ in Azure AI client/provider/responses code paths by removing _supports_keyword_argument gating and related fallback branching.

Also fix pyright typing in FoundryMemoryProvider memory store calls by using ResponseInputItemParam-typed items.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* check fixes

* Python: remove unsupported foundry_features option

Drop foundry_features from Azure AI client and provider surfaces because azure-ai-projects 2.0.0 does not expose that create_version parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: add allow_preview to Foundry memory provider

Propagate allow_preview when FoundryMemoryProvider constructs an AIProjectClient and update tests accordingly.

Also finish wiring allow_preview through AzureAIClient-facing surfaces and related docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* aligning docstrings

* udpated lock

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-03-09 11:12:47 +01:00
committed by GitHub
Unverified
parent d5e240b375
commit 23d6d91c8f
15 changed files with 326 additions and 321 deletions
@@ -37,9 +37,8 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import (
ApproximateLocation,
CodeInterpreterContainerAuto,
AutoCodeInterpreterToolParam,
CodeInterpreterTool,
FoundryFeaturesOptInKeys,
ImageGenTool,
MCPTool,
PromptAgentDefinition,
@@ -66,7 +65,6 @@ if sys.version_info >= (3, 11):
else:
from typing_extensions import Self, TypedDict # type: ignore # pragma: no cover
logger = logging.getLogger("agent_framework.azure")
@@ -79,9 +77,6 @@ class AzureAIProjectAgentOptions(OpenAIResponsesOptions, total=False):
reasoning: Reasoning # type: ignore[misc]
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
foundry_features: FoundryFeaturesOptInKeys | str
"""Optional Foundry preview feature opt-in for agent version creation."""
AzureAIClientOptionsT = TypeVar(
"AzureAIClientOptionsT",
@@ -123,6 +118,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
model_deployment_name: str | None = None,
credential: AzureCredentialTypes | None = None,
use_latest_version: bool | None = None,
allow_preview: bool | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
**kwargs: Any,
@@ -148,6 +144,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
AsyncTokenCredential, or a callable token provider.
use_latest_version: Boolean flag that indicates whether to use latest agent version
if it exists in the service.
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
env_file_path: Path to environment file for loading settings.
env_file_encoding: Encoding of the environment file.
kwargs: Additional keyword arguments passed to the parent class.
@@ -208,11 +205,14 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
# Use provided credential
if not credential:
raise ValueError("Azure credential is required when project_client is not provided.")
project_client = AIProjectClient(
endpoint=resolved_endpoint,
credential=credential, # type: ignore[arg-type]
user_agent=AGENT_FRAMEWORK_USER_AGENT,
)
project_client_kwargs: dict[str, Any] = {
"endpoint": resolved_endpoint,
"credential": credential, # type: ignore[arg-type]
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
}
if allow_preview is not None:
project_client_kwargs["allow_preview"] = allow_preview
project_client = AIProjectClient(**project_client_kwargs)
should_close_client = True
# Initialize parent
@@ -413,8 +413,6 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
"definition": PromptAgentDefinition(**args),
"description": self.agent_description,
}
if foundry_features := run_options.get("foundry_features"):
create_version_kwargs["foundry_features"] = foundry_features
created_agent = await self.project_client.agents.create_version(**create_version_kwargs)
@@ -513,7 +511,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
"temperature": ("temperature",),
"top_p": ("top_p",),
"reasoning": ("reasoning",),
"foundry_features": ("foundry_features",),
"allow_preview": ("allow_preview",),
}
for run_keys in agent_level_option_to_run_keys.values():
@@ -939,7 +937,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
if file_ids is None and isinstance(container, dict):
file_ids = container.get("file_ids")
resolved = resolve_file_ids(file_ids)
tool_container = CodeInterpreterContainerAuto(file_ids=resolved)
tool_container = AutoCodeInterpreterToolParam(file_ids=resolved)
return CodeInterpreterTool(container=tool_container, **kwargs)
@staticmethod
@@ -1244,6 +1242,7 @@ class AzureAIClient(
model_deployment_name: str | None = None,
credential: AzureCredentialTypes | None = None,
use_latest_version: bool | None = None,
allow_preview: bool | None = None,
middleware: Sequence[ChatAndFunctionMiddlewareTypes] | None = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
env_file_path: str | None = None,
@@ -1268,6 +1267,7 @@ class AzureAIClient(
or AsyncTokenCredential.
use_latest_version: Boolean flag that indicates whether to use latest agent version
if it exists in the service.
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``
middleware: Optional sequence of chat middlewares to include.
function_invocation_configuration: Optional function invocation configuration.
env_file_path: Path to environment file for loading settings.
@@ -1318,6 +1318,7 @@ class AzureAIClient(
model_deployment_name=model_deployment_name,
credential=credential,
use_latest_version=use_latest_version,
allow_preview=allow_preview,
middleware=middleware,
function_invocation_configuration=function_invocation_configuration,
env_file_path=env_file_path,
@@ -18,6 +18,7 @@ from agent_framework._sessions import AgentSession, BaseContextProvider, Session
from agent_framework._settings import load_settings
from agent_framework.azure._entra_id_authentication import AzureCredentialTypes
from azure.ai.projects.aio import AIProjectClient
from openai.types.responses import ResponseInputItemParam
from ._shared import AzureAISettings
@@ -58,6 +59,7 @@ class FoundryMemoryProvider(BaseContextProvider):
project_client: AIProjectClient | None = None,
project_endpoint: str | None = None,
credential: AzureCredentialTypes | None = None,
allow_preview: bool | None = None,
memory_store_name: str,
scope: str | None = None,
context_prompt: str | None = None,
@@ -74,6 +76,7 @@ class FoundryMemoryProvider(BaseContextProvider):
credential: Azure credential for authentication. Accepts a TokenCredential,
AsyncTokenCredential, or a callable token provider.
Required when project_client is not provided.
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
memory_store_name: The name of the memory store to use.
scope: The namespace that logically groups and isolates memories (e.g., user ID).
If None, `session_id` will be used.
@@ -100,11 +103,14 @@ class FoundryMemoryProvider(BaseContextProvider):
)
if not credential:
raise ValueError("Azure credential is required when project_client is not provided.")
project_client = AIProjectClient(
endpoint=resolved_endpoint,
credential=credential, # type: ignore[arg-type]
user_agent=AGENT_FRAMEWORK_USER_AGENT,
)
project_client_kwargs: dict[str, Any] = {
"endpoint": resolved_endpoint,
"credential": credential, # type: ignore[arg-type]
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
}
if allow_preview is not None:
project_client_kwargs["allow_preview"] = allow_preview
project_client = AIProjectClient(**project_client_kwargs)
if not memory_store_name:
raise ValueError("memory_store_name is required")
@@ -169,8 +175,8 @@ class FoundryMemoryProvider(BaseContextProvider):
return
# Convert input messages to memory search item format
items = [
{"type": "text", "text": msg.text}
items: list[ResponseInputItemParam] = [
{"type": "message", "role": "user", "content": msg.text}
for msg in context.input_messages
if msg and msg.text and msg.text.strip()
]
@@ -224,7 +230,7 @@ class FoundryMemoryProvider(BaseContextProvider):
messages_to_store.extend(context.response.messages)
# Filter and convert messages to memory update item format
items: list[dict[str, str]] = []
items: list[ResponseInputItemParam] = []
for message in messages_to_store:
if message.role in {"user", "assistant", "system"} and message.text and message.text.strip():
if message.role == "user":
@@ -102,6 +102,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
project_endpoint: str | None = None,
model: str | None = None,
credential: AzureCredentialTypes | None = None,
allow_preview: bool | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
@@ -117,6 +118,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
credential: Azure credential for authentication. Accepts a TokenCredential,
AsyncTokenCredential, or a callable token provider.
Required when project_client is not provided.
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
env_file_path: Path to environment file for loading settings.
env_file_encoding: Encoding of the environment file.
@@ -146,11 +148,14 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
if not credential:
raise ValueError("Azure credential is required when project_client is not provided.")
project_client = AIProjectClient(
endpoint=resolved_endpoint,
credential=credential, # type: ignore[arg-type]
user_agent=AGENT_FRAMEWORK_USER_AGENT,
)
project_client_kwargs: dict[str, Any] = {
"endpoint": resolved_endpoint,
"credential": credential, # type: ignore[arg-type]
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
}
if allow_preview is not None:
project_client_kwargs["allow_preview"] = allow_preview
project_client = AIProjectClient(**project_client_kwargs)
self._should_close_client = True
self._project_client = project_client
@@ -199,7 +204,6 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
response_format = opts.get("response_format")
rai_config = opts.get("rai_config")
reasoning = opts.get("reasoning")
foundry_features = opts.get("foundry_features")
args: dict[str, Any] = {"model": resolved_model}
@@ -246,8 +250,6 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
"definition": PromptAgentDefinition(**args),
"description": description,
}
if foundry_features:
create_version_kwargs["foundry_features"] = foundry_features
created_agent = await self._project_client.agents.create_version(**create_version_kwargs)
@@ -19,9 +19,9 @@ from azure.ai.agents.models import (
from azure.ai.projects.models import (
CodeInterpreterTool,
MCPTool,
TextResponseFormatConfigurationResponseFormatJsonObject,
TextResponseFormatConfigurationResponseFormatText,
TextResponseFormatJsonObject,
TextResponseFormatJsonSchema,
TextResponseFormatText,
Tool,
WebSearchPreviewTool,
)
@@ -479,11 +479,7 @@ def _prepare_mcp_tool_dict_for_azure_ai(tool_dict: dict[str, Any]) -> MCPTool:
def create_text_format_config(
response_format: type[BaseModel] | Mapping[str, Any],
) -> (
TextResponseFormatJsonSchema
| TextResponseFormatConfigurationResponseFormatJsonObject
| TextResponseFormatConfigurationResponseFormatText
):
) -> TextResponseFormatJsonSchema | TextResponseFormatJsonObject | TextResponseFormatText:
"""Convert response_format into Azure text format configuration."""
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
schema = response_format.model_json_schema()
@@ -513,9 +509,9 @@ def create_text_format_config(
config_kwargs["description"] = format_config["description"]
return TextResponseFormatJsonSchema(**config_kwargs)
if format_type == "json_object":
return TextResponseFormatConfigurationResponseFormatJsonObject()
return TextResponseFormatJsonObject()
if format_type == "text":
return TextResponseFormatConfigurationResponseFormatText()
return TextResponseFormatText()
raise IntegrationInvalidRequestException("response_format must be a Pydantic model or mapping.")
@@ -28,7 +28,7 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
from azure.ai.projects.aio import AIProjectClient
from azure.ai.projects.models import (
ApproximateLocation,
CodeInterpreterContainerAuto,
AutoCodeInterpreterToolParam,
CodeInterpreterTool,
FileSearchTool,
ImageGenTool,
@@ -1296,7 +1296,7 @@ def test_from_azure_ai_tools_mcp() -> None:
def test_from_azure_ai_tools_code_interpreter() -> None:
"""Test from_azure_ai_tools with Code Interpreter tool."""
ci_tool = CodeInterpreterTool(container=CodeInterpreterContainerAuto(file_ids=["file-1"]))
ci_tool = CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=["file-1"]))
parsed_tools = from_azure_ai_tools([ci_tool])
assert len(parsed_tools) == 1
assert parsed_tools[0]["type"] == "code_interpreter"
@@ -86,6 +86,7 @@ class TestInit:
provider = FoundryMemoryProvider(
project_endpoint="https://test.project.endpoint",
credential=mock_credential, # type: ignore[arg-type]
allow_preview=True,
memory_store_name="test_store",
scope="user_123",
)
@@ -93,6 +94,7 @@ class TestInit:
mock_ai_project_client.assert_called_once_with(
endpoint="https://test.project.endpoint",
credential=mock_credential,
allow_preview=True,
user_agent=AGENT_FRAMEWORK_USER_AGENT,
)
@@ -112,9 +112,7 @@ class SkillResource:
self._accepts_kwargs: bool = False
if function is not None:
sig = inspect.signature(function)
self._accepts_kwargs = any(
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
)
self._accepts_kwargs = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
class Skill:
@@ -73,6 +73,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
async_client: AsyncOpenAI | None = None,
project_client: Any | None = None,
project_endpoint: str | None = None,
allow_preview: bool | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
instruction_role: str | None = None,
@@ -120,6 +121,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
project_endpoint: The Azure AI Foundry project endpoint URL.
When provided with ``credential``, an ``AIProjectClient`` will be created
and used to obtain the OpenAI client. Requires the ``azure-ai-projects`` package.
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
env_file_path: Use the environment settings file as a fallback to using env vars.
env_file_encoding: The encoding of the environment settings file, defaults to 'utf-8'.
instruction_role: The role to use for 'instruction' messages, for example, summarization
@@ -189,6 +191,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
project_client=project_client,
project_endpoint=project_endpoint,
credential=credential,
allow_preview=allow_preview,
)
azure_openai_settings = load_settings(
@@ -246,21 +249,9 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
project_client: AIProjectClient | None,
project_endpoint: str | None,
credential: AzureCredentialTypes | AzureTokenProvider | None,
allow_preview: bool | None = None,
) -> AsyncOpenAI:
"""Create an AsyncOpenAI client from an Azure AI Foundry project.
Args:
project_client: An existing AIProjectClient to use.
project_endpoint: The Azure AI Foundry project endpoint URL.
credential: Azure credential for authentication.
Returns:
An AsyncAzureOpenAI client obtained from the project client.
Raises:
ValueError: If required parameters are missing or
the azure-ai-projects package is not installed.
"""
"""Create an AsyncOpenAI client from an Azure AI Foundry project."""
if project_client is not None:
return project_client.get_openai_client()
@@ -268,11 +259,14 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
raise ValueError("Azure AI project endpoint is required when project_client is not provided.")
if not credential:
raise ValueError("Azure credential is required when using project_endpoint without a project_client.")
project_client = AIProjectClient(
endpoint=project_endpoint,
credential=credential, # type: ignore[arg-type]
user_agent=AGENT_FRAMEWORK_USER_AGENT,
)
project_client_kwargs: dict[str, Any] = {
"endpoint": project_endpoint,
"credential": credential, # type: ignore[arg-type]
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
}
if allow_preview is not None:
project_client_kwargs["allow_preview"] = allow_preview
project_client = AIProjectClient(**project_client_kwargs)
return project_client.get_openai_client()
@override
+1 -1
View File
@@ -34,7 +34,7 @@ dependencies = [
# connectors and functions
"openai>=1.99.0",
"azure-identity>=1,<2",
"azure-ai-projects == 2.0.0b4",
"azure-ai-projects>=2.0.0,<3.0",
"mcp[ws]>=1.24.0,<2",
"packaging>=24.1",
]
@@ -544,19 +544,19 @@ class TestFunctionExecutor:
static_wrapped = staticmethod(my_async_func)
# Direct check on descriptor object fails (this is the bug)
assert not asyncio.iscoroutinefunction(static_wrapped)
assert not asyncio.iscoroutinefunction(static_wrapped) # type: ignore[reportDeprecated]
assert isinstance(static_wrapped, staticmethod)
# But unwrapping __func__ reveals the async function
unwrapped = static_wrapped.__func__
assert asyncio.iscoroutinefunction(unwrapped)
assert asyncio.iscoroutinefunction(unwrapped) # type: ignore[reportDeprecated]
# When accessed via class attribute, Python's descriptor protocol
# automatically unwraps it, so it works:
class C:
async_static = static_wrapped
assert asyncio.iscoroutinefunction(C.async_static) # Works via descriptor protocol
assert asyncio.iscoroutinefunction(C.async_static) # type: ignore[reportDeprecated] # Works via descriptor protocol
class TestExecutorExplicitTypes:
@@ -15,11 +15,10 @@ import json
import logging
import uuid
from abc import abstractmethod
from collections.abc import Mapping
from collections.abc import Callable, Mapping
from dataclasses import dataclass, field
from inspect import isawaitable
from typing import Any, cast
from collections.abc import Callable
from agent_framework import (
Content,
@@ -5,8 +5,8 @@
from __future__ import annotations
import re
from typing import Any, cast
from collections.abc import Callable
from typing import Any, cast
from pydantic import BaseModel, Field, field_validator