Skip to content

Commit e3eff65

Browse files
authored
Python: Complete durableagent package (#3058)
* Add worker and clients * Clean code and refactor common code * Implement sample * Add sample * Update readmes * Fix tests * Fix tests * Update requirements * Fix typo * Address comments * use response.text
1 parent a5b36dc commit e3eff65

46 files changed

Lines changed: 4466 additions & 1633 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,14 @@ WARP.md
208208
**/projectBrief.md
209209

210210
# Azurite storage emulator files
211-
*/__azurite_db_blob__.json
212-
*/__azurite_db_blob_extent__.json
213-
*/__azurite_db_queue__.json
214-
*/__azurite_db_queue_extent__.json
215-
*/__azurite_db_table__.json
211+
*/__azurite_db_blob__.json*
212+
*/__azurite_db_blob_extent__.json*
213+
*/__azurite_db_queue__.json*
214+
*/__azurite_db_queue_extent__.json*
215+
*/__azurite_db_table__.json*
216216
*/__blobstorage__/
217217
*/__queuestorage__/
218+
*/AzuriteConfig
218219

219220
# Azure Functions local settings
220221
local.settings.json

python/packages/azurefunctions/agent_framework_azurefunctions/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
import importlib.metadata
44

5-
from agent_framework_durabletask import AgentCallbackContext, AgentResponseCallbackProtocol
5+
from agent_framework_durabletask import AgentCallbackContext, AgentResponseCallbackProtocol, DurableAIAgent
66

77
from ._app import AgentFunctionApp
8-
from ._orchestration import DurableAIAgent
98

109
try:
1110
__version__ = importlib.metadata.version(__name__)

python/packages/azurefunctions/agent_framework_azurefunctions/_app.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import json
1010
import re
11+
import uuid
1112
from collections.abc import Callable, Mapping
1213
from dataclasses import dataclass
1314
from datetime import datetime, timezone
@@ -28,14 +29,16 @@
2829
WAIT_FOR_RESPONSE_FIELD,
2930
WAIT_FOR_RESPONSE_HEADER,
3031
AgentResponseCallbackProtocol,
32+
AgentSessionId,
33+
ApiResponseFields,
3134
DurableAgentState,
35+
DurableAIAgent,
3236
RunRequest,
3337
)
3438

3539
from ._entities import create_agent_entity
3640
from ._errors import IncomingRequestError
37-
from ._models import AgentSessionId
38-
from ._orchestration import AgentOrchestrationContextType, DurableAIAgent
41+
from ._orchestration import AgentOrchestrationContextType, AgentTask, AzureFunctionsAgentExecutor
3942

4043
logger = get_logger("agent_framework.azurefunctions")
4144

@@ -296,7 +299,7 @@ def get_agent(
296299
self,
297300
context: AgentOrchestrationContextType,
298301
agent_name: str,
299-
) -> DurableAIAgent:
302+
) -> DurableAIAgent[AgentTask]:
300303
"""Return a DurableAIAgent proxy for a registered agent.
301304
302305
Args:
@@ -307,14 +310,15 @@ def get_agent(
307310
ValueError: If the requested agent has not been registered.
308311
309312
Returns:
310-
DurableAIAgent wrapper bound to the orchestration context.
313+
DurableAIAgent[AgentTask] wrapper bound to the orchestration context.
311314
"""
312315
normalized_name = str(agent_name)
313316

314317
if normalized_name not in self._agent_metadata:
315318
raise ValueError(f"Agent '{normalized_name}' is not registered with this app.")
316319

317-
return DurableAIAgent(context, normalized_name)
320+
executor = AzureFunctionsAgentExecutor(context)
321+
return DurableAIAgent(executor, normalized_name)
318322

