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 @@ -199,6 +199,7 @@ class Activity(AgentsModel):
text_highlights: list[TextHighlight] = None
semantic_action: SemanticAction = None
caller_id: NonEmptyString = None
request_id: str | None = Field(None, exclude=True)

@field_validator("entities", mode="before")
@classmethod
Expand Down Expand Up @@ -300,12 +301,13 @@ def apply_conversation_reference(
:returns: This activity, updated with the delivery information.

.. remarks::
Call GetConversationReference on an incoming activity to get a conversation reference that you can then use to update an
Call get_conversation_reference on an incoming activity to get a conversation reference that you can then use to update an
outgoing activity with the correct delivery information.
"""
self.channel_id = reference.channel_id
self.service_url = reference.service_url
self.conversation = reference.conversation
self.request_id = reference.request_id
Comment thread
rodrigobr-msft marked this conversation as resolved.

if reference.locale is not None:
self.locale = reference.locale
Expand Down Expand Up @@ -1043,6 +1045,7 @@ def get_conversation_reference(
),
locale=self.locale,
service_url=self.service_url,
request_id=self.request_id,
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ConversationReference(AgentsModel):
channel_id: Optional[ChannelId] = None
locale: Optional[NonEmptyString] = None
service_url: NonEmptyString = None
request_id: str | None = None
Comment thread
rodrigobr-msft marked this conversation as resolved.

def get_continuation_activity(self) -> Activity:
from .activity import Activity
Expand All @@ -68,4 +69,5 @@ def get_continuation_activity(self) -> Activity:
recipient=self.agent,
from_property=SkipNone(self.user),
relates_to=self,
request_id=self.request_id,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""Base HTTP adapter with shared processing logic."""

from abc import ABC
from uuid import uuid4
from http import HTTPStatus
from traceback import format_exc

Expand Down Expand Up @@ -116,6 +117,8 @@ async def process_request(
)

activity: Activity = Activity.model_validate(body)
activity.request_id = str(uuid4())
Comment thread
rodrigobr-msft marked this conversation as resolved.

span.share(activity=activity)

# Get claims identity (default to anonymous if not set by middleware)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ async def post_activity(
channel_id=activity_copy.channel_id,
locale=activity_copy.locale,
conversation=copy(activity_copy.conversation),
request_id=activity_copy.request_id,
Comment thread
rodrigobr-msft marked this conversation as resolved.
)

activity_copy.conversation.id = conversation_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ def apply_conversation_reference(
activity.locale = reference.locale
activity.service_url = reference.service_url
activity.conversation = reference.conversation
activity.request_id = reference.request_id

if is_incoming:
activity.from_property = reference.user
activity.recipient = reference.agent
Expand Down
26 changes: 26 additions & 0 deletions tests/activity/test_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ def test_get_conversation_reference(self, activity):
assert activity.locale == conversation_reference.locale
assert activity.service_url == conversation_reference.service_url

def test_get_conversation_reference_propagates_request_id(self, activity):
activity.request_id = "request-123"

conversation_reference = activity.get_conversation_reference()

assert conversation_reference.request_id == "request-123"

def test_get_conversation_reference_force_base_channel(self, activity):
activity.channel_id = "msteams:copilot-web"

Expand Down Expand Up @@ -198,6 +205,25 @@ def test_apply_conversation_reference_is_incoming(self):
assert conversation_reference.activity_id == activity.id
assert activity.locale == activity_to_send.locale

def test_apply_conversation_reference_propagates_request_id(self):
activity = create_test_activity("en-us")
conversation_reference = ConversationReference(
channel_id="cr_123",
service_url="cr_serviceUrl",
conversation=ConversationAccount(id="cr_456"),
user=ChannelAccount(id="cr_abc"),
agent=ChannelAccount(id="cr_def"),
activity_id="cr_12345",
locale="en-us",
request_id="request-123",
)

activity_to_send = activity.apply_conversation_reference(
conversation_reference, is_incoming=True
)

assert activity_to_send.request_id == "request-123"

@pytest.mark.parametrize("locale", ["EN-US", "en-uS"])
def test_apply_conversation_reference(self, locale):
activity = create_test_activity(locale)
Expand Down
21 changes: 20 additions & 1 deletion tests/activity/test_conversation_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
)


