Skip to content

Commit da0c624

Browse files
committed
added more dependencies to pyproject.toml, added another test file for code coverage
1 parent 354fdbb commit da0c624

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

aws-opentelemetry-distro/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ dependencies = [
8989
"langgraph == 0.6.3",
9090
"duckduckgo-search == 8.1.1",
9191
"pytest-asyncio == 0.21.0",
92+
"pytest-vcr == 1.0.2",
9293
]
9394

9495
[project.optional-dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import unittest
5+
import uuid
6+
from unittest.mock import MagicMock, patch
7+
8+
from amazon.opentelemetry.distro.opentelemetry.instrumentation.langchain_v2.callback_handler import (
9+
OpenTelemetryCallbackHandler,
10+
SpanHolder,
11+
_sanitize_metadata_value,
12+
_set_request_params,
13+
_set_span_attribute,
14+
)
15+
from amazon.opentelemetry.distro.opentelemetry.instrumentation.langchain_v2.span_attributes import SpanAttributes
16+
from opentelemetry.trace import SpanKind
17+
18+
19+
class TestOpenTelemetryCallbackHandler(unittest.TestCase):
20+
def setUp(self):
21+
self.mock_tracer = MagicMock()
22+
self.mock_span = MagicMock()
23+
self.mock_tracer.start_span.return_value = self.mock_span
24+
self.handler = OpenTelemetryCallbackHandler(self.mock_tracer)
25+
self.run_id = uuid.uuid4()
26+
self.parent_run_id = uuid.uuid4()
27+
28+
def test_set_span_attribute(self):
29+
"""Test the _set_span_attribute function with various inputs."""
30+
# Value is not None
31+
_set_span_attribute(self.mock_span, "test.attribute", "test_value")
32+
self.mock_span.set_attribute.assert_called_with("test.attribute", "test_value")
33+
34+
# Value is None
35+
self.mock_span.reset_mock()
36+
_set_span_attribute(self.mock_span, "test.attribute", None)
37+
self.mock_span.set_attribute.assert_not_called()
38+
39+
# Value is empty string
40+
self.mock_span.reset_mock()
41+
_set_span_attribute(self.mock_span, "test.attribute", "")
42+
self.mock_span.set_attribute.assert_not_called()
43+
44+
# Value is number
45+
self.mock_span.reset_mock()
46+
_set_span_attribute(self.mock_span, "test.attribute", 123)
47+
self.mock_span.set_attribute.assert_called_with("test.attribute", 123)
48+
49+
def test_sanitize_metadata_value(self):
50+
"""Test _sanitize_metadata_value function with various inputs."""
51+
# Basic types
52+
self.assertEqual(_sanitize_metadata_value(None), None)
53+
self.assertEqual(_sanitize_metadata_value("string"), "string")
54+
self.assertEqual(_sanitize_metadata_value(123), 123)
55+
self.assertEqual(_sanitize_metadata_value(123.45), 123.45)
56+
self.assertEqual(_sanitize_metadata_value(True), True)
57+
58+
# List type
59+
self.assertEqual(_sanitize_metadata_value([1, 2, 3]), ["1", "2", "3"])
60+
self.assertEqual(_sanitize_metadata_value(["a", "b", "c"]), ["a", "b", "c"])
61+
62+
# Complex object
63+
class TestClass:
64+
def __str__(self):
65+
return "TestClass"
66+
67+
self.assertEqual(_sanitize_metadata_value(TestClass()), "TestClass")
68+
69+
# Nested list
70+
self.assertEqual(_sanitize_metadata_value([1, [2, 3], 4]), ["1", "['2', '3']", "4"])
71+
72+
@patch("time.time", return_value=12345.0)
73+
def test_set_request_params(self, mock_time):
74+
"""Test _set_request_params function."""
75+
span = MagicMock()
76+
77+
# Create SpanHolder manually with fields to avoid factory issue
78+
span_holder = SpanHolder(span=span, children=[], start_time=12345.0, request_model=None)
79+
80+
# Test with model_id in kwargs
81+
kwargs = {"model_id": "gpt-4", "temperature": 0.7, "max_tokens": 100, "top_p": 0.9}
82+
_set_request_params(span, kwargs, span_holder)
83+
84+
self.assertEqual(span_holder.request_model, "gpt-4")
85+
86+
# Verify the appropriate attributes were set
87+
span.set_attribute.assert_any_call(SpanAttributes.GEN_AI_REQUEST_MODEL, "gpt-4")
88+
span.set_attribute.assert_any_call(SpanAttributes.GEN_AI_RESPONSE_MODEL, "gpt-4")
89+
span.set_attribute.assert_any_call(SpanAttributes.GEN_AI_REQUEST_TEMPERATURE, 0.7)
90+
span.set_attribute.assert_any_call(SpanAttributes.GEN_AI_REQUEST_MAX_TOKENS, 100)
91+
span.set_attribute.assert_any_call(SpanAttributes.GEN_AI_REQUEST_TOP_P, 0.9)
92+
93+
# Test with invocation_params
94+
span.reset_mock()
95+
span_holder = SpanHolder(span=span, children=[], start_time=12345.0, request_model=None)
96+
97+
kwargs = {"invocation_params": {"model_id": "claude-3", "temperature": 0.5, "max_tokens": 200, "top_p": 0.8}}
98+
_set_request_params(span, kwargs, span_holder)
99+
100+
self.assertEqual(span_holder.request_model, "claude-3")
101+
span.set_attribute.assert_any_call(SpanAttributes.GEN_AI_REQUEST_MODEL, "claude-3")
102+
103+
def test_create_span(self):
104+
"""Test _create_span method."""
105+
# Test creating span without parent
106+
with patch("time.time", return_value=12345.0):
107+
span = self.handler._create_span(self.run_id, None, "test_span", metadata={"key": "value"})
108+
109+
self.assertEqual(span, self.mock_span)
110+
# Fix: Use SpanKind.INTERNAL instead of the integer value
111+
self.mock_tracer.start_span.assert_called_with("test_span", kind=SpanKind.INTERNAL)
112+
self.assertIn(self.run_id, self.handler.span_mapping)
113+
self.assertEqual(self.handler.span_mapping[self.run_id].span, self.mock_span)
114+
self.assertEqual(self.handler.span_mapping[self.run_id].children, [])
115+
116+
# Test creating span with parent
117+
with patch("time.time", return_value=12345.0):
118+
parent_run_id = uuid.uuid4()
119+
parent_span = MagicMock()
120+
self.handler.span_mapping[parent_run_id] = SpanHolder(
121+
span=parent_span, children=[], start_time=12345.0, request_model=None
122+
)
123+
124+
span = self.handler._create_span(self.run_id, parent_run_id, "child_span")
125+
126+
self.assertEqual(len(self.handler.span_mapping[parent_run_id].children), 1)
127+
self.assertEqual(self.handler.span_mapping[parent_run_id].children[0], self.run_id)
128+
129+
def test_get_name_from_callback(self):
130+
"""Test _get_name_from_callback method."""
131+
# Test with name in kwargs
132+
serialized = {"kwargs": {"name": "test_name"}}
133+
name = self.handler._get_name_from_callback(serialized)
134+
self.assertEqual(name, "test_name")
135+
136+
# Test with name in direct kwargs
137+
name = self.handler._get_name_from_callback({}, kwargs={"name": "direct_name"})
138+
self.assertEqual(name, "unknown")
139+
140+
# Test with name in serialized
141+
name = self.handler

0 commit comments

Comments
 (0)