diff --git a/libraries/microsoft-agents-a365-observability-extensions-agentframework/README.md b/libraries/microsoft-agents-a365-observability-extensions-agentframework/README.md new file mode 100644 index 00000000..8cf5f263 --- /dev/null +++ b/libraries/microsoft-agents-a365-observability-extensions-agentframework/README.md @@ -0,0 +1,16 @@ +# microsoft-agents-a365-observability-extensions-agentframework + +## Overview + +This package provides Agent365 SDK observability extensions for the agent framework. + +## Features + +- Seamless integration with the Agent Framework +- Supports OpenTelemetry (OTEL) for distributed tracing +- Optional capture of sensitive data for deeper diagnostics +- Easy configuration via environment variables + +To allow the A365 observability SDK to capture input and output messages, please enable the flags in environment: +ENABLE_OTEL=true +ENABLE_SENSITIVE_DATA=true \ No newline at end of file diff --git a/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/__init__.py b/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/__init__.py new file mode 100644 index 00000000..59e481eb --- /dev/null +++ b/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. diff --git a/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/span_processor.py b/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/span_processor.py new file mode 100644 index 00000000..0b41b761 --- /dev/null +++ b/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/span_processor.py @@ -0,0 +1,35 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Custom Span Processor + +from opentelemetry.sdk.trace.export import SpanProcessor + +from microsoft_agents_a365.observability.core.constants import ( + GEN_AI_OPERATION_NAME_KEY, + EXECUTE_TOOL_OPERATION_NAME, + GEN_AI_EVENT_CONTENT, +) + + +class AgentFrameworkSpanProcessor(SpanProcessor): + """ + SpanProcessor for Agent Framework. + """ + + TOOL_CALL_RESULT_TAG = "gen_ai.tool.call.result" + + def __init__(self, service_name: str | None = None): + self.service_name = service_name + super().__init__() + + def on_start(self, span, parent_context): + if hasattr(span, "attributes"): + operation_name = span.attributes.get(GEN_AI_OPERATION_NAME_KEY) + if isinstance(operation_name, str) and operation_name == EXECUTE_TOOL_OPERATION_NAME: + tool_call_result = span.attributes.get(self.TOOL_CALL_RESULT_TAG) + if tool_call_result is not None: + span.set_attribute(GEN_AI_EVENT_CONTENT, tool_call_result) + + def on_end(self, span): + pass diff --git a/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/trace_instrumentor.py b/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/trace_instrumentor.py new file mode 100644 index 00000000..ad7d82fa --- /dev/null +++ b/libraries/microsoft-agents-a365-observability-extensions-agentframework/microsoft_agents_a365/observability/extensions/agentframework/trace_instrumentor.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from __future__ import annotations + +from collections.abc import Collection +from typing import Any + +from microsoft_agents_a365.observability.core.config import get_tracer_provider, is_configured +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor + +from microsoft_agents_a365.observability.extensions.agentframework.span_processor import ( + AgentFrameworkSpanProcessor, +) + +# ----------------------------- +# 3) The Instrumentor class +# ----------------------------- +_instruments = ("agent-framework-azure-ai >= 0.1.0",) + + +class AgentFrameworkInstrumentor(BaseInstrumentor): + """ + Instruments Agent Framework: + • Installs your custom OTel SpanProcessor + """ + + def __init__(self): + if not is_configured(): + raise RuntimeError( + "Agent365 (or your telemetry config) is not initialized. Configure it before instrumenting." + ) + super().__init__() + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs: Any) -> None: + """ + kwargs (all optional): + """ + + # Ensure we have an SDK TracerProvider + provider = get_tracer_provider() + self._processor = AgentFrameworkSpanProcessor() + provider.add_span_processor(self._processor) + + def _uninstrument(self, **kwargs: Any) -> None: + pass diff --git a/libraries/microsoft-agents-a365-observability-extensions-agentframework/pyproject.toml b/libraries/microsoft-agents-a365-observability-extensions-agentframework/pyproject.toml new file mode 100644 index 00000000..fe1aa3ee --- /dev/null +++ b/libraries/microsoft-agents-a365-observability-extensions-agentframework/pyproject.toml @@ -0,0 +1,75 @@ +[build-system] +requires = ["setuptools>=68", "wheel", "tzdata"] +build-backend = "setuptools.build_meta" + +[project] +name = "microsoft-agents-a365-observability-extensions-agent-framework" +dynamic = ["version"] +authors = [ + { name = "Microsoft", email = "support@microsoft.com" }, +] +description = "Agent Framework observability and tracing extensions for Microsoft Agents A365" +readme = "README.md" +requires-python = ">=3.11" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: System :: Monitoring", +] +license = "MIT" +keywords = ["observability", "telemetry", "tracing", "opentelemetry", "agent-framework", "agents", "ai"] +dependencies = [ + "microsoft-agents-a365-observability-core >= 0.1.0", + "opentelemetry-api >= 1.36.0", + "opentelemetry-sdk >= 1.36.0", + "opentelemetry-instrumentation >= 0.47b0", +] + +[project.urls] +Homepage = "https://github.com/microsoft/Agent365" +Repository = "https://github.com/microsoft/Agent365" +Issues = "https://github.com/microsoft/Agent365/issues" +Documentation = "https://github.com/microsoft/Agent365-python/tree/main/libraries/microsoft-agents-a365-observability-extensions-agentframework" + +[project.optional-dependencies] +dev = [ + "pytest >= 7.0.0", + "pytest-asyncio >= 0.21.0", + "ruff >= 0.1.0", + "black >= 23.0.0", + "mypy >= 1.0.0", +] +test = [ + "pytest >= 7.0.0", + "pytest-asyncio >= 0.21.0", +] + +[tool.setuptools.packages.find] +where = ["."] + +[tool.setuptools] +license-files = ["../../LICENSE"] +include-package-data = true + +[tool.setuptools.package-data] +"*" = ["../../LICENSE"] + +[tool.black] +line-length = 100 +target-version = ['py311'] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.mypy] +python_version = "3.11" +strict = true +warn_return_any = true +warn_unused_configs = true \ No newline at end of file diff --git a/libraries/microsoft-agents-a365-observability-extensions-agentframework/setup.py b/libraries/microsoft-agents-a365-observability-extensions-agentframework/setup.py new file mode 100644 index 00000000..a04ba9a5 --- /dev/null +++ b/libraries/microsoft-agents-a365-observability-extensions-agentframework/setup.py @@ -0,0 +1,13 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from os import environ +from setuptools import setup + +# Get version from environment variable set by CI/CD +# This will be set by setuptools-git-versioning in the CI pipeline +package_version = environ.get("AGENT365_PYTHON_SDK_PACKAGE_VERSION", "0.0.0") + +setup( + version=package_version, +) diff --git a/pyproject.toml b/pyproject.toml index 802d2989..39d67077 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ members = [ "libraries/microsoft-agents-a365-observability-extensions-langchain", "libraries/microsoft-agents-a365-observability-extensions-openai", "libraries/microsoft-agents-a365-observability-extensions-semantickernel", + "libraries/microsoft-agents-a365-observability-extensions-agentframework", "libraries/microsoft-agents-a365-runtime", "libraries/microsoft-agents-a365-tooling", "libraries/microsoft-agents-a365-tooling-extensions-azureaifoundry", @@ -37,6 +38,7 @@ dev-dependencies = [ "ruff>=0.1.0", "python-dotenv>=1.0.0", "openai>=1.0.0", + "agent-framework-azure-ai >= 0.1.0", "azure-identity>=1.12.0", "openai-agents >= 0.2.6", ] diff --git a/tests/observability/extensions/agentframework/integration/__init__.py b/tests/observability/extensions/agentframework/integration/__init__.py new file mode 100644 index 00000000..f50dabf5 --- /dev/null +++ b/tests/observability/extensions/agentframework/integration/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +"""Integration tests for Agent365 Python SDK.""" diff --git a/tests/observability/extensions/agentframework/integration/conftest.py b/tests/observability/extensions/agentframework/integration/conftest.py new file mode 100644 index 00000000..96362ac1 --- /dev/null +++ b/tests/observability/extensions/agentframework/integration/conftest.py @@ -0,0 +1,59 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import os +from pathlib import Path +from typing import Any + +import pytest + +# Load .env file if it exists (for local development) +try: + from dotenv import load_dotenv + + # Look for .env file in tests directory + # Navigate from conftest.py location: integration -> openai -> extensions -> observability -> tests + current_file = Path(__file__) + tests_dir = current_file.parent.parent.parent.parent.parent # Go up to tests/ directory + env_file = tests_dir / ".env" + if env_file.exists(): + load_dotenv(env_file) +except ImportError: + # python-dotenv not installed, skip loading .env file + pass + + +def pytest_configure(config): + """Add integration marker.""" + config.addinivalue_line("markers", "integration: marks tests as integration tests") + + +@pytest.fixture(scope="session") +def azure_openai_config() -> dict[str, Any]: + """Azure OpenAI configuration for integration tests.""" + api_key = os.getenv("AZURE_OPENAI_API_KEY") + endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") + deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-4") + api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2024-08-01-preview") + + if not api_key or not endpoint: + pytest.skip("Integration tests require AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT") + + return { + "api_key": api_key, + "endpoint": endpoint, + "deployment": deployment, + "api_version": api_version, + } + + +@pytest.fixture(scope="session") +def agent365_config() -> dict[str, Any]: + """Agent365 configuration for integration tests.""" + tenant_id = os.getenv("AGENT365_TEST_TENANT_ID", "4d44f041-f91e-4d00-b107-61e47b26f5a8") + agent_id = os.getenv("AGENT365_TEST_AGENT_ID", "3bccd52b-daaa-4b11-af40-47443852137c") + + if not tenant_id: + pytest.skip("Integration tests require AGENT365_TEST_TENANT_ID") + + return {"tenant_id": tenant_id, "agent_id": agent_id} diff --git a/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py b/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py new file mode 100644 index 00000000..f3e319cb --- /dev/null +++ b/tests/observability/extensions/agentframework/integration/test_agentframework_trace_processor.py @@ -0,0 +1,297 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import time + +import pytest +from microsoft_agents_a365.observability.core import configure, get_tracer_provider +from microsoft_agents_a365.observability.core.constants import ( + GEN_AI_INPUT_MESSAGES_KEY, + GEN_AI_OUTPUT_MESSAGES_KEY, + GEN_AI_REQUEST_MODEL_KEY, + GEN_AI_SYSTEM_KEY, + TENANT_ID_KEY, +) +from microsoft_agents_a365.observability.extensions.agentframework.trace_instrumentor import ( + AgentFrameworkInstrumentor, +) + +# AgentFramework SDK +try: + from agent_framework.azure import AzureOpenAIChatClient + from agent_framework import ChatAgent, ai_function + from azure.identity import AzureCliCredential + from agent_framework.observability import setup_observability +except ImportError: + pytest.skip( + "AgentFramework library and dependencies required for integration tests", + allow_module_level=True, + ) + + +@ai_function +def add_numbers(a: float, b: float) -> float: + """Add two numbers together. + Args: + a: First number + b: Second number + Returns: + The sum of a and b + """ + return a + b + + +@pytest.mark.integration +class TestAgentFrameworkTraceProcessorIntegration: + """Integration tests for AgentFramework trace processor with real Azure OpenAI.""" + + def setup_method(self): + """Set up test method with mock exporter.""" + self.captured_spans = [] + self.mock_exporter = MockAgent365Exporter(self.captured_spans) + + def test_agentframework_trace_processor_integration(self, azure_openai_config, agent365_config): + """Test AgentFramework trace processor with real Azure OpenAI call.""" + + # Configure observability + configure( + service_name="integration-test-service", + service_namespace="agent365-tests", + logger_name="test-logger", + ) + + # Get the tracer provider and add our mock exporter + provider = get_tracer_provider() + provider.add_span_processor(self.mock_exporter) + + setup_observability() + + # Initialize the instrumentor + instrumentor = AgentFrameworkInstrumentor() + instrumentor.instrument() + + try: + # Create Azure OpenAI ChatClient + chat_client = AzureOpenAIChatClient( + endpoint=azure_openai_config["endpoint"], + credential=AzureCliCredential(), + deployment_name=azure_openai_config["deployment"], + api_version=azure_openai_config["api_version"], + ) + + # Create agent framework agent + agent = ChatAgent( + chat_client=chat_client, + instructions="You are a helpful assistant.", + tools=[], + ) + + # Execute a simple prompt using async runner + import asyncio + + async def run_agent(): + result = await agent.run("What can you do with agent framework?") + return result + + response = asyncio.run(run_agent()) + print(f"Agent response: {response}") + # Give some time for spans to be processed + time.sleep(1) + + # Verify that spans were captured + assert len(self.captured_spans) > 0, "No spans were captured" + + # Verify we have the expected span types + span_names = [span.name for span in self.captured_spans] + print(f"Captured spans: {span_names}") + + # Validate attributes on spans + self._validate_span_attributes(agent365_config) + + # Verify the response content + assert response is not None + assert len(response.text) > 0 + print(f"Agent response: {response.text}") + + finally: + # Clean up + instrumentor.uninstrument() + + def test_agentframework_trace_processor_with_tool_calls( + self, azure_openai_config, agent365_config + ): + """Test AgentFramework trace processor with tool calls.""" + + # Configure observability + configure( + service_name="integration-test-service-tools", + service_namespace="agent365-tests", + logger_name="test-logger", + ) + + # Get the tracer provider and add our mock exporter + provider = get_tracer_provider() + provider.add_span_processor(self.mock_exporter) + + setup_observability() + + # Initialize the instrumentor + instrumentor = AgentFrameworkInstrumentor() + instrumentor.instrument() + + try: + # Create Azure OpenAI ChatClient + chat_client = AzureOpenAIChatClient( + endpoint=azure_openai_config["endpoint"], + credential=AzureCliCredential(), + deployment_name=azure_openai_config["deployment"], + api_version=azure_openai_config["api_version"], + ) + + # Create agent framework agent + agent = ChatAgent( + chat_client=chat_client, + instructions="You are a helpful agent framework assistant.", + tools=[add_numbers], + ) + + # Execute a prompt that requires tool usage + import asyncio + + async def run_agent_with_tool(): + result = await agent.run("What is 15 + 27?") + return result + + response = asyncio.run(run_agent_with_tool()) + + # Give some time for spans to be processed + time.sleep(1) + + # Verify that spans were captured + assert len(self.captured_spans) > 0, "No spans were captured" + + # Verify we have the expected span types + span_names = [span.name for span in self.captured_spans] + print(f"Captured spans with tools: {span_names}") + + # Validate attributes on spans including tool calls + self._validate_tool_span_attributes(agent365_config) + + # Verify the response content includes the calculation result + assert response is not None + assert len(response.text) > 0 + assert "42" in response.text # 15 + 27 = 42 + print(f"Agent response with tool: {response.text}") + + finally: + # Clean up + instrumentor.uninstrument() + + def _validate_span_attributes(self, agent365_config): + """Validate that spans have the expected attributes.""" + llm_spans_found = 0 + agent_spans_found = 0 + + for span in self.captured_spans: + attributes = dict(span.attributes or {}) + print(f"Span '{span.name}' attributes: {list(attributes.keys())}") + + # Check common attributes + if TENANT_ID_KEY in attributes: + assert attributes[TENANT_ID_KEY] == agent365_config["tenant_id"] + + # Check for LLM spans (generation spans) + if GEN_AI_SYSTEM_KEY in attributes and attributes[GEN_AI_SYSTEM_KEY] == "openai": + if GEN_AI_REQUEST_MODEL_KEY in attributes: + llm_spans_found += 1 + # Validate LLM span attributes + assert GEN_AI_REQUEST_MODEL_KEY in attributes + assert attributes[GEN_AI_REQUEST_MODEL_KEY] is not None + print(f"✓ Found LLM span with model: {attributes[GEN_AI_REQUEST_MODEL_KEY]}") + + # Check for input/output messages + if GEN_AI_INPUT_MESSAGES_KEY in attributes: + input_messages = attributes[GEN_AI_INPUT_MESSAGES_KEY] + assert input_messages is not None + print(f"✓ Input messages found: {input_messages[:100]}...") + + if GEN_AI_OUTPUT_MESSAGES_KEY in attributes: + output_messages = attributes[GEN_AI_OUTPUT_MESSAGES_KEY] + assert output_messages is not None + print(f"✓ Output messages found: {output_messages[:100]}...") + + # Check for agent spans + if "agent" in span.name.lower(): + agent_spans_found += 1 + print(f"✓ Found agent span: {span.name}") + + # Ensure we found at least some spans with telemetry data + assert len(self.captured_spans) > 0, "No spans were captured" + print(f"✓ Captured {len(self.captured_spans)} spans total") + print(f"✓ Found {llm_spans_found} LLM spans and {agent_spans_found} agent spans") + + def _validate_tool_span_attributes(self, agent365_config): + """Validate that spans have the expected attributes including tool calls.""" + llm_spans_found = 0 + agent_spans_found = 0 + tool_spans_found = 0 + + for span in self.captured_spans: + attributes = dict(span.attributes or {}) + print(f"Span '{span.name}' attributes: {list(attributes.keys())}") + + # Check common attributes + if TENANT_ID_KEY in attributes: + assert attributes[TENANT_ID_KEY] == agent365_config["tenant_id"] + + # Check for LLM spans + if "chat" in span.name.lower(): + if GEN_AI_REQUEST_MODEL_KEY in attributes: + llm_spans_found += 1 + print(f"✓ Found LLM span with model: {attributes[GEN_AI_REQUEST_MODEL_KEY]}") + + # Check for tool calls in messages + if GEN_AI_OUTPUT_MESSAGES_KEY in attributes: + output_messages = attributes[GEN_AI_OUTPUT_MESSAGES_KEY] + if "tool_calls" in output_messages: + print("✓ Found tool calls in LLM output messages") + + # Check for agent spans + if "agent" in span.name.lower(): + agent_spans_found += 1 + print(f"✓ Found agent span: {span.name}") + + # Check for tool execution spans + if "execute_tool" in span.name.lower() or "add_numbers" in span.name.lower(): + tool_spans_found += 1 + print(f"✓ Found tool execution span: {span.name}") + + # Ensure we found the expected span types + assert len(self.captured_spans) > 0, "No spans were captured" + print(f"✓ Captured {len(self.captured_spans)} spans total") + print( + f"✓ Found {llm_spans_found} LLM spans, {agent_spans_found} agent spans, and {tool_spans_found} tool spans" + ) + + +class MockAgent365Exporter: + """Mock span processor that captures spans instead of sending them.""" + + def __init__(self, captured_spans): + self.captured_spans = captured_spans + + def on_start(self, span, parent_context=None): + """Called when a span starts.""" + pass + + def on_end(self, span): + """Called when a span ends.""" + self.captured_spans.append(span) + + def shutdown(self): + """Mock shutdown.""" + pass + + def force_flush(self, timeout_millis: int = 30000) -> bool: + """Mock force flush.""" + return True diff --git a/uv.lock b/uv.lock index 58b48ea7..ffb691a0 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,7 @@ resolution-markers = [ members = [ "microsoft-agents-a365-notifications", "microsoft-agents-a365-observability-core", + "microsoft-agents-a365-observability-extensions-agent-framework", "microsoft-agents-a365-observability-extensions-langchain", "microsoft-agents-a365-observability-extensions-openai", "microsoft-agents-a365-observability-extensions-semantic-kernel", @@ -24,6 +25,7 @@ members = [ [manifest.dependency-groups] dev = [ + { name = "agent-framework-azure-ai", specifier = ">=0.1.0" }, { name = "azure-identity", specifier = ">=1.12.0" }, { name = "openai", specifier = ">=1.0.0" }, { name = "openai-agents", specifier = ">=0.2.6" }, @@ -339,16 +341,16 @@ wheels = [ [[package]] name = "azure-ai-agents" -version = "1.2.0b6" +version = "1.2.0b5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/32/f4e534dc05dfb714705df56a190d690c5452cd4dd7e936612cb1adddc44f/azure_ai_agents-1.2.0b6.tar.gz", hash = "sha256:d3c10848c3b19dec98a292f8c10cee4ba4aac1050d4faabf9c2e2456b727f528", size = 396865, upload-time = "2025-10-24T18:04:47.877Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/57/8adeed578fa8984856c67b4229e93a58e3f6024417d448d0037aafa4ee9b/azure_ai_agents-1.2.0b5.tar.gz", hash = "sha256:1a16ef3f305898aac552269f01536c34a00473dedee0bca731a21fdb739ff9d5", size = 394876, upload-time = "2025-09-30T01:55:02.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/d0/930c522f5fa9da163de057e57f8b44539424e13f46618c52624ebc712293/azure_ai_agents-1.2.0b6-py3-none-any.whl", hash = "sha256:ce23ad8fb9791118905be1ec8eae5c907cca2e536a455f1d3b830062c72cf2a7", size = 217950, upload-time = "2025-10-24T18:04:49.72Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6d/15070d23d7a94833a210da09d5d7ed3c24838bb84f0463895e5d159f1695/azure_ai_agents-1.2.0b5-py3-none-any.whl", hash = "sha256:257d0d24a6bf13eed4819cfa5c12fb222e5908deafb3cbfd5711d3a511cc4e88", size = 217948, upload-time = "2025-09-30T01:55:04.155Z" }, ] [[package]] @@ -1583,6 +1585,9 @@ dev = [ { name = "pytest-asyncio" }, { name = "ruff" }, ] +jaeger = [ + { name = "opentelemetry-exporter-otlp-proto-grpc" }, +] test = [ { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1598,6 +1603,7 @@ requires-dist = [ { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, { name = "opentelemetry-api", specifier = ">=1.36.0" }, { name = "opentelemetry-exporter-otlp", specifier = ">=1.36.0" }, + { name = "opentelemetry-exporter-otlp-proto-grpc", marker = "extra == 'jaeger'", specifier = ">=1.36.0" }, { name = "opentelemetry-sdk", specifier = ">=1.36.0" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, @@ -1607,7 +1613,46 @@ requires-dist = [ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, { name = "typing-extensions", specifier = ">=4.0.0" }, ] -provides-extras = ["azure", "dev", "test"] +provides-extras = ["azure", "jaeger", "dev", "test"] + +[[package]] +name = "microsoft-agents-a365-observability-extensions-agent-framework" +source = { editable = "libraries/microsoft-agents-a365-observability-extensions-agentframework" } +dependencies = [ + { name = "microsoft-agents-a365-observability-core" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, + { name = "microsoft-agents-a365-observability-core", editable = "libraries/microsoft-agents-a365-observability-core" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, + { name = "opentelemetry-api", specifier = ">=1.36.0" }, + { name = "opentelemetry-instrumentation", specifier = ">=0.47b0" }, + { name = "opentelemetry-sdk", specifier = ">=1.36.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=7.0.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" }, + { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.21.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, +] +provides-extras = ["dev", "test"] [[package]] name = "microsoft-agents-a365-observability-extensions-langchain"