Skip to content

Add thread message delete operation #41625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: feature/azure-ai-agents/1.1.0b3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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-agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

## 1.1.0b3 (Unreleased)

### Features Added
- Added delete operation for `ThreadMessages`.

### Sample updates
- The file search samples were updated to demonstrate retrieving text associated with citations.
- Added samples for file search citation with streaming.


## 1.1.0b2 (2025-06-09)

### Bugs Fixed
Expand Down
14 changes: 9 additions & 5 deletions sdk/ai/azure-ai-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,16 +382,20 @@ Here is an example to integrate Azure AI Search:
with AIProjectClient(
endpoint=os.environ["PROJECT_ENDPOINT"],
credential=DefaultAzureCredential(),
) as project_client:
) as project_client:
conn_id = project_client.connections.get_default(ConnectionType.AZURE_AI_SEARCH).id

print(conn_id)

# Initialize agent AI search tool and add the search index connection id
ai_search = AzureAISearchTool(
index_connection_id=conn_id, index_name="sample_index", query_type=AzureAISearchQueryType.SIMPLE, top_k=3, filter=""
index_connection_id=conn_id,
index_name="sample_index",
query_type=AzureAISearchQueryType.SIMPLE,
top_k=3,
filter="",
)

# Create agent with AI search tool and process agent run
agents_client = project_client.agents

Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-agents/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ai/azure-ai-agents",
"Tag": "python/ai/azure-ai-agents_daaac60e4f"
"Tag": "python/ai/azure-ai-agents_0057bc62e6"
}
66 changes: 66 additions & 0 deletions sdk/ai/azure-ai-agents/azure/ai/agents/_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# Code generated by Microsoft (R) Python Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------
import functools


def api_version_validation(**kwargs):
params_added_on = kwargs.pop("params_added_on", {})
method_added_on = kwargs.pop("method_added_on", "")
api_versions_list = kwargs.pop("api_versions_list", [])

def _index_with_default(value: str, default: int = -1) -> int:
"""Get the index of value in lst, or return default if not found.

:param value: The value to search for in the api_versions_list.
:type value: str
:param default: The default value to return if the value is not found.
:type default: int
:return: The index of the value in the list, or the default value if not found.
:rtype: int
"""
try:
return api_versions_list.index(value)
except ValueError:
return default

def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
# this assumes the client has an _api_version attribute
client = args[0]
client_api_version = client._config.api_version # pylint: disable=protected-access
except AttributeError:
return func(*args, **kwargs)

if _index_with_default(method_added_on) > _index_with_default(client_api_version):
raise ValueError(
f"'{func.__name__}' is not available in API version "
f"{client_api_version}. Pass service API version {method_added_on} or newer to your client."
)

unsupported = {
parameter: api_version
for api_version, parameters in params_added_on.items()
for parameter in parameters
if parameter in kwargs and _index_with_default(api_version) > _index_with_default(client_api_version)
}
if unsupported:
raise ValueError(
"".join(
[
f"'{param}' is not available in API version {client_api_version}. "
f"Use service API version {version} or newer.\n"
for param, version in unsupported.items()
]
)
)
return func(*args, **kwargs)

return wrapper

return decorator
106 changes: 84 additions & 22 deletions sdk/ai/azure-ai-agents/azure/ai/agents/aio/operations/_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,7 @@
from collections.abc import MutableMapping
from io import IOBase
import json
from typing import (
Any,
AsyncIterable,
AsyncIterator,
Callable,
Dict,
IO,
List,
Optional,
TYPE_CHECKING,
TypeVar,
Union,
overload,
)
from typing import Any, AsyncIterator, Callable, Dict, IO, List, Optional, TYPE_CHECKING, TypeVar, Union, overload
import urllib.parse

