langtrace-python-sdk 1.3.5__py3-none-any.whl → 1.3.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langtrace_python_sdk/instrumentation/openai/patch.py +60 -20
- langtrace_python_sdk/version.py +1 -1
- {langtrace_python_sdk-1.3.5.dist-info → langtrace_python_sdk-1.3.7.dist-info}/METADATA +5 -1
- {langtrace_python_sdk-1.3.5.dist-info → langtrace_python_sdk-1.3.7.dist-info}/RECORD +23 -6
- {langtrace_python_sdk-1.3.5.dist-info → langtrace_python_sdk-1.3.7.dist-info}/WHEEL +1 -1
- tests/__init__.py +0 -0
- tests/anthropic/test_anthropic.py +73 -0
- tests/chroma/test_chroma.py +64 -0
- tests/langchain/test_langchain.py +69 -0
- tests/langchain/test_langchain_community.py +69 -0
- tests/langchain/test_langchain_core.py +115 -0
- tests/openai/cassettes/test_async_chat_completion_streaming.yaml +158 -0
- tests/openai/cassettes/test_async_image_generation.yaml +97 -0
- tests/openai/cassettes/test_chat_completion.yaml +101 -0
- tests/openai/cassettes/test_chat_completion_streaming.yaml +200860 -0
- tests/openai/cassettes/test_image_generation.yaml +97 -0
- tests/openai/conftest.py +45 -0
- tests/openai/test_chat_completion.py +142 -0
- tests/openai/test_embeddings.py +0 -0
- tests/openai/test_image_generation.py +77 -0
- tests/pinecone/test_pinecone.py +72 -0
- tests/utils.py +21 -0
- {langtrace_python_sdk-1.3.5.dist-info → langtrace_python_sdk-1.3.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -121,7 +121,8 @@ def async_images_generate(original_method, version, tracer):
|
|
|
121
121
|
with tracer.start_as_current_span(
|
|
122
122
|
APIS["IMAGES_GENERATION"]["METHOD"], kind=SpanKind.CLIENT
|
|
123
123
|
) as span:
|
|
124
|
-
|
|
124
|
+
items = attributes.model_dump(by_alias=True).items()
|
|
125
|
+
for field, value in items:
|
|
125
126
|
if value is not None:
|
|
126
127
|
span.set_attribute(field, value)
|
|
127
128
|
try:
|
|
@@ -325,7 +326,9 @@ def chat_completions_create(original_method, version, tracer):
|
|
|
325
326
|
span.end()
|
|
326
327
|
raise
|
|
327
328
|
|
|
328
|
-
def handle_streaming_response(
|
|
329
|
+
def handle_streaming_response(
|
|
330
|
+
result, span, prompt_tokens, function_call=False, tool_calls=False
|
|
331
|
+
):
|
|
329
332
|
"""Process and yield streaming response chunks."""
|
|
330
333
|
result_content = []
|
|
331
334
|
span.add_event(Event.STREAM_START.value)
|
|
@@ -343,12 +346,16 @@ def chat_completions_create(original_method, version, tracer):
|
|
|
343
346
|
content = [choice.delta.content]
|
|
344
347
|
elif function_call:
|
|
345
348
|
for choice in chunk.choices:
|
|
346
|
-
if
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
349
|
+
if (
|
|
350
|
+
choice.delta
|
|
351
|
+
and choice.delta.function_call
|
|
352
|
+
and choice.delta.function_call.arguments is not None
|
|
353
|
+
):
|
|
354
|
+
token_counts = estimate_tokens(
|
|
350
355
|
choice.delta.function_call.arguments
|
|
351
|
-
|
|
356
|
+
)
|
|
357
|
+
completion_tokens += token_counts
|
|
358
|
+
content = [choice.delta.function_call.arguments]
|
|
352
359
|
elif tool_calls:
|
|
353
360
|
# TODO(Karthik): Tool calls streaming is tricky. The chunks after the
|
|
354
361
|
# first one are missing the function name and id though the arguments
|
|
@@ -357,7 +364,14 @@ def chat_completions_create(original_method, version, tracer):
|
|
|
357
364
|
else:
|
|
358
365
|
content = []
|
|
359
366
|
span.add_event(
|
|
360
|
-
Event.STREAM_OUTPUT.value,
|
|
367
|
+
Event.STREAM_OUTPUT.value,
|
|
368
|
+
{
|
|
369
|
+
"response": (
|
|
370
|
+
"".join(content)
|
|
371
|
+
if len(content) > 0 and content[0] is not None
|
|
372
|
+
else ""
|
|
373
|
+
)
|
|
374
|
+
},
|
|
361
375
|
)
|
|
362
376
|
result_content.append(content[0] if len(content) > 0 else "")
|
|
363
377
|
yield chunk
|
|
@@ -559,7 +573,9 @@ def async_chat_completions_create(original_method, version, tracer):
|
|
|
559
573
|
span.end()
|
|
560
574
|
raise
|
|
561
575
|
|
|
562
|
-
async def ahandle_streaming_response(
|
|
576
|
+
async def ahandle_streaming_response(
|
|
577
|
+
result, span, prompt_tokens, function_call=False, tool_calls=False
|
|
578
|
+
):
|
|
563
579
|
"""Process and yield streaming response chunks."""
|
|
564
580
|
result_content = []
|
|
565
581
|
span.add_event(Event.STREAM_START.value)
|
|
@@ -577,12 +593,16 @@ def async_chat_completions_create(original_method, version, tracer):
|
|
|
577
593
|
content = [choice.delta.content]
|
|
578
594
|
elif function_call:
|
|
579
595
|
for choice in chunk.choices:
|
|
580
|
-
if
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
596
|
+
if (
|
|
597
|
+
choice.delta
|
|
598
|
+
and choice.delta.function_call
|
|
599
|
+
and choice.delta.function_call.arguments is not None
|
|
600
|
+
):
|
|
601
|
+
token_counts = estimate_tokens(
|
|
584
602
|
choice.delta.function_call.arguments
|
|
585
|
-
|
|
603
|
+
)
|
|
604
|
+
completion_tokens += token_counts
|
|
605
|
+
content = [choice.delta.function_call.arguments]
|
|
586
606
|
elif tool_calls:
|
|
587
607
|
# TODO(Karthik): Tool calls streaming is tricky. The chunks after the
|
|
588
608
|
# first one are missing the function name and id though the arguments
|
|
@@ -591,7 +611,14 @@ def async_chat_completions_create(original_method, version, tracer):
|
|
|
591
611
|
else:
|
|
592
612
|
content = []
|
|
593
613
|
span.add_event(
|
|
594
|
-
Event.STREAM_OUTPUT.value,
|
|
614
|
+
Event.STREAM_OUTPUT.value,
|
|
615
|
+
{
|
|
616
|
+
"response": (
|
|
617
|
+
"".join(content)
|
|
618
|
+
if len(content) > 0 and content[0] is not None
|
|
619
|
+
else ""
|
|
620
|
+
)
|
|
621
|
+
},
|
|
595
622
|
)
|
|
596
623
|
result_content.append(content[0] if len(content) > 0 else "")
|
|
597
624
|
yield chunk
|
|
@@ -756,11 +783,19 @@ def async_embeddings_create(original_method, version, tracer):
|
|
|
756
783
|
|
|
757
784
|
def extract_content(choice):
|
|
758
785
|
# Check if choice.message exists and has a content attribute
|
|
759
|
-
if
|
|
786
|
+
if (
|
|
787
|
+
hasattr(choice, "message")
|
|
788
|
+
and hasattr(choice.message, "content")
|
|
789
|
+
and choice.message.content is not None
|
|
790
|
+
):
|
|
760
791
|
return choice.message.content
|
|
761
792
|
|
|
762
793
|
# Check if choice.message has tool_calls and extract information accordingly
|
|
763
|
-
elif
|
|
794
|
+
elif (
|
|
795
|
+
hasattr(choice, "message")
|
|
796
|
+
and hasattr(choice.message, "tool_calls")
|
|
797
|
+
and choice.message.tool_calls is not None
|
|
798
|
+
):
|
|
764
799
|
result = [
|
|
765
800
|
{
|
|
766
801
|
"id": tool_call.id,
|
|
@@ -768,13 +803,18 @@ def extract_content(choice):
|
|
|
768
803
|
"function": {
|
|
769
804
|
"name": tool_call.function.name,
|
|
770
805
|
"arguments": tool_call.function.arguments,
|
|
771
|
-
}
|
|
772
|
-
}
|
|
806
|
+
},
|
|
807
|
+
}
|
|
808
|
+
for tool_call in choice.message.tool_calls
|
|
773
809
|
]
|
|
774
810
|
return result
|
|
775
811
|
|
|
776
812
|
# Check if choice.message has a function_call and extract information accordingly
|
|
777
|
-
elif
|
|
813
|
+
elif (
|
|
814
|
+
hasattr(choice, "message")
|
|
815
|
+
and hasattr(choice.message, "function_call")
|
|
816
|
+
and choice.message.function_call is not None
|
|
817
|
+
):
|
|
778
818
|
return {
|
|
779
819
|
"name": choice.message.function_call.name,
|
|
780
820
|
"arguments": choice.message.function_call.arguments,
|
langtrace_python_sdk/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.3.
|
|
1
|
+
__version__ = "1.3.7"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: langtrace-python-sdk
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.7
|
|
4
4
|
Summary: Python SDK for LangTrace
|
|
5
5
|
Project-URL: Homepage, https://github.com/Scale3-Labs/langtrace-python-sdk
|
|
6
6
|
Author-email: Scale3 Labs <engineering@scale3labs.com>
|
|
@@ -25,6 +25,10 @@ Requires-Dist: langchain-openai; extra == 'dev'
|
|
|
25
25
|
Requires-Dist: llama-index; extra == 'dev'
|
|
26
26
|
Requires-Dist: openai; extra == 'dev'
|
|
27
27
|
Requires-Dist: python-dotenv; extra == 'dev'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-asyncio; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest-vcr; extra == 'test'
|
|
28
32
|
Description-Content-Type: text/markdown
|
|
29
33
|
|
|
30
34
|
# [Langtrace](https://www.langtrace.ai)
|
|
@@ -30,7 +30,7 @@ examples/pinecone_example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
30
30
|
examples/pinecone_example/basic.py,sha256=OkYjN3J5kxw-kloOV3Q-iyI6opkbarWsMom-_AMP2ZA,893
|
|
31
31
|
langtrace_python_sdk/__init__.py,sha256=SlHg447-nQBbw8exRNJP_OyHUZ39Sldb7aaQ35hIRm8,262
|
|
32
32
|
langtrace_python_sdk/langtrace.py,sha256=83-AkdASO7UF9FHR9BDZUSeYv9GFZkJJQD2YLKbqzo8,3562
|
|
33
|
-
langtrace_python_sdk/version.py,sha256=
|
|
33
|
+
langtrace_python_sdk/version.py,sha256=9peaXOar2qezOPJEKG6cD_A0aaXrzdVN8h-v6fBoBEk,22
|
|
34
34
|
langtrace_python_sdk/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
35
|
langtrace_python_sdk/constants/exporter/langtrace_exporter.py,sha256=5MNjnAOg-4am78J3gVMH6FSwq5N8TOj72ugkhsw4vi0,46
|
|
36
36
|
langtrace_python_sdk/constants/instrumentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -66,14 +66,31 @@ langtrace_python_sdk/instrumentation/llamaindex/instrumentation.py,sha256=D7_HPv
|
|
|
66
66
|
langtrace_python_sdk/instrumentation/llamaindex/patch.py,sha256=8IM2dedF81w8_vVyA56JptyvlQl_bQO4UcB56sptuGs,3700
|
|
67
67
|
langtrace_python_sdk/instrumentation/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
68
|
langtrace_python_sdk/instrumentation/openai/instrumentation.py,sha256=Pv4n4z_kSxvZGVxrj3AopBoWQSxIOtMKolkxHrchRdM,2162
|
|
69
|
-
langtrace_python_sdk/instrumentation/openai/patch.py,sha256=
|
|
69
|
+
langtrace_python_sdk/instrumentation/openai/patch.py,sha256=ou8_48lmHmtI9UTU--QVl7KaAIeBnNtqOuHtnUQqjpU,34590
|
|
70
70
|
langtrace_python_sdk/instrumentation/pinecone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
71
|
langtrace_python_sdk/instrumentation/pinecone/instrumentation.py,sha256=o0EUd5jvHaDKOUTj4NjnL5UfDHDHxyXkWGlTW4oeRDk,1784
|
|
72
72
|
langtrace_python_sdk/instrumentation/pinecone/patch.py,sha256=5lF7hQmg2-U2EWtOC0w8_peRaNMysBomb0fjiNoS6eQ,2200
|
|
73
73
|
langtrace_python_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
74
|
langtrace_python_sdk/utils/llm.py,sha256=4z2e-md_ELXCEuOIRVWracR6qH2pmsOxCqpkuF9_3Nw,1589
|
|
75
75
|
langtrace_python_sdk/utils/with_root_span.py,sha256=N7ONrcF0myZbHBy5gpQffDbX-Kf63Crsz9szG0i3m08,1889
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
|
+
tests/utils.py,sha256=wdbR00LjYfDXzTBFvkksQYEz1hZjGTNKEiw5KPE_bqI,625
|
|
78
|
+
tests/anthropic/test_anthropic.py,sha256=vvrDJCg9KCws72NEuvPklq8RqQBgGwmV-fSkxGSvUFw,2632
|
|
79
|
+
tests/chroma/test_chroma.py,sha256=5zeInDcP5VplrM9ex2iuVKcpVKMDfEf_ZDK9D6Tc700,2392
|
|
80
|
+
tests/langchain/test_langchain.py,sha256=GGGRcxz0isNmegeum37XFrlJqI6jB6_iUvv8AJ5iG24,2481
|
|
81
|
+
tests/langchain/test_langchain_community.py,sha256=m9lBmMZIeUouKSq1JfdBupV0-0ef39GD6BKsA0Cf_08,2515
|
|
82
|
+
tests/langchain/test_langchain_core.py,sha256=hCuKkIUvDQOUBM2oEgMG3Iq_KNTwC2sH7_Y_IR5FIno,4238
|
|
83
|
+
tests/openai/conftest.py,sha256=r-Scvq1pP62gkvI4CC13nR19twlRQFUx8WuMe9qcesM,1138
|
|
84
|
+
tests/openai/test_chat_completion.py,sha256=iXz8RTU5oCfP2CWOhKJXlWwK-IfLubI8SW396sPxnV0,5310
|
|
85
|
+
tests/openai/test_embeddings.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
|
+
tests/openai/test_image_generation.py,sha256=tUyLTELi-nBOlp6yZ0hyPbLp04S_-qo-EtqAIJZeXuQ,3889
|
|
87
|
+
tests/openai/cassettes/test_async_chat_completion_streaming.yaml,sha256=0aZHFy9NvXegEDjGWyoG-_ItLr7JYAKbaBLIPSl-pfM,6844
|
|
88
|
+
tests/openai/cassettes/test_async_image_generation.yaml,sha256=_LYZcrqxrnSqcWVQn2Y0XMVGxF-wBrSAd-v3LTAIAeo,3597
|
|
89
|
+
tests/openai/cassettes/test_chat_completion.yaml,sha256=YkNFgK9VHAzNqGWuxFcTiE194GdEie8eDf1FSsffjd8,2944
|
|
90
|
+
tests/openai/cassettes/test_chat_completion_streaming.yaml,sha256=nkx_TemQMYSZxUF_b-LCEFwCRDm0AkQHLf4sdJVuZBw,2592394
|
|
91
|
+
tests/openai/cassettes/test_image_generation.yaml,sha256=gn5aSVp6V6_hb_rt2NnkAWd_idzDxo-7VzhZII0Wslw,3562
|
|
92
|
+
tests/pinecone/test_pinecone.py,sha256=_wlJbSKnY7gyzVcwxIWKft1P_t8dWwcIKNfGCrRLiHs,2633
|
|
93
|
+
langtrace_python_sdk-1.3.7.dist-info/METADATA,sha256=3GGehwdTtmz8Hs-UPF8CLs4P3PLz_nek1e9FOSnlPxs,9244
|
|
94
|
+
langtrace_python_sdk-1.3.7.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
95
|
+
langtrace_python_sdk-1.3.7.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
96
|
+
langtrace_python_sdk-1.3.7.dist-info/RECORD,,
|
tests/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import MagicMock, call
|
|
3
|
+
from langtrace_python_sdk.instrumentation.anthropic.patch import messages_create
|
|
4
|
+
from opentelemetry.trace import SpanKind
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
from langtrace_python_sdk.constants.instrumentation.anthropic import APIS
|
|
7
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
8
|
+
import json
|
|
9
|
+
from langtrace.trace_attributes import Event, LLMSpanAttributes
|
|
10
|
+
|
|
11
|
+
from tests.utils import common_setup
|
|
12
|
+
|
|
13
|
+
class TestAnthropic(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
data = {
|
|
16
|
+
"content" : [MagicMock(text="Some text", type="text")],
|
|
17
|
+
"system_fingerprint" : "None",
|
|
18
|
+
"usage" : MagicMock(input_tokens=23, output_tokens=44),
|
|
19
|
+
"chunks" : [MagicMock(delta="Some text", message="text")]}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def setUp(self):
|
|
23
|
+
|
|
24
|
+
# Mock the original method
|
|
25
|
+
self.anthropic_mock, self.tracer, self.span = common_setup(self.data, None)
|
|
26
|
+
|
|
27
|
+
def tearDown(self):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def test_anthropic(self):
|
|
31
|
+
# Arrange
|
|
32
|
+
version = importlib.metadata.version('anthropic')
|
|
33
|
+
kwargs = {
|
|
34
|
+
"model": "claude-3-opus-20240229",
|
|
35
|
+
"messages" : [{"role": "user", "content": "How are you today?"}],
|
|
36
|
+
"stream": False
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Act
|
|
40
|
+
wrapped_function = messages_create("anthropic.messages.create", version, self.tracer)
|
|
41
|
+
result = wrapped_function(self.anthropic_mock, MagicMock(), (), kwargs)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Assert
|
|
45
|
+
self.assertTrue(self.tracer.start_as_current_span.called_once_with("anthropic.messages.create", kind=SpanKind.CLIENT))
|
|
46
|
+
self.assertTrue(self.span.set_status.has_calls([call(Status(StatusCode.OK))]))
|
|
47
|
+
|
|
48
|
+
expected_attributes = {
|
|
49
|
+
"langtrace.sdk.name": "langtrace-python-sdk",
|
|
50
|
+
"langtrace.service.name": "Anthropic",
|
|
51
|
+
"langtrace.service.type": "llm",
|
|
52
|
+
"langtrace.service.version": version,
|
|
53
|
+
"langtrace.version": "1.0.0",
|
|
54
|
+
"url.full": "/v1/messages",
|
|
55
|
+
"llm.api": APIS["MESSAGES_CREATE"]["ENDPOINT"],
|
|
56
|
+
"llm.model": kwargs.get("model"),
|
|
57
|
+
"llm.prompts": json.dumps(kwargs.get("messages", [])),
|
|
58
|
+
"llm.stream": kwargs.get("stream"),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
self.assertTrue(
|
|
62
|
+
self.span.set_attribute.has_calls(
|
|
63
|
+
[call(key, value) for key, value in expected_attributes.items()], any_order=True
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
expected_result_data = {"system_fingerprint": "None" }
|
|
68
|
+
|
|
69
|
+
self.assertEqual(result.system_fingerprint, expected_result_data["system_fingerprint"])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == '__main__':
|
|
73
|
+
unittest.main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch, call
|
|
2
|
+
from langtrace_python_sdk.constants.instrumentation.common import \
|
|
3
|
+
SERVICE_PROVIDERS
|
|
4
|
+
from langtrace_python_sdk.constants.instrumentation.openai import APIS
|
|
5
|
+
from opentelemetry.trace.status import StatusCode,Status
|
|
6
|
+
from langtrace_python_sdk.instrumentation.chroma.patch import collection_patch
|
|
7
|
+
from opentelemetry.trace import SpanKind
|
|
8
|
+
from tests.utils import common_setup
|
|
9
|
+
import unittest
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestChromaPatch(unittest.TestCase):
|
|
14
|
+
data = {
|
|
15
|
+
"status": "success",
|
|
16
|
+
}
|
|
17
|
+
def setUp(self):
|
|
18
|
+
self.chroma_mock, self.tracer, self.span = common_setup(self.data, 'chromadb.Collection.add')
|
|
19
|
+
self.wrapped_method = MagicMock(return_value="mocked method result")
|
|
20
|
+
self.instance = MagicMock()
|
|
21
|
+
self.instance.name = "aa"
|
|
22
|
+
|
|
23
|
+
def tearDown(self):
|
|
24
|
+
self.chroma_mock.stop()
|
|
25
|
+
|
|
26
|
+
def test_collection_patch_success(self):
|
|
27
|
+
# Arrange
|
|
28
|
+
traced_method = collection_patch("ADD", "1.2.3", self.tracer)
|
|
29
|
+
|
|
30
|
+
# Act
|
|
31
|
+
result = traced_method(self.wrapped_method, self.instance, (), {})
|
|
32
|
+
|
|
33
|
+
# Assert
|
|
34
|
+
# Assert the result of the original method is returned
|
|
35
|
+
self.assertEqual(result, "mocked method result")
|
|
36
|
+
|
|
37
|
+
# Assert the span is started with the correct parameters
|
|
38
|
+
self.assertTrue(self.tracer.start_as_current_span.called_once_with("chromadb.Collection.add", kind=SpanKind.CLIENT))
|
|
39
|
+
|
|
40
|
+
# Verify span attributes are set as expected
|
|
41
|
+
expected_attributes = {
|
|
42
|
+
'langtrace.sdk.name': 'langtrace-python-sdk',
|
|
43
|
+
'langtrace.service.name': 'Chroma',
|
|
44
|
+
'langtrace.service.type': 'vectordb',
|
|
45
|
+
'langtrace.service.version': '1.2.3',
|
|
46
|
+
'langtrace.version': '1.0.0',
|
|
47
|
+
'db.system': 'chromadb',
|
|
48
|
+
'db.operation': 'add',
|
|
49
|
+
'db.collection.name': 'aa',
|
|
50
|
+
}
|
|
51
|
+
for key, value in expected_attributes.items():
|
|
52
|
+
self.span.set_attribute.assert_has_calls([call(key, value)], any_order=True)
|
|
53
|
+
|
|
54
|
+
actual_calls = self.span.set_attribute.call_args_list
|
|
55
|
+
|
|
56
|
+
for key, value in expected_attributes.items():
|
|
57
|
+
self.assertIn(call(key, value), actual_calls)
|
|
58
|
+
|
|
59
|
+
# Assert the span status is set to OK
|
|
60
|
+
self.span.set_status.assert_called_with(StatusCode.OK)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
unittest.main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import MagicMock, call
|
|
4
|
+
from langtrace_python_sdk.instrumentation.langchain.patch import generic_patch
|
|
5
|
+
from opentelemetry.trace import SpanKind
|
|
6
|
+
from opentelemetry.trace import get_tracer
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
from langtrace_python_sdk.constants.instrumentation.openai import APIS
|
|
9
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
10
|
+
from tests.utils import common_setup
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
class TestGenericPatch(unittest.TestCase):
|
|
14
|
+
data = {"key": "value"}
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.langchain_mock, self.tracer, self.span = common_setup(self.data, None)
|
|
17
|
+
|
|
18
|
+
def tearDown(self):
|
|
19
|
+
# Clean up after each test case
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def test_generic_patch(self):
|
|
23
|
+
# Arrange
|
|
24
|
+
method_name = "example_method"
|
|
25
|
+
trace_output = False
|
|
26
|
+
trace_input = False # Change as per your requirement
|
|
27
|
+
args = (1, 2, 3)
|
|
28
|
+
task = "split_text"
|
|
29
|
+
kwargs = {'key': 'value'}
|
|
30
|
+
version = importlib.metadata.version('langchain')
|
|
31
|
+
|
|
32
|
+
# Act
|
|
33
|
+
wrapped_function = generic_patch("langchain.text_splitter", task, self.tracer, version, trace_output, trace_input)
|
|
34
|
+
result = wrapped_function(self.langchain_mock, MagicMock(), args, kwargs)
|
|
35
|
+
|
|
36
|
+
# Assert
|
|
37
|
+
self.assertTrue(self.tracer.start_as_current_span.called_once_with(method_name, kind=SpanKind.CLIENT))
|
|
38
|
+
|
|
39
|
+
service_provider = "Langchain"
|
|
40
|
+
expected_attributes = {
|
|
41
|
+
'langtrace.sdk.name': 'langtrace-python-sdk',
|
|
42
|
+
"langtrace.service.name": service_provider,
|
|
43
|
+
"langtrace.service.type": "framework",
|
|
44
|
+
"langtrace.service.version": version,
|
|
45
|
+
"langtrace.version": "1.0.0",
|
|
46
|
+
"langchain.task.name": task,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
self.assertTrue(
|
|
51
|
+
self.span.set_attribute.has_calls(
|
|
52
|
+
[call(key, value) for key, value in expected_attributes.items()], any_order=True
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
actual_calls = self.span.set_attribute.call_args_list
|
|
57
|
+
|
|
58
|
+
for key, value in expected_attributes.items():
|
|
59
|
+
self.assertIn(call(key, value), actual_calls)
|
|
60
|
+
|
|
61
|
+
self.assertEqual(self.span.set_status.call_count, 1)
|
|
62
|
+
self.assertTrue(self.span.set_status.has_calls([call(Status(StatusCode.OK))]))
|
|
63
|
+
|
|
64
|
+
expected_result_data = {"key": "value" }
|
|
65
|
+
|
|
66
|
+
self.assertEqual(result.key, expected_result_data["key"])
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
unittest.main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import MagicMock, Mock, patch, call
|
|
4
|
+
from langtrace_python_sdk.instrumentation.langchain_community.patch import generic_patch
|
|
5
|
+
from opentelemetry.trace import SpanKind
|
|
6
|
+
from opentelemetry.trace import get_tracer
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
import openai
|
|
9
|
+
from langtrace_python_sdk.constants.instrumentation.openai import APIS
|
|
10
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
11
|
+
import json
|
|
12
|
+
from tests.utils import common_setup
|
|
13
|
+
class TestGenericPatch(unittest.TestCase):
|
|
14
|
+
data = {"key": "value"}
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.langchain_mock, self.tracer, self.span = common_setup(self.data, None)
|
|
17
|
+
|
|
18
|
+
def tearDown(self):
|
|
19
|
+
# Clean up after each test case
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def test_generic_patch(self):
|
|
23
|
+
# Arrange
|
|
24
|
+
method_name = "example_method"
|
|
25
|
+
trace_output = False
|
|
26
|
+
trace_input = False
|
|
27
|
+
args = (1, 2, 3)
|
|
28
|
+
task = "vector_store"
|
|
29
|
+
kwargs = {'key': 'value'}
|
|
30
|
+
version = importlib.metadata.version("langchain-community")
|
|
31
|
+
|
|
32
|
+
# Act
|
|
33
|
+
wrapped_function = generic_patch("langchain_community.vectorstores.faiss", task, self.tracer, version, trace_output, trace_input)
|
|
34
|
+
result = wrapped_function(self.langchain_mock, MagicMock(), args, kwargs)
|
|
35
|
+
|
|
36
|
+
# Assert
|
|
37
|
+
self.assertTrue(self.tracer.start_as_current_span.called_once_with(method_name, kind=SpanKind.CLIENT))
|
|
38
|
+
|
|
39
|
+
service_provider = "Langchain Community"
|
|
40
|
+
expected_attributes = {
|
|
41
|
+
'langtrace.sdk.name': 'langtrace-python-sdk',
|
|
42
|
+
"langtrace.service.name": service_provider,
|
|
43
|
+
"langtrace.service.type": "framework",
|
|
44
|
+
"langtrace.service.version": version,
|
|
45
|
+
"langtrace.version": "1.0.0",
|
|
46
|
+
"langchain.task.name": task,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
self.assertTrue(
|
|
51
|
+
self.span.set_attribute.has_calls(
|
|
52
|
+
[call(key, value) for key, value in expected_attributes.items()], any_order=True
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
actual_calls = self.span.set_attribute.call_args_list
|
|
57
|
+
|
|
58
|
+
for key, value in expected_attributes.items():
|
|
59
|
+
self.assertIn(call(key, value), actual_calls)
|
|
60
|
+
|
|
61
|
+
self.assertEqual(self.span.set_status.call_count, 1)
|
|
62
|
+
self.assertTrue(self.span.set_status.has_calls([call(Status(StatusCode.OK))]))
|
|
63
|
+
|
|
64
|
+
expected_result_data = {"key": "value" }
|
|
65
|
+
self.assertEqual(result.key, expected_result_data["key"])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
unittest.main()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import MagicMock, Mock, patch, call
|
|
4
|
+
from langtrace_python_sdk.instrumentation.langchain_core.patch import generic_patch, runnable_patch
|
|
5
|
+
from opentelemetry.trace import SpanKind
|
|
6
|
+
from opentelemetry.trace import get_tracer
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
import openai
|
|
9
|
+
from langtrace_python_sdk.constants.instrumentation.openai import APIS
|
|
10
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
11
|
+
import json
|
|
12
|
+
from tests.utils import common_setup
|
|
13
|
+
class TestGenericPatch(unittest.TestCase):
|
|
14
|
+
data = {"items": "value"}
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.langchain_mock, self.tracer, self.span = common_setup(self.data, None)
|
|
17
|
+
|
|
18
|
+
def tearDown(self):
|
|
19
|
+
# Clean up after each test case
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def test_generic_patch(self):
|
|
23
|
+
# Arrange
|
|
24
|
+
method_name = "example_method"
|
|
25
|
+
trace_output = False
|
|
26
|
+
trace_input = True
|
|
27
|
+
task = "retriever"
|
|
28
|
+
args = (1, 2, 3)
|
|
29
|
+
kwargs = {'key': 'value'}
|
|
30
|
+
version = importlib.metadata.version("langchain-core")
|
|
31
|
+
|
|
32
|
+
# Act
|
|
33
|
+
wrapped_function = generic_patch("langchain_core.retrievers", task , self.tracer, version, trace_output, trace_input)
|
|
34
|
+
result = wrapped_function(self.langchain_mock, MagicMock(), args, kwargs)
|
|
35
|
+
|
|
36
|
+
# Assert
|
|
37
|
+
self.assertTrue(self.tracer.start_as_current_span.called_once_with(method_name, kind=SpanKind.CLIENT))
|
|
38
|
+
|
|
39
|
+
service_provider = "Langchain Core"
|
|
40
|
+
expected_attributes = {
|
|
41
|
+
'langtrace.sdk.name': 'langtrace-python-sdk',
|
|
42
|
+
"langtrace.service.name": service_provider,
|
|
43
|
+
"langtrace.service.type": "framework",
|
|
44
|
+
"langtrace.service.version": version,
|
|
45
|
+
"langtrace.version": "1.0.0",
|
|
46
|
+
"langchain.task.name": task,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
self.assertTrue(
|
|
50
|
+
self.span.set_attribute.has_calls(
|
|
51
|
+
[call(key, value) for key, value in expected_attributes.items()], any_order=True
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
actual_calls = self.span.set_attribute.call_args_list
|
|
56
|
+
for key, value in expected_attributes.items():
|
|
57
|
+
self.assertIn(call(key, value), actual_calls)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
self.assertEqual(self.span.set_status.call_count, 1)
|
|
61
|
+
self.assertTrue(self.span.set_status.has_calls([call(Status(StatusCode.OK))]))
|
|
62
|
+
|
|
63
|
+
expected_result_data = {"items": "value" }
|
|
64
|
+
self.assertEqual(result.items, expected_result_data["items"])
|
|
65
|
+
|
|
66
|
+
def test_runnable_patch(self):
|
|
67
|
+
# Arrange
|
|
68
|
+
method_name = "example_method"
|
|
69
|
+
trace_output = False
|
|
70
|
+
trace_input = True
|
|
71
|
+
args = (1, 2, 3)
|
|
72
|
+
kwargs = {'key': 'value'}
|
|
73
|
+
version = importlib.metadata.version("langchain-core")
|
|
74
|
+
|
|
75
|
+
# Act
|
|
76
|
+
wrapped_function = runnable_patch("langchain_core.runnables.passthrough",
|
|
77
|
+
"runnablepassthrough", self.tracer, version, trace_output, trace_input)
|
|
78
|
+
|
|
79
|
+
result = wrapped_function(self.langchain_mock, MagicMock(), args, kwargs)
|
|
80
|
+
|
|
81
|
+
# Assert
|
|
82
|
+
self.assertTrue(self.tracer.start_as_current_span.called_once_with(method_name, kind=SpanKind.CLIENT))
|
|
83
|
+
|
|
84
|
+
service_provider = "Langchain Core"
|
|
85
|
+
expected_attributes = {
|
|
86
|
+
'langtrace.sdk.name': 'langtrace-python-sdk',
|
|
87
|
+
"langtrace.service.name": service_provider,
|
|
88
|
+
"langtrace.service.type": "framework",
|
|
89
|
+
"langtrace.service.version": version,
|
|
90
|
+
"langtrace.version": "1.0.0",
|
|
91
|
+
"langchain.task.name": "runnablepassthrough",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
self.assertTrue(
|
|
95
|
+
self.span.set_attribute.has_calls(
|
|
96
|
+
[call(key, value) for key, value in expected_attributes.items()], any_order=True
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
actual_calls = self.span.set_attribute.call_args_list
|
|
101
|
+
|
|
102
|
+
for key, value in expected_attributes.items():
|
|
103
|
+
self.assertIn(call(key, value), actual_calls)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
self.assertEqual(self.span.set_status.call_count, 1)
|
|
107
|
+
self.assertTrue(self.span.set_status.has_calls([call(Status(StatusCode.OK))]))
|
|
108
|
+
|
|
109
|
+
expected_result_data = {"items": "value" }
|
|
110
|
+
|
|
111
|
+
self.assertEqual(result.items, expected_result_data["items"])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == '__main__':
|
|
115
|
+
unittest.main()
|