Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ab77791
Rename `items_property` to `items_property` in emitted code
dargilco Nov 13, 2025
0422663
Patch model and sync operations
dargilco Nov 13, 2025
f51db46
Fix sync operations patch
dargilco Nov 13, 2025
9d5efa6
Add async operations and classes
dargilco Nov 13, 2025
119b0c9
Rename file
dargilco Nov 13, 2025
302aea0
Fix missing import of Tuple
dargilco Nov 13, 2025
95d72ea
Merge branch 'main' into dargilco/memory-store
dargilco Nov 13, 2025
e430b88
Add env variables for Memory Store samples
dargilco Nov 13, 2025
498734b
Merge branch 'dargilco/memory-store' of https://github.com/Azure/azur…
dargilco Nov 13, 2025
32f2f01
Updates to sample_agent_memory.py
dargilco Nov 13, 2025
5d5d925
Move/rename samples
dargilco Nov 13, 2025
b4eb822
Fix import in async memory operations. Add async MemorySearchTool sample
dargilco Nov 13, 2025
04affee
Merge branch 'main' into dargilco/memory-store
dargilco Nov 13, 2025
3426033
Update changelog and package readme
dargilco Nov 13, 2025
1300023
Fix MyPy errors
dargilco Nov 13, 2025
3805ebb
Fix quality gates
dargilco Nov 13, 2025
4e2235e
Minor updates to CRUD sample. Add async CRUD sample
dargilco Nov 13, 2025
deb35ee
Add default options to MemoryStoreDefaultDefinition to workaround ser…
dargilco Nov 14, 2025
97a9d20
Sample updates
dargilco Nov 14, 2025
e4d1a53
Fix bug in sync patched code. Add async basic sample
dargilco Nov 14, 2025
430dd3c
Run `black --config ../../../eng/black-pyproject.toml .`
dargilco Nov 14, 2025
9476ba4
Add advanced aasync sample by CoPilot. Have not run it yet
dargilco Nov 14, 2025
416fc72
Upate README.md
dargilco Nov 14, 2025
b8b59b5
Fix code snippet in README.md
dargilco Nov 14, 2025
58c30a8
Fix typo in README.md
dargilco Nov 14, 2025
9d80501
Address Copilot code review comments
dargilco Nov 14, 2025
3f3a529
Merge branch 'main' into dargilco/memory-store
dargilco Nov 14, 2025
d739ee5
Add failed to finished state.
bojunehsu Nov 14, 2025
42c0fcc
Merge branch 'main' into dargilco/memory-store
dargilco Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sdk/ai/azure-ai-projects/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions sdk/ai/azure-ai-projects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions sdk/ai/azure-ai-projects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

<!-- SNIPPET:sample_agent_memory_search.memory_search_tool_declaration -->
```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)
)
```
<!-- END SNIPPET -->

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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
Loading