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.
@@ -100,8 +100,8 @@ def _setup_span(
100
100
  ]:
101
101
  set_workflow_name(entity_name)
102
102
  # if tlp_span_kind == "graph":
103
- # execution_id = entity_name + "_" + str(uuid.uuid4())
104
- # set_execution_id(execution_id)
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
- # execution_id = get_value("execution.id")
130
- # if execution_id is not None:
131
- # span.set_attribute("execution.id", execution_id)
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 execution_id:
149
- # print(f"Execution ID: {execution_id}")
150
- # span.set_attribute("execution.id", execution_id)
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 set_execution_id, get_current_traceparent
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
- execution_id = None
53
+ session_id = None
54
54
  if traceparent:
55
55
  with _kv_lock:
56
- execution_id = kv_store.get(f"execution.{traceparent}")
57
- if execution_id:
58
- kv_store.set(f"execution.{traceparent}", execution_id)
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
- "execution_id": execution_id if execution_id else None,
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 execution_id:
67
- baggage.set_baggage(f"execution.{traceparent}", execution_id)
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
- execution_id = headers.get("execution_id")
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 set_execution_id
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 execution_id and execution_id != "None":
139
+ if session_id and session_id != "None":
140
140
  # Pass the traceparent explicitly to prevent new context creation
141
- set_execution_id(execution_id, traceparent=traceparent)
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}", execution_id)
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 execution_id or execution_id == "None"):
148
+ if traceparent and (not session_id or session_id == "None"):
149
149
  with _kv_lock:
150
- stored_execution_id = kv_store.get(f"execution.{traceparent}")
151
- if stored_execution_id:
152
- execution_id = stored_execution_id
153
- set_execution_id(execution_id, traceparent=traceparent)
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
- set_execution_id,
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
- "set_execution_id",
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 set_execution_id, get_current_traceparent
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 execution_id into headers.
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
- execution_id = None
34
+ session_id = None
34
35
  if traceparent:
35
- execution_id = kv_store.get(f"execution.{traceparent}")
36
- if execution_id:
37
- carrier["execution_id"] = execution_id
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 execution_id from headers.
44
+ Restores the trace context, baggage, and session_id from headers.
44
45
  """
45
- # Extract trace context and baggage
46
- _global_tracer = TracerWrapper().get_tracer()
47
- with _global_tracer.start_as_current_span("set_context_from_headers"):
48
- ctx = TraceContextTextMapPropagator().extract(carrier=headers)
49
- ctx = W3CBaggagePropagator().extract(carrier=headers, context=ctx)
50
- # Restore execution_id if present
51
- if headers is not None:
52
- if "traceparent" in headers:
53
- traceparent = headers.get("traceparent")
54
- if "executionID" in headers:
55
- execution_id = headers.get("executionID")
56
- if traceparent and execution_id and execution_id != "None":
57
- set_execution_id(execution_id, traceparent=traceparent)
58
- kv_store.set(f"execution.{traceparent}", execution_id)
59
- return ctx
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 get_current_execution_id():
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 set_execution_id_from_headers(headers):
78
+ def set_session_id_from_headers(headers):
78
79
  traceparent = headers.get("traceparent")
79
- execution_id = headers.get("execution_id")
80
- if traceparent and execution_id:
81
- set_execution_id(execution_id, traceparent=traceparent)
82
- kv_store.set(f"execution.{traceparent}", execution_id)
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
- execution_id = get_value("execution.id")
265
- if execution_id is not None:
266
- span.set_attribute("execution.id", execution_id)
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
- execution_id = TracerWrapper.app_name + "_" + str(uuid.uuid4())
412
- set_execution_id(execution_id)
412
+ session_id = TracerWrapper.app_name + "_" + str(uuid.uuid4())
413
+ set_session_id(session_id)
413
414
  metadata = {
414
- "executionID": get_value("execution.id") or execution_id,
415
- "traceparent": get_current_traceparent(),
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 None
431
+ return contextlib.nullcontext(metadata)
431
432
 
432
433
 
433
- def set_execution_id(execution_id: str, traceparent: str = None) -> None:
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
- execution_id: The execution ID to set
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 execution_id:
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, execution_id)
458
+ kv_store.set(kv_key, session_id)
458
459
 
459
460
  # Store in OpenTelemetry context
460
- attach(set_value("execution.id", execution_id))
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("set_execution_id"):
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, execution_id)
485
+ kv_store.set(kv_key, session_id)
485
486
 
486
487
  # Also store in OpenTelemetry context
487
- attach(set_value("execution.id", execution_id))
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.5
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==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
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==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
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==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
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==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
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.2
80
- Requires-Dist: opentelemetry-instrumentation-crewai>=0.40.2
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
+ [![PyPI version](https://img.shields.io/pypi/v/ioa-observe-sdk.svg)](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=5NAfap6adbemrSAPCSo-rOWsBoh0-T4xY0eiA4OcOzg,24261
14
- ioa_observe/sdk/decorators/util.py,sha256=QZH5k2vIvf7UVjV5Exw3I4S5NrlWVQdkOyd4bpAsjCw,25097
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/slim.py,sha256=odOnULHV5WqlyiMQ1W8RmoyWJ1jeU66opiuvl6r2U4s,7708
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=RBc_lmUEdCDy1ICW6TtRor_0Tew1SyzYFjT4Sn1pkdo,437
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=CzIXY402ner15E64Dssd1YMJYI3rsYXqFV47gY2O7Lg,3018
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=DuEd_VYdDkA7ZNtASDuDhO_O-LnQET8UwCgdqclGmcw,37879
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.5.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
41
- ioa_observe_sdk-1.0.5.dist-info/METADATA,sha256=kcLchJ5m1Hsvvj_LAsmOuYIjl394oWKTvQWZEE_Y8cQ,5428
42
- ioa_observe_sdk-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
- ioa_observe_sdk-1.0.5.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
44
- ioa_observe_sdk-1.0.5.dist-info/RECORD,,
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,,