319323
def _setup_agent_functions(
320324
self,
@@ -377,8 +381,6 @@ async def http_start(req: func.HttpRequest, client: df.DurableOrchestrationClien
377381
"enable_tool_calls": true|false (optional, default: true)
378382
}
379383
"""
380-
logger.debug(f"[HTTP Trigger] Received request on route: /api/agents/{agent_name}/run")
381-
382384
request_response_format: str = REQUEST_RESPONSE_FORMAT_JSON
383385
thread_id: str | None = None
384386

@@ -387,9 +389,9 @@ async def http_start(req: func.HttpRequest, client: df.DurableOrchestrationClien
387389
thread_id = self._resolve_thread_id(req=req, req_body=req_body)
388390
wait_for_response = self._should_wait_for_response(req=req, req_body=req_body)
389391

390-
logger.debug(f"[HTTP Trigger] Message: {message}")
391-
logger.debug(f"[HTTP Trigger] Thread ID: {thread_id}")
392-
logger.debug(f"[HTTP Trigger] wait_for_response: {wait_for_response}")
392+
logger.debug(
393+
f"[HTTP Trigger] Message: {message}, Thread ID: {thread_id}, wait_for_response: {wait_for_response}"
394+
)
393395

394396
if not message:
395397
logger.warning("[HTTP Trigger] Request rejected: Missing message")
@@ -403,15 +405,18 @@ async def http_start(req: func.HttpRequest, client: df.DurableOrchestrationClien
403405
session_id = self._create_session_id(agent_name, thread_id)
404406
correlation_id = self._generate_unique_id()
405407

406-
logger.debug(f"[HTTP Trigger] Using session ID: {session_id}")
407-
logger.debug(f"[HTTP Trigger] Generated correlation ID: {correlation_id}")
408-
logger.debug("[HTTP Trigger] Calling entity to run agent...")
408+
logger.debug(
409+
f"[HTTP Trigger] Calling entity to run agent using session ID: {session_id} "
410+
f"and correlation ID: {correlation_id}"
411+
)
409412

410-
entity_instance_id = session_id.to_entity_id()
413+
entity_instance_id = df.EntityId(
414+
name=session_id.entity_name,
415+
key=session_id.key,
416+
)
411417
run_request = self._build_request_data(
412418
req_body,
413419
message,
414-
thread_id,
415420
correlation_id,
416421
request_response_format,
417422
)
@@ -624,14 +629,16 @@ async def _handle_mcp_tool_invocation(
624629
session_id = AgentSessionId.with_random_key(agent_name)
625630

626631
# Build entity instance ID
627-
entity_instance_id = session_id.to_entity_id()
632+
entity_instance_id = df.EntityId(
633+
name=session_id.entity_name,
634+
key=session_id.key,
635+
)
628636

629637
# Create run request
630638
correlation_id = self._generate_unique_id()
631639
run_request = self._build_request_data(
632640
req_body={"message": query, "role": "user"},
633641
message=query,
634-
thread_id=str(session_id),
635642
correlation_id=correlation_id,
636643
request_response_format=REQUEST_RESPONSE_FORMAT_TEXT,
637644
)
@@ -783,7 +790,7 @@ async def _poll_entity_for_response(
783790
agent_response = state.try_get_agent_response(correlation_id)
784791
if agent_response:
785792
result = self._build_success_result(
786-
response_data=agent_response,
793+
response_message=agent_response.text,
787794
message=message,
788795
thread_id=thread_id,
789796
correlation_id=correlation_id,
@@ -829,23 +836,22 @@ async def _build_timeout_result(self, message: str, thread_id: str, correlation_
829836
)
830837

831838
def _build_success_result(
832-
self, response_data: dict[str, Any], message: str, thread_id: str, correlation_id: str, state: DurableAgentState
839+
self, response_message: str, message: str, thread_id: str, correlation_id: str, state: DurableAgentState
833840
) -> dict[str, Any]:
834841
"""Build the success result returned to the HTTP caller."""
835842
return self._build_response_payload(
836-
response=response_data.get("content"),
843+
response=response_message,
837844
message=message,
838845
thread_id=thread_id,
839846
status="success",
840847
correlation_id=correlation_id,
841-
extra_fields={"message_count": response_data.get("message_count", state.message_count)},
848+
extra_fields={ApiResponseFields.MESSAGE_COUNT: state.message_count},
842849
)
843850

844851
def _build_request_data(
845852
self,
846853
req_body: dict[str, Any],
847854
message: str,
848-
thread_id: str,
849855
correlation_id: str,
850856
request_response_format: str,
851857
) -> dict[str, Any]:
@@ -912,15 +918,13 @@ def _convert_payload_to_text(self, payload: dict[str, Any]) -> str:
912918

913919
def _generate_unique_id(self) -> str:
914920
"""Generate a new unique identifier."""
915-
import uuid
916-
917921
return uuid.uuid4().hex
918922

919-
def _create_session_id(self, func_name: str, thread_id: str | None) -> AgentSessionId:
923+
def _create_session_id(self, agent_name: str, thread_id: str | None) -> AgentSessionId:
920924
"""Create a session identifier using the provided thread id or a random value."""
921925
if thread_id:
922-
return AgentSessionId(name=func_name, key=thread_id)
923-
return AgentSessionId.with_random_key(name=func_name)
926+
return AgentSessionId(name=agent_name, key=thread_id)
927+
return AgentSessionId.with_random_key(name=agent_name)
924928

925929
def _resolve_thread_id(self, req: func.HttpRequest, req_body: dict[str, Any]) -> str:
926930
"""Retrieve the thread identifier from request body or query parameters."""

0 commit comments

Comments
 (0)