Skip to content

Commit df6f71f

Browse files
authored
Merge branch 'main' into actionlint
2 parents 383d90f + 167234f commit df6f71f

716 files changed

Lines changed: 133256 additions & 74182 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

sdk/agentserver/azure-ai-agentserver-core/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Release History
22

3+
## 2.0.0b6 (2026-06-12)
4+
5+
### Bugs Fixed
6+
7+
- Populated agent metadata when operation IDs are zeroed so agent metadata remains available for telemetry and downstream processing.
8+
- Suppressed noisy observability/exporter INFO logs by default in tracing setup while preserving DEBUG visibility when explicitly enabled.
9+
310
## 2.0.0b5 (2026-05-25)
411

512
### Bugs Fixed

sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,15 @@ def resolve_project_id() -> str:
345345
return os.environ.get(_ENV_FOUNDRY_PROJECT_ARM_ID, "")
346346

347347

348+
def resolve_session_id() -> str:
349+
"""Resolve the default session ID from the ``FOUNDRY_AGENT_SESSION_ID`` environment variable.
350+
351+
:return: The default session ID, or an empty string if not set.
352+
:rtype: str
353+
"""
354+
return os.environ.get(_ENV_FOUNDRY_AGENT_SESSION_ID, "")
355+
356+
348357
def resolve_sse_keepalive_interval(interval: Optional[int] = None) -> int:
349358
"""Resolve the SSE keep-alive interval from argument, env var, or default.
350359

sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,16 @@ def configure_observability(
146146
logging.getLogger(_noisy).setLevel(logging.WARNING)
147147

148148
# Tracing and OTel export
149-
_configure_tracing(connection_string=connection_string, enable_sensitive_data=enable_sensitive_data)
149+
_configure_tracing(
150+
connection_string=connection_string,
151+
enable_sensitive_data=enable_sensitive_data,
152+
)
150153

151154

152-
def _configure_tracing(connection_string: Optional[str] = None, enable_sensitive_data: bool = False) -> None:
155+
def _configure_tracing(
156+
connection_string: Optional[str] = None,
157+
enable_sensitive_data: bool = False,
158+
) -> None:
153159
"""Configure OpenTelemetry exporters via the microsoft-opentelemetry distro.
154160
155161
Internal helper called by :func:`configure_observability`.
@@ -182,7 +188,13 @@ def _configure_tracing(connection_string: Optional[str] = None, enable_sensitive
182188
agent_tenant_id=agent_tenant_id,
183189
),
184190
]
185-
log_record_processors = [_BaggageLogRecordProcessor()] # type: ignore[list-item]
191+
log_record_processors = [ # type: ignore[list-item]
192+
_BaggageLogRecordProcessor(
193+
agent_name=agent_name,
194+
agent_version=agent_version,
195+
session_id=_config.resolve_session_id() or None,
196+
),
197+
]
186198

