mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Upgraded azure-ai-projects to 2.0.0b4 (#4438)
* Upgraded azure-ai-projects to 2.0.0b4 * Fixed tests
This commit is contained in:
committed by
GitHub
Unverified
parent
5ba1c6f0cc
commit
b5edb529b7
@@ -37,12 +37,13 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
ApproximateLocation,
|
||||
CodeInterpreterContainerAuto,
|
||||
CodeInterpreterTool,
|
||||
CodeInterpreterToolAuto,
|
||||
FoundryFeaturesOptInKeys,
|
||||
ImageGenTool,
|
||||
MCPTool,
|
||||
PromptAgentDefinition,
|
||||
PromptAgentDefinitionText,
|
||||
PromptAgentDefinitionTextOptions,
|
||||
RaiConfig,
|
||||
Reasoning,
|
||||
WebSearchPreviewTool,
|
||||
@@ -78,6 +79,9 @@ class AzureAIProjectAgentOptions(OpenAIResponsesOptions, total=False):
|
||||
reasoning: Reasoning # type: ignore[misc]
|
||||
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
|
||||
|
||||
foundry_features: FoundryFeaturesOptInKeys | str
|
||||
"""Optional Foundry preview feature opt-in for agent version creation."""
|
||||
|
||||
|
||||
AzureAIClientOptionsT = TypeVar(
|
||||
"AzureAIClientOptionsT",
|
||||
@@ -392,7 +396,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
# response_format is accessed from chat_options or additional_properties
|
||||
# since the base class excludes it from run_options
|
||||
if chat_options and (response_format := chat_options.get("response_format")):
|
||||
args["text"] = PromptAgentDefinitionText(format=create_text_format_config(response_format))
|
||||
args["text"] = PromptAgentDefinitionTextOptions(format=create_text_format_config(response_format))
|
||||
|
||||
# Combine instructions from messages and options
|
||||
# instructions is accessed from chat_options since the base class excludes it from run_options
|
||||
@@ -404,11 +408,15 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
if combined_instructions:
|
||||
args["instructions"] = "".join(combined_instructions)
|
||||
|
||||
created_agent = await self.project_client.agents.create_version(
|
||||
agent_name=self.agent_name,
|
||||
definition=PromptAgentDefinition(**args),
|
||||
description=self.agent_description,
|
||||
)
|
||||
create_version_kwargs: dict[str, Any] = {
|
||||
"agent_name": self.agent_name,
|
||||
"definition": PromptAgentDefinition(**args),
|
||||
"description": self.agent_description,
|
||||
}
|
||||
if foundry_features := run_options.get("foundry_features"):
|
||||
create_version_kwargs["foundry_features"] = foundry_features
|
||||
|
||||
created_agent = await self.project_client.agents.create_version(**create_version_kwargs)
|
||||
|
||||
self.agent_version = created_agent.version
|
||||
self.warn_runtime_tools_and_structure_changed = True
|
||||
@@ -500,6 +508,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
"temperature": ("temperature",),
|
||||
"top_p": ("top_p",),
|
||||
"reasoning": ("reasoning",),
|
||||
"foundry_features": ("foundry_features",),
|
||||
}
|
||||
|
||||
for run_keys in agent_level_option_to_run_keys.values():
|
||||
@@ -526,9 +535,9 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
run_options["input"] = self._transform_input_for_azure_ai(cast(list[dict[str, Any]], run_options["input"]))
|
||||
|
||||
if not self._is_application_endpoint:
|
||||
# Application-scoped response APIs do not support "agent" property.
|
||||
# Application-scoped response APIs do not support "agent_reference" property.
|
||||
agent_reference = await self._get_agent_reference_or_create(run_options, instructions, options)
|
||||
run_options["extra_body"] = {"agent": agent_reference}
|
||||
run_options["extra_body"] = {"agent_reference": agent_reference}
|
||||
|
||||
# Remove only keys that map to this client's declared options TypedDict.
|
||||
self._remove_agent_level_run_options(run_options, options)
|
||||
@@ -922,7 +931,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
if file_ids is None and isinstance(container, dict):
|
||||
file_ids = container.get("file_ids")
|
||||
resolved = resolve_file_ids(file_ids)
|
||||
tool_container = CodeInterpreterToolAuto(file_ids=resolved)
|
||||
tool_container = CodeInterpreterContainerAuto(file_ids=resolved)
|
||||
return CodeInterpreterTool(container=tool_container, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -18,7 +18,6 @@ from agent_framework._sessions import AgentSession, BaseContextProvider, Session
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework.azure._entra_id_authentication import AzureCredentialTypes
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import ItemParam, ResponsesAssistantMessageItemParam, ResponsesUserMessageItemParam
|
||||
|
||||
from ._shared import AzureAISettings
|
||||
|
||||
@@ -149,7 +148,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
# On first run, retrieve static memories (user profile memories)
|
||||
if not state.get("initialized"):
|
||||
try:
|
||||
static_search_result = await self.project_client.memory_stores.search_memories(
|
||||
static_search_result = await self.project_client.beta.memory_stores.search_memories(
|
||||
name=self.memory_store_name,
|
||||
scope=self.scope or context.session_id, # type: ignore[arg-type]
|
||||
)
|
||||
@@ -169,15 +168,15 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
if not has_input:
|
||||
return
|
||||
|
||||
# Convert input messages to ItemParam format for search
|
||||
# Convert input messages to memory search item format
|
||||
items = [
|
||||
ItemParam({"type": "text", "text": msg.text})
|
||||
{"type": "text", "text": msg.text}
|
||||
for msg in context.input_messages
|
||||
if msg and msg.text and msg.text.strip()
|
||||
]
|
||||
|
||||
try:
|
||||
search_result = await self.project_client.memory_stores.search_memories(
|
||||
search_result = await self.project_client.beta.memory_stores.search_memories(
|
||||
name=self.memory_store_name,
|
||||
scope=self.scope or context.session_id, # type: ignore[arg-type]
|
||||
items=items,
|
||||
@@ -224,24 +223,24 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
if context.response and context.response.messages:
|
||||
messages_to_store.extend(context.response.messages)
|
||||
|
||||
# Filter and convert messages to ItemParam format
|
||||
items: list[ResponsesUserMessageItemParam | ResponsesAssistantMessageItemParam] = []
|
||||
# Filter and convert messages to memory update item format
|
||||
items: list[dict[str, str]] = []
|
||||
for message in messages_to_store:
|
||||
if message.role in {"user", "assistant", "system"} and message.text and message.text.strip():
|
||||
if message.role == "user":
|
||||
items.append(ResponsesUserMessageItemParam(content=message.text))
|
||||
items.append({"role": "user", "type": "message", "content": message.text})
|
||||
elif message.role == "assistant":
|
||||
items.append(ResponsesAssistantMessageItemParam(content=message.text))
|
||||
items.append({"role": "assistant", "type": "message", "content": message.text})
|
||||
|
||||
if not items:
|
||||
return
|
||||
|
||||
try:
|
||||
# Fire and forget - don't wait for the update to complete
|
||||
update_poller = await self.project_client.memory_stores.begin_update_memories(
|
||||
update_poller = await self.project_client.beta.memory_stores.begin_update_memories(
|
||||
name=self.memory_store_name,
|
||||
scope=self.scope or context.session_id, # type: ignore[arg-type]
|
||||
items=items, # type: ignore[arg-type]
|
||||
items=items,
|
||||
previous_update_id=state.get("previous_update_id"),
|
||||
update_delay=self.update_delay,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Callable, MutableMapping, Sequence
|
||||
from collections.abc import Callable, Mapping, MutableMapping, Sequence
|
||||
from typing import Any, Generic
|
||||
|
||||
from agent_framework import (
|
||||
@@ -21,10 +21,9 @@ from agent_framework._tools import ToolTypes
|
||||
from agent_framework.azure._entra_id_authentication import AzureCredentialTypes
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
AgentReference,
|
||||
AgentVersionDetails,
|
||||
PromptAgentDefinition,
|
||||
PromptAgentDefinitionText,
|
||||
PromptAgentDefinitionTextOptions,
|
||||
)
|
||||
from azure.ai.projects.models import (
|
||||
FunctionTool as AzureFunctionTool,
|
||||
@@ -200,13 +199,14 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
response_format = opts.get("response_format")
|
||||
rai_config = opts.get("rai_config")
|
||||
reasoning = opts.get("reasoning")
|
||||
foundry_features = opts.get("foundry_features")
|
||||
|
||||
args: dict[str, Any] = {"model": resolved_model}
|
||||
|
||||
if instructions:
|
||||
args["instructions"] = instructions
|
||||
if response_format and isinstance(response_format, (type, dict)):
|
||||
args["text"] = PromptAgentDefinitionText(
|
||||
args["text"] = PromptAgentDefinitionTextOptions(
|
||||
format=create_text_format_config(response_format) # type: ignore[arg-type]
|
||||
)
|
||||
if rai_config:
|
||||
@@ -241,11 +241,15 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
if all_tools_for_azure:
|
||||
args["tools"] = to_azure_ai_tools(all_tools_for_azure)
|
||||
|
||||
created_agent = await self._project_client.agents.create_version(
|
||||
agent_name=name,
|
||||
definition=PromptAgentDefinition(**args),
|
||||
description=description,
|
||||
)
|
||||
create_version_kwargs: dict[str, Any] = {
|
||||
"agent_name": name,
|
||||
"definition": PromptAgentDefinition(**args),
|
||||
"description": description,
|
||||
}
|
||||
if foundry_features:
|
||||
create_version_kwargs["foundry_features"] = foundry_features
|
||||
|
||||
created_agent = await self._project_client.agents.create_version(**create_version_kwargs)
|
||||
|
||||
return self._to_chat_agent_from_details(
|
||||
created_agent,
|
||||
@@ -259,7 +263,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
self,
|
||||
*,
|
||||
name: str | None = None,
|
||||
reference: AgentReference | None = None,
|
||||
reference: Mapping[str, str | None] | None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
@@ -272,7 +276,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
|
||||
Args:
|
||||
name: The name of the agent to retrieve (fetches latest version).
|
||||
reference: Reference containing the agent's name and optionally a specific version.
|
||||
reference: Mapping containing the agent's ``name`` and optionally a specific ``version``.
|
||||
tools: Tools to make available to the agent. Required if the agent has function tools.
|
||||
default_options: A TypedDict containing default chat options for the agent.
|
||||
These options are applied to every run unless overridden.
|
||||
@@ -287,12 +291,15 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
"""
|
||||
existing_agent: AgentVersionDetails
|
||||
|
||||
if reference and reference.version:
|
||||
reference_name = str(reference.get("name")) if reference and reference.get("name") else None
|
||||
reference_version = str(reference.get("version")) if reference and reference.get("version") else None
|
||||
|
||||
if reference_name and reference_version:
|
||||
# Fetch specific version
|
||||
existing_agent = await self._project_client.agents.get_version(
|
||||
agent_name=reference.name, agent_version=reference.version
|
||||
agent_name=reference_name, agent_version=reference_version
|
||||
)
|
||||
elif agent_name := (reference.name if reference else name):
|
||||
elif agent_name := (reference_name if reference_name else name):
|
||||
# Fetch latest version
|
||||
details = await self._project_client.agents.get(agent_name=agent_name)
|
||||
existing_agent = details.versions.latest
|
||||
|
||||
@@ -19,9 +19,9 @@ from azure.ai.agents.models import (
|
||||
from azure.ai.projects.models import (
|
||||
CodeInterpreterTool,
|
||||
MCPTool,
|
||||
ResponseTextFormatConfigurationJsonObject,
|
||||
ResponseTextFormatConfigurationJsonSchema,
|
||||
ResponseTextFormatConfigurationText,
|
||||
TextResponseFormatConfigurationResponseFormatJsonObject,
|
||||
TextResponseFormatConfigurationResponseFormatText,
|
||||
TextResponseFormatJsonSchema,
|
||||
Tool,
|
||||
WebSearchPreviewTool,
|
||||
)
|
||||
@@ -463,9 +463,9 @@ def _prepare_mcp_tool_dict_for_azure_ai(tool_dict: dict[str, Any]) -> MCPTool:
|
||||
def create_text_format_config(
|
||||
response_format: type[BaseModel] | Mapping[str, Any],
|
||||
) -> (
|
||||
ResponseTextFormatConfigurationJsonSchema
|
||||
| ResponseTextFormatConfigurationJsonObject
|
||||
| ResponseTextFormatConfigurationText
|
||||
TextResponseFormatJsonSchema
|
||||
| TextResponseFormatConfigurationResponseFormatJsonObject
|
||||
| TextResponseFormatConfigurationResponseFormatText
|
||||
):
|
||||
"""Convert response_format into Azure text format configuration."""
|
||||
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
||||
@@ -473,7 +473,7 @@ def create_text_format_config(
|
||||
# Ensure additionalProperties is explicitly false to satisfy Azure validation
|
||||
if isinstance(schema, dict):
|
||||
schema.setdefault("additionalProperties", False)
|
||||
return ResponseTextFormatConfigurationJsonSchema(
|
||||
return TextResponseFormatJsonSchema(
|
||||
name=response_format.__name__,
|
||||
schema=schema,
|
||||
strict=True,
|
||||
@@ -494,11 +494,11 @@ def create_text_format_config(
|
||||
config_kwargs["strict"] = format_config["strict"]
|
||||
if "description" in format_config:
|
||||
config_kwargs["description"] = format_config["description"]
|
||||
return ResponseTextFormatConfigurationJsonSchema(**config_kwargs)
|
||||
return TextResponseFormatJsonSchema(**config_kwargs)
|
||||
if format_type == "json_object":
|
||||
return ResponseTextFormatConfigurationJsonObject()
|
||||
return TextResponseFormatConfigurationResponseFormatJsonObject()
|
||||
if format_type == "text":
|
||||
return ResponseTextFormatConfigurationText()
|
||||
return TextResponseFormatConfigurationResponseFormatText()
|
||||
|
||||
raise IntegrationInvalidRequestException("response_format must be a Pydantic model or mapping.")
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
ApproximateLocation,
|
||||
CodeInterpreterContainerAuto,
|
||||
CodeInterpreterTool,
|
||||
CodeInterpreterToolAuto,
|
||||
FileSearchTool,
|
||||
ImageGenTool,
|
||||
MCPTool,
|
||||
ResponseTextFormatConfigurationJsonSchema,
|
||||
TextResponseFormatJsonSchema,
|
||||
WebSearchPreviewTool,
|
||||
)
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
@@ -427,7 +427,7 @@ async def test_prepare_options_basic(mock_project_client: MagicMock) -> None:
|
||||
run_options = await client._prepare_options(messages, {})
|
||||
|
||||
assert "extra_body" in run_options
|
||||
assert run_options["extra_body"]["agent"]["name"] == "test-agent"
|
||||
assert run_options["extra_body"]["agent_reference"]["name"] == "test-agent"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -465,7 +465,7 @@ async def test_prepare_options_with_application_endpoint(
|
||||
|
||||
if expects_agent:
|
||||
assert "extra_body" in run_options
|
||||
assert run_options["extra_body"]["agent"]["name"] == "test-agent"
|
||||
assert run_options["extra_body"]["agent_reference"]["name"] == "test-agent"
|
||||
else:
|
||||
assert "extra_body" not in run_options
|
||||
|
||||
@@ -507,7 +507,7 @@ async def test_prepare_options_with_application_project_client(
|
||||
|
||||
if expects_agent:
|
||||
assert "extra_body" in run_options
|
||||
assert run_options["extra_body"]["agent"]["name"] == "test-agent"
|
||||
assert run_options["extra_body"]["agent_reference"]["name"] == "test-agent"
|
||||
else:
|
||||
assert "extra_body" not in run_options
|
||||
|
||||
@@ -979,10 +979,10 @@ async def test_agent_creation_with_response_format(
|
||||
assert hasattr(created_definition, "text")
|
||||
assert created_definition.text is not None
|
||||
|
||||
# Check that the format is a ResponseTextFormatConfigurationJsonSchema
|
||||
# Check that the format is a TextResponseFormatJsonSchema
|
||||
assert hasattr(created_definition.text, "format")
|
||||
format_config = created_definition.text.format
|
||||
assert isinstance(format_config, ResponseTextFormatConfigurationJsonSchema)
|
||||
assert isinstance(format_config, TextResponseFormatJsonSchema)
|
||||
|
||||
# Check the schema name matches the model class name
|
||||
assert format_config.name == "ResponseFormatModel"
|
||||
@@ -1040,7 +1040,7 @@ async def test_agent_creation_with_mapping_response_format(
|
||||
assert hasattr(created_definition, "text")
|
||||
assert created_definition.text is not None
|
||||
format_config = created_definition.text.format
|
||||
assert isinstance(format_config, ResponseTextFormatConfigurationJsonSchema)
|
||||
assert isinstance(format_config, TextResponseFormatJsonSchema)
|
||||
assert format_config.name == runtime_schema["title"]
|
||||
assert format_config.schema == runtime_schema
|
||||
assert format_config.strict is True
|
||||
@@ -1110,7 +1110,7 @@ async def test_prepare_options_excludes_response_format(
|
||||
assert "text_format" not in run_options
|
||||
# But extra_body should contain agent reference
|
||||
assert "extra_body" in run_options
|
||||
assert run_options["extra_body"]["agent"]["name"] == "test-agent"
|
||||
assert run_options["extra_body"]["agent_reference"]["name"] == "test-agent"
|
||||
|
||||
|
||||
async def test_prepare_options_keeps_values_for_unsupported_option_keys(
|
||||
@@ -1254,7 +1254,7 @@ def test_from_azure_ai_tools_mcp() -> None:
|
||||
|
||||
def test_from_azure_ai_tools_code_interpreter() -> None:
|
||||
"""Test from_azure_ai_tools with Code Interpreter tool."""
|
||||
ci_tool = CodeInterpreterTool(container=CodeInterpreterToolAuto(file_ids=["file-1"]))
|
||||
ci_tool = CodeInterpreterTool(container=CodeInterpreterContainerAuto(file_ids=["file-1"]))
|
||||
parsed_tools = from_azure_ai_tools([ci_tool])
|
||||
assert len(parsed_tools) == 1
|
||||
assert parsed_tools[0]["type"] == "code_interpreter"
|
||||
|
||||
@@ -17,9 +17,10 @@ from agent_framework_azure_ai._foundry_memory_provider import FoundryMemoryProvi
|
||||
def mock_project_client() -> AsyncMock:
|
||||
"""Create a mock AIProjectClient."""
|
||||
mock_client = AsyncMock()
|
||||
mock_client.memory_stores = AsyncMock()
|
||||
mock_client.memory_stores.search_memories = AsyncMock()
|
||||
mock_client.memory_stores.begin_update_memories = AsyncMock()
|
||||
mock_client.beta = AsyncMock()
|
||||
mock_client.beta.memory_stores = AsyncMock()
|
||||
mock_client.beta.memory_stores.search_memories = AsyncMock()
|
||||
mock_client.beta.memory_stores.begin_update_memories = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock()
|
||||
return mock_client
|
||||
@@ -146,7 +147,7 @@ class TestBeforeRun:
|
||||
mem2.memory_item.content = "User is based in Seattle"
|
||||
mock_search_result = Mock()
|
||||
mock_search_result.memories = [mem1, mem2]
|
||||
mock_project_client.memory_stores.search_memories.return_value = mock_search_result
|
||||
mock_project_client.beta.memory_stores.search_memories.return_value = mock_search_result
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -161,7 +162,7 @@ class TestBeforeRun:
|
||||
)
|
||||
|
||||
# Should call search_memories twice: once for static, once for contextual
|
||||
assert mock_project_client.memory_stores.search_memories.call_count == 2
|
||||
assert mock_project_client.beta.memory_stores.search_memories.call_count == 2
|
||||
# Static memories should be cached
|
||||
assert len(session.state[provider.source_id]["static_memories"]) == 2
|
||||
assert session.state[provider.source_id]["initialized"] is True
|
||||
@@ -181,7 +182,7 @@ class TestBeforeRun:
|
||||
contextual_result.memories = [contextual_mem]
|
||||
contextual_result.search_id = "search-123"
|
||||
|
||||
mock_project_client.memory_stores.search_memories.side_effect = [static_result, contextual_result]
|
||||
mock_project_client.beta.memory_stores.search_memories.side_effect = [static_result, contextual_result]
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -208,7 +209,7 @@ class TestBeforeRun:
|
||||
"""Empty input messages → only static search performed, no contextual search."""
|
||||
static_result = Mock()
|
||||
static_result.memories = []
|
||||
mock_project_client.memory_stores.search_memories.return_value = static_result
|
||||
mock_project_client.beta.memory_stores.search_memories.return_value = static_result
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -223,14 +224,14 @@ class TestBeforeRun:
|
||||
)
|
||||
|
||||
# Should only call search_memories once for static memories
|
||||
assert mock_project_client.memory_stores.search_memories.call_count == 1
|
||||
assert mock_project_client.beta.memory_stores.search_memories.call_count == 1
|
||||
assert provider.source_id not in ctx.context_messages
|
||||
|
||||
async def test_empty_search_results_no_messages(self, mock_project_client: AsyncMock) -> None:
|
||||
"""Empty search results → no messages added."""
|
||||
mock_search_result = Mock()
|
||||
mock_search_result.memories = []
|
||||
mock_project_client.memory_stores.search_memories.return_value = mock_search_result
|
||||
mock_project_client.beta.memory_stores.search_memories.return_value = mock_search_result
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -255,7 +256,7 @@ class TestBeforeRun:
|
||||
contextual_result = Mock()
|
||||
contextual_result.memories = []
|
||||
|
||||
mock_project_client.memory_stores.search_memories.side_effect = [static_result, contextual_result]
|
||||
mock_project_client.beta.memory_stores.search_memories.side_effect = [static_result, contextual_result]
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -269,24 +270,24 @@ class TestBeforeRun:
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
assert mock_project_client.memory_stores.search_memories.call_count == 2
|
||||
assert mock_project_client.beta.memory_stores.search_memories.call_count == 2
|
||||
|
||||
# Reset mock for second call
|
||||
mock_project_client.memory_stores.search_memories.reset_mock()
|
||||
mock_project_client.beta.memory_stores.search_memories.reset_mock()
|
||||
contextual_result2 = Mock()
|
||||
contextual_result2.memories = []
|
||||
mock_project_client.memory_stores.search_memories.return_value = contextual_result2
|
||||
mock_project_client.beta.memory_stores.search_memories.return_value = contextual_result2
|
||||
|
||||
# Second call - should only search contextual, not static
|
||||
ctx2 = SessionContext(input_messages=[Message(role="user", text="World")], session_id="s1")
|
||||
await provider.before_run( # type: ignore[arg-type]
|
||||
agent=None, session=session, context=ctx2, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
assert mock_project_client.memory_stores.search_memories.call_count == 1
|
||||
assert mock_project_client.beta.memory_stores.search_memories.call_count == 1
|
||||
|
||||
async def test_handles_search_exception_gracefully(self, mock_project_client: AsyncMock) -> None:
|
||||
"""Search exception is logged but doesn't fail the operation."""
|
||||
mock_project_client.memory_stores.search_memories.side_effect = Exception("API error")
|
||||
mock_project_client.beta.memory_stores.search_memories.side_effect = Exception("API error")
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -315,7 +316,7 @@ class TestAfterRun:
|
||||
"""Stores input+response messages via begin_update_memories."""
|
||||
mock_poller = Mock()
|
||||
mock_poller.update_id = "update-456"
|
||||
mock_project_client.memory_stores.begin_update_memories.return_value = mock_poller
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.return_value = mock_poller
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -330,8 +331,8 @@ class TestAfterRun:
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
|
||||
mock_project_client.memory_stores.begin_update_memories.assert_awaited_once()
|
||||
call_kwargs = mock_project_client.memory_stores.begin_update_memories.call_args.kwargs
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.assert_awaited_once()
|
||||
call_kwargs = mock_project_client.beta.memory_stores.begin_update_memories.call_args.kwargs
|
||||
assert call_kwargs["name"] == "test_store"
|
||||
assert call_kwargs["scope"] == "user_123"
|
||||
assert len(call_kwargs["items"]) == 2
|
||||
@@ -342,7 +343,7 @@ class TestAfterRun:
|
||||
async def test_only_stores_user_assistant_system(self, mock_project_client: AsyncMock) -> None:
|
||||
"""Only stores user/assistant/system messages with text."""
|
||||
mock_poller = Mock()
|
||||
mock_project_client.memory_stores.begin_update_memories.return_value = mock_poller
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.return_value = mock_poller
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -363,7 +364,7 @@ class TestAfterRun:
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
|
||||
call_kwargs = mock_project_client.memory_stores.begin_update_memories.call_args.kwargs
|
||||
call_kwargs = mock_project_client.beta.memory_stores.begin_update_memories.call_args.kwargs
|
||||
items = call_kwargs["items"]
|
||||
assert len(items) == 2
|
||||
assert items[0]["content"] == "hello"
|
||||
@@ -390,12 +391,12 @@ class TestAfterRun:
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
|
||||
mock_project_client.memory_stores.begin_update_memories.assert_not_awaited()
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.assert_not_awaited()
|
||||
|
||||
async def test_uses_configured_update_delay(self, mock_project_client: AsyncMock) -> None:
|
||||
"""Uses the configured update_delay parameter."""
|
||||
mock_poller = Mock()
|
||||
mock_project_client.memory_stores.begin_update_memories.return_value = mock_poller
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.return_value = mock_poller
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -411,7 +412,7 @@ class TestAfterRun:
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
|
||||
call_kwargs = mock_project_client.memory_stores.begin_update_memories.call_args.kwargs
|
||||
call_kwargs = mock_project_client.beta.memory_stores.begin_update_memories.call_args.kwargs
|
||||
assert call_kwargs["update_delay"] == 60
|
||||
|
||||
async def test_uses_previous_update_id_for_incremental_updates(self, mock_project_client: AsyncMock) -> None:
|
||||
@@ -421,7 +422,7 @@ class TestAfterRun:
|
||||
mock_poller2 = Mock()
|
||||
mock_poller2.update_id = "update-2"
|
||||
|
||||
mock_project_client.memory_stores.begin_update_memories.side_effect = [mock_poller1, mock_poller2]
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.side_effect = [mock_poller1, mock_poller2]
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
@@ -446,13 +447,13 @@ class TestAfterRun:
|
||||
agent=None, session=session, context=ctx2, state=session.state.setdefault(provider.source_id, {})
|
||||
)
|
||||
|
||||
call_kwargs = mock_project_client.memory_stores.begin_update_memories.call_args.kwargs
|
||||
call_kwargs = mock_project_client.beta.memory_stores.begin_update_memories.call_args.kwargs
|
||||
assert call_kwargs["previous_update_id"] == "update-1"
|
||||
assert session.state[provider.source_id]["previous_update_id"] == "update-2"
|
||||
|
||||
async def test_handles_update_exception_gracefully(self, mock_project_client: AsyncMock) -> None:
|
||||
"""Update exception is logged but doesn't fail the operation."""
|
||||
mock_project_client.memory_stores.begin_update_memories.side_effect = Exception("API error")
|
||||
mock_project_client.beta.memory_stores.begin_update_memories.side_effect = Exception("API error")
|
||||
|
||||
provider = FoundryMemoryProvider(
|
||||
project_client=mock_project_client,
|
||||
|
||||
@@ -8,7 +8,6 @@ from agent_framework import Agent, FunctionTool
|
||||
from agent_framework._mcp import MCPTool
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
AgentReference,
|
||||
AgentVersionDetails,
|
||||
PromptAgentDefinition,
|
||||
)
|
||||
@@ -345,7 +344,7 @@ async def test_provider_get_agent_with_reference(mock_project_client: MagicMock)
|
||||
mock_project_client.agents = AsyncMock()
|
||||
mock_project_client.agents.get_version.return_value = mock_agent_version
|
||||
|
||||
agent_reference = AgentReference(name="test-agent", version="1.0")
|
||||
agent_reference = {"name": "test-agent", "version": "1.0"}
|
||||
agent = await provider.get_agent(reference=agent_reference)
|
||||
|
||||
assert isinstance(agent, Agent)
|
||||
|
||||
@@ -34,8 +34,7 @@ dependencies = [
|
||||
# connectors and functions
|
||||
"openai>=1.99.0",
|
||||
"azure-identity>=1,<2",
|
||||
# Pinned to 2.0.0b3 - breaking changes in 2.0.0b4, unpin once upgrades complete
|
||||
"azure-ai-projects == 2.0.0b3",
|
||||
"azure-ai-projects == 2.0.0b4",
|
||||
"mcp[ws]>=1.24.0,<2",
|
||||
"packaging>=24.1",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user