Skip to content

Conversation

@nikhilNava
Copy link
Contributor

@nikhilNava nikhilNava commented Jan 13, 2026

Task
Add support to input and output scopes using agents SDK middleware

Solution

{
    "name": "Chat gpt-4o-mini",
    "context": {
        "trace_id": "0xa581c4548fc344b570e66489b84021ee",
        "span_id": "0x0c75b40a84bb067b",
        "trace_state": "[]"
    },
    "kind": "SpanKind.CLIENT",
    "parent_id": "0xd3a36144fee5f4e0",
    "start_time": "2026-01-13T15:01:01.883979Z",
    "end_time": "2026-01-13T15:01:04.351775Z",
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {
        "operation.source": "SDK",
        "correlation.id": "7ff6dca0-917c-4bb0-b31a-794e533d8aad",
        "gen_ai.system": "az.ai.agent365",
        "gen_ai.agent.id": "a70248c0-9daa-4b4a-a498-b3c95c8422a5",
        "gen_ai.agent.name": "Azure OpenAI Agent",
        "gen_ai.agent.description": "An AI agent powered by Azure OpenAI",        
        "gen_ai.agent.applicationid": "4a380e3b-7092-4d73-bb9d-b6a54702684af",    
        "gen_ai.conversation.id": "__PERSONAL_CHAT_ID__",
        "tenant.id": "badf1f56-284d-4dc5-ac59-0dd53900e743",
        "gen_ai.input.messages": "hi, what can you do",
        "gen_ai.operation.name": "Chat",
        "gen_ai.request.model": "gpt-4o-mini",
        "gen_ai.provider.name": "Azure OpenAI",
        "gen_ai.output.messages": "\"Hello! I can assist you with a variety of tasks, including answering questions, providing information, helping with problem-solving, offering writing suggestions, and more. Just let me know what you need help with!\"",
        "gen_ai.usage.input_tokens": "33",
        "gen_ai.usage.output_tokens": "42",
        "gen_ai.response.finish_reasons": "[\"stop\"]"
    },
    "events": [],
    "links": [],
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.38.0",
            "service.namespace": "AzureOpenAiKairoTesting",
            "service.name": "AzureOpenAiKairoTracing"
        },
        "schema_url": ""
    }
}
{
    "name": "output_messages [email protected]",  
    "context": {
        "trace_id": "0xa581c4548fc344b570e66489b84021ee",
        "span_id": "0x618d9b8326ca7feb",
        "trace_state": "[]"
    },
    "kind": "SpanKind.CLIENT",
    "parent_id": "0xd3a36144fee5f4e0",
    "start_time": "2026-01-13T15:01:04.354286Z",
    "end_time": "2026-01-13T15:01:04.354286Z",
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {
        "operation.source": "SDK",
        "correlation.id": "7ff6dca0-917c-4bb0-b31a-794e533d8aad",
        "gen_ai.system": "az.ai.agent365",
        "gen_ai.operation.name": "output_messages",
        "gen_ai.agent.id": "[email protected]",   
        "gen_ai.agent.name": "Perplexity02 Agent",
        "gen_ai.conversation.id": "__PERSONAL_CHAT_ID__",
        "tenant.id": "00000000-0000-0000-0000-0000000000001",
        "gen_ai.output.messages": "[\"Hello! I can assist you with a variety of tasks, including answering questions, providing information, helping with problem-solving, offering writing suggestions, and more. Just let me know what you need help with!\"]"
    },
    "events": [],
    "links": [],
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.38.0",
            "service.namespace": "AzureOpenAiKairoTesting",
            "service.name": "AzureOpenAiKairoTracing"
        },
        "schema_url": ""
    }
}
{
    "name": "invoke_agent Azure OpenAI Agent",
    "context": {
        "trace_id": "0xa581c4548fc344b570e66489b84021ee",
        "span_id": "0xd3a36144fee5f4e0",
        "trace_state": "[]"
    },
    "kind": "SpanKind.CLIENT",
    "parent_id": "0xefe4a583978bb0b9",
    "start_time": "2026-01-13T15:01:00.051411Z",
    "end_time": "2026-01-13T15:01:04.390141Z",
    "status": {
        "status_code": "ERROR",
        "description": "404, message='Not Found', url='http://localhost:56150/_connector/v3/conversations/__PERSONAL_CHAT_ID__/activities/17152ca3-e58e-4cd1-85ad-1918dbfb5ca3'"
    },
    "attributes": {
        "operation.source": "SDK",
        "correlation.id": "7ff6dca0-917c-4bb0-b31a-794e533d8aad",
        "gen_ai.caller.id": "a92962f3-9ed4-4bcd-9ae0-ad0002b6ca76",
        "gen_ai.caller.name": "Alex Wilber",
        "gen_ai.caller.upn": "Sample UPN",
        "gen_ai.system": "az.ai.agent365",
        "gen_ai.operation.name": "invoke_agent",
        "gen_ai.agent.id": "a70248c0-9daa-4b4a-a498-b3c95c8422a5",
        "gen_ai.agent.name": "Azure OpenAI Agent",
        "gen_ai.agent.description": "An AI agent powered by Azure OpenAI",        
        "gen_ai.agent.applicationid": "4a380e3b-7092-4d73-bb9d-b6a54702684af",    
        "gen_ai.conversation.id": "__PERSONAL_CHAT_ID__",
        "tenant.id": "badf1f56-284d-4dc5-ac59-0dd53900e743",
        "session.id": "__PERSONAL_CHAT_ID__",
        "gen_ai.execution.type": "HumanToAgent",
        "gen_ai.input.messages": "[\"hi, what can you do\"]",
        "gen_ai.output.messages": "[\"Hello! I can assist you with a variety of tasks, including answering questions, providing information, helping with problem-solving, offering writing suggestions, and more. Just let me know what you need help with!\"]",
        "error.type": "ClientResponseError"
    },
    "events": [
        {
            "name": "exception",
            "timestamp": "2026-01-13T15:01:04.390141Z",
            "attributes": {
                "exception.type": "aiohttp.client_exceptions.ClientResponseError",
                "exception.message": "404, message='Not Found', url='http://localhost:56150/_connector/v3/conversations/__PERSONAL_CHAT_ID__/activities/17152ca3-e58e-4cd1-85ad-1918dbfb5ca3'",
                "exception.stacktrace": "Traceback (most recent call last):\n  File \"C:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\src\\agent.py\", line 156, in on_message\n    await context.send_activity(ai_response)\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\microsoft_agents\\hosting\\core\\turn_context.py\", line 210, in send_activity\n    result = await self.send_activities([activity_or_text])\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\microsoft_agents\\hosting\\core\\turn_context.py\", line 263, in send_activities\n    return await self._emit(self._on_send_activities, output, logic())\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\microsoft_agents\\hosting\\core\\turn_context.py\", line 341, in _emit\n    return await logic\n           ^^^^^^^^^^^\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\microsoft_agents\\hosting\\core\\turn_context.py\", line 258, in logic\n    responses = await self.adapter.send_activities(self, output)\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\microsoft_agents\\hosting\\core\\channel_service_adapter.py\", line 103, in send_activities\n    response = await connector_client.conversations.reply_to_activity(\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\microsoft_agents\\hosting\\core\\connector\\client\\connector_client.py\", line 220, in reply_to_activity\n    response.raise_for_status()\n  File \"c:\\Users\\nikhilc\\repos\\Agent365SDK\\Agent365-python\\getting_started_with_kairo_exporter\\.venv\\Lib\\site-packages\\aiohttp\\client_reqrep.py\", line 636, in raise_for_status\n    raise ClientResponseError(\naiohttp.client_exceptions.ClientResponseError: 404, message='Not Found', url='http://localhost:56150/_connector/v3/conversations/__PERSONAL_CHAT_ID__/activities/17152ca3-e58e-4cd1-85ad-1918dbfb5ca3'\n",
                "exception.escaped": "False"
            }
        }
    ],
    "links": [],
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.38.0",
            "service.namespace": "AzureOpenAiKairoTesting",
            "service.name": "AzureOpenAiKairoTracing"
        },
        "schema_url": ""
    }
}
{
    "name": "output_messages [email protected]",  
    "context": {
        "trace_id": "0xa581c4548fc344b570e66489b84021ee",
        "span_id": "0xd60a73542f90477d",
        "trace_state": "[]"
    },
    "kind": "SpanKind.CLIENT",
    "parent_id": "0xefe4a583978bb0b9",
    "start_time": "2026-01-13T15:01:04.393131Z",
    "end_time": "2026-01-13T15:01:04.393131Z",
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {
        "operation.source": "SDK",
        "correlation.id": "7ff6dca0-917c-4bb0-b31a-794e533d8aad",
        "gen_ai.system": "az.ai.agent365",
        "gen_ai.operation.name": "output_messages",
        "gen_ai.agent.id": "[email protected]",   
        "gen_ai.agent.name": "Perplexity02 Agent",
        "gen_ai.conversation.id": "__PERSONAL_CHAT_ID__",
        "tenant.id": "00000000-0000-0000-0000-0000000000001",
        "gen_ai.output.messages": "[\"I encountered an error while processing your message. Please try again.\"]"
    },
    "events": [],
    "links": [],
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.38.0",
            "service.namespace": "AzureOpenAiKairoTesting",
            "service.name": "AzureOpenAiKairoTracing"
        },
        "schema_url": ""
    }
}
{
    "name": "input_messages [email protected]",   
    "context": {
        "trace_id": "0xa581c4548fc344b570e66489b84021ee",
        "span_id": "0xefe4a583978bb0b9",
        "trace_state": "[]"
    },
    "kind": "SpanKind.CLIENT",
    "parent_id": null,
    "start_time": "2026-01-13T15:00:58.219919Z",
    "end_time": "2026-01-13T15:01:04.404908Z",
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {
        "operation.source": "SDK",
        "gen_ai.system": "az.ai.agent365",
        "gen_ai.input.messages": "[\"hi, what can you do\"]"
    },
    "events": [],
    "links": [],
    "resource": {
        "attributes": {
            "telemetry.sdk.language": "python",
            "telemetry.sdk.name": "opentelemetry",
            "telemetry.sdk.version": "1.38.0",
            "service.namespace": "AzureOpenAiKairoTesting",
            "service.name": "AzureOpenAiKairoTracing"
        },
        "schema_url": ""
    }
}

