Python: Switch to new "run" method name. (#2890)

* Switch to `run` method.

* Add support for deprecated `run_agent`.

* Fix entity method name.

* Fix method name and improve tests.

* Update comment.

* Update Python CHANGELOG.
This commit is contained in:
Phillip Hoff
2025-12-16 14:08:12 -08:00
committed by GitHub
Unverified
parent 03a403d2fa
commit 2bde58f915
8 changed files with 131 additions and 54 deletions
+4
View File
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- **agent-framework-azurefunctions**: Durable Agents: platforms should use consistent entity method names (#2234)
## [1.0.0b251216] - 2025-12-16
### Added
@@ -414,7 +414,7 @@ class AgentFunctionApp(DFAppBase):
request_response_format,
)
logger.debug("Signalling entity %s with request: %s", entity_instance_id, run_request)
await client.signal_entity(entity_instance_id, "run_agent", run_request)
await client.signal_entity(entity_instance_id, "run", run_request)
logger.debug(f"[HTTP Trigger] Signal sent to entity {session_id}")
@@ -495,7 +495,8 @@ class AgentFunctionApp(DFAppBase):
"""Durable entity that manages agent execution and conversation state.
Operations:
- run_agent: Execute the agent with a message
- run: Execute the agent with a message
- run_agent: (Deprecated) Execute the agent with a message
- reset: Clear conversation history
"""
entity_handler = create_agent_entity(agent, callback)
@@ -637,7 +638,7 @@ class AgentFunctionApp(DFAppBase):
logger.info("[MCP Tool] Invoking agent '%s' with query: %s", agent_name, query_preview)
# Signal entity to run agent
await client.signal_entity(entity_instance_id, "run_agent", run_request)
await client.signal_entity(entity_instance_id, "run", run_request)
# Poll for response (similar to HTTP handler)
try:
@@ -46,7 +46,8 @@ class AgentEntity:
- Handles tool execution
Operations:
- run_agent: Execute the agent with a message
- run: Execute the agent with a message
- run_agent: (Deprecated) Execute the agent with a message
- reset: Clear conversation history
Attributes:
@@ -94,6 +95,22 @@ class AgentEntity:
self,
context: df.DurableEntityContext,
request: RunRequest | dict[str, Any] | str,
) -> AgentRunResponse:
"""(Deprecated) Execute the agent with a message directly in the entity.
Args:
context: Entity context
request: RunRequest object, dict, or string message (for backward compatibility)
Returns:
AgentRunResponse enriched with execution metadata.
"""
return await self.run(context, request)
async def run(
self,
context: df.DurableEntityContext,
request: RunRequest | dict[str, Any] | str,
) -> AgentRunResponse:
"""Execute the agent with a message directly in the entity.
@@ -124,7 +141,7 @@ class AgentEntity:
state_request = DurableAgentStateRequest.from_run_request(run_request)
self.state.data.conversation_history.append(state_request)
logger.debug(f"[AgentEntity.run_agent] Received Message: {state_request}")
logger.debug(f"[AgentEntity.run] Received Message: {state_request}")
try:
# Build messages from conversation history, excluding error responses
@@ -150,7 +167,7 @@ class AgentEntity:
)
logger.debug(
"[AgentEntity.run_agent] Agent invocation completed - response type: %s",
"[AgentEntity.run] Agent invocation completed - response type: %s",
type(agent_run_response).__name__,
)
@@ -167,12 +184,12 @@ class AgentEntity:
state_response = DurableAgentStateResponse.from_run_response(correlation_id, agent_run_response)
self.state.data.conversation_history.append(state_response)
logger.debug("[AgentEntity.run_agent] AgentRunResponse stored in conversation history")
logger.debug("[AgentEntity.run] AgentRunResponse stored in conversation history")
return agent_run_response
except Exception as exc:
logger.exception("[AgentEntity.run_agent] Agent execution failed.")
logger.exception("[AgentEntity.run] Agent execution failed.")
# Create error message
error_message = ChatMessage(
@@ -367,7 +384,7 @@ def create_agent_entity(
operation = context.operation_name
if operation == "run_agent":
if operation == "run" or operation == "run_agent":
input_data: Any = context.get_input()
request: str | dict[str, Any]
@@ -377,7 +394,7 @@ def create_agent_entity(
# Fall back to treating input as message string
request = "" if input_data is None else str(cast(object, input_data))
result = await entity.run_agent(context, request)
result = await entity.run(context, request)
context.set_result(result.to_dict())
elif operation == "reset":
@@ -285,7 +285,7 @@ class DurableAIAgent(AgentProtocol):
logger.debug("[DurableAIAgent] Calling entity %s with message: %s", entity_id, message_str[:100])
# Call the entity to get the underlying task
entity_task = self.context.call_entity(entity_id, "run_agent", run_request.to_dict())
entity_task = self.context.call_entity(entity_id, "run", run_request.to_dict())
# Wrap it in an AgentTask that will convert the result to AgentRunResponse
agent_task = AgentTask(
@@ -29,7 +29,7 @@ docker run -d -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azu
**Durable Task Scheduler:**
```bash
docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
docker run -d -p 8080:8080 -p 8082:8082 -e DTS_USE_DYNAMIC_TASK_HUBS=true mcr.microsoft.com/dts/dts-emulator:latest
```
## Running Tests
@@ -338,7 +338,7 @@ class TestAgentEntityOperations:
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context,
{"message": "Test message", "thread_id": "test-conv-123", "correlationId": "corr-app-entity-1"},
)
@@ -358,7 +358,7 @@ class TestAgentEntityOperations:
mock_context = Mock()
# Send first message
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-app-entity-2"}
)
@@ -367,7 +367,7 @@ class TestAgentEntityOperations:
assert len(history) == 1 # Just the user message
# Send second message
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 2", "thread_id": "conv-2", "correlationId": "corr-app-entity-2b"}
)
@@ -398,12 +398,12 @@ class TestAgentEntityOperations:
assert len(entity.state.data.conversation_history) == 0
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-app-entity-3a"}
)
assert len(entity.state.data.conversation_history) == 2
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 2", "thread_id": "conv-1", "correlationId": "corr-app-entity-3b"}
)
assert len(entity.state.data.conversation_history) == 4
@@ -433,8 +433,36 @@ class TestAgentEntityFactory:
assert callable(entity_function)
def test_entity_function_handles_run_operation(self) -> None:
"""Test that the entity function handles the run operation."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Response")])
)
entity_function = create_agent_entity(mock_agent)
# Mock context
mock_context = Mock()
mock_context.operation_name = "run"
mock_context.get_input.return_value = {
"message": "Test message",
"thread_id": "conv-123",
"correlationId": "corr-app-factory-1",
}
mock_context.get_state.return_value = None
# Execute entity function
entity_function(mock_context)
# Verify result was set
assert mock_context.set_result.called
assert mock_context.set_state.called
result_call = mock_context.set_result.call_args[0][0]
assert "error" not in result_call
def test_entity_function_handles_run_agent_operation(self) -> None:
"""Test that the entity function handles the run_agent operation."""
"""Test that the entity function handles the deprecated run_agent operation for backward compatibility."""
mock_agent = Mock()
mock_agent.run = AsyncMock(
return_value=AgentRunResponse(messages=[ChatMessage(role="assistant", text="Response")])
@@ -458,6 +486,8 @@ class TestAgentEntityFactory:
# Verify result was set
assert mock_context.set_result.called
assert mock_context.set_state.called
result_call = mock_context.set_result.call_args[0][0]
assert "error" not in result_call
def test_entity_function_handles_reset_operation(self) -> None:
"""Test that the entity function handles the reset operation."""
@@ -585,7 +615,7 @@ class TestErrorHandling:
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context, {"message": "Test message", "thread_id": "conv-1", "correlationId": "corr-app-error-1"}
)
@@ -605,7 +635,7 @@ class TestErrorHandling:
entity_function = create_agent_entity(mock_agent)
mock_context = Mock()
mock_context.operation_name = "run_agent"
mock_context.operation_name = "run"
mock_context.get_input.side_effect = Exception("Input error")
mock_context.get_state.return_value = None
@@ -108,6 +108,33 @@ class TestAgentEntityInit:
class TestAgentEntityRunAgent:
"""Test suite for the run_agent operation."""
async def test_run_executes_agent(self) -> None:
"""Test that run executes the agent."""
mock_agent = Mock()
mock_response = _agent_response("Test response")
mock_agent.run = AsyncMock(return_value=mock_response)
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run(
mock_context, {"message": "Test message", "thread_id": "conv-123", "correlationId": "corr-entity-1"}
)
# Verify agent.run was called
mock_agent.run.assert_called_once()
_, kwargs = mock_agent.run.call_args
sent_messages: list[Any] = kwargs.get("messages")
assert len(sent_messages) == 1
sent_message = sent_messages[0]
assert isinstance(sent_message, ChatMessage)
assert getattr(sent_message, "text", None) == "Test message"
assert getattr(sent_message.role, "value", sent_message.role) == "user"
# Verify result
assert isinstance(result, AgentRunResponse)
assert result.text == "Test response"
async def test_run_agent_executes_agent(self) -> None:
"""Test that run_agent executes the agent."""
mock_agent = Mock()
@@ -156,7 +183,7 @@ class TestAgentEntityRunAgent:
entity = AgentEntity(mock_agent, callback=callback)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context,
{
"message": "Tell me something",
@@ -203,7 +230,7 @@ class TestAgentEntityRunAgent:
entity = AgentEntity(mock_agent, callback=callback)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context,
{
"message": "Hi",
@@ -235,7 +262,7 @@ class TestAgentEntityRunAgent:
entity = AgentEntity(mock_agent)
mock_context = Mock()
await entity.run_agent(
await entity.run(
mock_context, {"message": "User message", "thread_id": "conv-1", "correlationId": "corr-entity-2"}
)
@@ -263,17 +290,17 @@ class TestAgentEntityRunAgent:
assert len(entity.state.data.conversation_history) == 0
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-entity-3a"}
)
assert len(entity.state.data.conversation_history) == 2
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 2", "thread_id": "conv-1", "correlationId": "corr-entity-3b"}
)
assert len(entity.state.data.conversation_history) == 4
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 3", "thread_id": "conv-1", "correlationId": "corr-entity-3c"}
)
assert len(entity.state.data.conversation_history) == 6
@@ -287,9 +314,7 @@ class TestAgentEntityRunAgent:
mock_context = Mock()
with pytest.raises(ValueError, match="thread_id"):
await entity.run_agent(
mock_context, {"message": "Message", "thread_id": None, "correlationId": "corr-entity-5"}
)
await entity.run(mock_context, {"message": "Message", "thread_id": None, "correlationId": "corr-entity-5"})
async def test_run_agent_multiple_conversations(self) -> None:
"""Test that run_agent maintains history across multiple messages."""
@@ -300,13 +325,13 @@ class TestAgentEntityRunAgent:
mock_context = Mock()
# Send multiple messages
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-entity-8a"}
)
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 2", "thread_id": "conv-1", "correlationId": "corr-entity-8b"}
)
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 3", "thread_id": "conv-1", "correlationId": "corr-entity-8c"}
)
@@ -374,10 +399,10 @@ class TestAgentEntityReset:
mock_context = Mock()
# Have a conversation
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-entity-10a"}
)
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message 2", "thread_id": "conv-1", "correlationId": "corr-entity-10b"}
)
@@ -413,7 +438,7 @@ class TestCreateAgentEntity:
# Mock context
mock_context = Mock()
mock_context.operation_name = "run_agent"
mock_context.operation_name = "run"
mock_context.get_input.return_value = {
"message": "Test message",
"thread_id": "conv-123",
@@ -576,7 +601,7 @@ class TestErrorHandling:
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-error-1"}
)
@@ -595,7 +620,7 @@ class TestErrorHandling:
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-error-2"}
)
@@ -614,7 +639,7 @@ class TestErrorHandling:
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-error-3"}
)
@@ -631,7 +656,7 @@ class TestErrorHandling:
entity_function = create_agent_entity(mock_agent)
mock_context = Mock()
mock_context.operation_name = "run_agent"
mock_context.operation_name = "run"
mock_context.get_input.side_effect = Exception("Input error")
mock_context.get_state.return_value = None
@@ -651,7 +676,7 @@ class TestErrorHandling:
entity = AgentEntity(mock_agent)
mock_context = Mock()
result = await entity.run_agent(
result = await entity.run(
mock_context,
{"message": "Test message", "thread_id": "conv-123", "correlationId": "corr-entity-error-4"},
)
@@ -674,7 +699,7 @@ class TestConversationHistory:
entity = AgentEntity(mock_agent)
mock_context = Mock()
await entity.run_agent(
await entity.run(
mock_context, {"message": "Message", "thread_id": "conv-1", "correlationId": "corr-entity-history-1"}
)
@@ -694,19 +719,19 @@ class TestConversationHistory:
# Send multiple messages with different responses
mock_agent.run = AsyncMock(return_value=_agent_response("Response 1"))
await entity.run_agent(
await entity.run(
mock_context,
{"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-entity-history-2a"},
)
mock_agent.run = AsyncMock(return_value=_agent_response("Response 2"))
await entity.run_agent(
await entity.run(
mock_context,
{"message": "Message 2", "thread_id": "conv-1", "correlationId": "corr-entity-history-2b"},
)
mock_agent.run = AsyncMock(return_value=_agent_response("Response 3"))
await entity.run_agent(
await entity.run(
mock_context,
{"message": "Message 3", "thread_id": "conv-1", "correlationId": "corr-entity-history-2c"},
)
@@ -729,11 +754,11 @@ class TestConversationHistory:
entity = AgentEntity(mock_agent)
mock_context = Mock()
await entity.run_agent(
await entity.run(
mock_context,
{"message": "Message 1", "thread_id": "conv-1", "correlationId": "corr-entity-history-3a"},
)
await entity.run_agent(
await entity.run(
mock_context,
{"message": "Message 2", "thread_id": "conv-1", "correlationId": "corr-entity-history-3b"},
)
@@ -766,7 +791,7 @@ class TestRunRequestSupport:
correlation_id="corr-runreq-1",
)
result = await entity.run_agent(mock_context, request)
result = await entity.run(mock_context, request)
assert isinstance(result, AgentRunResponse)
assert result.text == "Response"
@@ -787,7 +812,7 @@ class TestRunRequestSupport:
"correlationId": "corr-runreq-2",
}
result = await entity.run_agent(mock_context, request_dict)
result = await entity.run(mock_context, request_dict)
assert isinstance(result, AgentRunResponse)
assert result.text == "Response"
@@ -801,7 +826,7 @@ class TestRunRequestSupport:
mock_context = Mock()
with pytest.raises(ValueError):
await entity.run_agent(mock_context, "Simple message")
await entity.run(mock_context, "Simple message")
async def test_run_agent_stores_role_in_history(self) -> None:
"""Test that run_agent stores the role in conversation history."""
@@ -819,7 +844,7 @@ class TestRunRequestSupport:
correlation_id="corr-runreq-3",
)
await entity.run_agent(mock_context, request)
await entity.run(mock_context, request)
# Check that system role was stored
history = entity.state.data.conversation_history
@@ -842,7 +867,7 @@ class TestRunRequestSupport:
correlation_id="corr-runreq-4",
)
result = await entity.run_agent(mock_context, request)
result = await entity.run(mock_context, request)
assert isinstance(result, AgentRunResponse)
assert result.text == '{"answer": 42}'
@@ -860,7 +885,7 @@ class TestRunRequestSupport:
message="Test", thread_id="conv-runreq-5", enable_tool_calls=False, correlation_id="corr-runreq-5"
)
result = await entity.run_agent(mock_context, request)
result = await entity.run(mock_context, request)
assert isinstance(result, AgentRunResponse)
# Agent should have been called (tool disabling is framework-dependent)
@@ -874,7 +899,7 @@ class TestRunRequestSupport:
entity_function = create_agent_entity(mock_agent)
mock_context = Mock()
mock_context.operation_name = "run_agent"
mock_context.operation_name = "run"
mock_context.get_input.return_value = {
"message": "Test message",
"thread_id": "conv-789",
@@ -295,7 +295,7 @@ class TestDurableAIAgent:
call_args = mock_context.call_entity.call_args
entity_id, operation, request = call_args[0]
assert operation == "run_agent"
assert operation == "run"
assert request["message"] == "Test message"
assert request["enable_tool_calls"] is True
assert "correlationId" in request