187199
try:
188200
_setup_distro_export(
@@ -521,18 +533,44 @@ class _BaggageLogRecordProcessor:
521533
for end-to-end correlation.
522534
"""
523535

536+
def __init__(
537+
self,
538+
*,
539+
agent_name: Optional[str] = None,
540+
agent_version: Optional[str] = None,
541+
session_id: Optional[str] = None,
542+
) -> None:
543+
self.agent_name = agent_name
544+
self.agent_version = agent_version
545+
self.session_id = session_id
546+
524547
def on_emit(self, log_data: Any) -> None: # pylint: disable=unused-argument
525548
"""Copy baggage entries into the log record's attributes.
526549
527550
:param log_data: The log data being emitted.
528551
:type log_data: any
529552
"""
530553
try:
554+
if not hasattr(log_data, "log_record") or not log_data.log_record:
555+
return
556+
557+
attrs = log_data.log_record.attributes # type: ignore[assignment]
558+
531559
ctx = _otel_context.get_current()
532560
entries = _otel_baggage.get_all(context=ctx)
533-
if entries and hasattr(log_data, 'log_record') and log_data.log_record:
561+
if entries:
534562
for key, value in entries.items():
535-
log_data.log_record.attributes[key] = value # type: ignore[index]
563+
attrs[key] = value # type: ignore[index]
564+
565+
if self.agent_name and _ATTR_GEN_AI_AGENT_NAME not in attrs:
566+
attrs[_ATTR_GEN_AI_AGENT_NAME] = self.agent_name
567+
if self.agent_version and _ATTR_GEN_AI_AGENT_VERSION not in attrs:
568+
attrs[_ATTR_GEN_AI_AGENT_VERSION] = self.agent_version
569+
570+
bag_session = _otel_baggage.get_baggage(_BAGGAGE_SESSION_ID, context=ctx)
571+
resolved_session = bag_session or self.session_id
572+
if resolved_session and _ATTR_SESSION_ID not in attrs:
573+
attrs[_ATTR_SESSION_ID] = resolved_session
536574
except Exception: # pylint: disable=broad-except
537575
pass
538576

sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# ---------------------------------------------------------
44

5-
VERSION = "2.0.0b5"
5+
VERSION = "2.0.0b6"

sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
resolve_agent_name,
1616
resolve_agent_version,
1717
resolve_appinsights_connection_string,
18+
resolve_session_id,
19+
)
20+
from azure.ai.agentserver.core._tracing import (
21+
_BaggageLogRecordProcessor,
22+
_FoundryEnrichmentSpanProcessor,
1823
)
19-
from azure.ai.agentserver.core._tracing import _FoundryEnrichmentSpanProcessor
2024

2125

2226
class _CollectorExporter(SpanExporter):
@@ -408,7 +412,7 @@ def test_invocation_id_propagates_to_child_spans(self) -> None:
408412

409413

410414
class TestAgentIdentityResolution:
411-
"""Tests for resolve_agent_name() and resolve_agent_version()."""
415+
"""Tests for agent identity/session resolution helpers."""
412416

413417
def test_agent_name_from_env(self) -> None:
414418
with mock.patch.dict(os.environ, {"FOUNDRY_AGENT_NAME": "my-agent"}):
@@ -430,5 +434,78 @@ def test_agent_version_default_empty(self) -> None:
430434
with mock.patch.dict(os.environ, env, clear=True):
431435
assert resolve_agent_version() == ""
432436

437+
def test_session_id_from_env(self) -> None:
438+
with mock.patch.dict(os.environ, {"FOUNDRY_AGENT_SESSION_ID": "session-1"}):
439+
assert resolve_session_id() == "session-1"
440+
441+
def test_session_id_default_empty(self) -> None:
442+
env = os.environ.copy()
443+
env.pop("FOUNDRY_AGENT_SESSION_ID", None)
444+
with mock.patch.dict(os.environ, env, clear=True):
445+
assert resolve_session_id() == ""
446+
447+
448+
class _FakeLogRecord:
449+
def __init__(self, attributes):
450+
self.attributes = attributes
451+
452+
453+
class _FakeLogData:
454+
def __init__(self, attributes):
455+
self.log_record = _FakeLogRecord(attributes)
456+
457+
458+
class TestBaggageLogRecordProcessor:
459+
def test_adds_agent_and_fallback_session_attributes(self) -> None:
460+
proc = _BaggageLogRecordProcessor(
461+
agent_name="agent-a",
462+
agent_version="1.2.3",
463+
session_id="session-fallback-1",
464+
)
465+
log_data = _FakeLogData({})
466+
467+
proc.on_emit(log_data)
433468

469+
attrs = log_data.log_record.attributes
470+
assert attrs["gen_ai.agent.name"] == "agent-a"
471+
assert attrs["gen_ai.agent.version"] == "1.2.3"
472+
assert attrs["microsoft.session.id"] == "session-fallback-1"
434473

474+
def test_prefers_baggage_session_id_over_fallback(self) -> None:
475+
proc = _BaggageLogRecordProcessor(
476+
agent_name="agent-a",
477+
agent_version="1.2.3",
478+
session_id="session-fallback-1",
479+
)
480+
log_data = _FakeLogData({})
481+
482+
ctx = _otel_baggage.set_baggage(
483+
"azure.ai.agentserver.session_id", "session-from-baggage",
484+
)
485+
token = _otel_context.attach(ctx)
486+
try:
487+
proc.on_emit(log_data)
488+
finally:
489+
_otel_context.detach(token)
490+
491+
attrs = log_data.log_record.attributes
492+
assert attrs["microsoft.session.id"] == "session-from-baggage"
493+
494+
def test_does_not_overwrite_existing_log_attributes(self) -> None:
495+
proc = _BaggageLogRecordProcessor(
496+
agent_name="agent-a",
497+
agent_version="1.2.3",
498+
session_id="session-fallback-1",
499+
)
500+
attrs = {
501+
"gen_ai.agent.name": "existing-name",
502+
"gen_ai.agent.version": "0.0.1",
503+
"microsoft.session.id": "existing-session",
504+
}
505+
log_data = _FakeLogData(attrs)
506+
507+
proc.on_emit(log_data)
508+
509+
assert attrs["gen_ai.agent.name"] == "existing-name"
510+
assert attrs["gen_ai.agent.version"] == "0.0.1"
511+
assert attrs["microsoft.session.id"] == "existing-session"

sdk/agentserver/azure-ai-agentserver-invocations/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release History
22

3+
## 1.0.0b5 (2026-06-12)
4+
5+
### Bugs Fixed
6+
7+
- Fixed exception tracing for streaming responses so errors raised while iterating streaming results are captured correctly and invocation/session logging context is reset after streaming completes.
8+
39
## 1.0.0b4 (2026-05-21)
410

511
### Features Added

sdk/agentserver/azure-ai-agentserver-invocations/azure/ai/agentserver/invocations/_invocation.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
import re
1313
import threading
1414
import uuid
15-
from collections.abc import Awaitable, Callable # pylint: disable=import-error
15+
from collections.abc import AsyncIterator, Awaitable, Callable # pylint: disable=import-error
1616
from typing import Any, Optional
1717

18-
from opentelemetry import baggage as _otel_baggage, context as _otel_context
18+
from opentelemetry import baggage as _otel_baggage, context as _otel_context, trace
1919
from starlette.requests import Request
20-
from starlette.responses import JSONResponse, Response
20+
from starlette.responses import JSONResponse, Response, StreamingResponse
2121
from starlette.routing import Route
2222

2323
from azure.ai.agentserver.core import ( # pylint: disable=no-name-in-module
@@ -357,6 +357,51 @@ async def _get_openapi_spec_endpoint(self, request: Request) -> Response: # pyl
357357
)
358358
return JSONResponse(spec)
359359

360+
def _wrap_streaming_response(
361+
self,
362+
response: StreamingResponse,
363+
invocation_id: str,
364+
session_id: str,
365+
) -> StreamingResponse:
366+
"""Wrap streaming body iteration with invocation logging/tracing context.
367+
368+
:param response: Streaming response to wrap.
369+
:type response: StreamingResponse
370+
:param invocation_id: Invocation identifier to stamp in context/logging.
371+
:type invocation_id: str
372+
:param session_id: Session identifier to stamp in context/logging.
373+
:type session_id: str
374+
:return: The response with a wrapped body_iterator.
375+
:rtype: StreamingResponse
376+
"""
377+
original_iterator = response.body_iterator
378+
379+
async def _wrapped_body() -> AsyncIterator[Any]:
380+
# Re-establish the invocation context for the streaming task.
381+
stream_inv_token = _invocation_id_var.set(invocation_id)
382+
stream_session_token = _session_id_var.set(session_id)
383+
try:
384+
async for chunk in original_iterator:
385+
yield chunk
386+
except Exception as exc: # pylint: disable=broad-exception-caught
387+
logger.error(
388+
"Error processing invocation %s: %s",
389+
invocation_id, exc, exc_info=True,
390+
)
391+
# Record the exception on the current span.
392+
span = trace.get_current_span()
393+
if span and span.is_recording():
394+
span.set_status(trace.StatusCode.ERROR, str(exc))
395+
span.set_attribute("error.type", type(exc).__name__)
396+
span.record_exception(exc)
397+
raise
398+
finally:
399+
_invocation_id_var.reset(stream_inv_token)
400+
_session_id_var.reset(stream_session_token)
401+
402+
response.body_iterator = _wrapped_body()
403+
return response
404+
360405
async def _create_invocation_endpoint(self, request: Request) -> Response:
361406
generated_id = str(uuid.uuid4())
362407
raw_invocation_id = request.headers.get(InvocationConstants.INVOCATION_ID_HEADER) or ""
@@ -427,13 +472,21 @@ async def _create_invocation_endpoint(self, request: Request) -> Response:
427472
),
428473
)
429474
finally:
475+
# Always reset the request-scope tokens and detach baggage from the
476+
# calling context here. The streaming wrapper separately resets the
477+
# tokens it sets for stream iteration.
430478
_invocation_id_var.reset(inv_token)
431479
_session_id_var.reset(session_token)
432480
try:
433481
_otel_context.detach(baggage_token)
434482
except ValueError:
435483
pass
436484

485+
# Wrap streaming response body so exceptions during iteration are
486+
# recorded on the current trace span and logged as invocation errors.
487+
if isinstance(response, StreamingResponse):
488+
response = self._wrap_streaming_response(response, invocation_id, session_id)
489+
437490
return response
438491

439492
async def _traced_invocation_endpoint(

sdk/agentserver/azure-ai-agentserver-invocations/azure/ai/agentserver/invocations/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# ---------------------------------------------------------
44

5-
VERSION = "1.0.0b4"
5+
VERSION = "1.0.0b5"

0 commit comments

Comments
 (0)