Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def __init__(
agents_client: AgentsClient | None = None,
agent_id: str | None = None,
agent_name: str | None = None,
agent_description: str | None = None,
thread_id: str | None = None,
project_endpoint: str | None = None,
model_deployment_name: str | None = None,
Expand All @@ -135,6 +136,7 @@ def __init__(
a new agent will be created (and deleted after the request). If neither agents_client
nor agent_id is provided, both will be created and managed automatically.
agent_name: The name to use when creating new agents.
agent_description: The description to use when creating new agents.
thread_id: Default thread ID to use for conversations. Can be overridden by
conversation_id property when making a request.
project_endpoint: The Azure AI Project endpoint URL.
Expand Down Expand Up @@ -215,6 +217,7 @@ def __init__(
self.credential = async_credential
self.agent_id = agent_id
self.agent_name = agent_name
self.agent_description = agent_description
self.model_id = azure_ai_settings.model_deployment_name
self.thread_id = thread_id
self.should_cleanup_agent = should_cleanup_agent # Track whether we should delete the agent
Expand Down Expand Up @@ -311,6 +314,7 @@ async def _get_agent_id_or_create(self, run_options: dict[str, Any] | None = Non
args: dict[str, Any] = {
"model": run_options["model"],
"name": agent_name,
"description": self.agent_description,
}
if "tools" in run_options:
args["tools"] = run_options["tools"]
Expand Down Expand Up @@ -1038,16 +1042,19 @@ def _convert_required_action_to_tool_output(

return run_id, tool_outputs, tool_approvals

def _update_agent_name(self, agent_name: str | None) -> None:
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None) -> None:
"""Update the agent name in the chat client.

Args:
agent_name: The new name for the agent.
description: The new description for the agent.
"""
# This is a no-op in the base class, but can be overridden by subclasses
# to update the agent name in the client.
if agent_name and not self.agent_name:
self.agent_name = agent_name
if description and not self.agent_description:
self.agent_description = description

def service_url(self) -> str:
"""Get the service URL for the chat client.
Expand Down
12 changes: 10 additions & 2 deletions python/packages/azure-ai/agent_framework_azure_ai/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def __init__(
project_client: AIProjectClient | None = None,
agent_name: str | None = None,
agent_version: str | None = None,
agent_description: str | None = None,
conversation_id: str | None = None,
project_endpoint: str | None = None,
model_deployment_name: str | None = None,
Expand All @@ -77,6 +78,7 @@ def __init__(
project_client: An existing AIProjectClient to use. If not provided, one will be created.
agent_name: The name to use when creating new agents or using existing agents.
agent_version: The version of the agent to use.
agent_description: The description to use when creating new agents.
conversation_id: Default conversation ID to use for conversations. Can be overridden by
conversation_id property when making a request.
project_endpoint: The Azure AI Project endpoint URL.
Expand Down Expand Up @@ -150,6 +152,7 @@ def __init__(
# Initialize instance variables
self.agent_name = agent_name
self.agent_version = agent_version
self.agent_description = agent_description
self.use_latest_version = use_latest_version
self.project_client = project_client
self.credential = async_credential
Expand Down Expand Up @@ -280,7 +283,9 @@ async def _get_agent_reference_or_create(
args["instructions"] = "".join(combined_instructions)

created_agent = await self.project_client.agents.create_version(
agent_name=self.agent_name, definition=PromptAgentDefinition(**args)
agent_name=self.agent_name,
definition=PromptAgentDefinition(**args),
description=self.agent_description,
)

self.agent_version = created_agent.version
Expand Down Expand Up @@ -352,16 +357,19 @@ async def initialize_client(self) -> None:
"""Initialize OpenAI client."""
self.client = self.project_client.get_openai_client() # type: ignore

def _update_agent_name(self, agent_name: str | None) -> None:
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None = None) -> None:
"""Update the agent name in the chat client.

Args:
agent_name: The new name for the agent.
description: The new description for the agent.
"""
# This is a no-op in the base class, but can be overridden by subclasses
# to update the agent name in the client.
if agent_name and not self.agent_name:
self.agent_name = agent_name
if description and not self.agent_description:
self.agent_description = description

def get_mcp_tool(self, tool: HostedMCPTool) -> Any:
"""Get MCP tool from HostedMCPTool."""
Expand Down
28 changes: 19 additions & 9 deletions python/packages/azure-ai/tests/test_azure_ai_agent_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def create_test_azure_ai_chat_client(
client.credential = None
client.agent_id = agent_id
client.agent_name = agent_name
client.agent_description = None
client.model_id = azure_ai_settings.model_deployment_name
client.thread_id = thread_id
client.should_cleanup_agent = should_cleanup_agent
Expand Down Expand Up @@ -441,34 +442,43 @@ async def test_azure_ai_chat_client_close_client_when_should_close_false(mock_ag
mock_agents_client.close.assert_not_called()


def test_azure_ai_chat_client_update_agent_name_when_current_is_none(mock_agents_client: MagicMock) -> None:
"""Test _update_agent_name updates name when current agent_name is None."""
def test_azure_ai_chat_client_update_agent_name_and_description_when_current_is_none(
mock_agents_client: MagicMock,
) -> None:
"""Test _update_agent_name_and_description updates name when current agent_name is None."""
chat_client = create_test_azure_ai_chat_client(mock_agents_client)
chat_client.agent_name = None # type: ignore

chat_client._update_agent_name("NewAgentName") # type: ignore
chat_client._update_agent_name_and_description("NewAgentName", "description") # type: ignore

assert chat_client.agent_name == "NewAgentName"
assert chat_client.agent_description == "description"


def test_azure_ai_chat_client_update_agent_name_when_current_exists(mock_agents_client: MagicMock) -> None:
"""Test _update_agent_name does not update when current agent_name exists."""
def test_azure_ai_chat_client_update_agent_name_and_description_when_current_exists(
mock_agents_client: MagicMock,
) -> None:
"""Test _update_agent_name_and_description does not update when current agent_name exists."""
chat_client = create_test_azure_ai_chat_client(mock_agents_client)
chat_client.agent_name = "ExistingName" # type: ignore
chat_client.agent_description = "ExistingDescription" # type: ignore

chat_client._update_agent_name("NewAgentName") # type: ignore
chat_client._update_agent_name_and_description("NewAgentName", "description") # type: ignore

assert chat_client.agent_name == "ExistingName"
assert chat_client.agent_description == "ExistingDescription"


def test_azure_ai_chat_client_update_agent_name_with_none_input(mock_agents_client: MagicMock) -> None:
"""Test _update_agent_name with None input."""
def test_azure_ai_chat_client_update_agent_name_and_description_with_none_input(mock_agents_client: MagicMock) -> None:
"""Test _update_agent_name_and_description with None input."""
chat_client = create_test_azure_ai_chat_client(mock_agents_client)
chat_client.agent_name = None # type: ignore
chat_client.agent_description = None # type: ignore

chat_client._update_agent_name(None) # type: ignore
chat_client._update_agent_name_and_description(None, None) # type: ignore

assert chat_client.agent_name is None
assert chat_client.agent_description is None


async def test_azure_ai_chat_client_create_run_options_with_messages(mock_agents_client: MagicMock) -> None:
Expand Down
13 changes: 7 additions & 6 deletions python/packages/azure-ai/tests/test_azure_ai_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def create_test_azure_ai_client(
client.credential = None
client.agent_name = agent_name
client.agent_version = agent_version
client.agent_description = None
client.use_latest_version = use_latest_version
client.model_id = azure_ai_settings.model_deployment_name
client.conversation_id = conversation_id
Expand Down Expand Up @@ -397,24 +398,24 @@ async def test_azure_ai_client_initialize_client(mock_project_client: MagicMock)
mock_project_client.get_openai_client.assert_called_once()


def test_azure_ai_client_update_agent_name(mock_project_client: MagicMock) -> None:
"""Test _update_agent_name method."""
def test_azure_ai_client_update_agent_name_and_description(mock_project_client: MagicMock) -> None:
"""Test _update_agent_name_and_description method."""
client = create_test_azure_ai_client(mock_project_client)

# Test updating agent name when current is None
with patch.object(client, "_update_agent_name") as mock_update:
with patch.object(client, "_update_agent_name_and_description") as mock_update:
mock_update.return_value = None
client._update_agent_name("new-agent") # type: ignore
client._update_agent_name_and_description("new-agent") # type: ignore
mock_update.assert_called_once_with("new-agent")

# Test behavior when agent name is updated
assert client.agent_name is None # Should remain None since we didn't actually update
client.agent_name = "test-agent" # Manually set for the test

# Test with None input
with patch.object(client, "_update_agent_name") as mock_update:
with patch.object(client, "_update_agent_name_and_description") as mock_update:
mock_update.return_value = None
client._update_agent_name(None) # type: ignore
client._update_agent_name_and_description(None) # type: ignore
mock_update.assert_called_once_with(None)


Expand Down
10 changes: 6 additions & 4 deletions python/packages/core/agent_framework/_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ def __init__(
additional_properties=additional_chat_options or {}, # type: ignore
)
self._async_exit_stack = AsyncExitStack()
self._update_agent_name()
self._update_agent_name_and_description()

async def __aenter__(self) -> "Self":
"""Enter the async context manager.
Expand Down Expand Up @@ -755,15 +755,17 @@ async def __aexit__(
"""
await self._async_exit_stack.aclose()

def _update_agent_name(self) -> None:
def _update_agent_name_and_description(self) -> None:
"""Update the agent name in the chat client.

Checks if the chat client supports agent name updates. The implementation
should check if there is already an agent name defined, and if not
set it to this value.
"""
if hasattr(self.chat_client, "_update_agent_name") and callable(self.chat_client._update_agent_name): # type: ignore[reportAttributeAccessIssue, attr-defined]
self.chat_client._update_agent_name(self.name) # type: ignore[reportAttributeAccessIssue, attr-defined]
if hasattr(self.chat_client, "_update_agent_name_and_description") and callable(
self.chat_client._update_agent_name_and_description
): # type: ignore[reportAttributeAccessIssue, attr-defined]
self.chat_client._update_agent_name_and_description(self.name, self.description) # type: ignore[reportAttributeAccessIssue, attr-defined]

async def run(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(
deployment_name: str | None = None,
assistant_id: str | None = None,
assistant_name: str | None = None,
assistant_description: str | None = None,
thread_id: str | None = None,
api_key: str | None = None,
endpoint: str | None = None,
Expand All @@ -49,6 +50,7 @@ def __init__(
assistant_id: The ID of an Azure OpenAI assistant to use.
If not provided, a new assistant will be created (and deleted after the request).
assistant_name: The name to use when creating new assistants.
assistant_description: The description to use when creating new assistants.
thread_id: Default thread ID to use for conversations. Can be overridden by
conversation_id property when making a request.
If not provided, a new thread will be created (and deleted after the request).
Expand Down Expand Up @@ -155,6 +157,7 @@ def __init__(
model_id=azure_openai_settings.chat_deployment_name,
assistant_id=assistant_id,
assistant_name=assistant_name,
assistant_description=assistant_description,
thread_id=thread_id,
async_client=async_client, # type: ignore[reportArgumentType]
default_headers=default_headers,
Expand Down
6 changes: 3 additions & 3 deletions python/packages/core/agent_framework/observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,9 @@ def _get_otlp_exporters(endpoints: list[str]) -> list["LogRecordExporter | SpanE
exporters: list["LogRecordExporter | SpanExporter | MetricExporter"] = []

for endpoint in endpoints:
exporters.append(OTLPLogExporter(endpoint=endpoint))
exporters.append(OTLPSpanExporter(endpoint=endpoint))
exporters.append(OTLPMetricExporter(endpoint=endpoint))
exporters.append(OTLPLogExporter(endpoint=endpoint)) # type: ignore[arg-type]
exporters.append(OTLPSpanExporter(endpoint=endpoint)) # type: ignore[arg-type]
exporters.append(OTLPMetricExporter(endpoint=endpoint)) # type: ignore[arg-type]
return exporters


Expand Down
16 changes: 13 additions & 3 deletions python/packages/core/agent_framework/openai/_assistants_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def __init__(
model_id: str | None = None,
assistant_id: str | None = None,
assistant_name: str | None = None,
assistant_description: str | None = None,
thread_id: str | None = None,
api_key: str | Callable[[], str | Awaitable[str]] | None = None,
org_id: str | None = None,
Expand All @@ -82,6 +83,7 @@ def __init__(
assistant_id: The ID of an OpenAI assistant to use.
If not provided, a new assistant will be created (and deleted after the request).
assistant_name: The name to use when creating new assistants.
assistant_description: The description to use when creating new assistants.
thread_id: Default thread ID to use for conversations. Can be overridden by
conversation_id property when making a request.
If not provided, a new thread will be created (and deleted after the request).
Expand Down Expand Up @@ -147,6 +149,7 @@ def __init__(
)
self.assistant_id: str | None = assistant_id
self.assistant_name: str | None = assistant_name
self.assistant_description: str | None = assistant_description
self.thread_id: str | None = thread_id
self._should_delete_assistant: bool = False

Expand Down Expand Up @@ -220,7 +223,11 @@ async def _get_assistant_id_or_create(self) -> str:
raise ServiceInitializationError("Parameter 'model_id' is required for assistant creation.")

client = await self.ensure_client()
created_assistant = await client.beta.assistants.create(name=self.assistant_name, model=self.model_id)
created_assistant = await client.beta.assistants.create(
model=self.model_id,
description=self.assistant_description,
name=self.assistant_name,
)
self.assistant_id = created_assistant.id
self._should_delete_assistant = True

Expand Down Expand Up @@ -516,13 +523,16 @@ def _convert_function_results_to_tool_output(

return run_id, tool_outputs

def _update_agent_name(self, agent_name: str | None) -> None:
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None = None) -> None:
"""Update the agent name in the chat client.

Args:
agent_name: The new name for the agent.
description: The new description for the agent.
"""
# This is a no-op in the base class, but can be overridden by subclasses
# to update the agent name in the client.
if agent_name and not self.assistant_name:
object.__setattr__(self, "assistant_name", agent_name)
self.assistant_name = agent_name
if description and not self.assistant_description:
self.assistant_description = description
Original file line number Diff line number Diff line change
Expand Up @@ -872,36 +872,36 @@ def test_openai_assistants_client_convert_function_results_to_tool_output_mismat
assert tool_outputs[0].get("tool_call_id") == "call-456"


def test_openai_assistants_client_update_agent_name(mock_async_openai: MagicMock) -> None:
"""Test _update_agent_name method updates assistant_name when not already set."""
def test_openai_assistants_client_update_agent_name_and_description(mock_async_openai: MagicMock) -> None:
"""Test _update_agent_name_and_description method updates assistant_name when not already set."""
# Test updating agent name when assistant_name is None
chat_client = create_test_openai_assistants_client(mock_async_openai, assistant_name=None)

# Call the private method to update agent name
chat_client._update_agent_name("New Assistant Name") # type: ignore
chat_client._update_agent_name_and_description("New Assistant Name") # type: ignore

assert chat_client.assistant_name == "New Assistant Name"


def test_openai_assistants_client_update_agent_name_existing(mock_async_openai: MagicMock) -> None:
"""Test _update_agent_name method doesn't override existing assistant_name."""
def test_openai_assistants_client_update_agent_name_and_description_existing(mock_async_openai: MagicMock) -> None:
"""Test _update_agent_name_and_description method doesn't override existing assistant_name."""
# Test that existing assistant_name is not overridden
chat_client = create_test_openai_assistants_client(mock_async_openai, assistant_name="Existing Assistant")

# Call the private method to update agent name
chat_client._update_agent_name("New Assistant Name") # type: ignore
chat_client._update_agent_name_and_description("New Assistant Name") # type: ignore

# Should keep the existing name
assert chat_client.assistant_name == "Existing Assistant"


def test_openai_assistants_client_update_agent_name_none(mock_async_openai: MagicMock) -> None:
"""Test _update_agent_name method with None agent_name parameter."""
def test_openai_assistants_client_update_agent_name_and_description_none(mock_async_openai: MagicMock) -> None:
"""Test _update_agent_name_and_description method with None agent_name parameter."""
# Test that None agent_name doesn't change anything
chat_client = create_test_openai_assistants_client(mock_async_openai, assistant_name=None)

# Call the private method with None
chat_client._update_agent_name(None) # type: ignore
chat_client._update_agent_name_and_description(None) # type: ignore

# Should remain None
assert chat_client.assistant_name is None
Expand Down
Loading
Loading