Copilot AI review requested due to automatic review settings January 13, 2026 15:04
@nikhilNava nikhilNava requested a review from a team as a code owner January 13, 2026 15:04
@nikhilNava nikhilNava changed the title Add support for input and output scopes WIP | Add support for input and output scopes Jan 13, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces observability support for tracking input and output message flows in agent conversations. The changes add OpenTelemetry-based tracing capabilities through new scope classes and middleware that automatically capture and log message exchanges.

Changes:

  • Added InputScope and OutputScope classes for OpenTelemetry tracing of message flows
  • Created MessageLoggingMiddleware to intercept and log user and bot messages
  • Introduced ObservabilityMiddlewareRegistrar for fluent middleware configuration
  • Added Response model to encapsulate output message data

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
observability_middleware_registrar.py Provides a fluent API for registering observability middleware with CloudAdapter
message_logging_middleware.py Implements middleware that creates input/output telemetry scopes and logs messages
input_scope.py OpenTelemetry scope class for tracing input messages from users to agents
output_scope.py OpenTelemetry scope class for tracing output messages from agents to users
response.py Data model for agent response messages
__init__.py (2 files) Package initialization files with copyright headers

Comment on lines +45 to +67
if self.log_user_messages and turn_context.activity.text:
input_scope = self._create_input_scope(turn_context.activity)
input_scope.__enter__()
self.logger.info(f"📥 User input message: {turn_context.activity.text}")

