diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index ca368cf134..6184c58459 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **agent-framework-azure-ai-search**: Support the stable/GA (`azure-search-documents` `12.0.0`, api-version `2026-04-01`) and preview (`12.1.0b1`, api-version `2026-05-01-preview`) Azure AI Search SDKs across semantic and agentic modes. Bump the dependency to `>=12.0.0,<13`, add an `api_version` parameter plus `STABLE_API_VERSION`/`PREVIEW_API_VERSION` constants, and auto-detect preview-only agentic features (output mode, low/medium reasoning effort) with actionable errors on the stable SDK. + ## [1.9.0] - 2026-06-18 ### Added diff --git a/python/packages/azure-ai-search/AGENTS.md b/python/packages/azure-ai-search/AGENTS.md index 114ee9d9ab..706081a81c 100644 --- a/python/packages/azure-ai-search/AGENTS.md +++ b/python/packages/azure-ai-search/AGENTS.md @@ -7,6 +7,31 @@ Integration with Azure AI Search for RAG (Retrieval-Augmented Generation). - **`AzureAISearchContextProvider`** - Context provider that retrieves relevant documents from Azure AI Search - **`AzureAISearchSettings`** - Pydantic settings for Azure AI Search configuration +## Constants + +- **`STABLE_API_VERSION`** (`"2026-04-01"`) - data-plane REST api-version of the stable/GA SDK +- **`PREVIEW_API_VERSION`** (`"2026-05-01-preview"`) - data-plane REST api-version of the preview SDK + +## API versions: stable vs preview + +The package depends on `azure-search-documents>=12.0.0,<13`, which spans both channels: + +| Channel | Install | SDK | Default REST `api-version` | +| --- | --- | --- | --- | +| **Stable / GA** | `pip install azure-search-documents` | `12.0.0` | `2026-04-01` | +| **Preview** | `pip install --pre azure-search-documents` | `12.1.0b1` | `2026-05-01-preview` | + +`AzureAISearchContextProvider(..., api_version=...)` forwards the api-version to the +`SearchClient`, `SearchIndexClient`, and `KnowledgeBaseRetrievalClient`. When `api_version` +is `None` (default), the installed SDK chooses its own default. + +Capability gating is auto-detected via `_preview_features_active()`, which requires **both** +the preview SDK (`_preview_agentic_features_available`) **and** a preview `api_version` (a +pinned stable api-version such as `2026-04-01` uses the GA wire and disables preview fields). +Agentic **output mode** (`answer_synthesis`) and **extended reasoning effort** (`low`/`medium`) +are preview-only; otherwise the provider omits them (extractive + minimal) and raises an +actionable `ValueError` if they are explicitly requested. Semantic mode is unaffected. + ## Usage ```python diff --git a/python/packages/azure-ai-search/README.md b/python/packages/azure-ai-search/README.md index fcd3161f94..af827699e2 100644 --- a/python/packages/azure-ai-search/README.md +++ b/python/packages/azure-ai-search/README.md @@ -13,6 +13,29 @@ The Azure AI Search integration provides context providers for RAG (Retrieval Au - **Semantic Mode**: Fast hybrid search (vector + keyword) with semantic ranking - **Agentic Mode**: Multi-hop reasoning using Knowledge Bases for complex queries +### API versions: stable vs preview + +The integration follows the `azure-search-documents` SDK's stable/preview channels: + +| Channel | Install | Default REST `api-version` | +| --- | --- | --- | +| **Stable** | `pip install azure-search-documents` (`>=12.0.0`) | `2026-04-01` | +| **Preview** | `pip install --pre azure-search-documents` (`12.1.0b1`) | `2026-05-01-preview` | + +By default the provider lets the installed SDK pick its `api-version`. Pass `api_version` +to pin it explicitly using the exported constants: + +```python +from agent_framework_azure_ai_search import PREVIEW_API_VERSION, STABLE_API_VERSION + +provider = AzureAISearchContextProvider(..., api_version=STABLE_API_VERSION) +``` + +Agentic **output modes** (`answer_synthesis`) and **extended reasoning effort** +(`low`/`medium`) are preview-only: they require the preview SDK and `PREVIEW_API_VERSION`. +On the stable SDK the provider uses extractive output with minimal reasoning effort, and +raises an actionable error if a preview-only option is requested. + ### Basic Usage Example See the [Azure AI Search context provider examples](../../samples/02-agents/context_providers/azure_ai_search/) which demonstrate: diff --git a/python/packages/azure-ai-search/agent_framework_azure_ai_search/__init__.py b/python/packages/azure-ai-search/agent_framework_azure_ai_search/__init__.py index 9610be5774..a4cd20987d 100644 --- a/python/packages/azure-ai-search/agent_framework_azure_ai_search/__init__.py +++ b/python/packages/azure-ai-search/agent_framework_azure_ai_search/__init__.py @@ -2,7 +2,12 @@ import importlib.metadata -from ._context_provider import AzureAISearchContextProvider, AzureAISearchSettings +from ._context_provider import ( + PREVIEW_API_VERSION, + STABLE_API_VERSION, + AzureAISearchContextProvider, + AzureAISearchSettings, +) try: __version__ = importlib.metadata.version(__name__) @@ -10,6 +15,8 @@ __version__ = "0.0.0" # Fallback for development mode __all__ = [ + "PREVIEW_API_VERSION", + "STABLE_API_VERSION", "AzureAISearchContextProvider", "AzureAISearchSettings", "__version__", diff --git a/python/packages/azure-ai-search/agent_framework_azure_ai_search/_context_provider.py b/python/packages/azure-ai-search/agent_framework_azure_ai_search/_context_provider.py index 5a0b79f29d..cbb75f87de 100644 --- a/python/packages/azure-ai-search/agent_framework_azure_ai_search/_context_provider.py +++ b/python/packages/azure-ai-search/agent_framework_azure_ai_search/_context_provider.py @@ -35,11 +35,6 @@ AzureOpenAIVectorizerParameters, KnowledgeBase, KnowledgeBaseAzureOpenAIModel, - KnowledgeRetrievalLowReasoningEffort, - KnowledgeRetrievalMediumReasoningEffort, - KnowledgeRetrievalMinimalReasoningEffort, - KnowledgeRetrievalOutputMode, - KnowledgeRetrievalReasoningEffort, KnowledgeSourceReference, SearchIndexKnowledgeSource, SearchIndexKnowledgeSourceParameters, @@ -55,9 +50,9 @@ from agent_framework._agents import SupportsAgentRun from azure.search.documents.knowledgebases.aio import KnowledgeBaseRetrievalClient from azure.search.documents.knowledgebases.models import ( + KnowledgeBaseImageContent, KnowledgeBaseMessage, KnowledgeBaseMessageImageContent, - KnowledgeBaseMessageImageContentImage, KnowledgeBaseMessageTextContent, KnowledgeBaseReference, KnowledgeBaseRetrievalRequest, @@ -65,34 +60,24 @@ KnowledgeRetrievalIntent, KnowledgeRetrievalSemanticIntent, ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalLowReasoningEffort as KBRetrievalLowReasoningEffort, - ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalMediumReasoningEffort as KBRetrievalMediumReasoningEffort, - ) from azure.search.documents.knowledgebases.models import ( KnowledgeRetrievalMinimalReasoningEffort as KBRetrievalMinimalReasoningEffort, ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalOutputMode as KBRetrievalOutputMode, - ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalReasoningEffort as KBRetrievalReasoningEffort, - ) if sys.version_info >= (3, 11): from typing import Self # pragma: no cover else: from typing_extensions import Self # pragma: no cover -# Runtime imports for agentic mode (optional dependency) +# Runtime imports for agentic mode. Core knowledge base retrieval works on both the +# stable/GA SDK (api-version 2026-04-01) and the preview SDK (api-version +# 2026-05-01-preview). try: from azure.search.documents.knowledgebases.aio import KnowledgeBaseRetrievalClient from azure.search.documents.knowledgebases.models import ( + KnowledgeBaseImageContent, KnowledgeBaseMessage, KnowledgeBaseMessageImageContent, - KnowledgeBaseMessageImageContentImage, KnowledgeBaseMessageTextContent, KnowledgeBaseReference, KnowledgeBaseRetrievalRequest, @@ -100,31 +85,54 @@ KnowledgeRetrievalIntent, KnowledgeRetrievalSemanticIntent, ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalLowReasoningEffort as KBRetrievalLowReasoningEffort, - ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalMediumReasoningEffort as KBRetrievalMediumReasoningEffort, - ) from azure.search.documents.knowledgebases.models import ( KnowledgeRetrievalMinimalReasoningEffort as KBRetrievalMinimalReasoningEffort, ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalOutputMode as KBRetrievalOutputMode, - ) - from azure.search.documents.knowledgebases.models import ( - KnowledgeRetrievalReasoningEffort as KBRetrievalReasoningEffort, - ) _agentic_retrieval_available = True except ImportError: _agentic_retrieval_available = False +# Preview-only agentic capabilities (api-version 2026-05-01-preview). These symbols are +# absent from the stable/GA SDK (api-version 2026-04-01): there, the knowledge base +# definition and retrieval request do not expose an output mode or extended (low/medium) +# reasoning effort, and retrieval is intent-based only. They are accessed exclusively +# behind ``_preview_agentic_features_available`` checks. ``Any`` keeps the optional symbols +# usable under strict type checking against the stable SDK. +KBRetrievalLowReasoningEffort: Any = None +KBRetrievalMediumReasoningEffort: Any = None +KBRetrievalOutputMode: Any = None +try: + from azure.search.documents.knowledgebases.models import ( # type: ignore[attr-defined] + KnowledgeRetrievalLowReasoningEffort as _KBRetrievalLowReasoningEffort, + ) + from azure.search.documents.knowledgebases.models import ( # type: ignore[attr-defined] + KnowledgeRetrievalMediumReasoningEffort as _KBRetrievalMediumReasoningEffort, + ) + from azure.search.documents.knowledgebases.models import ( # type: ignore[attr-defined] + KnowledgeRetrievalOutputMode as _KBRetrievalOutputMode, + ) + + KBRetrievalLowReasoningEffort = _KBRetrievalLowReasoningEffort + KBRetrievalMediumReasoningEffort = _KBRetrievalMediumReasoningEffort + KBRetrievalOutputMode = _KBRetrievalOutputMode + _preview_agentic_features_available = True +except ImportError: + _preview_agentic_features_available = False + AzureCredentialTypes = TokenCredential | AsyncTokenCredential EmbeddingFunction = Callable[[str], Awaitable[list[float]]] | SupportsGetEmbeddings[str, list[float], Any] KnowledgeBaseOutputModeLiteral = Literal["extractive_data", "answer_synthesis"] RetrievalReasoningEffortLiteral = Literal["minimal", "medium", "low"] +#: Azure AI Search data-plane REST api-version used by the stable/GA SDK +#: (``azure-search-documents`` ``>=12.0.0``). Supports semantic and agentic retrieval. +STABLE_API_VERSION = "2026-04-01" +#: Azure AI Search data-plane REST api-version used by the preview SDK +#: (``pip install --pre azure-search-documents``). Adds agentic output modes and +#: extended (low/medium) reasoning effort on top of the stable surface. +PREVIEW_API_VERSION = "2026-05-01-preview" + logger = logging.getLogger("agent_framework.azure_ai_search") _DEFAULT_AGENTIC_MESSAGE_HISTORY_COUNT = 10 @@ -187,6 +195,7 @@ def __init__( knowledge_base_output_mode: KnowledgeBaseOutputModeLiteral = "extractive_data", retrieval_reasoning_effort: RetrievalReasoningEffortLiteral = "minimal", agentic_message_history_count: int = _DEFAULT_AGENTIC_MESSAGE_HISTORY_COUNT, + api_version: str | None = None, env_file_path: str | None = None, env_file_encoding: str | None = None, ) -> None: @@ -212,6 +221,9 @@ def __init__( knowledge_base_output_mode: Unused in semantic mode. retrieval_reasoning_effort: Unused in semantic mode. agentic_message_history_count: Unused in semantic mode. + api_version: Azure AI Search data-plane REST api-version. ``None`` uses the installed + SDK default (stable SDK -> 2026-04-01, preview SDK -> 2026-05-01-preview). Use + ``STABLE_API_VERSION`` or ``PREVIEW_API_VERSION`` to pin explicitly. env_file_path: Optional ``.env`` file checked before process environment variables. env_file_encoding: Encoding for the ``.env`` file. """ @@ -240,6 +252,7 @@ def __init__( knowledge_base_output_mode: KnowledgeBaseOutputModeLiteral = "extractive_data", retrieval_reasoning_effort: RetrievalReasoningEffortLiteral = "minimal", agentic_message_history_count: int = _DEFAULT_AGENTIC_MESSAGE_HISTORY_COUNT, + api_version: str | None = None, env_file_path: str | None = None, env_file_encoding: str | None = None, ) -> None: @@ -265,6 +278,9 @@ def __init__( knowledge_base_output_mode: Output mode for Knowledge Base retrieval. retrieval_reasoning_effort: Reasoning effort for query planning. agentic_message_history_count: Number of recent messages included in retrieval. + api_version: Azure AI Search data-plane REST api-version. ``None`` uses the installed + SDK default (stable SDK -> 2026-04-01, preview SDK -> 2026-05-01-preview). Use + ``STABLE_API_VERSION`` or ``PREVIEW_API_VERSION`` to pin explicitly. env_file_path: Optional ``.env`` file checked before process environment variables. env_file_encoding: Encoding for the ``.env`` file. """ @@ -293,6 +309,7 @@ def __init__( knowledge_base_output_mode: KnowledgeBaseOutputModeLiteral = "extractive_data", retrieval_reasoning_effort: RetrievalReasoningEffortLiteral = "minimal", agentic_message_history_count: int = _DEFAULT_AGENTIC_MESSAGE_HISTORY_COUNT, + api_version: str | None = None, env_file_path: str | None = None, env_file_encoding: str | None = None, ) -> None: @@ -318,6 +335,9 @@ def __init__( knowledge_base_output_mode: Output mode for Knowledge Base retrieval. retrieval_reasoning_effort: Reasoning effort for query planning. agentic_message_history_count: Number of recent messages included in retrieval. + api_version: Azure AI Search data-plane REST api-version. ``None`` uses the installed + SDK default (stable SDK -> 2026-04-01, preview SDK -> 2026-05-01-preview). Use + ``STABLE_API_VERSION`` or ``PREVIEW_API_VERSION`` to pin explicitly. env_file_path: Optional ``.env`` file checked before process environment variables. env_file_encoding: Encoding for the ``.env`` file. """ @@ -346,6 +366,7 @@ def __init__( knowledge_base_output_mode: KnowledgeBaseOutputModeLiteral = "extractive_data", retrieval_reasoning_effort: RetrievalReasoningEffortLiteral = "minimal", agentic_message_history_count: int = _DEFAULT_AGENTIC_MESSAGE_HISTORY_COUNT, + api_version: str | None = None, env_file_path: str | None = None, env_file_encoding: str | None = None, ) -> None: @@ -375,6 +396,9 @@ def __init__( knowledge_base_output_mode: Output mode for Knowledge Base retrieval. retrieval_reasoning_effort: Reasoning effort for query planning. agentic_message_history_count: Number of recent messages included in retrieval. + api_version: Azure AI Search data-plane REST api-version. ``None`` uses the installed + SDK default (stable SDK -> 2026-04-01, preview SDK -> 2026-05-01-preview). Use + ``STABLE_API_VERSION`` or ``PREVIEW_API_VERSION`` to pin explicitly. env_file_path: Optional ``.env`` file checked before process environment variables. env_file_encoding: Encoding for the ``.env`` file. """ @@ -402,6 +426,7 @@ def __init__( knowledge_base_output_mode: KnowledgeBaseOutputModeLiteral = "extractive_data", retrieval_reasoning_effort: RetrievalReasoningEffortLiteral = "minimal", agentic_message_history_count: int = _DEFAULT_AGENTIC_MESSAGE_HISTORY_COUNT, + api_version: str | None = None, env_file_path: str | None = None, env_file_encoding: str | None = None, ) -> None: @@ -430,6 +455,11 @@ def __init__( knowledge_base_output_mode: Output mode for Knowledge Base retrieval. retrieval_reasoning_effort: Reasoning effort for Knowledge Base query planning. agentic_message_history_count: Number of recent messages for agentic mode. + api_version: Azure AI Search data-plane REST api-version. ``None`` (default) uses the + installed SDK default (stable SDK -> 2026-04-01, preview SDK -> 2026-05-01-preview). + Pass ``STABLE_API_VERSION`` ("2026-04-01") or ``PREVIEW_API_VERSION`` + ("2026-05-01-preview") to pin explicitly. Agentic output modes and low/medium + reasoning effort require the preview SDK and ``PREVIEW_API_VERSION``. env_file_path: Path to environment file for loading settings. env_file_encoding: Encoding of the environment file. """ @@ -505,6 +535,7 @@ def __init__( self.knowledge_base_output_mode = knowledge_base_output_mode self.retrieval_reasoning_effort = retrieval_reasoning_effort self.agentic_message_history_count = agentic_message_history_count + self.api_version = api_version self._use_existing_knowledge_base = False if mode == "agentic": @@ -522,12 +553,31 @@ def __init__( if mode == "agentic": if not _agentic_retrieval_available: raise ImportError( - "Agentic retrieval requires azure-search-documents >= 11.7.0b1 with Knowledge Base support." + "Agentic retrieval requires azure-search-documents >= 12.0.0 with Knowledge Base support." ) if not self._use_existing_knowledge_base and not self.azure_openai_resource_url: raise ValueError( "azure_openai_resource_url is required for agentic mode when creating Knowledge Base from index." ) + if not self._preview_features_active(): + # Preview-only agentic options require BOTH the preview SDK and a preview + # api-version. On the stable/GA wire (api-version 2026-04-01) the knowledge + # base definition and retrieval request do not accept output mode or extended + # reasoning effort, so reject them up front instead of failing server-side. + if knowledge_base_output_mode != "extractive_data": + raise ValueError( + f"knowledge_base_output_mode={knowledge_base_output_mode!r} requires the preview " + "azure-search-documents SDK and a preview api-version. Install it with " + "`pip install --pre azure-search-documents` and set " + f"api_version=PREVIEW_API_VERSION ({PREVIEW_API_VERSION!r}), or use 'extractive_data'." + ) + if retrieval_reasoning_effort != "minimal": + raise ValueError( + f"retrieval_reasoning_effort={retrieval_reasoning_effort!r} requires the preview " + "azure-search-documents SDK and a preview api-version. Install it with " + "`pip install --pre azure-search-documents` and set " + f"api_version=PREVIEW_API_VERSION ({PREVIEW_API_VERSION!r}), or use 'minimal'." + ) self._search_client: SearchClient | None = None if self.index_name: @@ -535,7 +585,7 @@ def __init__( endpoint=self.endpoint, index_name=self.index_name, credential=self.credential, - user_agent=get_user_agent(), + **self._common_client_kwargs(), ) self._index_client: SearchIndexClient | None = None @@ -544,11 +594,33 @@ def __init__( self._index_client = SearchIndexClient( endpoint=self.endpoint, credential=self.credential, - user_agent=get_user_agent(), + **self._common_client_kwargs(), ) self._knowledge_base_initialized = False + def _common_client_kwargs(self) -> dict[str, Any]: + """Build the keyword arguments shared by every Azure AI Search client. + + ``api_version`` is only forwarded when explicitly set so that, when ``None``, the + installed SDK selects its own default (stable -> 2026-04-01, preview -> + 2026-05-01-preview). + """ + kwargs: dict[str, Any] = {"user_agent": get_user_agent()} + if self.api_version is not None: + kwargs["api_version"] = self.api_version + return kwargs + + def _preview_features_active(self) -> bool: + """Whether preview-only agentic features (output mode, low/medium effort) are usable. + + They require BOTH the preview SDK (``_preview_agentic_features_available``) and a + preview ``api-version``. A pinned stable api-version (e.g. ``2026-04-01``) uses the GA + wire protocol, which rejects those fields even when the preview SDK is installed. + """ + api_version_supports_preview = self.api_version is None or self.api_version.endswith("-preview") + return _preview_agentic_features_available and api_version_supports_preview + async def __aenter__(self) -> Self: """Async context manager entry.""" return self @@ -640,7 +712,7 @@ async def _auto_discover_vector_field(self) -> None: self._index_client = SearchIndexClient( endpoint=self.endpoint, credential=self.credential, - user_agent=get_user_agent(), + **self._common_client_kwargs(), ) if not self.index_name: logger.warning("Cannot auto-discover vector field: index_name is not set.") @@ -695,14 +767,18 @@ async def _semantic_search(self, query: str) -> list[Message]: if self.vector_field_name: vector_k = max(self.top_k, 50) if self.semantic_configuration_name else self.top_k if self._use_vectorizable_query: - vector_queries = [VectorizableTextQuery(text=query, k=vector_k, fields=self.vector_field_name)] + vector_queries = [ + VectorizableTextQuery(text=query, k_nearest_neighbors=vector_k, fields=self.vector_field_name) + ] elif self.embedding_function: if isinstance(self.embedding_function, SupportsGetEmbeddings): embeddings = await self.embedding_function.get_embeddings([query]) # type: ignore[reportUnknownVariableType] query_vector = embeddings[0].vector # type: ignore[reportUnknownVariableType] else: query_vector = await self.embedding_function(query) # type: ignore[reportUnknownVariableType] - vector_queries = [VectorizedQuery(vector=query_vector, k=vector_k, fields=self.vector_field_name)] # type: ignore[reportUnknownArgumentType] + vector_queries = [ + VectorizedQuery(vector=query_vector, k_nearest_neighbors=vector_k, fields=self.vector_field_name) # type: ignore[reportUnknownArgumentType] + ] search_params: dict[str, Any] = {"search_text": query, "top": self.top_k} if vector_queries: @@ -740,7 +816,7 @@ async def _ensure_knowledge_base(self) -> None: endpoint=self.endpoint, knowledge_base_name=knowledge_base_name, credential=self.credential, - user_agent=get_user_agent(), + **self._common_client_kwargs(), ) self._knowledge_base_initialized = True return @@ -774,26 +850,29 @@ async def _ensure_knowledge_base(self) -> None: api_key=self.azure_openai_api_key, ) - output_mode = ( - KnowledgeRetrievalOutputMode.EXTRACTIVE_DATA - if self.knowledge_base_output_mode == "extractive_data" - else KnowledgeRetrievalOutputMode.ANSWER_SYNTHESIS - ) - reasoning_effort_map: dict[str, KnowledgeRetrievalReasoningEffort] = { - "minimal": KnowledgeRetrievalMinimalReasoningEffort(), - "medium": KnowledgeRetrievalMediumReasoningEffort(), - "low": KnowledgeRetrievalLowReasoningEffort(), + kb_kwargs: dict[str, Any] = { + "name": knowledge_base_name, + "description": f"Knowledge Base for multi-hop retrieval across {self.index_name}", + "knowledge_sources": [KnowledgeSourceReference(name=knowledge_source_name)], + "models": [KnowledgeBaseAzureOpenAIModel(azure_open_ai_parameters=aoai_params)], } - reasoning_effort = reasoning_effort_map[self.retrieval_reasoning_effort] - - knowledge_base = KnowledgeBase( - name=knowledge_base_name, - description=f"Knowledge Base for multi-hop retrieval across {self.index_name}", - knowledge_sources=[KnowledgeSourceReference(name=knowledge_source_name)], - models=[KnowledgeBaseAzureOpenAIModel(azure_open_ai_parameters=aoai_params)], - output_mode=output_mode, - retrieval_reasoning_effort=reasoning_effort, - ) + if self._preview_features_active(): + # Output mode and reasoning effort on the knowledge base definition require the + # preview SDK and a preview api-version; the stable/GA wire omits them (validated + # as defaults in __init__). + kb_kwargs["output_mode"] = ( + KBRetrievalOutputMode.EXTRACTIVE_DATA + if self.knowledge_base_output_mode == "extractive_data" + else KBRetrievalOutputMode.ANSWER_SYNTHESIS + ) + kb_reasoning_effort_map = { + "minimal": KBRetrievalMinimalReasoningEffort(), + "medium": KBRetrievalMediumReasoningEffort(), + "low": KBRetrievalLowReasoningEffort(), + } + kb_kwargs["retrieval_reasoning_effort"] = kb_reasoning_effort_map[self.retrieval_reasoning_effort] + + knowledge_base = KnowledgeBase(**kb_kwargs) await self._index_client.create_or_update_knowledge_base(knowledge_base) self._knowledge_base_initialized = True @@ -802,43 +881,40 @@ async def _ensure_knowledge_base(self) -> None: endpoint=self.endpoint, knowledge_base_name=knowledge_base_name, credential=self.credential, - user_agent=get_user_agent(), + **self._common_client_kwargs(), ) async def _agentic_search(self, messages: list[Message]) -> list[Message]: """Perform agentic retrieval with multi-hop reasoning.""" await self._ensure_knowledge_base() - reasoning_effort_map: dict[str, KBRetrievalReasoningEffort] = { - "minimal": KBRetrievalMinimalReasoningEffort(), - "medium": KBRetrievalMediumReasoningEffort(), - "low": KBRetrievalLowReasoningEffort(), - } - reasoning_effort = reasoning_effort_map[self.retrieval_reasoning_effort] - - output_mode = ( - KBRetrievalOutputMode.EXTRACTIVE_DATA - if self.knowledge_base_output_mode == "extractive_data" - else KBRetrievalOutputMode.ANSWER_SYNTHESIS - ) + request_kwargs: dict[str, Any] = {"include_activity": True} + if self._preview_features_active(): + # Reasoning effort and output mode on the retrieval request require the preview + # SDK and a preview api-version; the stable/GA wire rejects them. + request_reasoning_effort_map = { + "minimal": KBRetrievalMinimalReasoningEffort(), + "medium": KBRetrievalMediumReasoningEffort(), + "low": KBRetrievalLowReasoningEffort(), + } + request_kwargs["retrieval_reasoning_effort"] = request_reasoning_effort_map[self.retrieval_reasoning_effort] + request_kwargs["output_mode"] = ( + KBRetrievalOutputMode.EXTRACTIVE_DATA + if self.knowledge_base_output_mode == "extractive_data" + else KBRetrievalOutputMode.ANSWER_SYNTHESIS + ) if self.retrieval_reasoning_effort == "minimal": query = "\n".join(msg.text for msg in messages if msg.text) intents: list[KnowledgeRetrievalIntent] = [KnowledgeRetrievalSemanticIntent(search=query)] - retrieval_request = KnowledgeBaseRetrievalRequest( - intents=intents, - retrieval_reasoning_effort=reasoning_effort, - output_mode=output_mode, - include_activity=True, - ) + request_kwargs["intents"] = intents else: - kb_messages = self._prepare_messages_for_kb_search(messages) - retrieval_request = KnowledgeBaseRetrievalRequest( - messages=kb_messages, - retrieval_reasoning_effort=reasoning_effort, - output_mode=output_mode, - include_activity=True, - ) + # Messages-based retrieval (multi-hop query planning) is preview-only; reaching + # this branch requires low/medium reasoning effort, which __init__ already + # rejects on the stable/GA SDK. + request_kwargs["messages"] = self._prepare_messages_for_kb_search(messages) + + retrieval_request = KnowledgeBaseRetrievalRequest(**request_kwargs) if not self._retrieval_client: raise RuntimeError("Retrieval client not initialized.") @@ -872,7 +948,7 @@ def _prepare_messages_for_kb_search(messages: list[Message]) -> list[KnowledgeBa ): kb_content.append( KnowledgeBaseMessageImageContent( - image=KnowledgeBaseMessageImageContentImage(url=content.uri), + image=KnowledgeBaseImageContent(url=content.uri), ) ) case _: @@ -924,8 +1000,9 @@ def _parse_references_to_annotations(references: list[KnowledgeBaseReference] | doc_key = getattr(ref, "doc_key", None) if doc_key: extra["doc_key"] = doc_key - if ref.additional_properties: - extra["sdk_additional_properties"] = ref.additional_properties + sdk_additional_properties = getattr(ref, "additional_properties", None) + if sdk_additional_properties: + extra["sdk_additional_properties"] = sdk_additional_properties sensitivity_info = getattr(ref, "search_sensitivity_label_info", None) if sensitivity_info: extra["sensitivity_label"] = { @@ -995,4 +1072,4 @@ def _extract_document_text(self, doc: dict[str, Any], doc_id: str | None = None) return text -__all__ = ["AzureAISearchContextProvider"] +__all__ = ["PREVIEW_API_VERSION", "STABLE_API_VERSION", "AzureAISearchContextProvider"] diff --git a/python/packages/azure-ai-search/pyproject.toml b/python/packages/azure-ai-search/pyproject.toml index dbfe618f8b..4f63f27539 100644 --- a/python/packages/azure-ai-search/pyproject.toml +++ b/python/packages/azure-ai-search/pyproject.toml @@ -4,7 +4,7 @@ description = "Azure AI Search integration for Microsoft Agent Framework." authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}] readme = "README.md" requires-python = ">=3.10" -version = "1.0.0b260521" +version = "1.0.0b260618" license-files = ["LICENSE"] urls.homepage = "https://aka.ms/agent-framework" urls.source = "https://github.com/microsoft/agent-framework/tree/main/python" @@ -24,7 +24,9 @@ classifiers = [ ] dependencies = [ "agent-framework-core>=1.6.0,<2", - "azure-search-documents>=11.7.0b2,<11.7.0b3", + # Stable/GA (12.0.0) targets Azure AI Search api-version 2026-04-01; the preview + # line (e.g. 12.1.0b1, installed with --pre) targets api-version 2026-05-01-preview. + "azure-search-documents>=12.0.0,<13", ] [tool.uv] diff --git a/python/packages/azure-ai-search/tests/test_aisearch_context_provider.py b/python/packages/azure-ai-search/tests/test_aisearch_context_provider.py index 64c12e0724..8a5f4ddbe5 100644 --- a/python/packages/azure-ai-search/tests/test_aisearch_context_provider.py +++ b/python/packages/azure-ai-search/tests/test_aisearch_context_provider.py @@ -2,6 +2,8 @@ # pyright: reportPrivateUsage=false import os +from collections.abc import Iterator +from contextlib import contextmanager from types import SimpleNamespace from unittest.mock import AsyncMock, Mock, patch @@ -11,6 +13,7 @@ from agent_framework.exceptions import SettingNotFoundError from azure.core.credentials import AzureKeyCredential +from agent_framework_azure_ai_search import _context_provider from agent_framework_azure_ai_search._context_provider import AzureAISearchContextProvider # -- Helpers ------------------------------------------------------------------- @@ -96,6 +99,44 @@ def _make_provider(**overrides) -> AzureAISearchContextProvider: return provider +# -- Preview-feature stubs ---------------------------------------------------- +# The stable/GA azure-search-documents SDK (api-version 2026-04-01) does not ship the +# preview-only agentic symbols (output mode, low/medium reasoning effort, image content, +# messages-based retrieval request). These stubs let the preview code paths be exercised +# deterministically regardless of which SDK is installed. + + +class _StubReasoningEffort: + def __init__(self, *args: object, **kwargs: object) -> None: ... + + +class _StubOutputMode: + EXTRACTIVE_DATA = "extractiveData" + ANSWER_SYNTHESIS = "answerSynthesis" + + +class _StubRetrievalRequest: + """Lenient stand-in for the preview KnowledgeBaseRetrievalRequest that accepts any kwargs.""" + + def __init__(self, **kwargs: object) -> None: + self.__dict__.update(kwargs) + + +@contextmanager +def force_preview_features() -> Iterator[None]: + """Force preview-only agentic features on (with lightweight stubs).""" + with patch.multiple( + _context_provider, + _preview_agentic_features_available=True, + KBRetrievalMinimalReasoningEffort=_StubReasoningEffort, + KBRetrievalMediumReasoningEffort=_StubReasoningEffort, + KBRetrievalLowReasoningEffort=_StubReasoningEffort, + KBRetrievalOutputMode=_StubOutputMode, + KnowledgeBaseRetrievalRequest=_StubRetrievalRequest, + ): + yield + + # -- Initialization: semantic mode --------------------------------------------- @@ -315,6 +356,148 @@ def test_agentic_explicit_index_ignores_env_kb_name(self) -> None: assert provider._use_existing_knowledge_base is False +# -- api_version + stable/preview feature gating ------------------------------ + + +class TestApiVersion: + """Tests for the api_version parameter and the version constants.""" + + def test_version_constants(self) -> None: + assert _context_provider.STABLE_API_VERSION == "2026-04-01" + assert _context_provider.PREVIEW_API_VERSION == "2026-05-01-preview" + + def test_api_version_defaults_to_none(self) -> None: + provider = _make_provider() + assert provider.api_version is None + + def test_api_version_none_not_forwarded(self) -> None: + provider = _make_provider() + kwargs = provider._common_client_kwargs() + assert "user_agent" in kwargs + assert "api_version" not in kwargs + + def test_api_version_explicit_forwarded(self) -> None: + provider = _make_provider(api_version=_context_provider.STABLE_API_VERSION) + assert provider.api_version == "2026-04-01" + assert provider._common_client_kwargs()["api_version"] == "2026-04-01" + + def test_api_version_passed_to_search_client(self) -> None: + with patch("agent_framework_azure_ai_search._context_provider.SearchClient") as mock_sc: + _make_provider(api_version=_context_provider.PREVIEW_API_VERSION) + _, kwargs = mock_sc.call_args + assert kwargs["api_version"] == "2026-05-01-preview" + + def test_api_version_passed_to_index_client_agentic(self) -> None: + with patch("agent_framework_azure_ai_search._context_provider.SearchIndexClient") as mock_ic: + AzureAISearchContextProvider( + endpoint="https://test.search.windows.net", + knowledge_base_name="kb", + api_key="key", + mode="agentic", + api_version=_context_provider.STABLE_API_VERSION, + ) + _, kwargs = mock_ic.call_args + assert kwargs["api_version"] == "2026-04-01" + + def test_preview_features_active_requires_preview_sdk_and_api(self) -> None: + with patch.object(_context_provider, "_preview_agentic_features_available", True): + assert _make_provider(api_version=None)._preview_features_active() is True + assert _make_provider(api_version=_context_provider.PREVIEW_API_VERSION)._preview_features_active() is True + # A pinned stable api-version disables preview features even on the preview SDK. + assert _make_provider(api_version=_context_provider.STABLE_API_VERSION)._preview_features_active() is False + with patch.object(_context_provider, "_preview_agentic_features_available", False): + assert _make_provider(api_version=_context_provider.PREVIEW_API_VERSION)._preview_features_active() is False + + +class TestPreviewFeatureGating: + """Auto-detect gating: preview-only agentic options require the preview SDK.""" + + def _agentic(self, **overrides: object) -> AzureAISearchContextProvider: + defaults: dict[str, object] = { + "endpoint": "https://test.search.windows.net", + "knowledge_base_name": "kb", + "api_key": "key", + "mode": "agentic", + } + defaults.update(overrides) + return AzureAISearchContextProvider(**defaults) # type: ignore[arg-type] + + def test_answer_synthesis_rejected_without_preview_sdk(self) -> None: + with ( + patch.object(_context_provider, "_preview_agentic_features_available", False), + pytest.raises(ValueError, match="answer_synthesis"), + ): + self._agentic(knowledge_base_output_mode="answer_synthesis") + + def test_medium_effort_rejected_without_preview_sdk(self) -> None: + with ( + patch.object(_context_provider, "_preview_agentic_features_available", False), + pytest.raises(ValueError, match="reasoning_effort"), + ): + self._agentic(retrieval_reasoning_effort="medium") + + def test_low_effort_rejected_without_preview_sdk(self) -> None: + with ( + patch.object(_context_provider, "_preview_agentic_features_available", False), + pytest.raises(ValueError, match="reasoning_effort"), + ): + self._agentic(retrieval_reasoning_effort="low") + + def test_defaults_allowed_without_preview_sdk(self) -> None: + with patch.object(_context_provider, "_preview_agentic_features_available", False): + provider = self._agentic() + assert provider.knowledge_base_output_mode == "extractive_data" + assert provider.retrieval_reasoning_effort == "minimal" + + def test_preview_options_allowed_with_preview_sdk(self) -> None: + with patch.object(_context_provider, "_preview_agentic_features_available", True): + provider = self._agentic( + knowledge_base_output_mode="answer_synthesis", + retrieval_reasoning_effort="medium", + ) + assert provider.knowledge_base_output_mode == "answer_synthesis" + assert provider.retrieval_reasoning_effort == "medium" + + def test_stable_api_version_rejects_preview_option_even_with_preview_sdk(self) -> None: + # Preview SDK installed, but a pinned stable api-version uses the GA wire protocol, + # which would reject the preview-only field server-side -> fail fast with an error. + with ( + patch.object(_context_provider, "_preview_agentic_features_available", True), + pytest.raises(ValueError, match="reasoning_effort"), + ): + self._agentic(api_version=_context_provider.STABLE_API_VERSION, retrieval_reasoning_effort="medium") + + async def test_kb_creation_omits_preview_fields_on_stable_sdk(self) -> None: + provider = _make_provider() + provider._knowledge_base_initialized = False + provider._use_existing_knowledge_base = False + provider.knowledge_base_name = "test-kb" + provider.azure_openai_resource_url = "https://aoai.openai.azure.com" + provider.azure_openai_model = "gpt-4" + provider.index_name = "test-index" + + captured: dict[str, object] = {} + + async def _capture(kb: object) -> None: + captured["kb"] = kb + + mock_index_client = AsyncMock() + mock_index_client.get_knowledge_source.return_value = Mock() + mock_index_client.create_or_update_knowledge_base = AsyncMock(side_effect=_capture) + provider._index_client = mock_index_client + + with ( + patch.object(_context_provider, "_preview_agentic_features_available", False), + patch("agent_framework_azure_ai_search._context_provider.KnowledgeBaseRetrievalClient") as mock_cls, + ): + mock_cls.return_value = AsyncMock() + await provider._ensure_knowledge_base() + + kb = captured["kb"] + assert getattr(kb, "output_mode", None) is None + assert getattr(kb, "retrieval_reasoning_effort", None) is None + + # -- __aenter__ / __aexit__ --------------------------------------------------- @@ -1182,9 +1365,12 @@ async def test_non_minimal_reasoning_uses_messages(self) -> None: mock_retrieval.retrieve = AsyncMock(return_value=mock_result) provider._retrieval_client = mock_retrieval - with patch( - "agent_framework_azure_ai_search._context_provider.KnowledgeBaseMessageTextContent", - type(mock_content), + with ( + force_preview_features(), + patch( + "agent_framework_azure_ai_search._context_provider.KnowledgeBaseMessageTextContent", + type(mock_content), + ), ): results = await provider._agentic_search([ Message(role="user", contents=["question"]), @@ -1253,9 +1439,12 @@ async def test_answer_synthesis_output_mode(self) -> None: mock_retrieval.retrieve = AsyncMock(return_value=mock_result) provider._retrieval_client = mock_retrieval - with patch( - "agent_framework_azure_ai_search._context_provider.KnowledgeBaseMessageTextContent", - type(mock_content), + with ( + force_preview_features(), + patch( + "agent_framework_azure_ai_search._context_provider.KnowledgeBaseMessageTextContent", + type(mock_content), + ), ): results = await provider._agentic_search([Message(role="user", contents=["query"])]) @@ -1336,7 +1525,6 @@ def test_text_only_messages(self) -> None: assert result[0].content[0].text == "hello" def test_image_uri_content(self) -> None: - img = Content.from_uri(uri="https://example.com/photo.png", media_type="image/png") messages = [Message(role="user", contents=[img])] result = AzureAISearchContextProvider._prepare_messages_for_kb_search(messages) @@ -1347,7 +1535,6 @@ def test_image_uri_content(self) -> None: assert result[0].content[0].image.url == "https://example.com/photo.png" def test_mixed_text_and_image_content(self) -> None: - text = Content.from_text("describe this image") img = Content.from_uri(uri="https://example.com/img.jpg", media_type="image/jpeg") messages = [Message(role="user", contents=[text, img])] @@ -1376,7 +1563,6 @@ def test_fallback_to_msg_text_when_no_contents(self) -> None: assert result[0].content[0].text == "fallback text" def test_data_uri_image(self) -> None: - img = Content.from_data(data=b"\x89PNG", media_type="image/png") messages = [Message(role="user", contents=[img])] result = AzureAISearchContextProvider._prepare_messages_for_kb_search(messages) @@ -1459,18 +1645,19 @@ def test_raw_representation_stores_original_ref(self) -> None: assert result[0]["raw_representation"] is ref def test_remote_sharepoint_captures_sensitivity_label(self) -> None: - from azure.search.documents.knowledgebases.models import ( - KnowledgeBaseRemoteSharePointReference, - SharePointSensitivityLabelInfo, + # KnowledgeBaseRemoteSharePointReference is preview-only; a SimpleNamespace fake keeps this + # test runnable on the stable/GA SDK while exercising the same parsing branches. + ref = SimpleNamespace( + id="ref-6", + activity_source=0, + reranker_score=None, + source_data=None, + web_url="https://sp.example.com/doc", + search_sensitivity_label_info=SimpleNamespace( + display_name="Confidential", sensitivity_label_id="lbl-1", is_encrypted=True + ), ) - - label = SharePointSensitivityLabelInfo( - display_name="Confidential", sensitivity_label_id="lbl-1", is_encrypted=True - ) - ref = KnowledgeBaseRemoteSharePointReference( - id="ref-6", activity_source=0, web_url="https://sp.example.com/doc", search_sensitivity_label_info=label - ) - result = AzureAISearchContextProvider._parse_references_to_annotations([ref]) + result = AzureAISearchContextProvider._parse_references_to_annotations([ref]) # type: ignore[list-item] assert result[0]["url"] == "https://sp.example.com/doc" sl = result[0]["additional_properties"]["sensitivity_label"] assert sl["display_name"] == "Confidential" @@ -1537,18 +1724,18 @@ def test_image_content(self) -> None: from azure.search.documents.knowledgebases.models import ( KnowledgeBaseMessage, KnowledgeBaseMessageImageContent, - KnowledgeBaseMessageImageContentImage, KnowledgeBaseRetrievalResponse, ) + # The inner image object differs across SDKs (preview KnowledgeBaseMessageImageContentImage + # vs stable KnowledgeBaseImageContent); both expose ``url``. A SimpleNamespace keeps the + # parsing assertion SDK-agnostic. response = KnowledgeBaseRetrievalResponse( response=[ KnowledgeBaseMessage( role="assistant", content=[ - KnowledgeBaseMessageImageContent( - image=KnowledgeBaseMessageImageContentImage(url="https://img.example.com/a.png") - ) + KnowledgeBaseMessageImageContent(image=SimpleNamespace(url="https://img.example.com/a.png")) ], ), ], @@ -1563,7 +1750,6 @@ def test_mixed_text_and_image_content(self) -> None: from azure.search.documents.knowledgebases.models import ( KnowledgeBaseMessage, KnowledgeBaseMessageImageContent, - KnowledgeBaseMessageImageContentImage, KnowledgeBaseMessageTextContent, KnowledgeBaseRetrievalResponse, ) @@ -1574,9 +1760,7 @@ def test_mixed_text_and_image_content(self) -> None: role="assistant", content=[ KnowledgeBaseMessageTextContent(text="description"), - KnowledgeBaseMessageImageContent( - image=KnowledgeBaseMessageImageContentImage(url="https://img.example.com/b.png") - ), + KnowledgeBaseMessageImageContent(image=SimpleNamespace(url="https://img.example.com/b.png")), ], ), ], diff --git a/python/packages/core/agent_framework/azure/__init__.py b/python/packages/core/agent_framework/azure/__init__.py index 7cff0150f1..14229f0c4d 100644 --- a/python/packages/core/agent_framework/azure/__init__.py +++ b/python/packages/core/agent_framework/azure/__init__.py @@ -19,6 +19,8 @@ "DurableAIAgentClient": ("agent_framework_durabletask", "agent-framework-durabletask"), "DurableAIAgentOrchestrationContext": ("agent_framework_durabletask", "agent-framework-durabletask"), "DurableAIAgentWorker": ("agent_framework_durabletask", "agent-framework-durabletask"), + "PREVIEW_API_VERSION": ("agent_framework_azure_ai_search", "agent-framework-azure-ai-search"), + "STABLE_API_VERSION": ("agent_framework_azure_ai_search", "agent-framework-azure-ai-search"), } diff --git a/python/samples/02-agents/context_providers/azure_ai_search/README.md b/python/samples/02-agents/context_providers/azure_ai_search/README.md index 2e32819003..1ac08ca3dc 100644 --- a/python/samples/02-agents/context_providers/azure_ai_search/README.md +++ b/python/samples/02-agents/context_providers/azure_ai_search/README.md @@ -14,7 +14,7 @@ This folder contains examples demonstrating how to use the Azure AI Search conte ## Installation ```bash -pip install agent-framework-foundry-search agent-framework-foundry +pip install agent-framework-azure-ai-search agent-framework-foundry ``` ## Prerequisites @@ -42,6 +42,27 @@ Both examples support two authentication methods: Run `az login` if using Entra ID authentication. +### API versions (stable vs preview) + +The provider follows the `azure-search-documents` SDK channels: + +- **Stable / GA** — `pip install azure-search-documents` (`12.0.0`), default api-version `2026-04-01`. +- **Preview** — `pip install --pre azure-search-documents` (`12.1.0b1`), default api-version `2026-05-01-preview`. + +Pass `api_version` to pin it explicitly (otherwise the installed SDK's default is used): + +```python +from agent_framework.azure import AzureAISearchContextProvider, STABLE_API_VERSION + +provider = AzureAISearchContextProvider(..., api_version=STABLE_API_VERSION) +``` + +Agentic `knowledge_base_output_mode="answer_synthesis"` and +`retrieval_reasoning_effort` of `"low"`/`"medium"` are **preview-only** (require the +preview SDK + `PREVIEW_API_VERSION`). On the stable SDK the provider uses extractive +output with minimal reasoning effort and raises an actionable error if a preview-only +option is requested. + ## Configuration ### Environment Variables @@ -211,6 +232,7 @@ async with Agent( - `credential`: Azure credential for Entra ID auth (e.g., `DefaultAzureCredential()`) - `mode`: Search mode - `"semantic"` (default) or `"agentic"` - `top_k`: Number of documents to retrieve (default: 3 for semantic, 5 for agentic) +- `api_version`: Data-plane REST api-version. `None` (default) uses the installed SDK's default; pass `STABLE_API_VERSION` (`"2026-04-01"`) or `PREVIEW_API_VERSION` (`"2026-05-01-preview"`) to pin it. ### Semantic Mode Parameters diff --git a/python/samples/02-agents/context_providers/azure_ai_search/search_context_agentic.py b/python/samples/02-agents/context_providers/azure_ai_search/search_context_agentic.py index 2d57a2906b..fe4ec99932 100644 --- a/python/samples/02-agents/context_providers/azure_ai_search/search_context_agentic.py +++ b/python/samples/02-agents/context_providers/azure_ai_search/search_context_agentic.py @@ -83,8 +83,8 @@ async def main() -> None: mode="agentic", knowledge_base_name=knowledge_base_name, # Optional: Configure retrieval behavior - knowledge_base_output_mode="extractive_data", # or "answer_synthesis" - retrieval_reasoning_effort="minimal", # or "medium", "low" + knowledge_base_output_mode="extractive_data", # or "answer_synthesis" (preview SDK only) + retrieval_reasoning_effort="minimal", # or "medium", "low" (preview SDK only) ) else: # Auto-create Knowledge Base from index @@ -100,10 +100,14 @@ async def main() -> None: credential=AzureCliCredential() if not search_key else None, mode="agentic", azure_openai_resource_url=azure_openai_resource_url, - model_deployment_name=model_deployment, + model=model_deployment, + # Optional: pin the data-plane api-version. Defaults to the installed SDK's + # default (stable -> 2026-04-01, preview -> 2026-05-01-preview). Output modes + # and low/medium reasoning effort below require the preview SDK + api_version. + # api_version=PREVIEW_API_VERSION, # Optional: Configure retrieval behavior - knowledge_base_output_mode="extractive_data", # or "answer_synthesis" - retrieval_reasoning_effort="minimal", # or "medium", "low" + knowledge_base_output_mode="extractive_data", # or "answer_synthesis" (preview SDK only) + retrieval_reasoning_effort="minimal", # or "medium", "low" (preview SDK only) top_k=3, ) diff --git a/python/samples/02-agents/context_providers/azure_ai_search/search_context_semantic.py b/python/samples/02-agents/context_providers/azure_ai_search/search_context_semantic.py index 5f2a57f511..c107488696 100644 --- a/python/samples/02-agents/context_providers/azure_ai_search/search_context_semantic.py +++ b/python/samples/02-agents/context_providers/azure_ai_search/search_context_semantic.py @@ -76,6 +76,10 @@ async def main() -> None: credential=credential if not search_key else None, mode="semantic", # Default mode top_k=3, # Retrieve top 3 most relevant documents + # Optional: pin the data-plane api-version (defaults to the installed SDK's default: + # stable -> 2026-04-01, preview -> 2026-05-01-preview). Import STABLE_API_VERSION / + # PREVIEW_API_VERSION from agent_framework.azure to set it explicitly. + # api_version=STABLE_API_VERSION, embedding_function=embedding_client, # Provide embedding function for hybrid search vector_field_name="DescriptionVector" if embedding_client diff --git a/python/uv.lock b/python/uv.lock index 6adc8f7d68..006e857eb6 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'darwin'", @@ -228,7 +228,7 @@ requires-dist = [ [[package]] name = "agent-framework-azure-ai-search" -version = "1.0.0b260521" +version = "1.0.0b260618" source = { editable = "packages/azure-ai-search" } dependencies = [ { name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -238,7 +238,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "agent-framework-core", editable = "packages/core" }, - { name = "azure-search-documents", specifier = ">=11.7.0b2,<11.7.0b3" }, + { name = "azure-search-documents", specifier = ">=12.0.0,<13" }, ] [[package]] @@ -1267,15 +1267,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/cf/90f27a2b48c9b748f84194b07e565f900e7f0ce0500da9b9f067dca599d3/azure_ai_projects-2.2.0-py3-none-any.whl", hash = "sha256:8f89bdaca4df1bd479d3bd2bd0f19a0905d60be6d17b84a69e8fabd82eac5906", size = 344307, upload-time = "2026-05-30T00:21:00.672Z" }, ] -[[package]] -name = "azure-common" -version = "1.1.28" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/71/f6f71a276e2e69264a97ad39ef850dca0a04fce67b12570730cb38d0ccac/azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3", size = 20914, upload-time = "2022-02-03T19:39:44.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/55/7f118b9c1b23ec15ca05d15a578d8207aa1706bc6f7c87218efffbbf875d/azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad", size = 14462, upload-time = "2022-02-03T19:39:42.417Z" }, -] - [[package]] name = "azure-core" version = "1.39.0" @@ -1404,17 +1395,16 @@ wheels = [ [[package]] name = "azure-search-documents" -version = "11.7.0b2" +version = "12.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "azure-common", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "isodate", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/ba/bde0f03e0a742ba3bbcc929f91ed2f3b1420c2bb84c9a7f878f3b87ebfce/azure_search_documents-11.7.0b2.tar.gz", hash = "sha256:b6e039f8038ff2210d2057e704e867c6e29bb46bfcd400da4383e45e4b8bb189", size = 423956, upload-time = "2025-11-14T20:09:32.876Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/dc/bb4db263381aa5b29414e280a8535a343d877a3831a501ef39332174c85c/azure_search_documents-12.0.0.tar.gz", hash = "sha256:8e6d73ec0ed1623083435b757e34324db65d72d4e09cca061a59fc7e90c8ddbc", size = 386222, upload-time = "2026-05-01T20:28:22.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/26/ed4498374f9088818278ac225f2bea688b4ec979d81bf83a5355c8c366af/azure_search_documents-11.7.0b2-py3-none-any.whl", hash = "sha256:f82117b321344a84474269ed26df194c24cca619adc024d981b1b86aee3c6f05", size = 432037, upload-time = "2025-11-14T20:09:34.347Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b1/4869a064dbb79fb4ecac684de51a8f8f7a93a315f3f9cc4bf8a65cc413cd/azure_search_documents-12.0.0-py3-none-any.whl", hash = "sha256:d88114e4179cd753845711042380a4571e7faa8619addf5e017928ebe37fc0d1", size = 352117, upload-time = "2026-05-01T20:28:23.989Z" }, ] [[package]] @@ -2720,6 +2710,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/3f/9859f655d11901e7b2996c6e3d33e0caa9a1d4572c3bc61ed0faa64b2f4c/greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d", size = 277747, upload-time = "2026-02-20T20:16:21.325Z" }, { url = "https://files.pythonhosted.org/packages/fb/07/cb284a8b5c6498dbd7cba35d31380bb123d7dceaa7907f606c8ff5993cbf/greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13", size = 579202, upload-time = "2026-02-20T20:47:28.955Z" }, { url = "https://files.pythonhosted.org/packages/ed/45/67922992b3a152f726163b19f890a85129a992f39607a2a53155de3448b8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e", size = 590620, upload-time = "2026-02-20T20:55:55.581Z" }, + { url = "https://files.pythonhosted.org/packages/03/5f/6e2a7d80c353587751ef3d44bb947f0565ec008a2e0927821c007e96d3a7/greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7", size = 602132, upload-time = "2026-02-20T21:02:43.261Z" }, { url = "https://files.pythonhosted.org/packages/ad/55/9f1ebb5a825215fadcc0f7d5073f6e79e3007e3282b14b22d6aba7ca6cb8/greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f", size = 591729, upload-time = "2026-02-20T20:20:58.395Z" }, { url = "https://files.pythonhosted.org/packages/24/b4/21f5455773d37f94b866eb3cf5caed88d6cea6dd2c6e1f9c34f463cba3ec/greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef", size = 1551946, upload-time = "2026-02-20T20:49:31.102Z" }, { url = "https://files.pythonhosted.org/packages/00/68/91f061a926abead128fe1a87f0b453ccf07368666bd59ffa46016627a930/greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca", size = 1618494, upload-time = "2026-02-20T20:21:06.541Z" }, @@ -2727,6 +2718,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/47/16400cb42d18d7a6bb46f0626852c1718612e35dcb0dffa16bbaffdf5dd2/greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86", size = 278890, upload-time = "2026-02-20T20:19:39.263Z" }, { url = "https://files.pythonhosted.org/packages/a3/90/42762b77a5b6aa96cd8c0e80612663d39211e8ae8a6cd47c7f1249a66262/greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f", size = 581120, upload-time = "2026-02-20T20:47:30.161Z" }, { url = "https://files.pythonhosted.org/packages/bf/6f/f3d64f4fa0a9c7b5c5b3c810ff1df614540d5aa7d519261b53fba55d4df9/greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55", size = 594363, upload-time = "2026-02-20T20:55:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/1430a04657735a3f23116c2e0d5eb10220928846e4537a938a41b350bed6/greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2", size = 605046, upload-time = "2026-02-20T21:02:45.234Z" }, { url = "https://files.pythonhosted.org/packages/72/83/3e06a52aca8128bdd4dcd67e932b809e76a96ab8c232a8b025b2850264c5/greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358", size = 594156, upload-time = "2026-02-20T20:20:59.955Z" }, { url = "https://files.pythonhosted.org/packages/70/79/0de5e62b873e08fe3cef7dbe84e5c4bc0e8ed0c7ff131bccb8405cd107c8/greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99", size = 1554649, upload-time = "2026-02-20T20:49:32.293Z" }, { url = "https://files.pythonhosted.org/packages/5a/00/32d30dee8389dc36d42170a9c66217757289e2afb0de59a3565260f38373/greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be", size = 1619472, upload-time = "2026-02-20T20:21:07.966Z" }, @@ -2735,6 +2727,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, @@ -2743,6 +2736,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, @@ -2751,6 +2745,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, @@ -2759,6 +2754,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" },