ioa-observe-sdk 1.0.5__py3-none-any.whl → 1.0.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.
- ioa_observe/sdk/decorators/base.py +8 -8
- ioa_observe/sdk/decorators/util.py +6 -0
- ioa_observe/sdk/instrumentations/a2a.py +111 -0
- ioa_observe/sdk/instrumentations/slim.py +18 -18
- ioa_observe/sdk/tracing/__init__.py +2 -2
- ioa_observe/sdk/tracing/context_utils.py +29 -28
- ioa_observe/sdk/tracing/tracing.py +17 -16
- {ioa_observe_sdk-1.0.5.dist-info → ioa_observe_sdk-1.0.7.dist-info}/METADATA +99 -47
- {ioa_observe_sdk-1.0.5.dist-info → ioa_observe_sdk-1.0.7.dist-info}/RECORD +12 -11
- {ioa_observe_sdk-1.0.5.dist-info → ioa_observe_sdk-1.0.7.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.5.dist-info → ioa_observe_sdk-1.0.7.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.5.dist-info → ioa_observe_sdk-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -100,8 +100,8 @@ def _setup_span(
|
|
|
100
100
|
]:
|
|
101
101
|
set_workflow_name(entity_name)
|
|
102
102
|
# if tlp_span_kind == "graph":
|
|
103
|
-
#
|
|
104
|
-
#
|
|
103
|
+
# session_id = entity_name + "_" + str(uuid.uuid4())
|
|
104
|
+
# set_session_id(session_id)
|
|
105
105
|
if tlp_span_kind == "graph":
|
|
106
106
|
span_name = f"{entity_name}.{tlp_span_kind}"
|
|
107
107
|
else:
|
|
@@ -126,9 +126,9 @@ def _setup_span(
|
|
|
126
126
|
},
|
|
127
127
|
)
|
|
128
128
|
# start_span.end() # end the span immediately
|
|
129
|
-
#
|
|
130
|
-
# if
|
|
131
|
-
# span.set_attribute("
|
|
129
|
+
# session_id = get_value("session.id")
|
|
130
|
+
# if session_id is not None:
|
|
131
|
+
# span.set_attribute("session.id", session_id)
|
|
132
132
|
if tlp_span_kind in [
|
|
133
133
|
ObserveSpanKindValues.TASK,
|
|
134
134
|
ObserveSpanKindValues.TOOL,
|
|
@@ -145,9 +145,9 @@ def _setup_span(
|
|
|
145
145
|
if version:
|
|
146
146
|
span.set_attribute(OBSERVE_ENTITY_VERSION, version)
|
|
147
147
|
|
|
148
|
-
# if
|
|
149
|
-
# print(f"Execution ID: {
|
|
150
|
-
# span.set_attribute("
|
|
148
|
+
# if session_id:
|
|
149
|
+
# print(f"Execution ID: {session_id}")
|
|
150
|
+
# span.set_attribute("session.id", session_id)
|
|
151
151
|
|
|
152
152
|
return span, ctx, ctx_token
|
|
153
153
|
|
|
@@ -21,6 +21,12 @@ def determine_workflow_type(workflow_obj: Any) -> Union[None, dict]:
|
|
|
21
21
|
for key, value in workflow_obj.items()
|
|
22
22
|
):
|
|
23
23
|
return build_agent_dict_topology(workflow_obj)
|
|
24
|
+
|
|
25
|
+
# Check if it's a list of agent names
|
|
26
|
+
elif isinstance(workflow_obj, list) and all(
|
|
27
|
+
isinstance(item, str) for item in workflow_obj
|
|
28
|
+
):
|
|
29
|
+
return build_agent_dict_topology(workflow_obj)
|
|
24
30
|
# Try LlamaIndex built-in workflow types first
|
|
25
31
|
result = determine_llama_index_workflow_type(workflow_obj)
|
|
26
32
|
if result:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from typing import Collection
|
|
5
|
+
import functools
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
from opentelemetry import baggage
|
|
9
|
+
from opentelemetry.baggage.propagation import W3CBaggagePropagator
|
|
10
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
11
|
+
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
12
|
+
|
|
13
|
+
from ioa_observe.sdk import TracerWrapper
|
|
14
|
+
from ioa_observe.sdk.client import kv_store
|
|
15
|
+
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
16
|
+
|
|
17
|
+
_instruments = ("a2a-sdk >= 0.2.5",)
|
|
18
|
+
_global_tracer = None
|
|
19
|
+
_kv_lock = threading.RLock() # Add thread-safety for kv_store operations
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class A2AInstrumentor(BaseInstrumentor):
|
|
23
|
+
def __init__(self):
|
|
24
|
+
super().__init__()
|
|
25
|
+
global _global_tracer
|
|
26
|
+
_global_tracer = TracerWrapper().get_tracer()
|
|
27
|
+
|
|
28
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
29
|
+
return _instruments
|
|
30
|
+
|
|
31
|
+
def _instrument(self, **kwargs):
|
|
32
|
+
import importlib
|
|
33
|
+
|
|
34
|
+
if importlib.util.find_spec("a2a") is None:
|
|
35
|
+
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
36
|
+
|
|
37
|
+
# Instrument `publish`
|
|
38
|
+
from a2a.client import A2AClient
|
|
39
|
+
|
|
40
|
+
original_send_message = A2AClient.send_message
|
|
41
|
+
|
|
42
|
+
@functools.wraps(original_send_message)
|
|
43
|
+
async def instrumented_send_message(
|
|
44
|
+
self, request, *, http_kwargs=None, context=None
|
|
45
|
+
):
|
|
46
|
+
with _global_tracer.start_as_current_span("a2a.send_message"):
|
|
47
|
+
traceparent = get_current_traceparent()
|
|
48
|
+
session_id = None
|
|
49
|
+
if traceparent:
|
|
50
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
51
|
+
if session_id:
|
|
52
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
53
|
+
# Inject headers into http_kwargs
|
|
54
|
+
if http_kwargs is None:
|
|
55
|
+
http_kwargs = {}
|
|
56
|
+
headers = http_kwargs.get("headers", {})
|
|
57
|
+
headers["traceparent"] = traceparent
|
|
58
|
+
if session_id:
|
|
59
|
+
headers["session_id"] = session_id
|
|
60
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
61
|
+
http_kwargs["headers"] = headers
|
|
62
|
+
return await original_send_message(self, request, http_kwargs=http_kwargs)
|
|
63
|
+
|
|
64
|
+
from a2a.client import A2AClient
|
|
65
|
+
|
|
66
|
+
A2AClient.send_message = instrumented_send_message
|
|
67
|
+
|
|
68
|
+
from a2a.server.request_handlers import DefaultRequestHandler
|
|
69
|
+
|
|
70
|
+
original_server_on_message_send = DefaultRequestHandler.on_message_send
|
|
71
|
+
|
|
72
|
+
@functools.wraps(original_server_on_message_send)
|
|
73
|
+
async def instrumented_execute(self, params, context):
|
|
74
|
+
# Extract headers from context (assume context.request.headers)
|
|
75
|
+
|
|
76
|
+
traceparent = context.state.get("headers", {}).get("traceparent")
|
|
77
|
+
session_id = context.state.get("headers", {}).get("session_id")
|
|
78
|
+
carrier = {
|
|
79
|
+
k.lower(): v
|
|
80
|
+
for k, v in context.state.get("headers", {}).items()
|
|
81
|
+
if k.lower() in ["traceparent", "baggage"]
|
|
82
|
+
}
|
|
83
|
+
if carrier and traceparent:
|
|
84
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
85
|
+
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
86
|
+
if session_id and session_id != "None":
|
|
87
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
88
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
89
|
+
return await original_server_on_message_send(self, params, context)
|
|
90
|
+
|
|
91
|
+
from a2a.server.request_handlers import DefaultRequestHandler
|
|
92
|
+
|
|
93
|
+
DefaultRequestHandler.on_message_send = instrumented_execute
|
|
94
|
+
|
|
95
|
+
def _uninstrument(self, **kwargs):
|
|
96
|
+
import importlib
|
|
97
|
+
|
|
98
|
+
if importlib.util.find_spec("a2a") is None:
|
|
99
|
+
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
100
|
+
|
|
101
|
+
# Uninstrument `send_message`
|
|
102
|
+
from a2a.client import A2AClient
|
|
103
|
+
|
|
104
|
+
A2AClient.send_message = A2AClient.send_message.__wrapped__
|
|
105
|
+
|
|
106
|
+
# Uninstrument `execute`
|
|
107
|
+
from a2a.server.request_handlers import DefaultRequestHandler
|
|
108
|
+
|
|
109
|
+
DefaultRequestHandler.on_message_send = (
|
|
110
|
+
DefaultRequestHandler.on_message_send.__wrapped__
|
|
111
|
+
)
|
|
@@ -14,7 +14,7 @@ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapProp
|
|
|
14
14
|
|
|
15
15
|
from ioa_observe.sdk import TracerWrapper
|
|
16
16
|
from ioa_observe.sdk.client import kv_store
|
|
17
|
-
from ioa_observe.sdk.tracing import
|
|
17
|
+
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
18
18
|
|
|
19
19
|
_instruments = ("slim-bindings >= 0.2",)
|
|
20
20
|
_global_tracer = None
|
|
@@ -50,21 +50,21 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
50
50
|
traceparent = get_current_traceparent()
|
|
51
51
|
|
|
52
52
|
# Thread-safe access to kv_store
|
|
53
|
-
|
|
53
|
+
session_id = None
|
|
54
54
|
if traceparent:
|
|
55
55
|
with _kv_lock:
|
|
56
|
-
|
|
57
|
-
if
|
|
58
|
-
kv_store.set(f"execution.{traceparent}",
|
|
56
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
57
|
+
if session_id:
|
|
58
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
59
59
|
# Add tracing context to the message headers
|
|
60
60
|
headers = {
|
|
61
|
-
"
|
|
61
|
+
"session_id": session_id if session_id else None,
|
|
62
62
|
"traceparent": traceparent,
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
# Set baggage context
|
|
66
|
-
if traceparent and
|
|
67
|
-
baggage.set_baggage(f"execution.{traceparent}",
|
|
66
|
+
if traceparent and session_id:
|
|
67
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
68
68
|
|
|
69
69
|
# Process message payload
|
|
70
70
|
if isinstance(message, bytes):
|
|
@@ -120,7 +120,7 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
120
120
|
|
|
121
121
|
# Extract traceparent from headers
|
|
122
122
|
traceparent = headers.get("traceparent")
|
|
123
|
-
|
|
123
|
+
session_id = headers.get("session_id")
|
|
124
124
|
|
|
125
125
|
# First, extract and restore the trace context from headers
|
|
126
126
|
carrier = {}
|
|
@@ -130,27 +130,27 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
130
130
|
if k.lower() == key.lower():
|
|
131
131
|
carrier[key.lower()] = headers[k]
|
|
132
132
|
|
|
133
|
-
# Restore the trace context BEFORE calling
|
|
133
|
+
# Restore the trace context BEFORE calling set_session_id
|
|
134
134
|
if carrier and traceparent:
|
|
135
135
|
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
136
136
|
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
137
137
|
|
|
138
138
|
# Now set execution ID with the restored context
|
|
139
|
-
if
|
|
139
|
+
if session_id and session_id != "None":
|
|
140
140
|
# Pass the traceparent explicitly to prevent new context creation
|
|
141
|
-
|
|
141
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
142
142
|
|
|
143
143
|
# Store in kv_store with thread safety
|
|
144
144
|
with _kv_lock:
|
|
145
|
-
kv_store.set(f"execution.{traceparent}",
|
|
145
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
146
146
|
|
|
147
147
|
# Fallback: check stored execution ID if not found in headers
|
|
148
|
-
if traceparent and (not
|
|
148
|
+
if traceparent and (not session_id or session_id == "None"):
|
|
149
149
|
with _kv_lock:
|
|
150
|
-
|
|
151
|
-
if
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
stored_session_id = kv_store.get(f"execution.{traceparent}")
|
|
151
|
+
if stored_session_id:
|
|
152
|
+
session_id = stored_session_id
|
|
153
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
154
154
|
|
|
155
155
|
# Process payload
|
|
156
156
|
payload = message_dict.get("payload", raw_message)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from ioa_observe.sdk.tracing.context_manager import get_tracer
|
|
5
5
|
from ioa_observe.sdk.tracing.tracing import (
|
|
6
6
|
set_workflow_name,
|
|
7
|
-
|
|
7
|
+
set_session_id,
|
|
8
8
|
get_current_traceparent,
|
|
9
9
|
session_start,
|
|
10
10
|
)
|
|
@@ -12,7 +12,7 @@ from ioa_observe.sdk.tracing.tracing import (
|
|
|
12
12
|
__all__ = [
|
|
13
13
|
"get_tracer",
|
|
14
14
|
"set_workflow_name",
|
|
15
|
-
"
|
|
15
|
+
"set_session_id",
|
|
16
16
|
"get_current_traceparent",
|
|
17
17
|
"session_start",
|
|
18
18
|
]
|
|
@@ -4,8 +4,9 @@ from opentelemetry import baggage
|
|
|
4
4
|
|
|
5
5
|
from ioa_observe.sdk import TracerWrapper
|
|
6
6
|
from ioa_observe.sdk.client import kv_store
|
|
7
|
-
from ioa_observe.sdk.tracing import
|
|
7
|
+
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
8
8
|
from opentelemetry import context as otel_context
|
|
9
|
+
from opentelemetry.context import attach
|
|
9
10
|
|
|
10
11
|
"""
|
|
11
12
|
Usage Example:
|
|
@@ -20,7 +21,7 @@ After receiving a message, extract headers and call set_context_from_headers(hea
|
|
|
20
21
|
|
|
21
22
|
def get_current_context_headers():
|
|
22
23
|
"""
|
|
23
|
-
Extracts the current trace context, baggage, and
|
|
24
|
+
Extracts the current trace context, baggage, and session_id into headers.
|
|
24
25
|
"""
|
|
25
26
|
_global_tracer = TracerWrapper().get_tracer()
|
|
26
27
|
with _global_tracer.start_as_current_span("get_current_context_headers"):
|
|
@@ -30,33 +31,33 @@ def get_current_context_headers():
|
|
|
30
31
|
TraceContextTextMapPropagator().inject(carrier, context=current_ctx)
|
|
31
32
|
W3CBaggagePropagator().inject(carrier, context=current_ctx)
|
|
32
33
|
traceparent = carrier.get("traceparent")
|
|
33
|
-
|
|
34
|
+
session_id = None
|
|
34
35
|
if traceparent:
|
|
35
|
-
|
|
36
|
-
if
|
|
37
|
-
carrier["
|
|
36
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
37
|
+
if session_id:
|
|
38
|
+
carrier["session_id"] = session_id
|
|
38
39
|
return carrier
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def set_context_from_headers(headers):
|
|
42
43
|
"""
|
|
43
|
-
Restores the trace context, baggage, and
|
|
44
|
+
Restores the trace context, baggage, and session_id from headers.
|
|
44
45
|
"""
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
carrierHeaders = {}
|
|
47
|
+
if "traceparentID" in headers:
|
|
48
|
+
carrierHeaders["traceparent"] = headers["traceparentID"]
|
|
49
|
+
if "executionID" in headers:
|
|
50
|
+
carrierHeaders["session_id"] = headers["executionID"]
|
|
51
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrierHeaders)
|
|
52
|
+
ctx = W3CBaggagePropagator().extract(carrier=carrierHeaders, context=ctx)
|
|
53
|
+
attach(ctx)
|
|
54
|
+
# Restore session_id if present
|
|
55
|
+
traceparent = headers.get("traceparentID")
|
|
56
|
+
session_id = headers.get("executionID")
|
|
57
|
+
if traceparent and session_id and session_id != "None":
|
|
58
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
59
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
60
|
+
return ctx
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def set_baggage_item(key, value):
|
|
@@ -67,16 +68,16 @@ def get_baggage_item(key):
|
|
|
67
68
|
return baggage.get_baggage(key)
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
def
|
|
71
|
+
def get_current_session_id():
|
|
71
72
|
traceparent = get_current_traceparent()
|
|
72
73
|
if traceparent:
|
|
73
74
|
return kv_store.get(f"execution.{traceparent}")
|
|
74
75
|
return None
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
def
|
|
78
|
+
def set_session_id_from_headers(headers):
|
|
78
79
|
traceparent = headers.get("traceparent")
|
|
79
|
-
|
|
80
|
-
if traceparent and
|
|
81
|
-
|
|
82
|
-
kv_store.set(f"execution.{traceparent}",
|
|
80
|
+
session_id = headers.get("session_id")
|
|
81
|
+
if traceparent and session_id:
|
|
82
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
83
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import atexit
|
|
5
|
+
import contextlib
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
@@ -261,9 +262,9 @@ class TracerWrapper(object):
|
|
|
261
262
|
if workflow_name is not None:
|
|
262
263
|
span.set_attribute(OBSERVE_WORKFLOW_NAME, workflow_name)
|
|
263
264
|
|
|
264
|
-
|
|
265
|
-
if
|
|
266
|
-
span.set_attribute("
|
|
265
|
+
session_id = get_value("session.id")
|
|
266
|
+
if session_id is not None:
|
|
267
|
+
span.set_attribute("session.id", session_id)
|
|
267
268
|
|
|
268
269
|
entity_path = get_value("entity_path")
|
|
269
270
|
if entity_path is not None:
|
|
@@ -408,11 +409,11 @@ def session_start():
|
|
|
408
409
|
As a context manager, yields session metadata.
|
|
409
410
|
As a normal function, just sets up the session.
|
|
410
411
|
"""
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
session_id = TracerWrapper.app_name + "_" + str(uuid.uuid4())
|
|
413
|
+
set_session_id(session_id)
|
|
413
414
|
metadata = {
|
|
414
|
-
"executionID": get_value("
|
|
415
|
-
"
|
|
415
|
+
"executionID": get_value("session.id") or session_id,
|
|
416
|
+
"traceparentID": get_current_traceparent(),
|
|
416
417
|
}
|
|
417
418
|
import inspect
|
|
418
419
|
|
|
@@ -427,10 +428,10 @@ def session_start():
|
|
|
427
428
|
|
|
428
429
|
return _cm()
|
|
429
430
|
# Used as a normal function
|
|
430
|
-
return
|
|
431
|
+
return contextlib.nullcontext(metadata)
|
|
431
432
|
|
|
432
433
|
|
|
433
|
-
def
|
|
434
|
+
def set_session_id(session_id: str, traceparent: str = None) -> None:
|
|
434
435
|
"""
|
|
435
436
|
Sets the execution ID in both the key-value store and OpenTelemetry context.
|
|
436
437
|
|
|
@@ -438,10 +439,10 @@ def set_execution_id(execution_id: str, traceparent: str = None) -> None:
|
|
|
438
439
|
proper trace correlation across distributed systems.
|
|
439
440
|
|
|
440
441
|
Args:
|
|
441
|
-
|
|
442
|
+
session_id: The execution ID to set
|
|
442
443
|
traceparent: Optional traceparent to use (if None, will extract from context)
|
|
443
444
|
"""
|
|
444
|
-
if not
|
|
445
|
+
if not session_id:
|
|
445
446
|
return
|
|
446
447
|
|
|
447
448
|
from opentelemetry import trace
|
|
@@ -454,10 +455,10 @@ def set_execution_id(execution_id: str, traceparent: str = None) -> None:
|
|
|
454
455
|
# Store execution ID with provided traceparent
|
|
455
456
|
kv_key = f"execution.{traceparent}"
|
|
456
457
|
if kv_store.get(kv_key) is None:
|
|
457
|
-
kv_store.set(kv_key,
|
|
458
|
+
kv_store.set(kv_key, session_id)
|
|
458
459
|
|
|
459
460
|
# Store in OpenTelemetry context
|
|
460
|
-
attach(set_value("
|
|
461
|
+
attach(set_value("session.id", session_id))
|
|
461
462
|
attach(set_value("current_traceparent", traceparent))
|
|
462
463
|
return
|
|
463
464
|
|
|
@@ -472,7 +473,7 @@ def set_execution_id(execution_id: str, traceparent: str = None) -> None:
|
|
|
472
473
|
else:
|
|
473
474
|
# Only create new span if absolutely necessary (no existing context)
|
|
474
475
|
tracer = trace.get_tracer(__name__)
|
|
475
|
-
with tracer.start_as_current_span("
|
|
476
|
+
with tracer.start_as_current_span("set_session_id"):
|
|
476
477
|
carrier = {}
|
|
477
478
|
TraceContextTextMapPropagator().inject(carrier)
|
|
478
479
|
extracted_traceparent = carrier.get("traceparent")
|
|
@@ -481,10 +482,10 @@ def set_execution_id(execution_id: str, traceparent: str = None) -> None:
|
|
|
481
482
|
if extracted_traceparent:
|
|
482
483
|
kv_key = f"execution.{extracted_traceparent}"
|
|
483
484
|
if kv_store.get(kv_key) is None:
|
|
484
|
-
kv_store.set(kv_key,
|
|
485
|
+
kv_store.set(kv_key, session_id)
|
|
485
486
|
|
|
486
487
|
# Also store in OpenTelemetry context
|
|
487
|
-
attach(set_value("
|
|
488
|
+
attach(set_value("session.id", session_id))
|
|
488
489
|
attach(set_value("current_traceparent", extracted_traceparent))
|
|
489
490
|
|
|
490
491
|
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ioa-observe-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
4
4
|
Summary: IOA Observability SDK
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE.md
|
|
8
|
-
Requires-Dist: aiohappyeyeballs
|
|
9
|
-
Requires-Dist: aiohttp
|
|
10
|
-
Requires-Dist: aiosignal
|
|
11
|
-
Requires-Dist: annotated-types
|
|
12
|
-
Requires-Dist: anyio
|
|
13
|
-
Requires-Dist: async-timeout
|
|
14
|
-
Requires-Dist: attrs
|
|
15
|
-
Requires-Dist: backoff
|
|
16
|
-
Requires-Dist: certifi
|
|
17
|
-
Requires-Dist: charset-normalizer
|
|
8
|
+
Requires-Dist: aiohappyeyeballs>=2.4.8
|
|
9
|
+
Requires-Dist: aiohttp>=3.11.18
|
|
10
|
+
Requires-Dist: aiosignal>=1.3.2
|
|
11
|
+
Requires-Dist: annotated-types>=0.7.0
|
|
12
|
+
Requires-Dist: anyio>=4.8.0
|
|
13
|
+
Requires-Dist: async-timeout>=4.0.3
|
|
14
|
+
Requires-Dist: attrs>=25.1.0
|
|
15
|
+
Requires-Dist: backoff>=2.2.1
|
|
16
|
+
Requires-Dist: certifi>=2025.1.31
|
|
17
|
+
Requires-Dist: charset-normalizer>=3.4.1
|
|
18
18
|
Requires-Dist: colorama==0.4.6
|
|
19
|
-
Requires-Dist: Deprecated
|
|
20
|
-
Requires-Dist: distro
|
|
21
|
-
Requires-Dist: exceptiongroup
|
|
22
|
-
Requires-Dist: frozenlist
|
|
23
|
-
Requires-Dist: googleapis-common-protos
|
|
24
|
-
Requires-Dist: grpcio
|
|
25
|
-
Requires-Dist: h11
|
|
26
|
-
Requires-Dist: httpcore
|
|
27
|
-
Requires-Dist: httpx
|
|
28
|
-
Requires-Dist: idna
|
|
29
|
-
Requires-Dist: importlib_metadata
|
|
30
|
-
Requires-Dist: Jinja2
|
|
31
|
-
Requires-Dist: jiter
|
|
32
|
-
Requires-Dist: MarkupSafe
|
|
33
|
-
Requires-Dist: monotonic
|
|
34
|
-
Requires-Dist: multidict
|
|
35
|
-
Requires-Dist: openai
|
|
19
|
+
Requires-Dist: Deprecated>=1.2.18
|
|
20
|
+
Requires-Dist: distro>=1.9.0
|
|
21
|
+
Requires-Dist: exceptiongroup>=1.2.2
|
|
22
|
+
Requires-Dist: frozenlist>=1.5.0
|
|
23
|
+
Requires-Dist: googleapis-common-protos>=1.69.0
|
|
24
|
+
Requires-Dist: grpcio>=1.70.0
|
|
25
|
+
Requires-Dist: h11>=0.16.0
|
|
26
|
+
Requires-Dist: httpcore>=1.0.9
|
|
27
|
+
Requires-Dist: httpx>=0.28.1
|
|
28
|
+
Requires-Dist: idna>=3.10
|
|
29
|
+
Requires-Dist: importlib_metadata>=8.5.0
|
|
30
|
+
Requires-Dist: Jinja2>=3.1.6
|
|
31
|
+
Requires-Dist: jiter>=0.8.2
|
|
32
|
+
Requires-Dist: MarkupSafe>=3.0.2
|
|
33
|
+
Requires-Dist: monotonic>=1.6
|
|
34
|
+
Requires-Dist: multidict>=6.1.0
|
|
35
|
+
Requires-Dist: openai>=1.75.0
|
|
36
36
|
Requires-Dist: opentelemetry-api==1.33.1
|
|
37
37
|
Requires-Dist: opentelemetry-distro
|
|
38
38
|
Requires-Dist: opentelemetry-exporter-otlp==1.33.1
|
|
@@ -52,32 +52,32 @@ Requires-Dist: opentelemetry-sdk==1.33.1
|
|
|
52
52
|
Requires-Dist: opentelemetry-semantic-conventions==0.54b1
|
|
53
53
|
Requires-Dist: opentelemetry-semantic-conventions-ai==0.4.9
|
|
54
54
|
Requires-Dist: opentelemetry-util-http==0.54b1
|
|
55
|
-
Requires-Dist: packaging
|
|
56
|
-
Requires-Dist: propcache
|
|
57
|
-
Requires-Dist: protobuf
|
|
58
|
-
Requires-Dist: pydantic
|
|
59
|
-
Requires-Dist: pydantic_core
|
|
60
|
-
Requires-Dist: python-dateutil
|
|
55
|
+
Requires-Dist: packaging>=24.2
|
|
56
|
+
Requires-Dist: propcache>=0.3.0
|
|
57
|
+
Requires-Dist: protobuf>=5.29.3
|
|
58
|
+
Requires-Dist: pydantic>=2.10.6
|
|
59
|
+
Requires-Dist: pydantic_core>=2.27.2
|
|
60
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
61
61
|
Requires-Dist: regex==2024.11.6
|
|
62
|
-
Requires-Dist: requests
|
|
63
|
-
Requires-Dist: six
|
|
64
|
-
Requires-Dist: sniffio
|
|
65
|
-
Requires-Dist: tenacity
|
|
66
|
-
Requires-Dist: tiktoken
|
|
67
|
-
Requires-Dist: tqdm
|
|
68
|
-
Requires-Dist: typing_extensions
|
|
69
|
-
Requires-Dist: urllib3
|
|
70
|
-
Requires-Dist: wrapt
|
|
71
|
-
Requires-Dist: yarl
|
|
72
|
-
Requires-Dist: zipp
|
|
62
|
+
Requires-Dist: requests>=2.32.3
|
|
63
|
+
Requires-Dist: six>=1.17.0
|
|
64
|
+
Requires-Dist: sniffio>=1.3.1
|
|
65
|
+
Requires-Dist: tenacity>=9.0.0
|
|
66
|
+
Requires-Dist: tiktoken>=0.9.0
|
|
67
|
+
Requires-Dist: tqdm>=4.67.1
|
|
68
|
+
Requires-Dist: typing_extensions>=4.12.2
|
|
69
|
+
Requires-Dist: urllib3>=2.3.0
|
|
70
|
+
Requires-Dist: wrapt>=1.17.2
|
|
71
|
+
Requires-Dist: yarl>=1.18.3
|
|
72
|
+
Requires-Dist: zipp>=3.21.0
|
|
73
73
|
Requires-Dist: langgraph>=0.3.2
|
|
74
74
|
Requires-Dist: langchain>=0.3.19
|
|
75
75
|
Requires-Dist: langchain-openai>=0.3.8
|
|
76
76
|
Requires-Dist: langchain-community>=0.3.25
|
|
77
77
|
Requires-Dist: llama-index>=0.12.34
|
|
78
78
|
Requires-Dist: opentelemetry-instrumentation-requests
|
|
79
|
-
Requires-Dist: opentelemetry-instrumentation-transformers>=0.40.
|
|
80
|
-
Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.
|
|
79
|
+
Requires-Dist: opentelemetry-instrumentation-transformers>=0.40.8
|
|
80
|
+
Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.8
|
|
81
81
|
Requires-Dist: llama-index-utils-workflow>=0.3.1
|
|
82
82
|
Requires-Dist: pytest
|
|
83
83
|
Requires-Dist: pytest-vcr
|
|
@@ -85,6 +85,8 @@ Dynamic: license-file
|
|
|
85
85
|
|
|
86
86
|
# Observe-SDK
|
|
87
87
|
|
|
88
|
+
[](https://pypi.org/project/ioa-observe-sdk/)
|
|
89
|
+
|
|
88
90
|
IOA observability SDK for your multi-agentic application.
|
|
89
91
|
|
|
90
92
|
## Table of Contents
|
|
@@ -119,6 +121,20 @@ Link: [AGNTCY Observability Schema](https://github.com/agntcy/observe/blob/main/
|
|
|
119
121
|
|
|
120
122
|
## Dev
|
|
121
123
|
|
|
124
|
+
Any Opentelemetry compatible backend can be used, but for this guide, we will use ClickhouseDB as the backend database.
|
|
125
|
+
|
|
126
|
+
### Opentelemetry collector
|
|
127
|
+
|
|
128
|
+
The OpenTelemetry Collector offers a vendor-agnostic implementation of how to receive, process and export telemetry data. It removes the need to run, operate, and maintain multiple agents/collectors.
|
|
129
|
+
|
|
130
|
+
### Clickhouse DB
|
|
131
|
+
|
|
132
|
+
ClickhouseDB is used as a backend database to store and query the collected telemetry data efficiently, enabling you to analyze and visualize observability information for your multi-agentic applications.
|
|
133
|
+
|
|
134
|
+
### Grafana (optional)
|
|
135
|
+
|
|
136
|
+
Grafana can be used to visualize the telemetry data collected by the OpenTelemetry Collector and stored in ClickhouseDB.
|
|
137
|
+
|
|
122
138
|
To get started with development, start a Clickhouse DB and an OTel collector container locally using docker-compose like so:
|
|
123
139
|
|
|
124
140
|
```
|
|
@@ -126,6 +142,8 @@ cd deploy/
|
|
|
126
142
|
docker compose up -d
|
|
127
143
|
```
|
|
128
144
|
|
|
145
|
+
Running both locally allows you to test, monitor, and debug your observability setup in a development environment before deploying to production.
|
|
146
|
+
|
|
129
147
|
Ensure the contents of `otel-collector.yaml` is correct.
|
|
130
148
|
|
|
131
149
|
Check the logs of the collector to ensure it is running correctly:
|
|
@@ -134,6 +152,18 @@ Check the logs of the collector to ensure it is running correctly:
|
|
|
134
152
|
docker logs -f otel-collector
|
|
135
153
|
```
|
|
136
154
|
|
|
155
|
+
Viewing data in Clickhouse DB can be done using the Clickhouse client. The collector is configured to export telemetry data to Clickhouse.
|
|
156
|
+
|
|
157
|
+
The clickhouse exporter creates various tables in the Clickhouse DB, including `otel_traces`, which is used to store trace data.
|
|
158
|
+
|
|
159
|
+
For more info, refer to the [OpenTelemetry Clickhouse Exporter documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/clickhouseexporter/README.md)
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
docker exec -it clickhouse-server clickhouse-client
|
|
163
|
+
|
|
164
|
+
select * from otel_traces LIMIT 10;
|
|
165
|
+
```
|
|
166
|
+
|
|
137
167
|
Create a `.env` file with the following content:
|
|
138
168
|
|
|
139
169
|
```bash
|
|
@@ -165,6 +195,28 @@ OPENAI_API_KEY=<KEY> make test
|
|
|
165
195
|
For getting started with the SDK, please refer to the [Getting Started](https://github.com/agntcy/observe/blob/main/GETTING-STARTED.md)
|
|
166
196
|
file. It contains detailed instructions on how to set up and use the SDK effectively.
|
|
167
197
|
|
|
198
|
+
### Grafana
|
|
199
|
+
|
|
200
|
+
To configure Grafana to visualize the telemetry data, follow these steps:
|
|
201
|
+
|
|
202
|
+
1. Spin up Grafana locally using Docker:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
docker run -d -p 3000:3000 --name=grafana grafana/grafana
|
|
206
|
+
```
|
|
207
|
+
2. Access Grafana by navigating to `http://localhost:3000` in your web browser.
|
|
208
|
+
- Default username: `admin`
|
|
209
|
+
- Default password: `admin`
|
|
210
|
+
|
|
211
|
+
3. Add a new data source:
|
|
212
|
+
- Choose "ClickHouse" as the data source type.
|
|
213
|
+
- Set the URL to `http://0.0.0.0:8123`.
|
|
214
|
+
- Configure the authentication settings if necessary.
|
|
215
|
+
- Save and test the connection to ensure it works correctly.
|
|
216
|
+
|
|
217
|
+
Refer to the [Grafana ClickHouse plugin documentation](https://grafana.com/grafana/plugins/grafana-clickhouse-datasource/) for more details on configuring ClickHouse as a data source.
|
|
218
|
+
|
|
219
|
+
|
|
168
220
|
## Contributing
|
|
169
221
|
|
|
170
222
|
Contributions are welcome! Please follow these steps to contribute:
|
|
@@ -10,10 +10,11 @@ ioa_observe/sdk/config/__init__.py,sha256=8aVNaw0yRNLFPxlf97iOZLlJVcV81ivSDnudH2
|
|
|
10
10
|
ioa_observe/sdk/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
ioa_observe/sdk/connectors/slim.py,sha256=NwbKEV7d5NIOqmG8zKqtgGigSJl7kf3QJ65z2gxpsY8,8498
|
|
12
12
|
ioa_observe/sdk/decorators/__init__.py,sha256=4Jf207nnJR1B371aAqYh17JWp2lsbG1IdX4-w1JlVwo,2380
|
|
13
|
-
ioa_observe/sdk/decorators/base.py,sha256=
|
|
14
|
-
ioa_observe/sdk/decorators/util.py,sha256=
|
|
13
|
+
ioa_observe/sdk/decorators/base.py,sha256=a6EGP3Xz5t958aqmnSh9qIb8C0S6b3-uqrTwF5ZGF4w,24237
|
|
14
|
+
ioa_observe/sdk/decorators/util.py,sha256=1apEuwDF0GKTcBVuO3p0R7BmUeUDFjwBaWA4o0SYuCQ,25306
|
|
15
15
|
ioa_observe/sdk/instrumentations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
ioa_observe/sdk/instrumentations/
|
|
16
|
+
ioa_observe/sdk/instrumentations/a2a.py,sha256=ov_9ckkymf_qFXG0iXVWfxlW-3kFcP-knrM_t-Cf72w,4414
|
|
17
|
+
ioa_observe/sdk/instrumentations/slim.py,sha256=w2-1JRB-I05bqH_Y4-98XMOh0x7xgAJWUYUBpe2alXQ,7656
|
|
17
18
|
ioa_observe/sdk/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
19
|
ioa_observe/sdk/logging/logging.py,sha256=HZxW9s8Due7jgiNkdI38cIjv5rC9D-Flta3RQMOnpow,2891
|
|
19
20
|
ioa_observe/sdk/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -26,19 +27,19 @@ ioa_observe/sdk/metrics/agents/heuristics.py,sha256=lcos_mzxXrTMpNWShW9Biy9pXoh8
|
|
|
26
27
|
ioa_observe/sdk/metrics/agents/recovery_tracker.py,sha256=I3bWmA11Ue2Cfxnn7C9r2zvh7Hqa8z6EV8wqQhDLALI,4811
|
|
27
28
|
ioa_observe/sdk/metrics/agents/tool_call_tracker.py,sha256=qmjaaR7rIaQsqCLlDaiYGUeEO3QijhMwyrcpNjvItzI,3169
|
|
28
29
|
ioa_observe/sdk/metrics/agents/tracker.py,sha256=KN3VFPXrgB1f5VF87ppqS4zG2vxn-9oFCG5K1W2IscE,1361
|
|
29
|
-
ioa_observe/sdk/tracing/__init__.py,sha256=
|
|
30
|
+
ioa_observe/sdk/tracing/__init__.py,sha256=jb3_vIAfrChfeaTNnpUKojnn8uUk_ukdFZ1lLru0kRM,433
|
|
30
31
|
ioa_observe/sdk/tracing/content_allow_list.py,sha256=1fAkpIwUQ7vDwCTkIVrqeltWQtrIbYvj8gz6_7P6NrE,945
|
|
31
32
|
ioa_observe/sdk/tracing/context_manager.py,sha256=O0JEXYa9h8anhW78R8KKBuqS0j4by1E1KXxNIMPnLr8,400
|
|
32
|
-
ioa_observe/sdk/tracing/context_utils.py,sha256
|
|
33
|
+
ioa_observe/sdk/tracing/context_utils.py,sha256=-sYS9vPLI87davV9ubneq5xqbV583CC_c0SmOQS1TAs,2933
|
|
33
34
|
ioa_observe/sdk/tracing/manual.py,sha256=KS6WN-zw9vAACzXYmnMoJm9d1fenYMfvzeK1GrGDPDE,1937
|
|
34
|
-
ioa_observe/sdk/tracing/tracing.py,sha256=
|
|
35
|
+
ioa_observe/sdk/tracing/tracing.py,sha256=Qsi7GYk1LytnzLhEXg7r_yfQhfQcBm2Y9s3VBrV2Eec,37885
|
|
35
36
|
ioa_observe/sdk/utils/__init__.py,sha256=UPn182U-UblF_XwXaFpx8F-TmQTbm1LYf9y89uSp5Hw,704
|
|
36
37
|
ioa_observe/sdk/utils/const.py,sha256=GwbHakKPjBL4wLqAVkDrSoKB-8p18EUrbaqPuRuV_xg,1099
|
|
37
38
|
ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
|
|
38
39
|
ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
|
|
39
40
|
ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
|
|
40
|
-
ioa_observe_sdk-1.0.
|
|
41
|
-
ioa_observe_sdk-1.0.
|
|
42
|
-
ioa_observe_sdk-1.0.
|
|
43
|
-
ioa_observe_sdk-1.0.
|
|
44
|
-
ioa_observe_sdk-1.0.
|
|
41
|
+
ioa_observe_sdk-1.0.7.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
|
|
42
|
+
ioa_observe_sdk-1.0.7.dist-info/METADATA,sha256=AJMuGPihvSbdLiCZh8AA_W_zGHGXTSPgzttA8I8KP0c,7746
|
|
43
|
+
ioa_observe_sdk-1.0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
+
ioa_observe_sdk-1.0.7.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
|
|
45
|
+
ioa_observe_sdk-1.0.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|