diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index b5072c59cfc3..0caaf2e72cb8 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -24,6 +24,10 @@ AZURE_AI_PROJECTS_AZURE_SUBSCRIPTION_ID= AZURE_AI_PROJECTS_AZURE_RESOURCE_GROUP= AZURE_AI_PROJECTS_AZURE_AOAI_ACCOUNT= +# Used in Memory Store samples +AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME= +AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME= + ####################################################################### # # Used tests, excluding Agent tests diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index a2ebf403d3cb..7f30ae07efd7 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -5,6 +5,9 @@ ### Features Added * Tracing: support for workflow agent tracing. +* Agent Memory operations, including code for custom LRO poller. See methods on the ".memory_store" +property of `AIProjectClient`. + ### Breaking changes * `get_openai_client()` method on the asynchronous AIProjectClient is no longer an "async" method. @@ -14,6 +17,9 @@ * Tracing: operation name attribute added to create agent span, token usage added to streaming response generation span. ### Sample updates + +* Added samples to show usage of the Memory Search Tool (see sample_agent_memory_search.py) and its async equivalent. +* Added samples to show Memory management. See samples in the folder `samples\memories`. * Added `finetuning` samples for operations create, retrieve, list, list_events, list_checkpoints, cancel, pause and resume. Also, these samples includes various finetuning techniques like Supervised (SFT), Reinforcement (RFT) and Direct performance optimization (DPO). * In all most samples, credential, project client, and openai client are combined into one context manager. * Remove `await` while calling `get_openai_client()` for samples using asynchronous clients. diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 1f3d94f07d0b..42d2eea2cb01 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -5,6 +5,7 @@ resources in your Microsoft Foundry Project. Use it to: * **Create and run Agents** using methods on methods on the `.agents` client property. * **Enhance Agents with specialized tools**: + * Agent Memory Search * Agent-to-Agent (A2A) * Azure AI Search * Bing Custom Search @@ -364,6 +365,29 @@ These tools work immediately without requiring external connections. See the full sample code in [sample_agent_function_tool.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py). +* **Memory Search Tool** + + The Memory Store Tool adds Memory to an Agent, allowing the Agent's AI model to search for past information related to the current user prompt. + + + ```python + # Set scope to associate the memories with + # You can also use "{{$userId}}" to take the oid of the request authentication header + scope = "user_123" + + tool = MemorySearchTool( + memory_store_name=memory_store.name, + scope=scope, + update_delay=1, # Wait 1 second of inactivity before updating memories + # In a real application, set this to a higher value like 300 (5 minutes, default) + ) + ``` + + + See the full [sample_agent_memory_search.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py) showing how to create an Agent with a memory store, and use it in multiple conversations. + + See also samples in the folder [samples\memories](https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/ai/azure-ai-projects/samples/memories) showing how to manage memory stores. + #### Connection-Based Tools These tools require configuring connections in your AI Foundry project and use `project_connection_id`. diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index d906e7dfc4cd..c0990aa1e261 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -2482,7 +2482,7 @@ async def search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -2572,7 +2572,7 @@ async def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index 1aef0cc952c0..6423852990a5 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -11,11 +11,13 @@ from ._patch_datasets_async import DatasetsOperations from ._patch_telemetry_async import TelemetryOperations from ._patch_connections_async import ConnectionsOperations +from ._patch_memories_async import MemoryStoresOperations __all__: List[str] = [ "TelemetryOperations", "DatasetsOperations", "ConnectionsOperations", + "MemoryStoresOperations", ] # Add all objects you want publicly available to users at this package level diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py new file mode 100644 index 000000000000..61b4ac43a42f --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py @@ -0,0 +1,230 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" +from typing import Union, Optional, Any, List, overload, IO, cast +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.polling import AsyncNoPolling +from azure.core.utils import case_insensitive_dict +from ... import models as _models +from ...models import ( + MemoryStoreOperationUsage, + MemoryStoreOperationUsageInputTokensDetails, + MemoryStoreOperationUsageOutputTokensDetails, + MemoryStoreUpdateCompletedResult, + AsyncUpdateMemoriesLROPoller, + AsyncUpdateMemoriesLROPollingMethod, +) +from ._operations import JSON, _Unset, ClsType, MemoryStoresOperations as GenerateMemoryStoresOperations +from ..._validation import api_version_validation +from ..._utils.model_base import _deserialize + + +class MemoryStoresOperations(GenerateMemoryStoresOperations): + + @overload + async def begin_update_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[List[_models.ItemParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword items: Conversation items from which to extract memories. Default value is None. + :paramtype items: list[~azure.ai.projects.models.ItemParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_update_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_update_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + method_added_on="2025-11-15-preview", + params_added_on={"2025-11-15-preview": ["api_version", "name", "content_type", "accept"]}, + api_versions_list=["2025-11-15-preview"], + ) + async def begin_update_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[List[_models.ItemParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: Conversation items from which to extract memories. Default value is None. + :paramtype items: list[~azure.ai.projects.models.ItemParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of AsyncLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncUpdateMemoriesLROPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._update_memories_initial( + name=name, + body=body, + scope=scope, + items=items, + previous_update_id=previous_update_id, + update_delay=update_delay, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs, + ) + await raw_result.http_response.read() # type: ignore + + raw_result.http_response.status_code = 202 # type: ignore + raw_result.http_response.headers["Operation-Location"] = ( # type: ignore + f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=2025-11-15-preview" # type: ignore + ) + + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) + if deserialized is None: + usage = MemoryStoreOperationUsage( + embedding_tokens=0, + input_tokens=0, + input_tokens_details=MemoryStoreOperationUsageInputTokensDetails(cached_tokens=0), + output_tokens=0, + output_tokens_details=MemoryStoreOperationUsageOutputTokensDetails(reasoning_tokens=0), + total_tokens=0, + ) + deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncUpdateMemoriesLROPollingMethod = AsyncUpdateMemoriesLROPollingMethod( + lro_delay, path_format_arguments=path_format_arguments, **kwargs + ) + elif polling is False: + polling_method = cast(AsyncUpdateMemoriesLROPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncUpdateMemoriesLROPoller.from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncUpdateMemoriesLROPoller( + self._client, + raw_result, # type: ignore[possibly-undefined] + get_long_running_output, + polling_method, # pylint: disable=possibly-used-before-assignment + ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index cbb449e5571e..fc88ae38be4a 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -6,8 +6,16 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import List, Dict +from typing import List, Dict, Optional, Any, Tuple +from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod +from azure.core.polling.base_polling import ( + LROBasePolling, + OperationFailed, + _raise_if_bad_http_status_and_method, +) +from azure.core.polling.async_base_polling import AsyncLROBasePolling from ._models import CustomCredential as CustomCredentialGenerated +from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult class CustomCredential(CustomCredentialGenerated): @@ -23,8 +31,261 @@ class CustomCredential(CustomCredentialGenerated): """The secret custom credential keys. Required.""" +_FINISHED = frozenset(["completed", "superseded", "failed"]) +_FAILED = frozenset(["failed"]) + + +class UpdateMemoriesLROPollingMethod(LROBasePolling): + """A custom polling method implementation for Memory Store updates.""" + + @property + def _current_body(self) -> MemoryStoreUpdateResult: + try: + return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) + except Exception: # pylint: disable=broad-exception-caught + return MemoryStoreUpdateResult() # type: ignore[call-overload] + + def finished(self) -> bool: + """Is this polling finished? + + :return: True/False for whether polling is complete. + :rtype: bool + """ + return self._finished(self.status()) + + @staticmethod + def _finished(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FINISHED + + @staticmethod + def _failed(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FAILED + + def get_continuation_token(self) -> str: + return self._current_body.update_id + + # pylint: disable=arguments-differ + def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] + try: + client = kwargs["client"] + except KeyError as exc: + raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc + + try: + deserialization_callback = kwargs["deserialization_callback"] + except KeyError as exc: + raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc + + return client, continuation_token, deserialization_callback + + def _poll(self) -> None: + """Poll status of operation so long as operation is incomplete and + we have an endpoint to query. + + :raises: OperationFailed if operation status 'Failed' or 'Canceled'. + :raises: BadStatus if response status invalid. + :raises: BadResponse if response invalid. + """ + + if not self.finished(): + self.update_status() + while not self.finished(): + self._delay() + self.update_status() + + if self._failed(self.status()): + raise OperationFailed("Operation failed or canceled") + + final_get_url = self._operation.get_final_get_url(self._pipeline_response) + if final_get_url: + self._pipeline_response = self.request_status(final_get_url) + _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) + + +class AsyncUpdateMemoriesLROPollingMethod(AsyncLROBasePolling): + """A custom polling method implementation for Memory Store updates.""" + + @property + def _current_body(self) -> MemoryStoreUpdateResult: + try: + return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) + except Exception: # pylint: disable=broad-exception-caught + return MemoryStoreUpdateResult() # type: ignore[call-overload] + + def finished(self) -> bool: + """Is this polling finished? + + :return: True/False for whether polling is complete. + :rtype: bool + """ + return self._finished(self.status()) + + @staticmethod + def _finished(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FINISHED + + @staticmethod + def _failed(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FAILED + + def get_continuation_token(self) -> str: + return self._current_body.update_id + + # pylint: disable=arguments-differ + def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] + try: + client = kwargs["client"] + except KeyError as exc: + raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc + + try: + deserialization_callback = kwargs["deserialization_callback"] + except KeyError as exc: + raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc + + return client, continuation_token, deserialization_callback + + async def _poll(self) -> None: + """Poll status of operation so long as operation is incomplete and + we have an endpoint to query. + + :raises: OperationFailed if operation status 'Failed' or 'Canceled'. + :raises: BadStatus if response status invalid. + :raises: BadResponse if response invalid. + """ + + if not self.finished(): + await self.update_status() + while not self.finished(): + await self._delay() + await self.update_status() + + if self._failed(self.status()): + raise OperationFailed("Operation failed or canceled") + + final_get_url = self._operation.get_final_get_url(self._pipeline_response) + if final_get_url: + self._pipeline_response = await self.request_status(final_get_url) + _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) + + +class UpdateMemoriesLROPoller(LROPoller[MemoryStoreUpdateCompletedResult]): + """Custom LROPoller for Memory Store update operations.""" + + _polling_method: "UpdateMemoriesLROPollingMethod" + + @property + def update_id(self) -> str: + """Returns the update ID associated with the long-running update memories operation. + + :return: Returns the update ID. + :rtype: str + """ + return self._polling_method._current_body.update_id # pylint: disable=protected-access + + @property + def superseded_by(self) -> Optional[str]: + """Returns the ID of the operation that superseded this update. + + :return: Returns the ID of the superseding operation, if it exists. + :rtype: Optional[str] + """ + return ( + self._polling_method._current_body.superseded_by # pylint: disable=protected-access + if self._polling_method._current_body # pylint: disable=protected-access + else None + ) + + @classmethod + def from_continuation_token( + cls, polling_method: PollingMethod[MemoryStoreUpdateCompletedResult], continuation_token: str, **kwargs: Any + ) -> "UpdateMemoriesLROPoller": + """Create a poller from a continuation token. + + :param polling_method: The polling strategy to adopt + :type polling_method: ~azure.core.polling.PollingMethod + :param continuation_token: An opaque continuation token + :type continuation_token: str + :return: An instance of UpdateMemoriesLROPoller + :rtype: UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. + """ + ( + client, + initial_response, + deserialization_callback, + ) = polling_method.from_continuation_token(continuation_token, **kwargs) + + return cls(client, initial_response, deserialization_callback, polling_method) + + +class AsyncUpdateMemoriesLROPoller(AsyncLROPoller[MemoryStoreUpdateCompletedResult]): + """Custom AsyncLROPoller for Memory Store update operations.""" + + _polling_method: "AsyncUpdateMemoriesLROPollingMethod" + + @property + def update_id(self) -> str: + """Returns the update ID associated with the long-running update memories operation. + + :return: Returns the update ID. + :rtype: str + """ + return self._polling_method._current_body.update_id # pylint: disable=protected-access + + @property + def superseded_by(self) -> Optional[str]: + """Returns the ID of the operation that superseded this update. + + :return: Returns the ID of the superseding operation, if it exists. + :rtype: Optional[str] + """ + return ( + self._polling_method._current_body.superseded_by # pylint: disable=protected-access + if self._polling_method._current_body # pylint: disable=protected-access + else None + ) + + @classmethod + def from_continuation_token( + cls, + polling_method: AsyncPollingMethod[MemoryStoreUpdateCompletedResult], + continuation_token: str, + **kwargs: Any + ) -> "AsyncUpdateMemoriesLROPoller": + """Create a poller from a continuation token. + + :param polling_method: The polling strategy to adopt + :type polling_method: ~azure.core.polling.PollingMethod + :param continuation_token: An opaque continuation token + :type continuation_token: str + :return: An instance of AsyncUpdateMemoriesLROPoller + :rtype: AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. + """ + ( + client, + initial_response, + deserialization_callback, + ) = polling_method.from_continuation_token(continuation_token, **kwargs) + + return cls(client, initial_response, deserialization_callback, polling_method) + + __all__: List[str] = [ "CustomCredential", + "UpdateMemoriesLROPollingMethod", + "AsyncUpdateMemoriesLROPollingMethod", + "UpdateMemoriesLROPoller", + "AsyncUpdateMemoriesLROPoller", ] # Add all objects you want publicly available to users at this package level diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index eb3e57f67656..c33de6e23003 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -4095,7 +4095,7 @@ def search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -4185,7 +4185,7 @@ def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index 413798a3ecc1..105063e2d4ee 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -11,11 +11,13 @@ from ._patch_datasets import DatasetsOperations from ._patch_telemetry import TelemetryOperations from ._patch_connections import ConnectionsOperations +from ._patch_memories import MemoryStoresOperations __all__: List[str] = [ "TelemetryOperations", "DatasetsOperations", "ConnectionsOperations", + "MemoryStoresOperations", ] # Add all objects you want publicly available to users at this package level diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py new file mode 100644 index 000000000000..e69e8d2988ec --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py @@ -0,0 +1,230 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" +from typing import Union, Optional, Any, List, overload, IO, cast +from azure.core.tracing.decorator import distributed_trace +from azure.core.polling import NoPolling +from azure.core.utils import case_insensitive_dict +from .. import models as _models +from ..models import ( + MemoryStoreOperationUsage, + MemoryStoreOperationUsageInputTokensDetails, + MemoryStoreOperationUsageOutputTokensDetails, + MemoryStoreUpdateCompletedResult, + UpdateMemoriesLROPoller, + UpdateMemoriesLROPollingMethod, +) +from ._operations import JSON, _Unset, ClsType, MemoryStoresOperations as GenerateMemoryStoresOperations +from .._validation import api_version_validation +from .._utils.model_base import _deserialize + + +class MemoryStoresOperations(GenerateMemoryStoresOperations): + + @overload + def begin_update_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[List[_models.ItemParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword items: Conversation items from which to extract memories. Default value is None. + :paramtype items: list[~azure.ai.projects.models.ItemParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_update_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_update_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + method_added_on="2025-11-15-preview", + params_added_on={"2025-11-15-preview": ["api_version", "name", "content_type", "accept"]}, + api_versions_list=["2025-11-15-preview"], + ) + def begin_update_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[List[_models.ItemParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: Conversation items from which to extract memories. Default value is None. + :paramtype items: list[~azure.ai.projects.models.ItemParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) + polling: Union[bool, UpdateMemoriesLROPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._update_memories_initial( + name=name, + body=body, + scope=scope, + items=items, + previous_update_id=previous_update_id, + update_delay=update_delay, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs, + ) + raw_result.http_response.read() # type: ignore + + raw_result.http_response.status_code = 202 # type: ignore + raw_result.http_response.headers["Operation-Location"] = ( # type: ignore + f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=2025-11-15-preview" # type: ignore + ) + + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) + if deserialized is None: + usage = MemoryStoreOperationUsage( + embedding_tokens=0, + input_tokens=0, + input_tokens_details=MemoryStoreOperationUsageInputTokensDetails(cached_tokens=0), + output_tokens=0, + output_tokens_details=MemoryStoreOperationUsageOutputTokensDetails(reasoning_tokens=0), + total_tokens=0, + ) + deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: UpdateMemoriesLROPollingMethod = UpdateMemoriesLROPollingMethod( + lro_delay, path_format_arguments=path_format_arguments, **kwargs + ) + elif polling is False: + polling_method = cast(UpdateMemoriesLROPollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return UpdateMemoriesLROPoller.from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return UpdateMemoriesLROPoller( + self._client, + raw_result, # type: ignore[possibly-undefined] + get_long_running_output, + polling_method, # pylint: disable=possibly-used-before-assignment + ) diff --git a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd index 7bd1b2e00b09..5baa4bc23f33 100644 --- a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd +++ b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd @@ -16,6 +16,10 @@ REM Rename "A2_A_PREVIEW" to "A2A_PREVIEW". Since this value is an extension to powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace 'A2_A_PREVIEW', 'A2A_PREVIEW' | Set-Content azure\ai\projects\models\_models.py" powershell -Command "(Get-Content azure\ai\projects\models\_enums.py) -replace 'A2_A_PREVIEW', 'A2A_PREVIEW' | Set-Content azure\ai\projects\models\_enums.py" +REM Rename `"items_property": items`, to `"items": items` in search_memories and begin_update_memories methods. "items" is specified in TypeSpec, but Python emitter does not allow it. +powershell -Command "(Get-Content azure\ai\projects\aio\operations\_operations.py) -replace '\"items_property\": items', '\"items\": items' | Set-Content azure\ai\projects\aio\operations\_operations.py" +powershell -Command "(Get-Content azure\ai\projects\operations\_operations.py) -replace '\"items_property\": items', '\"items\": items' | Set-Content azure\ai\projects\operations\_operations.py" + REM Add quotation marks around "str" in the expression: content: Union[str, list["_models.ItemContent"]] = rest_field( REM This fixes the serialization of this expression: item_param: ItemParam = ResponsesUserMessageItemParam(content="my text") powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace 'Union\[str, list\[\"_models\.ItemContent\"\]\] = rest_field\(', 'Union[\"str\", list[\"_models.ItemContent\"]] = rest_field(' | Set-Content azure\ai\projects\models\_models.py" diff --git a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_agent_memory.py b/sdk/ai/azure-ai-projects/samples/agents/memory/sample_agent_memory.py deleted file mode 100644 index 5d1f3018ac0b..000000000000 --- a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_agent_memory.py +++ /dev/null @@ -1,119 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ - -""" -DESCRIPTION: - This sample demonstrates how to integrate memory into a prompt agent. - -USAGE: - python sample_agent_memory.py - - Before running the sample: - - pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv - - Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). - Once you have deployed models, set the deployment name in the variables below. - - Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview - page of your Microsoft Foundry portal. - 2) AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for the agent, as found under the "Name" column in - the "Models + endpoints" tab in your Microsoft Foundry project. - 3) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in - the "Models + endpoints" tab in your Microsoft Foundry project. - 4) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model for memory, as found under the - "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. -""" - -# import os -# from dotenv import load_dotenv -# from azure.identity import DefaultAzureCredential -# from azure.ai.projects import AIProjectClient -# from azure.ai.projects.models import ( -# MemoryStoreDefaultDefinition, -# MemoryStoreDefaultOptions, -# MemorySearchOptions, -# ResponsesUserMessageItemParam, -# MemorySearchTool, -# PromptAgentDefinition, -# ) - -# load_dotenv() - -# project_client = AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential()) - -# with project_client: - -# openai_client = project_client.get_openai_client() - -# # Create a memory store -# definition = MemoryStoreDefaultDefinition( -# chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], -# embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], -# ) -# memory_store = project_client.memory_stores.create( -# name="my_memory_store", -# description="Example memory store for conversations", -# definition=definition, -# ) -# print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") - -# # Create a prompt agent with memory search tool -# agent = project_client.agents.create_version( -# agent_name="MyAgent", -# definition=PromptAgentDefinition( -# model=os.environ["AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME"], -# instructions="You are a helpful assistant that answers general questions", -# ), -# tools=[ -# MemorySearchTool( -# memory_store_name=memory_store.name, -# scope="{{$userId}}", -# update_delay=10, # Wait 5 seconds of inactivity before updating memories -# # In a real application, set this to a higher value like 300 (5 minutes, default) -# ) -# ], -# ) -# print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") - -# # Create a conversation with the agent with memory tool enabled -# conversation = openai_client.conversations.create() -# print(f"Created conversation (id: {conversation.id})") - -# # Create an agent response to initial user message -# response = openai_client.responses.create( -# conversation=conversation.id, -# extra_body={"agent": AgentReference(name=agent.name).as_dict()}, -# input=[ResponsesUserMessageItemParam(content="I prefer dark roast coffee")], -# ) -# print(f"Response output: {response.output_text}") - -# # After an inactivity in the conversation, memories will be extracted from the conversation and stored -# sleep(60) - -# # Create a new conversation -# new_conversation = openai_client.conversations.create() -# print(f"Created new conversation (id: {new_conversation.id})") - -# # Create an agent response with stored memories -# new_response = openai_client.responses.create( -# conversation=new_conversation.id, -# extra_body={"agent": AgentReference(name=agent.name).as_dict()}, -# input=[ResponsesUserMessageItemParam(content="Please order my usual coffee")], -# ) -# print(f"Response output: {new_response.output_text}") - -# # Clean up -# openai_client.conversations.delete(conversation.id) -# openai_client.conversations.delete(new_conversation.id) -# print("Conversations deleted") - -# project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) -# print("Agent deleted") - -# project_client.memory_stores.delete(memory_store.name) -# print("Memory store deleted") diff --git a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_advanced.py b/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_advanced.py deleted file mode 100644 index a1dc39ffc5be..000000000000 --- a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_advanced.py +++ /dev/null @@ -1,144 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ - -""" -DESCRIPTION: - This sample demonstrates how to interact with the memory store to add and retrieve memory. - -USAGE: - python sample_memory_advanced.py - - Before running the sample: - - pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv - - Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). - Once you have deployed models, set the deployment name in the variables below. - - Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview - page of your Microsoft Foundry portal. - 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in - the "Models + endpoints" tab in your Microsoft Foundry project. - 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the - "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. -""" - -# import os -# from dotenv import load_dotenv -# from azure.identity import DefaultAzureCredential -# from azure.ai.projects import AIProjectClient -# from azure.ai.projects.models import ( -# MemoryStoreDefaultDefinition, -# MemoryStoreDefaultOptions, -# MemorySearchOptions, -# ResponsesUserMessageItemParam, -# ResponsesAssistantMessageItemParam, -# MemorySearchTool, -# PromptAgentDefinition, -# ) - -# load_dotenv() - -# project_client = AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential()) - -# with project_client: - -# # Create memory store with advanced options -# options = MemoryStoreDefaultOptions( -# user_profile_enabled=True, -# user_profile_details="Preferences and interests relevant to coffee expert agent", -# chat_summary_enabled=True, -# ) -# definition = MemoryStoreDefaultDefinition( -# chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], -# embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], -# options=options, -# ) -# memory_store = project_client.memory_stores.create( -# name="my_memory_store_3", -# description="Example memory store for conversations", -# definition=definition, -# ) -# print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") - -# # Set scope to associate the memories with. -# # You can also use "{{$userId}}"" to take the oid of the request authentication header. -# scope = "user_123" - -# # Extract memories from messages and add them to the memory store -# user_message = ResponsesUserMessageItemParam( -# content="I prefer dark roast coffee and usually drink it in the morning" -# ) -# update_poller = project_client.memory_stores.begin_update_memories( -# name=memory_store.name, -# scope=scope, -# items=[user_message], # Pass conversation items that you want to add to memory -# # update_delay=300 # Keep default inactivity delay before starting update -# ) -# print(f"Scheduled memory update operation (Update ID: {update_poller.update_id}, Status: {update_poller.status()})") - -# # Extend the previous update with another update and more messages -# new_message = ResponsesUserMessageItemParam(content="I also like cappuccinos in the afternoon") -# new_update_poller = project_client.memory_stores.begin_update_memories( -# name=memory_store.name, -# scope=scope, -# items=[new_message], -# previous_update_id=update_poller.update_id, # Extend from previous update ID -# update_delay=0, # Trigger update immediately without waiting for inactivity -# ) -# print( -# f"Scheduled memory update operation (Update ID: {new_update_poller.update_id}, Status: {new_update_poller.status()})" -# ) - -# # As first update has not started yet, the new update will cancel the first update and cover both sets of messages -# print( -# f"Superseded first memory update operation (Update ID: {update_poller.update_id}, Status: {update_poller.status()})" -# ) - -# new_update_result = new_update_poller.result() -# print( -# f"Second update {new_update_poller.update_id} completed with {len(new_update_result.memory_operations)} memory operations" -# ) -# for operation in new_update_result.memory_operations: -# print( -# f" - Operation: {operation.kind}, Memory ID: {operation.memory_item.memory_id}, Content: {operation.memory_item.content}" -# ) - -# # Retrieve memories from the memory store -# query_message = ResponsesUserMessageItemParam(content="What are my morning coffee preferences?") -# search_response = project_client.memory_stores.search_memories( -# name=memory_store.name, scope=scope, items=[query_message], options=MemorySearchOptions(max_memories=5) -# ) -# print(f"Found {len(search_response.memories)} memories") -# for memory in search_response.memories: -# print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") - -# # Perform another search using the previous search as context -# agent_message = ResponsesAssistantMessageItemParam( -# content="You previously indicated a preference for dark roast coffee in the morning." -# ) -# followup_query = ResponsesUserMessageItemParam( -# content="What about afternoon?" # Follow-up assuming context from previous messages -# ) -# followup_search_response = project_client.memory_stores.search_memories( -# name=memory_store.name, -# scope=scope, -# items=[agent_message, followup_query], -# previous_search_id=search_response.search_id, -# options=MemorySearchOptions(max_memories=5), -# ) -# print(f"Found {len(followup_search_response.memories)} memories") -# for memory in followup_search_response.memories: -# print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") - -# # Delete memories for the current scope -# delete_scope_response = project_client.memory_stores.delete_scope(name=memory_store.name, scope=scope) -# print(f"Deleted memories for scope '{scope}': {delete_scope_response.deleted}") - -# # Delete memory store -# delete_response = project_client.memory_stores.delete(memory_store.name) -# print(f"Deleted: {delete_response.deleted}") diff --git a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_basic.py b/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_basic.py deleted file mode 100644 index 247ace2ae138..000000000000 --- a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_basic.py +++ /dev/null @@ -1,99 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ - -""" -DESCRIPTION: - This sample demonstrates how to interact with the memory store to add and retrieve memory. - -USAGE: - python sample_memory_basic.py - - Before running the sample: - - pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv - - Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). - Once you have deployed models, set the deployment name in the variables below. - - Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview - page of your Microsoft Foundry portal. - 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in - the "Models + endpoints" tab in your Microsoft Foundry project. - 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the - "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. -""" - -# import os -# from dotenv import load_dotenv -# from azure.identity import DefaultAzureCredential -# from azure.ai.projects import AIProjectClient -# from azure.ai.projects.models import ( -# MemoryStoreDefaultDefinition, -# MemoryStoreDefaultOptions, -# MemorySearchOptions, -# ResponsesUserMessageItemParam, -# MemorySearchTool, -# PromptAgentDefinition, -# ) - -# load_dotenv() - -# project_client = AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential()) - -# with project_client: - -# # Create a memory store -# definition = MemoryStoreDefaultDefinition( -# chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], -# embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], -# ) -# memory_store = project_client.memory_stores.create( -# name="my_memory_store", -# description="Example memory store for conversations", -# definition=definition, -# ) -# print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") - -# # Set scope to associate the memories with -# # You can also use "{{$userId}}"" to take the oid of the request authentication header -# scope = "user_123" - -# # Add memories to the memory store -# user_message = ResponsesUserMessageItemParam( -# content="I prefer dark roast coffee and usually drink it in the morning" -# ) -# update_poller = project_client.memory_stores.begin_update_memories( -# name=memory_store.name, -# scope=scope, -# items=[user_message], # Pass conversation items that you want to add to memory -# update_delay=0, # Trigger update immediately without waiting for inactivity -# ) - -# # Wait for the update operation to complete, but can also fire and forget -# update_result = update_poller.result() -# print(f"Updated with {len(update_result.memory_operations)} memory operations") -# for operation in update_result.memory_operations: -# print( -# f" - Operation: {operation.kind}, Memory ID: {operation.memory_item.memory_id}, Content: {operation.memory_item.content}" -# ) - -# # Retrieve memories from the memory store -# query_message = ResponsesUserMessageItemParam(content="What are my coffee preferences?") -# search_response = project_client.memory_stores.search_memories( -# name=memory_store.name, scope=scope, items=[query_message], options=MemorySearchOptions(max_memories=5) -# ) -# print(f"Found {len(search_response.memories)} memories") -# for memory in search_response.memories: -# print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") - -# # Delete memories for a specific scope -# delete_scope_response = project_client.memory_stores.delete_scope(name=memory_store.name, scope=scope) -# print(f"Deleted memories for scope '{scope}': {delete_scope_response.deleted}") - -# # Delete memory store -# delete_response = project_client.memory_stores.delete(memory_store.name) -# print(f"Deleted: {delete_response.deleted}") diff --git a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_crud.py b/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_crud.py deleted file mode 100644 index 9305d35ee5f5..000000000000 --- a/sdk/ai/azure-ai-projects/samples/agents/memory/sample_memory_crud.py +++ /dev/null @@ -1,65 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ - -""" -DESCRIPTION: - This sample demonstrates how to perform CRUD operations on a memory store using the Azure AI Projects SDK. - -USAGE: - python sample_memory_crud.py - - Before running the sample: - - pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv - - Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview - page of your Microsoft Foundry portal. - 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in - the "Models + endpoints" tab in your Microsoft Foundry project. - 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the - "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. -""" - -# import os -# from dotenv import load_dotenv -# from azure.identity import DefaultAzureCredential -# from azure.ai.projects import AIProjectClient -# from azure.ai.projects.models import MemoryStoreDefaultDefinition - -# load_dotenv() - -# project_client = AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential()) - -# with project_client: - -# # Create Memory Store -# definition = MemoryStoreDefaultDefinition( -# chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], -# embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], -# ) -# memory_store = project_client.memory_stores.create( -# name="my_memory_store", description="Example memory store for conversations", definition=definition -# ) -# print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") - -# # Get Memory Store -# get_store = project_client.memory_stores.get(memory_store.name) -# print(f"Retrieved: {get_store.name} ({get_store.id}): {get_store.description}") - -# # Update Memory Store -# updated_store = project_client.memory_stores.update(name=memory_store.name, description="Updated description") -# print(f"Updated: {updated_store.name} ({updated_store.id}): {updated_store.description}") - -# # List Memory Store -# memory_stores = list(project_client.memory_stores.list(limit=10)) -# print(f"Found {len(memory_stores)} memory stores") -# for store in memory_stores: -# print(f" - {store.name} ({store.id}): {store.description}") - -# # Delete Memory Store -# delete_response = project_client.memory_stores.delete(memory_store.name) -# print(f"Deleted: {delete_response.deleted}") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py new file mode 100644 index 000000000000..3e004acc6607 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py @@ -0,0 +1,144 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to integrate memory into a prompt agent, + by using the Memory Search Tool to retrieve relevant past user messages. + This sample uses the synchronous AIProjectClient and OpenAI clients. + + For memory management, see also samples in the folder "samples/memories" + folder. + +USAGE: + python sample_agent_memory_search.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv + + Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). + Once you have deployed models, set the deployment name in the variables below. + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, + as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. + 4) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model for memory, + as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +import time +from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.core.exceptions import ResourceNotFoundError +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import ( + MemoryStoreDefaultDefinition, + MemorySearchTool, + PromptAgentDefinition, + MemoryStoreDefaultOptions, +) + +load_dotenv() + +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as openai_client, +): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create a memory store + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + options=MemoryStoreDefaultOptions( + user_profile_enabled=True, chat_summary_enabled=True + ), # Note: This line will not be needed once the service is fixed to use correct defaults + ) + memory_store = project_client.memory_stores.create( + name=memory_store_name, + description="Example memory store for conversations", + definition=definition, + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + + # [START memory_search_tool_declaration] + # Set scope to associate the memories with + # You can also use "{{$userId}}" to take the oid of the request authentication header + scope = "user_123" + + tool = MemorySearchTool( + memory_store_name=memory_store.name, + scope=scope, + update_delay=1, # Wait 1 second of inactivity before updating memories + # In a real application, set this to a higher value like 300 (5 minutes, default) + ) + # [END memory_search_tool_declaration] + + # Create a prompt agent with memory search tool + agent = project_client.agents.create_version( + agent_name="MyAgent", + definition=PromptAgentDefinition( + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + instructions="You are a helpful assistant that answers general questions", + tools=[tool], + ), + ) + print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + # Create a conversation with the agent with memory tool enabled + conversation = openai_client.conversations.create() + print(f"Created conversation (id: {conversation.id})") + + # Create an agent response to initial user message + response = openai_client.responses.create( + input="I prefer dark roast coffee", + conversation=conversation.id, + extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {response.output_text}") + + # After an inactivity in the conversation, memories will be extracted from the conversation and stored + print("Waiting for memories to be stored...") + time.sleep(60) + + # Create a new conversation + new_conversation = openai_client.conversations.create() + print(f"Created new conversation (id: {new_conversation.id})") + + # Create an agent response with stored memories + new_response = openai_client.responses.create( + input="Please order my usual coffee", + conversation=new_conversation.id, + extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {new_response.output_text}") + + # Clean up + openai_client.conversations.delete(conversation.id) + openai_client.conversations.delete(new_conversation.id) + print("Conversations deleted") + + project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") + + project_client.memory_stores.delete(memory_store.name) + print("Memory store deleted") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py new file mode 100644 index 000000000000..e64961f2fb65 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py @@ -0,0 +1,149 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to integrate memory into a prompt agent, + by using the Memory Search Tool to retrieve relevant past user messages. + This sample uses the asynchronous AIProjectClient and AsyncOpenAI clients. + + For memory management, see also samples in the folder "samples/memories" + folder. + +USAGE: + python sample_agent_memory_search_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv aiohttp + + Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). + Once you have deployed models, set the deployment name in the variables below. + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, + as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. + 4) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model for memory, + as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import asyncio +import os +from dotenv import load_dotenv +from azure.identity.aio import DefaultAzureCredential +from azure.core.exceptions import ResourceNotFoundError +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import ( + MemoryStoreDefaultDefinition, + MemorySearchTool, + PromptAgentDefinition, + MemoryStoreDefaultOptions, +) + +load_dotenv() + + +async def main() -> None: + + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + + async with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as openai_client, + ): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + await project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create a memory store + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + options=MemoryStoreDefaultOptions( + user_profile_enabled=True, chat_summary_enabled=True + ), # Note: This line will not be needed once the service is fixed to use correct defaults + ) + memory_store = await project_client.memory_stores.create( + name=memory_store_name, + description="Example memory store for conversations", + definition=definition, + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + + # Set scope to associate the memories with + # You can also use "{{$userId}}" to take the oid of the request authentication header + scope = "user_123" + + # Create a prompt agent with memory search tool + agent = await project_client.agents.create_version( + agent_name="MyAgent", + definition=PromptAgentDefinition( + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + instructions="You are a helpful assistant that answers general questions", + tools=[ + MemorySearchTool( + memory_store_name=memory_store.name, + scope=scope, + update_delay=1, # Wait 1 second of inactivity before updating memories + # In a real application, set this to a higher value like 300 (5 minutes, default) + ) + ], + ), + ) + print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + # Create a conversation with the agent with memory tool enabled + conversation = await openai_client.conversations.create() + print(f"Created conversation (id: {conversation.id})") + + # Create an agent response to initial user message + response = await openai_client.responses.create( + input="I prefer dark roast coffee", + conversation=conversation.id, + extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {response.output_text}") + + # After an inactivity in the conversation, memories will be extracted from the conversation and stored + print("Waiting for memories to be stored...") + await asyncio.sleep(60) + + # Create a new conversation + new_conversation = await openai_client.conversations.create() + print(f"Created new conversation (id: {new_conversation.id})") + + # Create an agent response with stored memories + new_response = await openai_client.responses.create( + input="Please order my usual coffee", + conversation=new_conversation.id, + extra_body={"agent": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {new_response.output_text}") + + # Clean up + await openai_client.conversations.delete(conversation.id) + await openai_client.conversations.delete(new_conversation.id) + print("Conversations deleted") + + await project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") + + await project_client.memory_stores.delete(memory_store.name) + print("Memory store deleted") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py new file mode 100644 index 000000000000..37c8a434cb33 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py @@ -0,0 +1,159 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to interact with the memory store to add and retrieve memory + using the synchronous AIProjectClient. It uses some additional operations compared + to the basic memory sample. + + See also /samples/agents/tools/sample_agent_memory_search.py that shows + how to use the Memory Search Tool in a prompt agent. + +USAGE: + python sample_memory_advanced.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv + + Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). + Once you have deployed models, set the deployment name in the variables below. + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the + "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +from dotenv import load_dotenv +from azure.core.exceptions import ResourceNotFoundError +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import ( + MemoryStoreDefaultDefinition, + MemoryStoreDefaultOptions, + MemorySearchOptions, + ResponsesUserMessageItemParam, + ResponsesAssistantMessageItemParam, +) + +load_dotenv() + +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, +): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create memory store with advanced options + options = MemoryStoreDefaultOptions( + user_profile_enabled=True, + user_profile_details="Preferences and interests relevant to coffee expert agent", + chat_summary_enabled=True, + ) + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + options=options, + ) + memory_store = project_client.memory_stores.create( + name=memory_store_name, + description="Example memory store for conversations", + definition=definition, + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + + # Set scope to associate the memories with. + # You can also use "{{$userId}}" to take the oid of the request authentication header. + scope = "user_123" + + # Extract memories from messages and add them to the memory store + user_message = ResponsesUserMessageItemParam( + content="I prefer dark roast coffee and usually drink it in the morning" + ) + update_poller = project_client.memory_stores.begin_update_memories( + name=memory_store.name, + scope=scope, + items=[user_message], # Pass conversation items that you want to add to memory + # update_delay=300 # Keep default inactivity delay before starting update + ) + print(f"Scheduled memory update operation (Update ID: {update_poller.update_id}, Status: {update_poller.status()})") + + # Extend the previous update with another update and more messages + new_message = ResponsesUserMessageItemParam(content="I also like cappuccinos in the afternoon") + new_update_poller = project_client.memory_stores.begin_update_memories( + name=memory_store.name, + scope=scope, + items=[new_message], + previous_update_id=update_poller.update_id, # Extend from previous update ID + update_delay=0, # Trigger update immediately without waiting for inactivity + ) + print( + f"Scheduled memory update operation (Update ID: {new_update_poller.update_id}, Status: {new_update_poller.status()})" + ) + + # As first update has not started yet, the new update will cancel the first update and cover both sets of messages + print( + f"Superseded first memory update operation (Update ID: {update_poller.update_id}, Status: {update_poller.status()})" + ) + + new_update_result = new_update_poller.result() + print( + f"Second update {new_update_poller.update_id} completed with {len(new_update_result.memory_operations)} memory operations" + ) + for operation in new_update_result.memory_operations: + print( + f" - Operation: {operation.kind}, Memory ID: {operation.memory_item.memory_id}, Content: {operation.memory_item.content}" + ) + + # Retrieve memories from the memory store + query_message = ResponsesUserMessageItemParam(content="What are my morning coffee preferences?") + search_response = project_client.memory_stores.search_memories( + name=memory_store.name, scope=scope, items=[query_message], options=MemorySearchOptions(max_memories=5) + ) + print(f"Found {len(search_response.memories)} memories") + for memory in search_response.memories: + print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") + + # Perform another search using the previous search as context + agent_message = ResponsesAssistantMessageItemParam( + content="You previously indicated a preference for dark roast coffee in the morning." + ) + followup_query = ResponsesUserMessageItemParam( + content="What about afternoon?" # Follow-up assuming context from previous messages + ) + followup_search_response = project_client.memory_stores.search_memories( + name=memory_store.name, + scope=scope, + items=[agent_message, followup_query], + previous_search_id=search_response.search_id, + options=MemorySearchOptions(max_memories=5), + ) + print(f"Found {len(followup_search_response.memories)} memories") + for memory in followup_search_response.memories: + print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") + + # Delete memories for the current scope + project_client.memory_stores.delete_scope(name=memory_store.name, scope=scope) + print(f"Deleted memories for scope '{scope}'") + + # Delete memory store + project_client.memory_stores.delete(memory_store.name) + print(f"Deleted memory store `{memory_store.name}`") diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py new file mode 100644 index 000000000000..a23be53f6620 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py @@ -0,0 +1,169 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to interact with the memory store to add and retrieve memory + using the asynchronous AIProjectClient. It uses some additional operations compared + to the basic memory sample. + + See also /samples/agents/tools/sample_agent_memory_search_async.py that shows + how to use the Memory Search Tool in a prompt agent. + +USAGE: + python sample_memory_advanced_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv aiohttp + + Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). + Once you have deployed models, set the deployment name in the variables below. + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the + "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import asyncio +import os +from dotenv import load_dotenv +from azure.core.exceptions import ResourceNotFoundError +from azure.identity.aio import DefaultAzureCredential +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import ( + MemoryStoreDefaultDefinition, + MemoryStoreDefaultOptions, + MemorySearchOptions, + ResponsesUserMessageItemParam, + ResponsesAssistantMessageItemParam, +) + +load_dotenv() + + +async def main() -> None: + + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + + async with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + ): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + await project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create memory store with advanced options + options = MemoryStoreDefaultOptions( + user_profile_enabled=True, + user_profile_details="Preferences and interests relevant to coffee expert agent", + chat_summary_enabled=True, + ) + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + options=options, + ) + memory_store = await project_client.memory_stores.create( + name=memory_store_name, + description="Example memory store for conversations", + definition=definition, + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + + # Set scope to associate the memories with. + # You can also use "{{$userId}}" to take the oid of the request authentication header. + scope = "user_123" + + # Extract memories from messages and add them to the memory store + user_message = ResponsesUserMessageItemParam( + content="I prefer dark roast coffee and usually drink it in the morning" + ) + update_poller = await project_client.memory_stores.begin_update_memories( + name=memory_store.name, + scope=scope, + items=[user_message], # Pass conversation items that you want to add to memory + # update_delay=300 # Keep default inactivity delay before starting update + ) + print( + f"Scheduled memory update operation (Update ID: {update_poller.update_id}, Status: {update_poller.status()})" + ) + + # Extend the previous update with another update and more messages + new_message = ResponsesUserMessageItemParam(content="I also like cappuccinos in the afternoon") + new_update_poller = await project_client.memory_stores.begin_update_memories( + name=memory_store.name, + scope=scope, + items=[new_message], + previous_update_id=update_poller.update_id, # Extend from previous update ID + update_delay=0, # Trigger update immediately without waiting for inactivity + ) + print( + f"Scheduled memory update operation (Update ID: {new_update_poller.update_id}, Status: {new_update_poller.status()})" + ) + + # As first update has not started yet, the new update will cancel the first update and cover both sets of messages + print( + f"Superseded first memory update operation (Update ID: {update_poller.update_id}, Status: {update_poller.status()})" + ) + + new_update_result = await new_update_poller.result() + print( + f"Second update {new_update_poller.update_id} completed with {len(new_update_result.memory_operations)} memory operations" + ) + for operation in new_update_result.memory_operations: + print( + f" - Operation: {operation.kind}, Memory ID: {operation.memory_item.memory_id}, Content: {operation.memory_item.content}" + ) + + # Retrieve memories from the memory store + query_message = ResponsesUserMessageItemParam(content="What are my morning coffee preferences?") + search_response = await project_client.memory_stores.search_memories( + name=memory_store.name, scope=scope, items=[query_message], options=MemorySearchOptions(max_memories=5) + ) + print(f"Found {len(search_response.memories)} memories") + for memory in search_response.memories: + print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") + + # Perform another search using the previous search as context + agent_message = ResponsesAssistantMessageItemParam( + content="You previously indicated a preference for dark roast coffee in the morning." + ) + followup_query = ResponsesUserMessageItemParam( + content="What about afternoon?" # Follow-up assuming context from previous messages + ) + followup_search_response = await project_client.memory_stores.search_memories( + name=memory_store.name, + scope=scope, + items=[agent_message, followup_query], + previous_search_id=search_response.search_id, + options=MemorySearchOptions(max_memories=5), + ) + print(f"Found {len(followup_search_response.memories)} memories") + for memory in followup_search_response.memories: + print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") + + # Delete memories for the current scope + await project_client.memory_stores.delete_scope(name=memory_store.name, scope=scope) + print(f"Deleted memories for scope '{scope}'") + + # Delete memory store + await project_client.memory_stores.delete(memory_store.name) + print(f"Deleted memory store `{memory_store.name}`") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py new file mode 100644 index 000000000000..0e492e1ceeec --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py @@ -0,0 +1,118 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to interact with the memory store to add and retrieve memory. + + See also /samples/agents/tools/sample_agent_memory_search.py that shows + how to use the Memory Search Tool in a prompt agent. + +USAGE: + python sample_memory_basic.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv + + Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). + Once you have deployed models, set the deployment name in the variables below. + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the + "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +from dotenv import load_dotenv +from azure.core.exceptions import ResourceNotFoundError +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import ( + MemoryStoreDefaultDefinition, + MemoryStoreDefaultOptions, + MemorySearchOptions, + ResponsesUserMessageItemParam, +) + +load_dotenv() + +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, +): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create a memory store + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + options=MemoryStoreDefaultOptions( + user_profile_enabled=True, chat_summary_enabled=True + ), # Note: This line will not be needed once the service is fixed to use correct defaults + ) + memory_store = project_client.memory_stores.create( + name=memory_store_name, + description="Example memory store for conversations", + definition=definition, + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + if isinstance(memory_store.definition, MemoryStoreDefaultDefinition): + print(f" - Chat model: {memory_store.definition.chat_model}") + print(f" - Embedding model: {memory_store.definition.embedding_model}") + + # Set scope to associate the memories with + # You can also use "{{$userId}}" to take the oid of the request authentication header + scope = "user_123" + + # Add memories to the memory store + user_message = ResponsesUserMessageItemParam( + content="I prefer dark roast coffee and usually drink it in the morning" + ) + update_poller = project_client.memory_stores.begin_update_memories( + name=memory_store.name, + scope=scope, + items=[user_message], # Pass conversation items that you want to add to memory + update_delay=0, # Trigger update immediately without waiting for inactivity + ) + + # Wait for the update operation to complete, but can also fire and forget + update_result = update_poller.result() + print(f"Updated with {len(update_result.memory_operations)} memory operations") + for operation in update_result.memory_operations: + print( + f" - Operation: {operation.kind}, Memory ID: {operation.memory_item.memory_id}, Content: {operation.memory_item.content}" + ) + + # Retrieve memories from the memory store + query_message = ResponsesUserMessageItemParam(content="What are my coffee preferences?") + search_response = project_client.memory_stores.search_memories( + name=memory_store.name, scope=scope, items=[query_message], options=MemorySearchOptions(max_memories=5) + ) + print(f"Found {len(search_response.memories)} memories") + for memory in search_response.memories: + print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") + + # Delete memories for a specific scope + project_client.memory_stores.delete_scope(name=memory_store.name, scope=scope) + print(f"Deleted memories for scope '{scope}'") + + # Delete memory store + project_client.memory_stores.delete(memory_store.name) + print(f"Deleted memory store `{memory_store.name}`") diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py new file mode 100644 index 000000000000..36626ab838b7 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py @@ -0,0 +1,127 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to interact with the memory store to add and retrieve memory + using the asynchronous AIProjectClient. + + See also /samples/agents/tools/sample_agent_memory_search_async.py that shows + how to use the Memory Search Tool in a prompt agent. + +USAGE: + python sample_memory_basic_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv aiohttp + + Deploy a chat model (e.g. gpt-4.1) and an embedding model (e.g. text-embedding-3-small). + Once you have deployed models, set the deployment name in the variables below. + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the + "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import asyncio +import os +from dotenv import load_dotenv +from azure.core.exceptions import ResourceNotFoundError +from azure.identity.aio import DefaultAzureCredential +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import ( + MemoryStoreDefaultDefinition, + MemoryStoreDefaultOptions, + MemorySearchOptions, + ResponsesUserMessageItemParam, +) + +load_dotenv() + + +async def main() -> None: + + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + + async with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + ): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + await project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create a memory store + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + options=MemoryStoreDefaultOptions( + user_profile_enabled=True, chat_summary_enabled=True + ), # Note: This line will not be needed once the service is fixed to use correct defaults + ) + memory_store = await project_client.memory_stores.create( + name=memory_store_name, + description="Example memory store for conversations", + definition=definition, + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + if isinstance(memory_store.definition, MemoryStoreDefaultDefinition): + print(f" - Chat model: {memory_store.definition.chat_model}") + print(f" - Embedding model: {memory_store.definition.embedding_model}") + + # Set scope to associate the memories with + # You can also use "{{$userId}}" to take the oid of the request authentication header + scope = "user_123" + + # Add memories to the memory store + user_message = ResponsesUserMessageItemParam( + content="I prefer dark roast coffee and usually drink it in the morning" + ) + update_poller = await project_client.memory_stores.begin_update_memories( + name=memory_store.name, + scope=scope, + items=[user_message], # Pass conversation items that you want to add to memory + update_delay=0, # Trigger update immediately without waiting for inactivity + ) + + # Wait for the update operation to complete, but can also fire and forget + update_result = await update_poller.result() + print(f"Updated with {len(update_result.memory_operations)} memory operations") + for operation in update_result.memory_operations: + print( + f" - Operation: {operation.kind}, Memory ID: {operation.memory_item.memory_id}, Content: {operation.memory_item.content}" + ) + + # Retrieve memories from the memory store + query_message = ResponsesUserMessageItemParam(content="What are my coffee preferences?") + search_response = await project_client.memory_stores.search_memories( + name=memory_store.name, scope=scope, items=[query_message], options=MemorySearchOptions(max_memories=5) + ) + print(f"Found {len(search_response.memories)} memories") + for memory in search_response.memories: + print(f" - Memory ID: {memory.memory_item.memory_id}, Content: {memory.memory_item.content}") + + # Delete memories for a specific scope + await project_client.memory_stores.delete_scope(name=memory_store.name, scope=scope) + print(f"Deleted memories for scope '{scope}'") + + # Delete memory store + await project_client.memory_stores.delete(memory_store.name) + print(f"Deleted memory store `{memory_store.name}`") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py new file mode 100644 index 000000000000..b505f2b62263 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py @@ -0,0 +1,81 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to perform CRUD operations on a memory store + using the synchronous AIProjectClient. + + See also /samples/agents/tools/sample_agent_memory_search.py that shows + how to use the Memory Search Tool in a prompt agent. + +USAGE: + python sample_memory_crud.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the + "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +from dotenv import load_dotenv +from azure.core.exceptions import ResourceNotFoundError +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import MemoryStoreDefaultDefinition + +load_dotenv() + +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, +): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create Memory Store + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + ) + memory_store = project_client.memory_stores.create( + name=memory_store_name, description="Example memory store for conversations", definition=definition + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + + # Get Memory Store + get_store = project_client.memory_stores.get(memory_store.name) + print(f"Retrieved: {get_store.name} ({get_store.id}): {get_store.description}") + + # Update Memory Store + updated_store = project_client.memory_stores.update(name=memory_store.name, description="Updated description") + print(f"Updated: {updated_store.name} ({updated_store.id}): {updated_store.description}") + + # List Memory Store + memory_stores = list(project_client.memory_stores.list(limit=10)) + print(f"Found {len(memory_stores)} memory stores") + for store in memory_stores: + print(f" - {store.name} ({store.id}): {store.description}") + + # Delete Memory Store + delete_response = project_client.memory_stores.delete(memory_store.name) + print(f"Deleted: {delete_response.deleted}") diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py new file mode 100644 index 000000000000..b12d28ebffe9 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py @@ -0,0 +1,93 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to perform CRUD operations on a memory store + using the asynchronous AIProjectClient. + + See also /samples/agents/tools/sample_agent_memory_search_async.py that shows + how to use the Memory Search Tool in a prompt agent. + +USAGE: + python sample_memory_crud_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b1" azure-identity openai python-dotenv aiohttp + + Set these environment variables with your own values: + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. + 3) AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME - The deployment name of the embedding model, as found under the + "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import asyncio +import os +from dotenv import load_dotenv +from azure.core.exceptions import ResourceNotFoundError +from azure.identity.aio import DefaultAzureCredential +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import MemoryStoreDefaultDefinition + +load_dotenv() + + +async def main() -> None: + + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + + async with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + ): + + # Delete memory store, if it already exists + memory_store_name = "my_memory_store" + try: + await project_client.memory_stores.delete(memory_store_name) + print(f"Memory store `{memory_store_name}` deleted") + except ResourceNotFoundError: + pass + + # Create Memory Store + definition = MemoryStoreDefaultDefinition( + chat_model=os.environ["AZURE_AI_CHAT_MODEL_DEPLOYMENT_NAME"], + embedding_model=os.environ["AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME"], + ) + memory_store = await project_client.memory_stores.create( + name=memory_store_name, description="Example memory store for conversations", definition=definition + ) + print(f"Created memory store: {memory_store.name} ({memory_store.id}): {memory_store.description}") + + # Get Memory Store + get_store = await project_client.memory_stores.get(memory_store.name) + print(f"Retrieved: {get_store.name} ({get_store.id}): {get_store.description}") + + # Update Memory Store + updated_store = await project_client.memory_stores.update( + name=memory_store.name, description="Updated description" + ) + print(f"Updated: {updated_store.name} ({updated_store.id}): {updated_store.description}") + + # List Memory Store + memory_stores = [] + async for store in project_client.memory_stores.list(limit=10): + memory_stores.append(store) + print(f"Found {len(memory_stores)} memory stores") + for store in memory_stores: + print(f" - {store.name} ({store.id}): {store.description}") + + # Delete Memory Store + delete_response = await project_client.memory_stores.delete(memory_store.name) + print(f"Deleted: {delete_response.deleted}") + + +if __name__ == "__main__": + asyncio.run(main())