langtrace-python-sdk 2.0.13__py3-none-any.whl → 2.1.1__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.
- examples/pinecone_example/basic.py +7 -2
- examples/weaviate_example/query_text.py +1737 -0
- langtrace_python_sdk/__init__.py +7 -1
- langtrace_python_sdk/constants/instrumentation/common.py +1 -0
- langtrace_python_sdk/constants/instrumentation/weaviate.py +44 -0
- langtrace_python_sdk/instrumentation/chroma/patch.py +191 -0
- langtrace_python_sdk/instrumentation/openai/patch.py +1 -0
- langtrace_python_sdk/instrumentation/pinecone/patch.py +1 -0
- langtrace_python_sdk/instrumentation/qdrant/patch.py +28 -4
- langtrace_python_sdk/instrumentation/weaviate/__init__.py +0 -0
- langtrace_python_sdk/instrumentation/weaviate/instrumentation.py +66 -0
- langtrace_python_sdk/instrumentation/weaviate/patch.py +166 -0
- langtrace_python_sdk/langtrace.py +4 -0
- langtrace_python_sdk/types/__init__.py +1 -0
- langtrace_python_sdk/utils/__init__.py +0 -2
- langtrace_python_sdk/utils/llm.py +0 -1
- langtrace_python_sdk/utils/misc.py +30 -0
- langtrace_python_sdk/utils/types.py +19 -0
- langtrace_python_sdk/utils/with_root_span.py +99 -4
- langtrace_python_sdk/version.py +1 -1
- {langtrace_python_sdk-2.0.13.dist-info → langtrace_python_sdk-2.1.1.dist-info}/METADATA +30 -15
- {langtrace_python_sdk-2.0.13.dist-info → langtrace_python_sdk-2.1.1.dist-info}/RECORD +28 -22
- tests/cohere/cassettes/test_cohere_chat.yaml +19 -21
- tests/cohere/cassettes/test_cohere_chat_streaming.yaml +73 -135
- tests/cohere/cassettes/test_cohere_embed.yaml +9 -9
- tests/cohere/cassettes/test_cohere_rerank.yaml +8 -8
- {langtrace_python_sdk-2.0.13.dist-info → langtrace_python_sdk-2.1.1.dist-info}/WHEEL +0 -0
- {langtrace_python_sdk-2.0.13.dist-info → langtrace_python_sdk-2.1.1.dist-info}/licenses/LICENSE +0 -0
langtrace_python_sdk/__init__.py
CHANGED
|
@@ -17,5 +17,11 @@ limitations under the License.
|
|
|
17
17
|
from langtrace_python_sdk import langtrace
|
|
18
18
|
from langtrace_python_sdk.utils.with_root_span import with_langtrace_root_span
|
|
19
19
|
from langtrace_python_sdk.utils.prompt_registry import get_prompt_from_registry
|
|
20
|
+
from langtrace_python_sdk.utils.with_root_span import send_user_feedback
|
|
20
21
|
|
|
21
|
-
__all__ = [
|
|
22
|
+
__all__ = [
|
|
23
|
+
"langtrace",
|
|
24
|
+
"with_langtrace_root_span",
|
|
25
|
+
"get_prompt_from_registry",
|
|
26
|
+
"send_user_feedback",
|
|
27
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from langtrace.trace_attributes import WeaviateMethods
|
|
2
|
+
|
|
3
|
+
APIS = {
|
|
4
|
+
"weaviate.query.bm25": {
|
|
5
|
+
"MODULE": WeaviateMethods.QUERY_BM25.value,
|
|
6
|
+
"METHOD": "_BM25Query.bm25",
|
|
7
|
+
"OPERATION": "query",
|
|
8
|
+
},
|
|
9
|
+
"weaviate.query.fetch_object_by_id": {
|
|
10
|
+
"MODULE": WeaviateMethods.QUERY_FETCH_OBJECT_BY_ID.value,
|
|
11
|
+
"METHOD": "_FetchObjectByIDQuery.fetch_object_by_id",
|
|
12
|
+
"OPERATION": "query",
|
|
13
|
+
},
|
|
14
|
+
"weaviate.query.fetch_objects": {
|
|
15
|
+
"MODULE": WeaviateMethods.QUERY_FETCH_OBJECTS.value,
|
|
16
|
+
"METHOD": "_FetchObjectsQuery.fetch_objects",
|
|
17
|
+
"OPERATION": "query",
|
|
18
|
+
},
|
|
19
|
+
"weaviate.query.hybrid": {
|
|
20
|
+
"MODULE": WeaviateMethods.QUERY_HYBRID.value,
|
|
21
|
+
"METHOD": "_HybridQuery.hybrid",
|
|
22
|
+
"OPERATION": "query",
|
|
23
|
+
},
|
|
24
|
+
"weaviate.query.near_object": {
|
|
25
|
+
"MODULE": WeaviateMethods.QUERY_NEAR_OBJECT.value,
|
|
26
|
+
"METHOD": "_NearObjectQuery.near_object",
|
|
27
|
+
"OPERATION": "query",
|
|
28
|
+
},
|
|
29
|
+
"weaviate.query.near_text": {
|
|
30
|
+
"MODULE": WeaviateMethods.QUERY_NEAR_TEXT.value,
|
|
31
|
+
"METHOD": "_NearTextQuery.near_text",
|
|
32
|
+
"OPERATION": "query",
|
|
33
|
+
},
|
|
34
|
+
"weaviate.query.near_vector": {
|
|
35
|
+
"MODULE": WeaviateMethods.QUERY_NEAR_VECTOR.value,
|
|
36
|
+
"METHOD": "_NearVectorQuery.near_vector",
|
|
37
|
+
"OPERATION": "query",
|
|
38
|
+
},
|
|
39
|
+
"weaviate.collections.create": {
|
|
40
|
+
"MODULE": WeaviateMethods.COLLECTIONS_OPERATIONS.value,
|
|
41
|
+
"METHOD": "_Collections.create",
|
|
42
|
+
"OPERATION": "create",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
@@ -15,6 +15,8 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from langtrace.trace_attributes import DatabaseSpanAttributes
|
|
18
|
+
from langtrace_python_sdk.utils.llm import set_span_attributes
|
|
19
|
+
from langtrace_python_sdk.utils.silently_fail import silently_fail
|
|
18
20
|
from opentelemetry import baggage
|
|
19
21
|
from opentelemetry.trace import SpanKind
|
|
20
22
|
from opentelemetry.trace.status import Status, StatusCode
|
|
@@ -24,6 +26,7 @@ from langtrace_python_sdk.constants.instrumentation.common import (
|
|
|
24
26
|
LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY,
|
|
25
27
|
SERVICE_PROVIDERS,
|
|
26
28
|
)
|
|
29
|
+
import json
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
def collection_patch(method, version, tracer):
|
|
@@ -44,6 +47,7 @@ def collection_patch(method, version, tracer):
|
|
|
44
47
|
"langtrace.version": "1.0.0",
|
|
45
48
|
"db.system": "chromadb",
|
|
46
49
|
"db.operation": api["OPERATION"],
|
|
50
|
+
"db.query": json.dumps(kwargs.get("query")),
|
|
47
51
|
**(extra_attributes if extra_attributes is not None else {}),
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -57,8 +61,32 @@ def collection_patch(method, version, tracer):
|
|
|
57
61
|
if value is not None:
|
|
58
62
|
span.set_attribute(field, value)
|
|
59
63
|
try:
|
|
64
|
+
|
|
65
|
+
operation = api["OPERATION"]
|
|
66
|
+
if operation == "add":
|
|
67
|
+
_set_chroma_add_attributes(span, kwargs)
|
|
68
|
+
elif operation == "get":
|
|
69
|
+
_set_chroma_get_attributes(span, kwargs)
|
|
70
|
+
elif operation == "query":
|
|
71
|
+
_set_chroma_query_attributes(span, kwargs)
|
|
72
|
+
elif operation == "peek":
|
|
73
|
+
_set_chroma_peek_attributes(span, kwargs)
|
|
74
|
+
elif operation == "update":
|
|
75
|
+
_set_chroma_update_attributes(span, kwargs)
|
|
76
|
+
elif operation == "upsert":
|
|
77
|
+
_set_chroma_upsert_attributes(span, kwargs)
|
|
78
|
+
elif operation == "modify":
|
|
79
|
+
_set_chroma_modify_attributes(span, kwargs)
|
|
80
|
+
elif operation == "delete":
|
|
81
|
+
_set_chroma_delete_attributes(span, kwargs)
|
|
60
82
|
# Attempt to call the original method
|
|
61
83
|
result = wrapped(*args, **kwargs)
|
|
84
|
+
|
|
85
|
+
if operation == "query":
|
|
86
|
+
events = _set_chroma_query_response(span, result)
|
|
87
|
+
for event in events:
|
|
88
|
+
span.add_event(name="db.chroma.query.result", attributes=event)
|
|
89
|
+
|
|
62
90
|
span.set_status(StatusCode.OK)
|
|
63
91
|
return result
|
|
64
92
|
except Exception as err:
|
|
@@ -72,3 +100,166 @@ def collection_patch(method, version, tracer):
|
|
|
72
100
|
raise
|
|
73
101
|
|
|
74
102
|
return traced_method
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_count_or_none(value):
|
|
106
|
+
return len(value) if value is not None else None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def handle_null_params(param):
|
|
110
|
+
return str(param) if param else None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@silently_fail
|
|
114
|
+
def _set_chroma_add_attributes(span, kwargs):
|
|
115
|
+
set_span_attributes(
|
|
116
|
+
span, "db.chroma.add.ids_count", get_count_or_none(kwargs.get("ids"))
|
|
117
|
+
)
|
|
118
|
+
set_span_attributes(
|
|
119
|
+
span,
|
|
120
|
+
"db.chroma.add.embeddings_count",
|
|
121
|
+
get_count_or_none(kwargs.get("embeddings")),
|
|
122
|
+
)
|
|
123
|
+
set_span_attributes(
|
|
124
|
+
span,
|
|
125
|
+
"db.chroma.add.metadatas_count",
|
|
126
|
+
get_count_or_none(kwargs.get("metadatas")),
|
|
127
|
+
)
|
|
128
|
+
set_span_attributes(
|
|
129
|
+
span,
|
|
130
|
+
"db.chroma.add.documents_count",
|
|
131
|
+
get_count_or_none(kwargs.get("documents")),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@silently_fail
|
|
136
|
+
def _set_chroma_get_attributes(span, kwargs):
|
|
137
|
+
set_span_attributes(
|
|
138
|
+
span, "db.chroma.get.ids_count", get_count_or_none(kwargs.get("ids"))
|
|
139
|
+
)
|
|
140
|
+
set_span_attributes(
|
|
141
|
+
span, "db.chroma.get.where", handle_null_params(kwargs.get("where"))
|
|
142
|
+
)
|
|
143
|
+
set_span_attributes(span, "db.chroma.get.limit", kwargs.get("limit"))
|
|
144
|
+
set_span_attributes(span, "db.chroma.get.offset", kwargs.get("offset"))
|
|
145
|
+
set_span_attributes(
|
|
146
|
+
span,
|
|
147
|
+
"db.chroma.get.where_document",
|
|
148
|
+
handle_null_params(kwargs.get("where_document")),
|
|
149
|
+
)
|
|
150
|
+
set_span_attributes(
|
|
151
|
+
span, "db.chroma.get.include", handle_null_params(kwargs.get("include"))
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@silently_fail
|
|
156
|
+
def _set_chroma_query_attributes(span, kwargs):
|
|
157
|
+
set_span_attributes(
|
|
158
|
+
span,
|
|
159
|
+
"db.chroma.query.query_embeddings_count",
|
|
160
|
+
get_count_or_none(kwargs.get("query_embeddings")),
|
|
161
|
+
)
|
|
162
|
+
set_span_attributes(
|
|
163
|
+
span,
|
|
164
|
+
"db.chroma.query.query_texts_count",
|
|
165
|
+
get_count_or_none(kwargs.get("query_texts")),
|
|
166
|
+
)
|
|
167
|
+
set_span_attributes(span, "db.chroma.query.n_results", kwargs.get("n_results"))
|
|
168
|
+
set_span_attributes(
|
|
169
|
+
span, "db.chroma.query.where", handle_null_params(kwargs.get("where"))
|
|
170
|
+
)
|
|
171
|
+
set_span_attributes(
|
|
172
|
+
span,
|
|
173
|
+
"db.chroma.query.where_document",
|
|
174
|
+
handle_null_params(kwargs.get("where_document")),
|
|
175
|
+
)
|
|
176
|
+
set_span_attributes(
|
|
177
|
+
span, "db.chroma.query.include", handle_null_params(kwargs.get("include"))
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@silently_fail
|
|
182
|
+
def _set_chroma_peek_attributes(span, kwargs):
|
|
183
|
+
set_span_attributes(span, "db.chroma.peek.limit", kwargs.get("limit"))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@silently_fail
|
|
187
|
+
def _set_chroma_update_attributes(span, kwargs):
|
|
188
|
+
set_span_attributes(
|
|
189
|
+
span, "db.chroma.update.ids_count", get_count_or_none(kwargs.get("ids"))
|
|
190
|
+
)
|
|
191
|
+
set_span_attributes(
|
|
192
|
+
span,
|
|
193
|
+
"db.chroma.update.embeddings_count",
|
|
194
|
+
get_count_or_none(kwargs.get("embeddings")),
|
|
195
|
+
)
|
|
196
|
+
set_span_attributes(
|
|
197
|
+
span,
|
|
198
|
+
"db.chroma.update.metadatas_count",
|
|
199
|
+
get_count_or_none(kwargs.get("metadatas")),
|
|
200
|
+
)
|
|
201
|
+
set_span_attributes(
|
|
202
|
+
span,
|
|
203
|
+
"db.chroma.update.documents_count",
|
|
204
|
+
get_count_or_none(kwargs.get("documents")),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@silently_fail
|
|
209
|
+
def _set_chroma_modify_attributes(span, kwargs):
|
|
210
|
+
set_span_attributes(span, "db.chroma.modify.name", kwargs.get("name"))
|
|
211
|
+
# TODO: Add metadata attribute
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@silently_fail
|
|
215
|
+
def _set_chroma_upsert_attributes(span, kwargs):
|
|
216
|
+
set_span_attributes(
|
|
217
|
+
span,
|
|
218
|
+
"db.chroma.upsert.embeddings_count",
|
|
219
|
+
get_count_or_none(kwargs.get("embeddings")),
|
|
220
|
+
)
|
|
221
|
+
set_span_attributes(
|
|
222
|
+
span,
|
|
223
|
+
"db.chroma.upsert.metadatas_count",
|
|
224
|
+
get_count_or_none(kwargs.get("metadatas")),
|
|
225
|
+
)
|
|
226
|
+
set_span_attributes(
|
|
227
|
+
span,
|
|
228
|
+
"db.chroma.upsert.documents_count",
|
|
229
|
+
get_count_or_none(kwargs.get("documents")),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@silently_fail
|
|
234
|
+
def _set_chroma_delete_attributes(span, kwargs):
|
|
235
|
+
set_span_attributes(
|
|
236
|
+
span, "db.chroma.delete.ids_count", get_count_or_none(kwargs.get("ids"))
|
|
237
|
+
)
|
|
238
|
+
set_span_attributes(
|
|
239
|
+
span, "db.chroma.delete.where", handle_null_params(kwargs.get("where"))
|
|
240
|
+
)
|
|
241
|
+
set_span_attributes(
|
|
242
|
+
span,
|
|
243
|
+
"db.chroma.delete.where_document",
|
|
244
|
+
handle_null_params(kwargs.get("where_document")),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@silently_fail
|
|
249
|
+
def _set_chroma_query_response(span, result):
|
|
250
|
+
|
|
251
|
+
attributes = []
|
|
252
|
+
ids = result.get("ids")[0]
|
|
253
|
+
distances = result.get("distances")[0]
|
|
254
|
+
metadatas = result.get("metadatas")[0]
|
|
255
|
+
documents = result.get("documents")[0]
|
|
256
|
+
|
|
257
|
+
for idx, _ in enumerate(ids):
|
|
258
|
+
attribute = {
|
|
259
|
+
"id": ids[idx],
|
|
260
|
+
"distance": distances[idx],
|
|
261
|
+
"metadata": metadatas[idx],
|
|
262
|
+
"document": documents[idx],
|
|
263
|
+
}
|
|
264
|
+
attributes.append(attribute)
|
|
265
|
+
return attributes
|
|
@@ -616,6 +616,7 @@ def async_chat_completions_create(original_method, version, tracer):
|
|
|
616
616
|
span.add_event(Event.STREAM_START.value)
|
|
617
617
|
completion_tokens = 0
|
|
618
618
|
try:
|
|
619
|
+
content = []
|
|
619
620
|
async for chunk in result:
|
|
620
621
|
if hasattr(chunk, "model") and chunk.model is not None:
|
|
621
622
|
span.set_attribute("llm.model", chunk.model)
|
|
@@ -46,6 +46,7 @@ def generic_patch(original_method, method, version, tracer):
|
|
|
46
46
|
"langtrace.version": "1.0.0",
|
|
47
47
|
"db.system": "pinecone",
|
|
48
48
|
"db.operation": api["OPERATION"],
|
|
49
|
+
"db.query": json.dumps(kwargs.get("query")),
|
|
49
50
|
**(extra_attributes if extra_attributes is not None else {}),
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
import json
|
|
17
18
|
from langtrace.trace_attributes import DatabaseSpanAttributes
|
|
18
19
|
from langtrace_python_sdk.utils.silently_fail import silently_fail
|
|
19
20
|
from langtrace_python_sdk.utils.llm import set_span_attributes
|
|
@@ -46,6 +47,7 @@ def collection_patch(method, version, tracer):
|
|
|
46
47
|
"langtrace.version": "1.0.0",
|
|
47
48
|
"db.system": "qdrant",
|
|
48
49
|
"db.operation": api["OPERATION"],
|
|
50
|
+
"db.query": json.dumps(kwargs.get("query")),
|
|
49
51
|
**(extra_attributes if extra_attributes is not None else {}),
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -55,12 +57,22 @@ def collection_patch(method, version, tracer):
|
|
|
55
57
|
collection_name = kwargs.get("collection_name") or args[0]
|
|
56
58
|
operation = api["OPERATION"]
|
|
57
59
|
set_span_attributes(span, "db.collection.name", collection_name)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
elif operation == "add":
|
|
60
|
+
|
|
61
|
+
if operation == "add":
|
|
61
62
|
_set_upload_attributes(span, args, kwargs, "documents")
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
elif operation == "upsert":
|
|
65
|
+
_set_upsert_attributes(span, args, kwargs)
|
|
66
|
+
|
|
67
|
+
elif operation in ["query", "discover", "recommend", "retrieve", "search"]:
|
|
68
|
+
_set_search_attributes(span, args, kwargs)
|
|
69
|
+
elif operation in [
|
|
70
|
+
"query_batch",
|
|
71
|
+
"discover_batch",
|
|
72
|
+
"recommend_batch",
|
|
73
|
+
"search_batch",
|
|
74
|
+
]:
|
|
75
|
+
_set_batch_search_attributes(span, args, kwargs, operation)
|
|
64
76
|
|
|
65
77
|
for field, value in attributes.model_dump(by_alias=True).items():
|
|
66
78
|
if value is not None:
|
|
@@ -104,3 +116,15 @@ def _set_upload_attributes(span, args, kwargs, field):
|
|
|
104
116
|
length = len(docs.ids)
|
|
105
117
|
|
|
106
118
|
set_span_attributes(span, f"db.upload.{field}_count", length)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@silently_fail
|
|
122
|
+
def _set_search_attributes(span, args, kwargs):
|
|
123
|
+
limit = kwargs.get("limit") or 10
|
|
124
|
+
set_span_attributes(span, "db.query.top_k", limit)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@silently_fail
|
|
128
|
+
def _set_batch_search_attributes(span, args, kwargs, method):
|
|
129
|
+
requests = kwargs.get("requests") or []
|
|
130
|
+
set_span_attributes(span, f"db.{method}.requests_count", len(requests))
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2024 Scale3 Labs
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import importlib.metadata
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Collection
|
|
20
|
+
|
|
21
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
22
|
+
from opentelemetry.trace import get_tracer
|
|
23
|
+
from wrapt import wrap_function_wrapper
|
|
24
|
+
|
|
25
|
+
from langtrace_python_sdk.instrumentation.weaviate.patch import (
|
|
26
|
+
generic_collection_patch,
|
|
27
|
+
generic_query_patch,
|
|
28
|
+
)
|
|
29
|
+
from langtrace_python_sdk.constants.instrumentation.weaviate import APIS
|
|
30
|
+
|
|
31
|
+
logging.basicConfig(level=logging.FATAL)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WeaviateInstrumentation(BaseInstrumentor):
|
|
35
|
+
"""
|
|
36
|
+
The WeaviateInstrumentation class represents the instrumentation for the Weaviate SDK.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
40
|
+
return ["weaviate-client >= 4.6.1", "trace-attributes >= 4.0.2"]
|
|
41
|
+
|
|
42
|
+
def _instrument(self, **kwargs):
|
|
43
|
+
tracer_provider = kwargs.get("tracer_provider")
|
|
44
|
+
tracer = get_tracer(__name__, "", tracer_provider)
|
|
45
|
+
version = importlib.metadata.version("weaviate-client")
|
|
46
|
+
|
|
47
|
+
for api_name, api_config in APIS.items():
|
|
48
|
+
module_path, function_name = api_name.rsplit(".", 1)
|
|
49
|
+
if api_config.get("OPERATION") == "query":
|
|
50
|
+
wrap_function_wrapper(
|
|
51
|
+
api_config["MODULE"],
|
|
52
|
+
api_config["METHOD"],
|
|
53
|
+
generic_query_patch(api_name, version, tracer),
|
|
54
|
+
)
|
|
55
|
+
elif api_config.get("OPERATION") == "create":
|
|
56
|
+
wrap_function_wrapper(
|
|
57
|
+
api_config["MODULE"],
|
|
58
|
+
api_config["METHOD"],
|
|
59
|
+
generic_collection_patch(api_name, version, tracer),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _instrument_module(self, module_name):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def _uninstrument(self, **kwargs):
|
|
66
|
+
pass
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2024 Scale3 Labs
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
|
|
19
|
+
from langtrace.trace_attributes import DatabaseSpanAttributes
|
|
20
|
+
from opentelemetry import baggage
|
|
21
|
+
from opentelemetry.trace import SpanKind
|
|
22
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
23
|
+
|
|
24
|
+
from langtrace_python_sdk.constants.instrumentation.common import (
|
|
25
|
+
LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY,
|
|
26
|
+
SERVICE_PROVIDERS,
|
|
27
|
+
)
|
|
28
|
+
from langtrace_python_sdk.constants.instrumentation.weaviate import APIS
|
|
29
|
+
from langtrace_python_sdk.utils.misc import extract_input_params, to_iso_format
|
|
30
|
+
|
|
31
|
+
# Predefined metadata response attributes
|
|
32
|
+
METADATA_ATTRIBUTES = [
|
|
33
|
+
"creation_time",
|
|
34
|
+
"last_update_time",
|
|
35
|
+
"distance",
|
|
36
|
+
"certainty",
|
|
37
|
+
"score",
|
|
38
|
+
"explain_score",
|
|
39
|
+
"is_consistent",
|
|
40
|
+
"rerank_score",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_metadata(metadata):
|
|
45
|
+
# Extraction response Query metadata
|
|
46
|
+
extracted_metadata = {
|
|
47
|
+
attr: (
|
|
48
|
+
to_iso_format(getattr(metadata, attr))
|
|
49
|
+
if "time" in attr
|
|
50
|
+
else getattr(metadata, attr)
|
|
51
|
+
)
|
|
52
|
+
for attr in METADATA_ATTRIBUTES
|
|
53
|
+
if hasattr(metadata, attr)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {k: v for k, v in extracted_metadata.items() if v is not None}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def aggregate_responses(result):
|
|
60
|
+
all_responses = []
|
|
61
|
+
|
|
62
|
+
if hasattr(result, "objects") and result.objects is not None:
|
|
63
|
+
for each_obj in result.objects:
|
|
64
|
+
# Loop for multiple object responses
|
|
65
|
+
response_attributes = get_response_object_attributes(each_obj)
|
|
66
|
+
all_responses.append(response_attributes)
|
|
67
|
+
else:
|
|
68
|
+
# For single object responses
|
|
69
|
+
all_responses = get_response_object_attributes(result)
|
|
70
|
+
|
|
71
|
+
return json.dumps(all_responses)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_response_object_attributes(response_object):
|
|
75
|
+
|
|
76
|
+
response_attributes = {
|
|
77
|
+
**response_object.properties,
|
|
78
|
+
"uuid": str(response_object.uuid) if hasattr(response_object, "uuid") else None,
|
|
79
|
+
"collection": (
|
|
80
|
+
response_object.collection
|
|
81
|
+
if hasattr(response_object, "collection")
|
|
82
|
+
else None
|
|
83
|
+
),
|
|
84
|
+
"vector": (
|
|
85
|
+
response_object.vector if hasattr(response_object, "vector") else None
|
|
86
|
+
),
|
|
87
|
+
"references": (
|
|
88
|
+
response_object.references
|
|
89
|
+
if hasattr(response_object, "references")
|
|
90
|
+
else None
|
|
91
|
+
),
|
|
92
|
+
"metadata": (
|
|
93
|
+
extract_metadata(response_object.metadata)
|
|
94
|
+
if hasattr(response_object, "metadata")
|
|
95
|
+
else None
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
response_attributes = {
|
|
99
|
+
k: v for k, v in response_attributes.items() if v is not None
|
|
100
|
+
}
|
|
101
|
+
return response_attributes
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def create_traced_method(method_name, version, tracer, get_collection_name=None):
|
|
105
|
+
def traced_method(wrapped, instance, args, kwargs):
|
|
106
|
+
api = APIS[method_name]
|
|
107
|
+
service_provider = SERVICE_PROVIDERS["WEAVIATE"]
|
|
108
|
+
extra_attributes = baggage.get_baggage(LANGTRACE_ADDITIONAL_SPAN_ATTRIBUTES_KEY)
|
|
109
|
+
|
|
110
|
+
collection_name = (
|
|
111
|
+
get_collection_name(instance, kwargs)
|
|
112
|
+
if get_collection_name
|
|
113
|
+
else instance._name
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
span_attributes = {
|
|
117
|
+
"langtrace.sdk.name": "langtrace-python-sdk",
|
|
118
|
+
"langtrace.service.name": service_provider,
|
|
119
|
+
"langtrace.service.type": "vectordb",
|
|
120
|
+
"langtrace.service.version": version,
|
|
121
|
+
"langtrace.version": "1.0.0",
|
|
122
|
+
"db.system": "weaviate",
|
|
123
|
+
"db.operation": api["OPERATION"],
|
|
124
|
+
"db.collection.name": collection_name,
|
|
125
|
+
"db.query": json.dumps(extract_input_params(args, kwargs)),
|
|
126
|
+
**(extra_attributes if extra_attributes is not None else {}),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
attributes = DatabaseSpanAttributes(**span_attributes)
|
|
130
|
+
|
|
131
|
+
with tracer.start_as_current_span(method_name, kind=SpanKind.CLIENT) as span:
|
|
132
|
+
for field, value in attributes.model_dump(by_alias=True).items():
|
|
133
|
+
if value is not None:
|
|
134
|
+
span.set_attribute(field, value)
|
|
135
|
+
try:
|
|
136
|
+
# Attempt to call the original method
|
|
137
|
+
result = wrapped(*args, **kwargs)
|
|
138
|
+
if api["OPERATION"] == "query":
|
|
139
|
+
span.add_event(
|
|
140
|
+
name="db.response",
|
|
141
|
+
attributes={"db.response": aggregate_responses(result)},
|
|
142
|
+
)
|
|
143
|
+
span.set_status(StatusCode.OK)
|
|
144
|
+
return result
|
|
145
|
+
except Exception as err:
|
|
146
|
+
# Record the exception in the span
|
|
147
|
+
span.record_exception(err)
|
|
148
|
+
# Set the span status to indicate an error
|
|
149
|
+
span.set_status(Status(StatusCode.ERROR, str(err)))
|
|
150
|
+
# Reraise the exception to ensure it's not swallowed
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
return traced_method
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def generic_query_patch(method_name, version, tracer):
|
|
157
|
+
return create_traced_method(method_name, version, tracer)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def generic_collection_patch(method_name, version, tracer):
|
|
161
|
+
return create_traced_method(
|
|
162
|
+
method_name,
|
|
163
|
+
version,
|
|
164
|
+
tracer,
|
|
165
|
+
get_collection_name=lambda instance, kwargs: kwargs.get("name"),
|
|
166
|
+
)
|
|
@@ -64,6 +64,9 @@ from langtrace_python_sdk.instrumentation.pinecone.instrumentation import (
|
|
|
64
64
|
from langtrace_python_sdk.instrumentation.qdrant.instrumentation import (
|
|
65
65
|
QdrantInstrumentation,
|
|
66
66
|
)
|
|
67
|
+
from langtrace_python_sdk.instrumentation.weaviate.instrumentation import (
|
|
68
|
+
WeaviateInstrumentation,
|
|
69
|
+
)
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
def init(
|
|
@@ -119,6 +122,7 @@ def init(
|
|
|
119
122
|
"langgraph": LanggraphInstrumentation(),
|
|
120
123
|
"anthropic": AnthropicInstrumentation(),
|
|
121
124
|
"cohere": CohereInstrumentation(),
|
|
125
|
+
"weaviate": WeaviateInstrumentation(),
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
init_instrumentations(disable_instrumentations, all_instrumentations)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def extract_input_params(args, kwargs):
|
|
6
|
+
extracted_params = {}
|
|
7
|
+
for key, value in kwargs.items():
|
|
8
|
+
if hasattr(value, "__dict__"):
|
|
9
|
+
extracted_params[key] = json.dumps(vars(value))
|
|
10
|
+
else:
|
|
11
|
+
extracted_params[key] = value
|
|
12
|
+
for i, value in enumerate(args):
|
|
13
|
+
if hasattr(value, "__dict__"):
|
|
14
|
+
extracted_params[f"arg{i}"] = json.dumps(vars(value))
|
|
15
|
+
else:
|
|
16
|
+
extracted_params[f"arg{i}"] = value
|
|
17
|
+
# Remove None values
|
|
18
|
+
return {k: v for k, v in extracted_params.items() if v is not None}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def to_iso_format(value):
|
|
22
|
+
return (
|
|
23
|
+
None
|
|
24
|
+
if value is None
|
|
25
|
+
else (
|
|
26
|
+
value.isoformat(timespec="microseconds") + "Z"
|
|
27
|
+
if isinstance(value, datetime)
|
|
28
|
+
else None
|
|
29
|
+
)
|
|
30
|
+
)
|