Skip to content
Draft
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
33 changes: 33 additions & 0 deletions examples/agent/microsoft-365-agents-sdk/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# --- Splunk AO: Scenario A — O11Y (lab0 / us0 / us1) ---
SPLUNK_AO_REALM=your-realm # e.g. lab0, us0, us1
SPLUNK_AO_O11Y_TOKEN=your-o11y-ingest-token
SPLUNK_AO_O11Y_API_TOKEN=your-o11y-api-token

# --- Splunk AO: Scenario B — Standalone (commented out) ---
# SPLUNK_AO_API_KEY=your-splunk-ao-api-key
# SPLUNK_AO_CONSOLE_URL=https://console.your-splunk-ao-instance.com

# --- Routing ---
SPLUNK_AO_PROJECT=your-project-name
SPLUNK_AO_AGENT_STREAM=your-agent-stream-name

# --- Azure OpenAI ---
AZURE_OPENAI_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
AZURE_OPENAI_API_KEY=your-azure-openai-api-key
AZURE_OPENAI_DEPLOYMENT=gpt-4o-mini
AZURE_OPENAI_API_VERSION=2024-12-01-preview

# --- OTel: capture LLM prompt/completion content in spans ---
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_ONLY

# --- Microsoft 365 Agents SDK auth ---
# Leave empty for local unauthenticated testing with npx @microsoft/m365agentsplayground.
# For Teams / Copilot, fill in credentials from your Azure App Registration:
# portal.azure.com → Entra ID → App registrations → <your app>
# Supported account types: "Accounts in any org directory + personal Microsoft accounts"
MicrosoftAppId=
MicrosoftAppPassword=
MicrosoftAppTenantId=

# --- macOS SSL (if needed) ---
# SSL_CERT_FILE=/opt/homebrew/etc/openssl@3/cert.pem
1 change: 1 addition & 0 deletions examples/agent/microsoft-365-agents-sdk/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry=https://registry.npmjs.org/
147 changes: 147 additions & 0 deletions examples/agent/microsoft-365-agents-sdk/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
"""
Microsoft 365 Agents SDK demo — MS A365 auto-instrumentation via OpenAIAgentsTraceInstrumentor.

Instrumentation:
- OpenAIAgentsTraceInstrumentor hooks into OpenAI Agents SDK tracing → emits gen_ai.* spans.
- InvokeAgentScope wraps each turn manually (no auto-instrumentor for M365 turn layer yet).
- All spans → OTLP → local collector.

Run: uv run python start_server.py
Test: npx @microsoft/m365agentsplayground
"""

import os

from dotenv import load_dotenv
from opentelemetry import context as otel_context

load_dotenv(override=False)

# --- MS A365 Observability SDK: configure first, then attach OTLP exporter ---
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from microsoft_agents_a365.observability.core import (
AgentDetails,
Channel,
InvokeAgentScope,
InvokeAgentScopeDetails,
Request,
SpanDetails,
configure as a365_configure,
get_tracer_provider as a365_get_tracer_provider,
)

a365_configure(service_name="m365-splunk-ao-demo", service_namespace="splunk.ao.demo")

_otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")
a365_get_tracer_provider().add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint=f"{_otlp_endpoint}/v1/traces"))
)

# --- Auto-instrumentation: hooks into OpenAI Agents SDK tracing ---
from microsoft_agents_a365.observability.extensions.openai import OpenAIAgentsTraceInstrumentor

OpenAIAgentsTraceInstrumentor().instrument()

# --- OpenAI Agents SDK (wraps Azure OpenAI) ---
from agents import Agent, Runner
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
from openai import AsyncAzureOpenAI

_deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o-mini")
_azure_client = AsyncAzureOpenAI(
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_API_KEY"],
api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview"),
)
_agent = Agent(
name="QAAgent",
instructions="You are a concise, helpful assistant.",
model=OpenAIChatCompletionsModel(model=_deployment, openai_client=_azure_client),
)

# --- Splunk AO Logger (optional — skipped without creds) ---
from splunk_ao import SplunkAOLogger

_ao_logger: SplunkAOLogger | None = None
if os.environ.get("SPLUNK_AO_API_KEY") or os.environ.get("SPLUNK_AO_REALM"):
_ao_logger = SplunkAOLogger()
else:
print("[splunk-ao] No credentials — otel-tui-only mode.")

# --- Microsoft 365 Agents SDK ---
from microsoft_agents.hosting.aiohttp import CloudAdapter
from microsoft_agents.hosting.core import AgentApplication, MemoryStorage, TurnContext, TurnState
from microsoft_agents.hosting.core.authorization import (
AgentAuthConfiguration,
AnonymousTokenProvider,
ConnectionManager,
)

_app_id = os.environ.get("MicrosoftAppId", "")
_app_password = os.environ.get("MicrosoftAppPassword", "")
_tenant_id = os.environ.get("MicrosoftAppTenantId", "")

if _app_id and _app_password:
from microsoft_agents.authentication.msal import MsalConnectionManager
_connection_manager = MsalConnectionManager(
connections_configurations={
"SERVICE_CONNECTION": AgentAuthConfiguration(
client_id=_app_id,
client_secret=_app_password,
tenant_id=_tenant_id or "common",
)
}
)
else:
_connection_manager = ConnectionManager(
provider_factory=lambda config: AnonymousTokenProvider(),
connections_configurations={"SERVICE_CONNECTION": AgentAuthConfiguration(anonymous_allowed=True)},
)

STORAGE = MemoryStorage()
ADAPTER = CloudAdapter(connection_manager=_connection_manager)
AGENT_APP = AgentApplication[TurnState](storage=STORAGE, adapter=ADAPTER, connection_manager=_connection_manager)


@AGENT_APP.conversation_update("membersAdded")
async def on_members_added(context: TurnContext, state: TurnState) -> None:
await context.send_activity("Hello! MS A365 auto-instrumentation demo. Ask me anything.")


@AGENT_APP.activity("message")
async def on_message(context: TurnContext, state: TurnState) -> None:
user_text = context.activity.text or ""
if not user_text.strip():
await context.send_activity("Please send a message.")
return

conv_id = context.activity.conversation.id if context.activity.conversation else "unknown"
channel_id = context.activity.channel_id or "unknown"

request = Request(
content=user_text,
session_id=conv_id,
conversation_id=conv_id,
channel=Channel(name=channel_id),
)
agent_details = AgentDetails(agent_id="m365-splunk-ao-demo", agent_name="M365 Splunk AO Demo Agent")

# InvokeAgentScope: manual turn-level span (no auto-instrumentor for M365 turn layer)
with InvokeAgentScope.start(
request, InvokeAgentScopeDetails(), agent_details,
span_details=SpanDetails(parent_context=otel_context.get_current()),
):
# OpenAI Agents SDK run — auto-instrumented by OpenAIAgentsTraceInstrumentor
result = await Runner.run(_agent, input=user_text)
reply = result.final_output

# Splunk AO Logger (optional)
if _ao_logger:
_ao_logger.start_session(name=conv_id, metadata={"channel": channel_id})
_ao_logger.start_trace(input=user_text, metadata={"conversation_id": conv_id})
_ao_logger.conclude(output=reply)
_ao_logger.flush()

await context.send_activity(reply)
140 changes: 140 additions & 0 deletions examples/agent/microsoft-365-agents-sdk/app_auto_lab0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""
app_auto_lab0.py — Auto-instrumentation via SplunkAOSpanProcessor → Splunk AO lab0.

OpenAIAgentsTraceInstrumentor hooks into the OpenAI Agents SDK and emits spans
automatically. SplunkAOSpanProcessor exports them to Splunk AO — no local collector
or OTEL_EXPORTER_OTLP_ENDPOINT needed.

Prerequisites:
Copy .env.example to .env and fill in:
SPLUNK_AO_REALM, SPLUNK_AO_O11Y_TOKEN, SPLUNK_AO_PROJECT, SPLUNK_AO_AGENT_STREAM
AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY

Run: APP_MODE=auto_lab0 uv run python start_server.py
Test: npx @microsoft/m365agentsplayground (or send a POST to /api/messages)
Find: Splunk AO → Agent Stream: microsoft-365-agents-sdk → service: m365-auto-lab0
"""

import os

from dotenv import load_dotenv
from opentelemetry import context as otel_context

load_dotenv(override=False)

# --- MS A365 Observability SDK: configure TracerProvider first ---------------
from microsoft_agents_a365.observability.core import (
AgentDetails,
Channel,
InvokeAgentScope,
InvokeAgentScopeDetails,
Request,
SpanDetails,
configure as a365_configure,
get_tracer_provider as a365_get_tracer_provider,
)

a365_configure(
service_name=os.environ.get("OTEL_SERVICE_NAME", "m365-auto-lab0"),
service_namespace="splunk.ao.demo",
)

# --- Attach SplunkAOSpanProcessor to the MS A365 TracerProvider --------------
from splunk_ao.otel import SplunkAOSpanProcessor, add_splunk_ao_span_processor

add_splunk_ao_span_processor(
a365_get_tracer_provider(),
SplunkAOSpanProcessor(
project=os.environ.get("SPLUNK_AO_PROJECT"),
agentstream=os.environ.get("SPLUNK_AO_AGENT_STREAM"),
),
)

# --- Auto-instrumentation: hooks into OpenAI Agents SDK tracing --------------
from microsoft_agents_a365.observability.extensions.openai import OpenAIAgentsTraceInstrumentor

OpenAIAgentsTraceInstrumentor().instrument()

# --- OpenAI Agents SDK (wraps Azure OpenAI) ----------------------------------
from agents import Agent, Runner
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
from openai import AsyncAzureOpenAI

_deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o-mini")
_azure_client = AsyncAzureOpenAI(
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_API_KEY"],
api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-12-01-preview"),
)
_agent = Agent(
name="QAAgent",
instructions="You are a concise, helpful assistant.",
model=OpenAIChatCompletionsModel(model=_deployment, openai_client=_azure_client),
)

# --- Microsoft 365 Agents SDK ------------------------------------------------
from microsoft_agents.hosting.aiohttp import CloudAdapter
from microsoft_agents.hosting.core import AgentApplication, MemoryStorage, TurnContext, TurnState
from microsoft_agents.hosting.core.authorization import (
AgentAuthConfiguration,
AnonymousTokenProvider,
ConnectionManager,
)

_app_id = os.environ.get("MicrosoftAppId", "")
_app_password = os.environ.get("MicrosoftAppPassword", "")
_tenant_id = os.environ.get("MicrosoftAppTenantId", "")

if _app_id and _app_password:
from microsoft_agents.authentication.msal import MsalConnectionManager
_connection_manager = MsalConnectionManager(
connections_configurations={
"SERVICE_CONNECTION": AgentAuthConfiguration(
client_id=_app_id,
client_secret=_app_password,
tenant_id=_tenant_id or "common",
)
}
)
else:
_connection_manager = ConnectionManager(
provider_factory=lambda config: AnonymousTokenProvider(),
connections_configurations={"SERVICE_CONNECTION": AgentAuthConfiguration(anonymous_allowed=True)},
)

STORAGE = MemoryStorage()
ADAPTER = CloudAdapter(connection_manager=_connection_manager)
AGENT_APP = AgentApplication[TurnState](storage=STORAGE, adapter=ADAPTER, connection_manager=_connection_manager)


@AGENT_APP.conversation_update("membersAdded")
async def on_members_added(context: TurnContext, state: TurnState) -> None:
await context.send_activity("Hello! MS A365 auto-instrumentation (lab0) demo. Ask me anything.")


@AGENT_APP.activity("message")
async def on_message(context: TurnContext, state: TurnState) -> None:
user_text = context.activity.text or ""
if not user_text.strip():
await context.send_activity("Please send a message.")
return

conv_id = context.activity.conversation.id if context.activity.conversation else "unknown"
channel_id = context.activity.channel_id or "unknown"

request = Request(
content=user_text,
session_id=conv_id,
conversation_id=conv_id,
channel=Channel(name=channel_id),
)
agent_details = AgentDetails(agent_id="m365-splunk-ao-demo", agent_name="M365 Splunk AO Demo Agent")

with InvokeAgentScope.start(
request, InvokeAgentScopeDetails(), agent_details,
span_details=SpanDetails(parent_context=otel_context.get_current()),
):
result = await Runner.run(_agent, input=user_text)
reply = result.final_output

await context.send_activity(reply)
Loading
Loading