try:
# Hook into outgoing messages
if self.log_bot_messages:
# Pass activity to handler so we can create agent/tenant details
turn_context.on_send_activities(self._create_send_handler(turn_context.activity))

# Execute bot logic
await logic()
except Exception as exc:
# Clean up and propagate exception (let __exit__ handle error recording)
if input_scope:
input_scope.__exit__(type(exc), exc, exc.__traceback__)
input_scope = None # Prevent double cleanup
raise
finally:
# Clean up the input scope if not already done
if input_scope:
input_scope.__exit__(None, None, None)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method manually calls enter and exit on the InputScope context manager, which is an unusual pattern and potentially error-prone. The standard pattern would be to use a 'with' statement. However, if manual control is required due to the async execution flow, this should be documented with a comment explaining why the standard context manager pattern cannot be used here.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency in copyright headers across the codebase. Most existing files use 'Copyright (c) Microsoft. All rights reserved.' but the new files use 'Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.' While both are valid, this inconsistency could cause confusion. Consider aligning with the existing codebase standard.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency in copyright headers across the codebase. Most existing files use 'Copyright (c) Microsoft. All rights reserved.' but the new files use 'Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.' While both are valid, this inconsistency could cause confusion. Consider aligning with the existing codebase standard.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency in copyright headers across the codebase. Most existing files use 'Copyright (c) Microsoft. All rights reserved.' but the new files use 'Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.' While both are valid, this inconsistency could cause confusion. Consider aligning with the existing codebase standard.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +65
def record_output_messages(self, messages: list[str]) -> None:
"""Records the output messages for telemetry tracking.

Args:
messages: List of output messages
"""
self.set_tag_maybe(GEN_AI_OUTPUT_MESSAGES_KEY, safe_json_dumps(messages))
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method record_output_messages allows updating output messages after the scope has been created, but there's no documentation explaining when and why this method should be used versus passing the messages during initialization. The Response object is already passed in init, so the purpose of this additional method is unclear. Consider adding documentation to explain the use case, or removing this method if it's not needed.