def _create_conversation_reference(user: ChannelAccount | None = None):
def _create_conversation_reference(
user: ChannelAccount | None = None, request_id: str | None = None
):
return ConversationReference(
activity_id="activity-123",
channel_id="msteams",
Expand All @@ -18,6 +20,7 @@ def _create_conversation_reference(user: ChannelAccount | None = None):
user=user if user is not None else ChannelAccount(id="user-123", name="User"),
agent=ChannelAccount(id="agent-123", name="Agent"),
locale="en-US",
request_id=request_id,
)


Expand Down Expand Up @@ -47,6 +50,22 @@ def test_get_continuation_activity_generates_new_id_each_time():
assert first_activity.id != second_activity.id


def test_get_continuation_activity_propagates_request_id():
conversation_reference = _create_conversation_reference(request_id="request-123")

continuation_activity = conversation_reference.get_continuation_activity()

assert continuation_activity.request_id == "request-123"


def test_get_continuation_activity_request_id_defaults_to_none():
conversation_reference = _create_conversation_reference()

continuation_activity = conversation_reference.get_continuation_activity()

assert continuation_activity.request_id is None


def test_get_continuation_activity_omits_sender_when_user_is_missing():
conversation_reference = _create_conversation_reference(user=None)
conversation_reference.user = None
Expand Down
3 changes: 3 additions & 0 deletions tests/hosting_core/telemetry/test_http_adapter_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
which metrics fire for each code path through process_request.
"""

import uuid

import pytest
from unittest.mock import AsyncMock, MagicMock
from opentelemetry import trace

from microsoft_agents.activity import Activity
from microsoft_agents.hosting.core import HttpAdapterBase
from microsoft_agents.hosting.core.telemetry.adapter import constants
from microsoft_agents.hosting.core.telemetry import attributes
Expand Down
37 changes: 37 additions & 0 deletions tests/hosting_core/test_turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ def test_get_conversation_reference_should_return_valid_reference(self):
assert reference.channel_id == ACTIVITY.channel_id
assert reference.locale == ACTIVITY.locale
assert reference.service_url == ACTIVITY.service_url
assert reference.request_id == ACTIVITY.request_id

def test_apply_conversation_reference_should_return_prepare_reply_when_is_incoming_is_false(
self,
Expand Down Expand Up @@ -419,6 +420,42 @@ def test_apply_conversation_reference_when_is_incoming_is_true_should_not_prepar
assert reply.service_url == ACTIVITY.service_url
assert reply.channel_id == ACTIVITY.channel_id

def test_apply_conversation_reference_propagates_request_id_when_outgoing(
self,
):
reference = ACTIVITY.get_conversation_reference()
reference.request_id = "request-123"

reply = TurnContext.apply_conversation_reference(
Activity(type="message", text="reply"), reference
)

assert reply.request_id == "request-123"

def test_apply_conversation_reference_propagates_request_id_when_incoming(
self,
):
reference = ACTIVITY.get_conversation_reference()
reference.request_id = "request-123"

reply = TurnContext.apply_conversation_reference(
Activity(type="message", text="reply"), reference, True
)

assert reply.request_id == "request-123"

def test_apply_conversation_reference_clears_request_id_when_reference_has_none(
self,
):
reference = ACTIVITY.get_conversation_reference()
assert reference.request_id is None

reply = TurnContext.apply_conversation_reference(
Activity(type="message", text="reply", request_id="stale-id"), reference
)

assert reply.request_id is None

@pytest.mark.asyncio
async def test_should_get_conversation_reference_using_get_reply_conversation_reference(
self,
Expand Down