from azure.core import AsyncPipelineClient
Expand All @@ -47,6 +34,7 @@
from ..._utils.model_base import Model as _Model, SdkJSONEncoder, _deserialize, _failsafe_deserialize
from ..._utils.serialization import Deserializer, Serializer
from ..._utils.utils import ClientMixinABC, prepare_multipart_form_data
from ..._validation import api_version_validation
from ...operations._operations import (
build_agents_create_agent_request,
build_agents_create_thread_and_run_request,
Expand All @@ -60,6 +48,7 @@
build_files_list_request,
build_files_upload_file_request,
build_messages_create_request,
build_messages_delete_message_request,
build_messages_get_request,
build_messages_list_request,
build_messages_update_request,
Expand Down Expand Up @@ -284,7 +273,7 @@ def list(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.AgentThread"]:
) -> AsyncItemPaged["_models.AgentThread"]:
"""Gets a list of threads that were previously created.

:keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and
Expand Down Expand Up @@ -861,7 +850,7 @@ def list(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.ThreadMessage"]:
) -> AsyncItemPaged["_models.ThreadMessage"]:
"""Gets a list of messages that exist on a thread.

:param thread_id: Identifier of the thread. Required.
Expand Down Expand Up @@ -1164,6 +1153,79 @@ async def update(

return deserialized # type: ignore

@distributed_trace_async
@api_version_validation(
method_added_on="v1",
params_added_on={"v1": ["api_version", "thread_id", "message_id", "accept"]},
api_versions_list=["v1", "2025-05-15-preview"],
)
async def _delete_message(
self, thread_id: str, message_id: str, **kwargs: Any
) -> _models._models.MessageDeletionStatus:
"""Deletes an existing message on an existing thread.

:param thread_id: Identifier of the thread. Required.
:type thread_id: str
:param message_id: Identifier of the message. Required.
:type message_id: str
:return: MessageDeletionStatus. The MessageDeletionStatus is compatible with MutableMapping
:rtype: ~azure.ai.agents.models._models.MessageDeletionStatus
:raises ~azure.core.exceptions.HttpResponseError:
"""
error_map: MutableMapping = {
401: ClientAuthenticationError,
404: ResourceNotFoundError,
409: ResourceExistsError,
304: ResourceNotModifiedError,
}
error_map.update(kwargs.pop("error_map", {}) or {})

_headers = kwargs.pop("headers", {}) or {}
_params = kwargs.pop("params", {}) or {}

cls: ClsType[_models._models.MessageDeletionStatus] = kwargs.pop("cls", None)

_request = build_messages_delete_message_request(
thread_id=thread_id,
message_id=message_id,
api_version=self._config.api_version,
headers=_headers,
params=_params,
)
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
_request.url = self._client.format_url(_request.url, **path_format_arguments)

_stream = kwargs.pop("stream", False)
pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
_request, stream=_stream, **kwargs
)

response = pipeline_response.http_response

if response.status_code not in [200]:
if _stream:
try:
await response.read() # Load the body in memory and close the socket
except (StreamConsumedError, StreamClosedError):
pass
map_error(status_code=response.status_code, response=response, error_map=error_map)
error = _failsafe_deserialize(_models.AgentV1Error, response.json())
raise HttpResponseError(response=response, model=error)

if _stream:
deserialized = response.iter_bytes()
else:
deserialized = _deserialize(
_models._models.MessageDeletionStatus, response.json() # pylint: disable=protected-access
)

if cls:
return cls(pipeline_response, deserialized, {}) # type: ignore

return deserialized # type: ignore


class RunsOperations:
"""
Expand Down Expand Up @@ -1557,7 +1619,7 @@ def list(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.ThreadRun"]:
) -> AsyncItemPaged["_models.ThreadRun"]:
"""Gets a list of runs for a specified thread.

:param thread_id: Identifier of the thread. Required.
Expand Down Expand Up @@ -2197,7 +2259,7 @@ def list(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.RunStep"]:
) -> AsyncItemPaged["_models.RunStep"]:
"""Gets a list of run steps from a thread run.

:param thread_id: Identifier of the thread. Required.
Expand Down Expand Up @@ -2646,7 +2708,7 @@ def list(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.VectorStore"]:
) -> AsyncItemPaged["_models.VectorStore"]:
"""Returns a list of vector stores.

:keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and
Expand Down Expand Up @@ -3216,7 +3278,7 @@ def list(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.VectorStoreFile"]:
) -> AsyncItemPaged["_models.VectorStoreFile"]:
"""Returns a list of vector store files.

:param vector_store_id: Identifier of the vector store. Required.
Expand Down Expand Up @@ -3904,7 +3966,7 @@ def list_files(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.VectorStoreFile"]:
) -> AsyncItemPaged["_models.VectorStoreFile"]:
"""Returns a list of vector store files in a batch.

:param vector_store_id: Identifier of the vector store. Required.
Expand Down Expand Up @@ -4232,7 +4294,7 @@ def list_agents(
order: Optional[Union[str, _models.ListSortOrder]] = None,
before: Optional[str] = None,
**kwargs: Any
) -> AsyncIterable["_models.Agent"]:
) -> AsyncItemPaged["_models.Agent"]:
"""Gets a list of agents that were previously created.

:keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2198,7 +2198,7 @@ async def create_and_poll(

@distributed_trace_async
async def delete(self, vector_store_id: str, file_id: str, **kwargs: Any) -> None:
"""Deletes a vector store file. This removes the file‐to‐store link (does not delete the file
"""Deletes a vector store file. This removes the file-to-store link (does not delete the file
itself).

:param vector_store_id: Identifier of the vector store. Required.
Expand Down Expand Up @@ -2264,6 +2264,18 @@ async def get_last_message_text_by_role(
return text_contents[-1]
return None

@distributed_trace_async
async def delete(self, thread_id: str, message_id: str, **kwargs: Any) -> None:
"""Deletes an existing message on an existing thread.

:param thread_id: Identifier of the thread. Required.
:type thread_id: str
:param message_id: Identifier of the message. Required.
:type message_id: str
:raises ~azure.core.exceptions.HttpResponseError:
"""
await super()._delete_message(thread_id=thread_id, message_id=message_id, **kwargs)


__all__: List[str] = [
"MessagesOperations",
Expand Down
40 changes: 40 additions & 0 deletions sdk/ai/azure-ai-agents/azure/ai/agents/models/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,46 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)


class MessageDeletionStatus(_Model):
"""The status of a thread message deletion operation.

:ivar id: The ID of the resource specified for deletion. Required.
:vartype id: str
:ivar deleted: A value indicating whether deletion was successful. Required.
:vartype deleted: bool
:ivar object: The object type, which is always 'thread.message.deleted'. Required. Default
value is "thread.message.deleted".
:vartype object: str
"""

id: str = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""The ID of the resource specified for deletion. Required."""
deleted: bool = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""A value indicating whether deletion was successful. Required."""
object: Literal["thread.message.deleted"] = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""The object type, which is always 'thread.message.deleted'. Required. Default value is
\"thread.message.deleted\"."""

@overload
def __init__(
self,
*,
id: str, # pylint: disable=redefined-builtin
deleted: bool,
) -> None: ...

@overload
def __init__(self, mapping: Mapping[str, Any]) -> None:
"""
:param mapping: raw JSON to initialize the model.
:type mapping: Mapping[str, Any]
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.object: Literal["thread.message.deleted"] = "thread.message.deleted"


class MessageDelta(_Model):
"""Represents the typed 'delta' payload within a streaming message delta chunk.

Expand Down
Loading