Copilot uses AI. Check for mistakes.
with OutputScope.start(agent_details, tenant_details, response):
# Log each message
for message in messages:
self.logger.info(f"📤 Bot output message: {message}")
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The emoji usage in log messages ('📥' and '📤') may not render correctly in all logging environments and could cause issues with log parsing tools or text-based terminals. Consider using standard text prefixes like '[INPUT]' and '[OUTPUT]' instead, or make the emoji usage configurable.

Suggested change
self.logger.info(f"📤 Bot output message: {message}")
self.logger.info(f"[OUTPUT] Bot output message: {message}")

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency in copyright headers across the codebase. Most existing files use 'Copyright (c) Microsoft. All rights reserved.' but the new files use 'Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.' While both are valid, this inconsistency could cause confusion. Consider aligning with the existing codebase standard.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +78
"""Records the input messages for telemetry tracking.

Args:
messages: List of input messages
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method record_input_messages allows updating input messages after the scope has been created, but there's no documentation explaining when and why this method should be used versus passing the message during initialization. The Request object is already passed in init, so the purpose of this additional method is unclear. Consider adding documentation to explain the use case, or removing this method if it's not needed.

Suggested change
"""Records the input messages for telemetry tracking.
Args:
messages: List of input messages
"""Records or updates the input messages for telemetry tracking.
This method is intended for scenarios where the full set of input messages is
not yet available when the :class:`InputScope` is created, or when the initial
request content needs to be updated (for example, after normalization,
aggregation across multiple turns, or other preprocessing).
By default, the constructor records the initial input messages based on
``request.content``. Calling this method will overwrite the previously
recorded ``GEN_AI_INPUT_MESSAGES_KEY`` value with the provided ``messages``.
Args:
messages: The complete list of input messages to associate with this scope.

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +76
agent_details = AgentDetails(
agent_id=activity.recipient.id if activity.recipient else "unknown",
agent_name=activity.recipient.name if activity.recipient else None,
conversation_id=activity.conversation.id if activity.conversation else None,
)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The agent_id is set to "unknown" when activity.recipient is None, but the agent_name in the same case is set to None. This inconsistency could lead to confusion in logs and telemetry. Consider either setting both to "unknown" for consistency, or both to None to indicate missing data.

Copilot uses AI. Check for mistakes.

class MessageLoggingMiddleware(Middleware):
"""
Lightweight middleware for logging input and output messages.
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that the class provides message logging functionality, but it doesn't mention that it also creates OpenTelemetry spans through InputScope and OutputScope. This is a significant aspect of the middleware's behavior that should be documented, as it affects observability infrastructure and trace collection.

Suggested change
Lightweight middleware for logging input and output messages.
Lightweight middleware that logs input and output messages and creates
OpenTelemetry spans for these messages.
The middleware starts an ``InputScope`` for the turn when a user message
is present and uses an ``OutputScope`` for outgoing bot messages. This
behavior affects observability infrastructure and trace collection, in
addition to providing message logging.

Copilot uses AI. Check for mistakes.
class Response:
"""Response details from agent execution."""

messages: list[str]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should we name this Content to keep it similar to Request?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants