Skip to content
Open
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
2 changes: 1 addition & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ AZURE_AI_MODEL_DEPLOYMENT_NAME=""
APPLICATIONINSIGHTS_CONNECTION_STRING=""
APPLICATION_INSIGHTS_WORKSPACE_ID=""
ENABLE_OTEL=true
ENABLE_SENSITIVE_DATA=true
ENABLE_SENSITIVE_DATA=false #Set to true to enable collection of sensitive data such as input prompts, model responses, and evaluation results. This is disabled by default to protect user privacy and comply with data protection regulations. Only enable this if you have a clear need for this data and have implemented appropriate safeguards to protect it.

AZURE_OPENAI_ENDPOINT=""
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=""""
Expand Down
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Azure SDK dependencies
azure-identity>=1.25.3
azure-ai-projects>=2.1.0
azure-ai-evaluation==1.15.0
azure-ai-evaluation==1.16.8
azure-ai-inference>=1.0.0b9
# Core Python packages
python-dotenv>=1.2.2
pyyaml>=6.0.3
pip-system-certs>=5.3
azure-monitor-query>=2.0.0
azure-monitor-opentelemetry>=1.8.7
azure-monitor-opentelemetry>=1.8.8
aiohttp>=3.13.5
agent-framework==1.0.1
streamlit>=1.56.0
pandas>=2.3.3
agent-framework==1.5.0
streamlit>=1.57.0
pandas==2.3.3
plotly>=6.7.0
17 changes: 11 additions & 6 deletions src/agent_evaluation/agentic_ops/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import json
import logging
from openai import AzureOpenAI
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
import os
from dotenv import load_dotenv
import time
Expand All @@ -20,7 +21,6 @@

# Azure OpenAI Configuration
AZURE_ENDPOINT = os.getenv("EVAL_AZURE_OPENAI_ENDPOINT")
API_KEY = os.getenv("EVAL_AZURE_OPENAI_KEY")
API_VERSION = os.getenv("EVAL_AZURE_OPENAI_VERSION")
DEPLOYMENT_NAME = os.getenv("EVAL_AZURE_OPENAI_MODEL")

Expand All @@ -32,17 +32,22 @@


def get_llm_client_instance():
"""Get an instance of the Azure OpenAI client."""
if not all([AZURE_ENDPOINT, API_KEY, API_VERSION]):
"""Get an instance of the Azure OpenAI client using DefaultAzureCredential."""
if not all([AZURE_ENDPOINT, API_VERSION]):
raise ValueError(
"Missing required Azure OpenAI configuration. "
"Please check EVAL_AZURE_OPENAI_ENDPOINT, EVAL_AZURE_OPENAI_KEY, "
"Please check EVAL_AZURE_OPENAI_ENDPOINT "
"and EVAL_AZURE_OPENAI_VERSION environment variables."
)


credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(
credential, "https://cognitiveservices.azure.com/.default"
)

return AzureOpenAI(
azure_endpoint=AZURE_ENDPOINT,
api_key=API_KEY,
azure_ad_token_provider=token_provider,
api_version=API_VERSION,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from agent_framework import Agent, ChatOptions
from agent_framework.observability import enable_instrumentation, get_tracer
from azure.monitor.opentelemetry import configure_azure_monitor
from opentelemetry import trace
from opentelemetry import context as otel_context
from opentelemetry.trace import SpanKind
from opentelemetry.trace.span import format_trace_id
from agent_framework.openai import OpenAIChatClient
Expand Down Expand Up @@ -120,17 +120,17 @@ async def process_query(agent: Agent, query: str, query_id: str) -> tuple[str, s
Returns:
Tuple of (response, trace_id)
"""
# Create a new root span to get a unique trace ID
with trace.use_span(trace.NonRecordingSpan(trace.SpanContext(
trace_id=0,
span_id=0,
is_remote=False,
trace_flags=trace.TraceFlags(0)
)), end_on_exit=False):
with get_tracer().start_as_current_span(f"Query: {query_id}", kind=SpanKind.CLIENT) as span:
trace_id = format_trace_id(span.get_span_context().trace_id)
response = await agent.run(query)
return str(response), trace_id
# Start each query as a brand-new root trace by passing an empty Context.
# This avoids inheriting any ambient span state and prevents a NonRecordingSpan
# from leaking into agent_framework's instrumentation downstream.
with get_tracer().start_as_current_span(
f"Query: {query_id}",
kind=SpanKind.CLIENT,
context=otel_context.Context(),
) as span:
trace_id = format_trace_id(span.get_span_context().trace_id)
response = await agent.run(query)
return str(response), trace_id


async def run_inference_async(config: dict) -> None:
Expand Down Expand Up @@ -163,6 +163,21 @@ async def run_inference_async(config: dict) -> None:
if connection_string:
configure_azure_monitor(connection_string=connection_string)
enable_instrumentation()
# Workaround: enable_instrumentation() activates azure-ai-projects'
# ResponsesInstrumentor, which crashes with
# "'NonRecordingSpan' object has no attribute 'attributes'" when a
# single OpenAI Responses call carries multiple tool-result messages
# (parallel tool calls). The data needed for evaluation
# (gen_ai.tool.definitions / tool calls) is emitted by
# agent_framework's own spans, so disabling just this instrumentor
# is safe.
try:
from azure.ai.projects.telemetry._responses_instrumentor import ResponsesInstrumentor
if ResponsesInstrumentor().is_instrumented():
ResponsesInstrumentor().uninstrument()
logger.info("[AGENT] Disabled azure-ai-projects ResponsesInstrumentor (parallel-tool-call bug workaround)")
except Exception as ex: # pragma: no cover - best-effort workaround
logger.warning("[AGENT] Could not uninstrument ResponsesInstrumentor: %s", ex)
logger.info("[AGENT] Azure Monitor configured with instrumentation")
else:
logger.warning("[AGENT] APPLICATIONINSIGHTS_CONNECTION_STRING not set — telemetry disabled")
Expand Down