From 7d3b1561e66ce589dd7f9696de98fe6ac191b607 Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Thu, 26 Feb 2026 11:30:43 +0900 Subject: [PATCH 1/2] Fix Bedrock embedding test stub missing meta attribute --- .../bedrock/tests/bedrock/test_bedrock_embedding_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/packages/bedrock/tests/bedrock/test_bedrock_embedding_client.py b/python/packages/bedrock/tests/bedrock/test_bedrock_embedding_client.py index 729583e0bc..f6f96254a7 100644 --- a/python/packages/bedrock/tests/bedrock/test_bedrock_embedding_client.py +++ b/python/packages/bedrock/tests/bedrock/test_bedrock_embedding_client.py @@ -18,6 +18,7 @@ class _StubBedrockEmbeddingRuntime: def __init__(self) -> None: self.calls: list[dict[str, Any]] = [] + self.meta = MagicMock(endpoint_url="https://bedrock-runtime.us-west-2.amazonaws.com") def invoke_model(self, **kwargs: Any) -> dict[str, Any]: self.calls.append(kwargs) From ddefb022910fd00b57d67e7daaeaa31d56f8a538 Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Thu, 26 Feb 2026 11:42:48 +0900 Subject: [PATCH 2/2] Increase test coverage so gate passes --- .../core/tests/core/test_observability.py | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/python/packages/core/tests/core/test_observability.py b/python/packages/core/tests/core/test_observability.py index 0e81b7580c..838efcb0e9 100644 --- a/python/packages/core/tests/core/test_observability.py +++ b/python/packages/core/tests/core/test_observability.py @@ -2594,3 +2594,189 @@ async def test_agent_no_instructions_in_default_or_options( span = spans[0] assert OtelAttr.SYSTEM_INSTRUCTIONS not in span.attributes + + +# region Additional coverage tests + + +def test_get_instructions_from_options_none(): + """Test _get_instructions_from_options returns None for None input.""" + from agent_framework.observability import _get_instructions_from_options + + assert _get_instructions_from_options(None) is None + + +def test_get_instructions_from_options_non_dict(): + """Test _get_instructions_from_options returns None for non-dict input.""" + from agent_framework.observability import _get_instructions_from_options + + assert _get_instructions_from_options("not a dict") is None + assert _get_instructions_from_options(42) is None + + +def test_get_instructions_from_options_dict_with_instructions(): + """Test _get_instructions_from_options extracts instructions from dict.""" + from agent_framework.observability import _get_instructions_from_options + + assert _get_instructions_from_options({"instructions": "do stuff"}) == "do stuff" + assert _get_instructions_from_options({"other_key": "value"}) is None + + +def test_get_span_attributes_with_non_dict_options(): + """Test _get_span_attributes handles non-dict options gracefully.""" + from agent_framework.observability import _get_span_attributes + + # Pass options as a non-dict value; should not crash + attrs = _get_span_attributes( + operation_name="chat", + provider_name="test", + all_options="not_a_dict", + ) + assert attrs[OtelAttr.OPERATION] == "chat" + + +def test_capture_response_with_error_type(span_exporter: InMemorySpanExporter): + """Test _capture_response includes error_type in duration histogram attributes.""" + from agent_framework.observability import OtelAttr, _capture_response, get_tracer + + span_exporter.clear() + tracer = get_tracer() + + from agent_framework.observability import _get_duration_histogram, _get_token_usage_histogram + + token_histogram = _get_token_usage_histogram() + duration_histogram = _get_duration_histogram() + + attrs = { + "gen_ai.request.model": "test-model", + OtelAttr.ERROR_TYPE: "ValueError", + } + + with tracer.start_as_current_span("test_span") as span: + _capture_response( + span=span, + attributes=attrs, + token_usage_histogram=token_histogram, + operation_duration_histogram=duration_histogram, + duration=0.5, + ) + + spans = span_exporter.get_finished_spans() + assert len(spans) == 1 + assert spans[0].attributes.get(OtelAttr.ERROR_TYPE) == "ValueError" + + +def test_configure_otel_providers_with_env_file_path(monkeypatch, tmp_path): + """Test configure_otel_providers with env_file_path creates new settings.""" + import importlib + + monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false") + for key in [ + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + ]: + monkeypatch.delenv(key, raising=False) + + observability = importlib.import_module("agent_framework.observability") + importlib.reload(observability) + + env_file = tmp_path / ".env" + env_file.write_text("ENABLE_INSTRUMENTATION=true\n") + + observability.configure_otel_providers( + env_file_path=str(env_file), + enable_sensitive_data=True, + vs_code_extension_port=None, + ) + + assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True + assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is True + + +def test_configure_otel_providers_with_env_file_and_vs_code_port(monkeypatch, tmp_path): + """Test configure_otel_providers with env_file_path and vs_code_extension_port.""" + import importlib + + monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false") + for key in [ + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + ]: + monkeypatch.delenv(key, raising=False) + + observability = importlib.import_module("agent_framework.observability") + importlib.reload(observability) + + env_file = tmp_path / ".env" + env_file.write_text("ENABLE_INSTRUMENTATION=true\n") + + observability.configure_otel_providers( + env_file_path=str(env_file), + env_file_encoding="utf-8", + vs_code_extension_port=4317, + ) + + assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True + assert observability.OBSERVABILITY_SETTINGS.vs_code_extension_port == 4317 + + +def test_get_exporters_from_env_with_env_file_path(monkeypatch, tmp_path): + """Test _get_exporters_from_env loads dotenv when env_file_path is provided.""" + from agent_framework.observability import _get_exporters_from_env + + for key in [ + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + ]: + monkeypatch.delenv(key, raising=False) + + # Create a .env file with no OTEL endpoints so it returns empty + env_file = tmp_path / ".env" + env_file.write_text("SOME_VAR=value\n") + + exporters = _get_exporters_from_env(env_file_path=str(env_file)) + assert exporters == [] + + +def test_create_resource_with_env_file_path(monkeypatch, tmp_path): + """Test create_resource loads dotenv when env_file_path is provided.""" + from agent_framework.observability import create_resource + + monkeypatch.delenv("OTEL_SERVICE_NAME", raising=False) + monkeypatch.delenv("OTEL_SERVICE_VERSION", raising=False) + monkeypatch.delenv("OTEL_RESOURCE_ATTRIBUTES", raising=False) + + env_file = tmp_path / ".env" + env_file.write_text("OTEL_SERVICE_NAME=my_test_service\n") + + resource = create_resource(env_file_path=str(env_file)) + assert resource.attributes.get("service.name") == "my_test_service" + + +def test_get_meter_typeerror_fallback(): + """Test get_meter falls back when TypeError is raised (old OTel versions).""" + from unittest.mock import patch as mock_patch + + from agent_framework.observability import get_meter + + call_count = 0 + + def mock_get_meter(*args, **kwargs): + nonlocal call_count + call_count += 1 + if "attributes" in kwargs: + raise TypeError("unexpected keyword argument 'attributes'") + from opentelemetry import metrics + + return metrics.get_meter_provider().get_meter(*args, **{k: v for k, v in kwargs.items() if k != "attributes"}) + + with mock_patch("agent_framework.observability.metrics.get_meter", side_effect=mock_get_meter): + meter = get_meter(name="test", attributes={"key": "val"}) + assert meter is not None + assert call_count == 2