Skip to content

Commit c07a8cd

Browse files
JianHJimh333
andauthored
[Observability]Add agent frame observation extension (#12)
* Add af extension * reformat * clean up unsused import * Clean up * reformat * uv run * update setup versioning format * add integration test for agent framework * Fix integration test * format * clean up * change dependency version * update copyright * move process logic to onstart * reformat * add env setting info to readme * update readme --------- Co-authored-by: Jian Han <jianhan@microsoft.com>
1 parent e2df634 commit c07a8cd

11 files changed

Lines changed: 601 additions & 4 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# microsoft-agents-a365-observability-extensions-agentframework
2+
3+
## Overview
4+
5+
This package provides Agent365 SDK observability extensions for the agent framework.
6+
7+
## Features
8+
9+
- Seamless integration with the Agent Framework
10+
- Supports OpenTelemetry (OTEL) for distributed tracing
11+
- Optional capture of sensitive data for deeper diagnostics
12+
- Easy configuration via environment variables
13+
14+
To allow the A365 observability SDK to capture input and output messages, please enable the flags in environment:
15+
ENABLE_OTEL=true
16+
ENABLE_SENSITIVE_DATA=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
# Custom Span Processor
5+
6+
from opentelemetry.sdk.trace.export import SpanProcessor
7+
8+
from microsoft_agents_a365.observability.core.constants import (
9+
GEN_AI_OPERATION_NAME_KEY,
10+
EXECUTE_TOOL_OPERATION_NAME,
11+
GEN_AI_EVENT_CONTENT,
12+
)
13+
14+
15+
class AgentFrameworkSpanProcessor(SpanProcessor):
16+
"""
17+
SpanProcessor for Agent Framework.
18+
"""
19+
20+
TOOL_CALL_RESULT_TAG = "gen_ai.tool.call.result"
21+
22+
def __init__(self, service_name: str | None = None):
23+
self.service_name = service_name
24+
super().__init__()
25+
26+
def on_start(self, span, parent_context):
27+
if hasattr(span, "attributes"):
28+
operation_name = span.attributes.get(GEN_AI_OPERATION_NAME_KEY)
29+
if isinstance(operation_name, str) and operation_name == EXECUTE_TOOL_OPERATION_NAME:
30+
tool_call_result = span.attributes.get(self.TOOL_CALL_RESULT_TAG)
31+
if tool_call_result is not None:
32+
span.set_attribute(GEN_AI_EVENT_CONTENT, tool_call_result)
33+
34+
def on_end(self, span):
35+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
from __future__ import annotations
5+
6+
from collections.abc import Collection
7+
from typing import Any
8+
9+
from microsoft_agents_a365.observability.core.config import get_tracer_provider, is_configured
10+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
11+
12+
from microsoft_agents_a365.observability.extensions.agentframework.span_processor import (
13+
AgentFrameworkSpanProcessor,
14+
)
15+
16+
# -----------------------------
17+
# 3) The Instrumentor class
18+
# -----------------------------
19+
_instruments = ("agent-framework-azure-ai >= 0.1.0",)
20+
21+
22+
class AgentFrameworkInstrumentor(BaseInstrumentor):
23+
"""
24+
Instruments Agent Framework:
25+
• Installs your custom OTel SpanProcessor
26+
"""
27+
28+
def __init__(self):
29+
if not is_configured():
30+
raise RuntimeError(
31+
"Agent365 (or your telemetry config) is not initialized. Configure it before instrumenting."
32+
)
33+
super().__init__()
34+
35+
def instrumentation_dependencies(self) -> Collection[str]:
36+
return _instruments
37+
38+
def _instrument(self, **kwargs: Any) -> None:
39+
"""
40+
kwargs (all optional):
41+
"""
42+
43+
# Ensure we have an SDK TracerProvider
44+
provider = get_tracer_provider()
45+
self._processor = AgentFrameworkSpanProcessor()
46+
provider.add_span_processor(self._processor)
47+
48+
def _uninstrument(self, **kwargs: Any) -> None:
49+
pass
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
[build-system]
2+
requires = ["setuptools>=68", "wheel", "tzdata"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "microsoft-agents-a365-observability-extensions-agent-framework"
7+
dynamic = ["version"]
8+
authors = [
9+
{ name = "Microsoft", email = "support@microsoft.com" },
10+
]
11+
description = "Agent Framework observability and tracing extensions for Microsoft Agents A365"
12+
readme = "README.md"
13+
requires-python = ">=3.11"
14+
classifiers = [
15+
"Development Status :: 3 - Alpha",
16+
"Intended Audience :: Developers",
17+
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3.11",
19+
"Programming Language :: Python :: 3.12",
20+
"Operating System :: OS Independent",
21+
"Topic :: Software Development :: Libraries :: Python Modules",
22+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
23+
"Topic :: System :: Monitoring",
24+
]
25+
license = "MIT"
26+
keywords = ["observability", "telemetry", "tracing", "opentelemetry", "agent-framework", "agents", "ai"]
27+
dependencies = [
28+
"microsoft-agents-a365-observability-core >= 0.1.0",
29+
"opentelemetry-api >= 1.36.0",
30+
"opentelemetry-sdk >= 1.36.0",
31+
"opentelemetry-instrumentation >= 0.47b0",
32+
]
33+
34+
[project.urls]
35+
Homepage = "https://github.com/microsoft/Agent365"
36+
Repository = "https://github.com/microsoft/Agent365"
37+
Issues = "https://github.com/microsoft/Agent365/issues"
38+
Documentation = "https://github.com/microsoft/Agent365-python/tree/main/libraries/microsoft-agents-a365-observability-extensions-agentframework"
39+
40+
[project.optional-dependencies]
41+
dev = [
42+
"pytest >= 7.0.0",
43+
"pytest-asyncio >= 0.21.0",
44+
"ruff >= 0.1.0",
45+
"black >= 23.0.0",
46+
"mypy >= 1.0.0",
47+
]
48+
test = [
49+
"pytest >= 7.0.0",
50+
"pytest-asyncio >= 0.21.0",
51+
]
52+
53+
[tool.setuptools.packages.find]
54+
where = ["."]
55+
56+
[tool.setuptools]
57+
license-files = ["../../LICENSE"]
58+
include-package-data = true
59+
60+
[tool.setuptools.package-data]
61+
"*" = ["../../LICENSE"]
62+
63+
[tool.black]
64+
line-length = 100
65+
target-version = ['py311']
66+
67+
[tool.ruff]
68+
line-length = 100
69+
target-version = "py311"
70+
71+
[tool.mypy]
72+
python_version = "3.11"
73+
strict = true
74+
warn_return_any = true
75+
warn_unused_configs = true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
from os import environ
5+
from setuptools import setup
6+
7+
# Get version from environment variable set by CI/CD
8+
# This will be set by setuptools-git-versioning in the CI pipeline
9+
package_version = environ.get("AGENT365_PYTHON_SDK_PACKAGE_VERSION", "0.0.0")
10+
11+
setup(
12+
version=package_version,
13+
)

‎pyproject.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"libraries/microsoft-agents-a365-observability-extensions-langchain",
1010
"libraries/microsoft-agents-a365-observability-extensions-openai",
1111
"libraries/microsoft-agents-a365-observability-extensions-semantickernel",
12+
"libraries/microsoft-agents-a365-observability-extensions-agentframework",
1213
"libraries/microsoft-agents-a365-runtime",
1314
"libraries/microsoft-agents-a365-tooling",
1415
"libraries/microsoft-agents-a365-tooling-extensions-azureaifoundry",
@@ -37,6 +38,7 @@ dev-dependencies = [
3738
"ruff>=0.1.0",
3839
"python-dotenv>=1.0.0",
3940
"openai>=1.0.0",
41+
"agent-framework-azure-ai >= 0.1.0",
4042
"azure-identity>=1.12.0",
4143
"openai-agents >= 0.2.6",
4244
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
"""Integration tests for Agent365 Python SDK."""
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
import os
5+
from pathlib import Path
6+
from typing import Any
7+
8+
import pytest
9+
10+
# Load .env file if it exists (for local development)
11+
try:
12+
from dotenv import load_dotenv
13+
14+
# Look for .env file in tests directory
15+
# Navigate from conftest.py location: integration -> openai -> extensions -> observability -> tests
16+
current_file = Path(__file__)
17+
tests_dir = current_file.parent.parent.parent.parent.parent # Go up to tests/ directory
18+
env_file = tests_dir / ".env"
19+
if env_file.exists():
20+
load_dotenv(env_file)
21+
except ImportError:
22+
# python-dotenv not installed, skip loading .env file
23+
pass
24+
25+
26+
def pytest_configure(config):
27+
"""Add integration marker."""
28+
config.addinivalue_line("markers", "integration: marks tests as integration tests")
29+
30+
31+
@pytest.fixture(scope="session")
32+
def azure_openai_config() -> dict[str, Any]:
33+
"""Azure OpenAI configuration for integration tests."""
34+
api_key = os.getenv("AZURE_OPENAI_API_KEY")
35+
endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
36+
deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-4")
37+
api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2024-08-01-preview")
38+
39+
if not api_key or not endpoint:
40+
pytest.skip("Integration tests require AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT")
41+
42+
return {
43+
"api_key": api_key,
44+
"endpoint": endpoint,
45+
"deployment": deployment,
46+
"api_version": api_version,
47+
}
48+
49+
50+
@pytest.fixture(scope="session")
51+
def agent365_config() -> dict[str, Any]:
52+
"""Agent365 configuration for integration tests."""
53+
tenant_id = os.getenv("AGENT365_TEST_TENANT_ID", "4d44f041-f91e-4d00-b107-61e47b26f5a8")
54+
agent_id = os.getenv("AGENT365_TEST_AGENT_ID", "3bccd52b-daaa-4b11-af40-47443852137c")
55+
56+
if not tenant_id:
57+
pytest.skip("Integration tests require AGENT365_TEST_TENANT_ID")
58+
59+
return {"tenant_id": tenant_id, "agent_id": agent_id}

0 commit comments

Comments
 (0)