ioa-observe-sdk 1.0.24__tar.gz → 1.0.26__tar.gz

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.
Files changed (59) hide show
  1. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/PKG-INFO +32 -1
  2. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/README.md +31 -0
  3. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/connectors/slim.py +16 -17
  4. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/decorators/base.py +12 -12
  5. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/instrumentations/a2a.py +9 -4
  6. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/instrumentations/mcp.py +5 -2
  7. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/instrumentations/nats.py +9 -4
  8. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/instrumentations/slim.py +33 -8
  9. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/metrics.py +0 -22
  10. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/tracing.py +29 -33
  11. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe_sdk.egg-info/PKG-INFO +32 -1
  12. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/pyproject.toml +1 -1
  13. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/LICENSE.md +0 -0
  14. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/__init__.py +0 -0
  15. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/__init__.py +0 -0
  16. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/client/__init__.py +0 -0
  17. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/client/client.py +0 -0
  18. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/client/http.py +0 -0
  19. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/config/__init__.py +0 -0
  20. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/connectors/__init__.py +0 -0
  21. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/decorators/__init__.py +0 -0
  22. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/decorators/helpers.py +0 -0
  23. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/decorators/util.py +0 -0
  24. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/instrumentations/__init__.py +0 -0
  25. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/instruments.py +0 -0
  26. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/logging/__init__.py +0 -0
  27. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/logging/logging.py +0 -0
  28. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/__init__.py +0 -0
  29. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agent.py +0 -0
  30. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/__init__.py +0 -0
  31. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/agent_connections.py +0 -0
  32. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/availability.py +0 -0
  33. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/heuristics.py +0 -0
  34. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/recovery_tracker.py +0 -0
  35. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/tool_call_tracker.py +0 -0
  36. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/metrics/agents/tracker.py +0 -0
  37. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/telemetry.py +0 -0
  38. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/__init__.py +0 -0
  39. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/content_allow_list.py +0 -0
  40. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/context_manager.py +0 -0
  41. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/context_utils.py +0 -0
  42. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/manual.py +0 -0
  43. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/tracing/transform_span.py +0 -0
  44. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/utils/__init__.py +0 -0
  45. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/utils/const.py +0 -0
  46. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/utils/in_memory_span_exporter.py +0 -0
  47. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/utils/json_encoder.py +0 -0
  48. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/utils/package_check.py +0 -0
  49. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe/sdk/version.py +0 -0
  50. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe_sdk.egg-info/SOURCES.txt +0 -0
  51. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe_sdk.egg-info/dependency_links.txt +0 -0
  52. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe_sdk.egg-info/requires.txt +0 -0
  53. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/ioa_observe_sdk.egg-info/top_level.txt +0 -0
  54. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/setup.cfg +0 -0
  55. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/tests/test_client.py +0 -0
  56. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/tests/test_instrumentor.py +0 -0
  57. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/tests/test_manual_instrumentation.py +0 -0
  58. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/tests/test_transform_span.py +0 -0
  59. {ioa_observe_sdk-1.0.24 → ioa_observe_sdk-1.0.26}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ioa-observe-sdk
3
- Version: 1.0.24
3
+ Version: 1.0.26
4
4
  Summary: IOA Observability SDK
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -76,6 +76,37 @@ Alternatively, to download the SDK from git, you could also use the following co
76
76
  uv add "git+https://github.com/agntcy/observe"
77
77
  ```
78
78
 
79
+ ### Quick Start
80
+
81
+ After installation, import and initialize the SDK:
82
+
83
+ ```python
84
+ import os
85
+ from ioa_observe.sdk import Observe
86
+ from ioa_observe.sdk.decorators import agent
87
+ from ioa_observe.sdk.tracing import session_start
88
+
89
+ # Initialize Observe
90
+ Observe.init(
91
+ app_name="your_app_name",
92
+ api_endpoint=os.getenv("OTLP_HTTP_ENDPOINT", "http://localhost:4318")
93
+ )
94
+
95
+ # Use decorators to instrument your agents
96
+ @agent(name="my_agent", description="Example agent")
97
+ def my_agent_function(state):
98
+ # Your agent logic here
99
+ return {"result": "success"}
100
+
101
+ # Start a session for tracking
102
+ with session_start() as session_id:
103
+ result = my_agent_function({"input": "data"})
104
+ ```
105
+
106
+ **Note:** The package name for installation is `ioa_observe_sdk`, but imports use `ioa_observe` (underscore, not hyphen).
107
+
108
+ For comprehensive integration examples with LangGraph, LlamaIndex, and other frameworks, see the [Getting Started Guide](GETTING-STARTED.md).
109
+
79
110
  ## Schema
80
111
 
81
112
  The AGNTCY observability schema is an extension of the OTel LLM Semantic Conventions for Generative AI systems.
@@ -27,6 +27,37 @@ Alternatively, to download the SDK from git, you could also use the following co
27
27
  uv add "git+https://github.com/agntcy/observe"
28
28
  ```
29
29
 
30
+ ### Quick Start
31
+
32
+ After installation, import and initialize the SDK:
33
+
34
+ ```python
35
+ import os
36
+ from ioa_observe.sdk import Observe
37
+ from ioa_observe.sdk.decorators import agent
38
+ from ioa_observe.sdk.tracing import session_start
39
+
40
+ # Initialize Observe
41
+ Observe.init(
42
+ app_name="your_app_name",
43
+ api_endpoint=os.getenv("OTLP_HTTP_ENDPOINT", "http://localhost:4318")
44
+ )
45
+
46
+ # Use decorators to instrument your agents
47
+ @agent(name="my_agent", description="Example agent")
48
+ def my_agent_function(state):
49
+ # Your agent logic here
50
+ return {"result": "success"}
51
+
52
+ # Start a session for tracking
53
+ with session_start() as session_id:
54
+ result = my_agent_function({"input": "data"})
55
+ ```
56
+
57
+ **Note:** The package name for installation is `ioa_observe_sdk`, but imports use `ioa_observe` (underscore, not hyphen).
58
+
59
+ For comprehensive integration examples with LangGraph, LlamaIndex, and other frameworks, see the [Getting Started Guide](GETTING-STARTED.md).
60
+
30
61
  ## Schema
31
62
 
32
63
  The AGNTCY observability schema is an extension of the OTel LLM Semantic Conventions for Generative AI systems.
@@ -3,7 +3,6 @@
3
3
 
4
4
  import inspect
5
5
  import string
6
- import time
7
6
  from functools import wraps
8
7
  from json import JSONEncoder
9
8
  from typing import Optional
@@ -82,23 +81,23 @@ def process_slim_msg(name: Optional[str] = None):
82
81
  span, ctx, ctx_token = _setup_span(entity_name)
83
82
  _handle_span_input(span, args, kwargs, cls=JSONEncoder)
84
83
 
85
- start_time = time.time()
84
+ # start_time = time.time()
86
85
 
87
86
  try:
88
87
  async for item in _ahandle_generator(
89
88
  span, ctx_token, fn(*args, **kwargs)
90
89
  ):
91
90
  # Measure throughput and processing time per item
92
- item_process_time = time.time() - start_time
93
- TracerWrapper().processing_time.record(
94
- item_process_time, {"agent": entity_name}
95
- )
91
+ # item_process_time = time.time() - start_time
92
+ # TracerWrapper().processing_time.record(
93
+ # item_process_time, {"agent": entity_name}
94
+ # )
96
95
  TracerWrapper().throughput_counter.add(
97
96
  1, {"agent": entity_name}
98
97
  )
99
98
 
100
99
  # Reset timer for next item
101
- start_time = time.time()
100
+ # start_time = time.time()
102
101
  # Count each yielded item as a published message
103
102
  TracerWrapper().messages_received_counter.add(
104
103
  1, {"agent": entity_name}
@@ -122,16 +121,16 @@ def process_slim_msg(name: Optional[str] = None):
122
121
 
123
122
  # span, ctx, ctx_token = _setup_span(entity_name)
124
123
  # _handle_span_input(span, args, kwargs, cls=JSONEncoder)
125
- start_time = time.time()
124
+ # start_time = time.time()
126
125
 
127
126
  try:
128
127
  res = await fn(*args, **kwargs)
129
128
 
130
129
  # Measure processing time
131
- process_time = time.time() - start_time
132
- TracerWrapper().processing_time.record(
133
- process_time, {"agent": entity_name}
134
- )
130
+ # process_time = time.time() - start_time
131
+ # TracerWrapper().processing_time.record(
132
+ # process_time, {"agent": entity_name}
133
+ # )
135
134
  TracerWrapper().throughput_counter.add(
136
135
  1, {"agent": entity_name}
137
136
  )
@@ -172,16 +171,16 @@ def process_slim_msg(name: Optional[str] = None):
172
171
  # span, ctx, ctx_token = _setup_span(entity_name)
173
172
  # _handle_span_input(span, args, kwargs, cls=JSONEncoder)
174
173
 
175
- start_time = time.time()
174
+ # start_time = time.time()
176
175
 
177
176
  try:
178
177
  res = fn(*args, **kwargs)
179
178
 
180
179
  # Measure processing time
181
- process_time = time.time() - start_time
182
- TracerWrapper().processing_time.record(
183
- process_time, {"agent": entity_name}
184
- )
180
+ # process_time = time.time() - start_time
181
+ # TracerWrapper().processing_time.record(
182
+ # process_time, {"agent": entity_name}
183
+ # )
185
184
  TracerWrapper().throughput_counter.add(1, {"agent": entity_name})
186
185
  # span will be ended in the generator
187
186
  # if isinstance(res, types.GeneratorType):
@@ -312,18 +312,18 @@ def _cleanup_span(span, ctx_token):
312
312
  """End the span process and detach the context token"""
313
313
 
314
314
  # Calculate agent chain completion time before ending span
315
- span_kind = span.attributes.get(OBSERVE_SPAN_KIND)
316
- if span_kind == ObserveSpanKindValues.AGENT.value:
317
- start_time = span.attributes.get("agent_chain_start_time")
318
- if start_time is not None:
319
- import time
320
-
321
- completion_time = time.time() - start_time
322
-
323
- # Emit the metric
324
- TracerWrapper().agent_chain_completion_time_histogram.record(
325
- completion_time, attributes=span.attributes
326
- )
315
+ # span_kind = span.attributes.get(OBSERVE_SPAN_KIND)
316
+ # if span_kind == ObserveSpanKindValues.AGENT.value:
317
+ # start_time = span.attributes.get("agent_chain_start_time")
318
+ # if start_time is not None:
319
+ # import time
320
+ #
321
+ # # completion_time = time.time() - start_time
322
+ #
323
+ # # Emit the metric
324
+ # # TracerWrapper().agent_chain_completion_time_histogram.record(
325
+ # # completion_time, attributes=span.attributes
326
+ # # )
327
327
  span.end()
328
328
  context_api.detach(ctx_token)
329
329
 
@@ -5,6 +5,7 @@ from typing import Collection
5
5
  import functools
6
6
  import threading
7
7
 
8
+ from opentelemetry.context import get_value
8
9
  from opentelemetry import baggage
9
10
  from opentelemetry.baggage.propagation import W3CBaggagePropagator
10
11
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@@ -47,8 +48,10 @@ class A2AInstrumentor(BaseInstrumentor):
47
48
  session_id = None
48
49
  if traceparent:
49
50
  session_id = kv_store.get(f"execution.{traceparent}")
50
- if session_id:
51
- kv_store.set(f"execution.{traceparent}", session_id)
51
+ if not session_id:
52
+ session_id = get_value("session.id")
53
+ if session_id:
54
+ kv_store.set(f"execution.{traceparent}", session_id)
52
55
 
53
56
  # Ensure metadata dict exists
54
57
  try:
@@ -101,8 +104,10 @@ class A2AInstrumentor(BaseInstrumentor):
101
104
  session_id = None
102
105
  if traceparent:
103
106
  session_id = kv_store.get(f"execution.{traceparent}")
104
- if session_id:
105
- kv_store.set(f"execution.{traceparent}", session_id)
107
+ if not session_id:
108
+ session_id = get_value("session.id")
109
+ if session_id:
110
+ kv_store.set(f"execution.{traceparent}", session_id)
106
111
 
107
112
  # Ensure metadata dict exists
108
113
  try:
@@ -10,6 +10,7 @@ import traceback
10
10
  import re
11
11
  from http import HTTPStatus
12
12
 
13
+ from opentelemetry.context import get_value
13
14
  from opentelemetry import context, propagate
14
15
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
15
16
  from opentelemetry.instrumentation.utils import unwrap
@@ -212,8 +213,10 @@ class McpInstrumentor(BaseInstrumentor):
212
213
  session_id = None
213
214
  if traceparent:
214
215
  session_id = kv_store.get(f"execution.{traceparent}")
215
- if session_id:
216
- kv_store.set(f"execution.{traceparent}", session_id)
216
+ if not session_id:
217
+ session_id = get_value("session.id")
218
+ if session_id:
219
+ kv_store.set(f"execution.{traceparent}", session_id)
217
220
 
218
221
  meta = meta or {}
219
222
  if isinstance(meta, dict):
@@ -7,6 +7,7 @@ import json
7
7
  import base64
8
8
  import threading
9
9
 
10
+ from opentelemetry.context import get_value
10
11
  from opentelemetry import baggage, context
11
12
  from opentelemetry.baggage.propagation import W3CBaggagePropagator
12
13
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@@ -53,8 +54,10 @@ class NATSInstrumentor(BaseInstrumentor):
53
54
  if traceparent:
54
55
  with _kv_lock:
55
56
  session_id = kv_store.get(f"execution.{traceparent}")
56
- if session_id:
57
- kv_store.set(f"execution.{traceparent}", session_id)
57
+ if not session_id:
58
+ session_id = get_value("session.id")
59
+ if session_id:
60
+ kv_store.set(f"execution.{traceparent}", session_id)
58
61
 
59
62
  headers = {
60
63
  "session_id": session_id if session_id else None,
@@ -104,8 +107,10 @@ class NATSInstrumentor(BaseInstrumentor):
104
107
  if traceparent:
105
108
  with _kv_lock:
106
109
  session_id = kv_store.get(f"execution.{traceparent}")
107
- if session_id:
108
- kv_store.set(f"execution.{traceparent}", session_id)
110
+ if not session_id:
111
+ session_id = get_value("session.id")
112
+ if session_id:
113
+ kv_store.set(f"execution.{traceparent}", session_id)
109
114
 
110
115
  headers = {
111
116
  "session_id": session_id if session_id else None,
@@ -7,6 +7,7 @@ import json
7
7
  import base64
8
8
  import threading
9
9
 
10
+ from opentelemetry.context import get_value
10
11
  from opentelemetry import baggage, context
11
12
  from opentelemetry.baggage.propagation import W3CBaggagePropagator
12
13
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@@ -15,6 +16,7 @@ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapProp
15
16
  from ioa_observe.sdk import TracerWrapper
16
17
  from ioa_observe.sdk.client import kv_store
17
18
  from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
19
+ from ioa_observe.sdk.tracing.context_manager import get_tracer
18
20
 
19
21
  _instruments = ("slim-bindings >= 0.4.0",)
20
22
  _global_tracer = None
@@ -79,8 +81,10 @@ class SLIMInstrumentor(BaseInstrumentor):
79
81
  if traceparent:
80
82
  with _kv_lock:
81
83
  session_id = kv_store.get(f"execution.{traceparent}")
82
- if session_id:
83
- kv_store.set(f"execution.{traceparent}", session_id)
84
+ if not session_id:
85
+ session_id = get_value("session.id")
86
+ if session_id:
87
+ kv_store.set(f"execution.{traceparent}", session_id)
84
88
 
85
89
  headers = {
86
90
  "session_id": session_id if session_id else None,
@@ -138,8 +142,10 @@ class SLIMInstrumentor(BaseInstrumentor):
138
142
  if traceparent:
139
143
  with _kv_lock:
140
144
  session_id = kv_store.get(f"execution.{traceparent}")
141
- if session_id:
142
- kv_store.set(f"execution.{traceparent}", session_id)
145
+ if not session_id:
146
+ session_id = get_value("session.id")
147
+ if session_id:
148
+ kv_store.set(f"execution.{traceparent}", session_id)
143
149
 
144
150
  headers = {
145
151
  "session_id": session_id if session_id else None,
@@ -201,8 +207,10 @@ class SLIMInstrumentor(BaseInstrumentor):
201
207
  if traceparent:
202
208
  with _kv_lock:
203
209
  session_id = kv_store.get(f"execution.{traceparent}")
204
- if session_id:
205
- kv_store.set(f"execution.{traceparent}", session_id)
210
+ if not session_id:
211
+ session_id = get_value("session.id")
212
+ if session_id:
213
+ kv_store.set(f"execution.{traceparent}", session_id)
206
214
 
207
215
  headers = {
208
216
  "session_id": session_id if session_id else None,
@@ -670,6 +678,8 @@ class SLIMInstrumentor(BaseInstrumentor):
670
678
  if traceparent:
671
679
  with _kv_lock:
672
680
  session_id = kv_store.get(f"execution.{traceparent}")
681
+ if not session_id:
682
+ session_id = get_value("session.id")
673
683
  if session_id:
674
684
  kv_store.set(f"execution.{traceparent}", session_id)
675
685
 
@@ -686,7 +696,13 @@ class SLIMInstrumentor(BaseInstrumentor):
686
696
  baggage.set_baggage(f"execution.{traceparent}", session_id)
687
697
 
688
698
  # Wrap the message (first argument for publish, second for publish_to)
689
- message_idx = 1 if method_name == "publish_to" else 0
699
+ # If the session_class is SessionContext, the message is always in second position
700
+ message_idx = (
701
+ 1
702
+ if method_name == "publish_to"
703
+ or session_class.__name__ == "SessionContext"
704
+ else 0
705
+ )
690
706
  if len(args) > message_idx:
691
707
  args_list = list(args)
692
708
  message = args_list[message_idx]
@@ -716,6 +732,10 @@ class SLIMInstrumentor(BaseInstrumentor):
716
732
  if traceparent:
717
733
  with _kv_lock:
718
734
  session_id = kv_store.get(f"execution.{traceparent}")
735
+ if not session_id:
736
+ session_id = get_value("session.id")
737
+ if session_id:
738
+ kv_store.set(f"execution.{traceparent}", session_id)
719
739
 
720
740
  if traceparent or session_id:
721
741
  headers = {
@@ -772,7 +792,8 @@ class SLIMInstrumentor(BaseInstrumentor):
772
792
  @functools.wraps(original_method)
773
793
  async def instrumented_session_method(self, *args, **kwargs):
774
794
  if _global_tracer:
775
- with _global_tracer.start_as_current_span(f"session.{method_name}"):
795
+ tracer = get_tracer()
796
+ with tracer.start_as_current_span(f"session.{method_name}"):
776
797
  traceparent = get_current_traceparent()
777
798
 
778
799
  # Handle message wrapping for publish methods
@@ -828,6 +849,10 @@ class SLIMInstrumentor(BaseInstrumentor):
828
849
  if traceparent:
829
850
  with _kv_lock:
830
851
  session_id = kv_store.get(f"execution.{traceparent}")
852
+ if not session_id:
853
+ session_id = get_value("session.id")
854
+ if session_id:
855
+ kv_store.set(f"execution.{traceparent}", session_id)
831
856
 
832
857
  if traceparent or session_id:
833
858
  headers = {
@@ -194,26 +194,4 @@ def metric_views() -> Sequence[View]:
194
194
  ]
195
195
  ),
196
196
  ),
197
- # response latency in ms
198
- View(
199
- instrument_name="gen_ai.ioa.llm.response_latency",
200
- aggregation=ExplicitBucketHistogramAggregation(
201
- [
202
- 0.01,
203
- 0.02,
204
- 0.04,
205
- 0.08,
206
- 0.16,
207
- 0.32,
208
- 0.64,
209
- 1.28,
210
- 2.56,
211
- 5.12,
212
- 10.24,
213
- 20.48,
214
- 40.96,
215
- 81.92,
216
- ]
217
- ),
218
- ),
219
197
  ]
@@ -205,11 +205,11 @@ class TracerWrapper(object):
205
205
  description="Counts agent failures by agent and reason",
206
206
  unit="1",
207
207
  )
208
- obj.response_latency_histogram = meter.create_histogram(
209
- name="response_latency",
210
- description="Records the latency of responses",
211
- unit="ms",
212
- )
208
+ # obj.response_latency_histogram = meter.create_histogram(
209
+ # name="response_latency",
210
+ # description="Records the latency of responses",
211
+ # unit="ms",
212
+ # )
213
213
  obj.messages_received_counter = meter.create_counter(
214
214
  "slim.messages.received",
215
215
  description="Number of SLIM messages received per agent",
@@ -226,10 +226,10 @@ class TracerWrapper(object):
226
226
  "slim.messages.processed",
227
227
  description="Number of SLIM messages processed",
228
228
  )
229
- obj.processing_time = meter.create_histogram(
230
- "slim.message.processing_time",
231
- description="Time taken to process SLIM messages",
232
- )
229
+ # obj.processing_time = meter.create_histogram(
230
+ # "slim.message.processing_time",
231
+ # description="Time taken to process SLIM messages",
232
+ # )
233
233
  obj.throughput_counter = meter.create_counter(
234
234
  "slim.message.throughput",
235
235
  description="Message throughput for SLIM operations",
@@ -237,11 +237,11 @@ class TracerWrapper(object):
237
237
  obj.error_counter = meter.create_counter(
238
238
  "slim.errors", description="Number of SLIM message errors or drops"
239
239
  )
240
- obj.agent_chain_completion_time_histogram = meter.create_histogram(
241
- name="gen_ai.client.ioa.agent.end_to_end_chain_completion_time",
242
- description="Records the end-to-end chain completion time for a single agent",
243
- unit="s",
244
- )
240
+ # obj.agent_chain_completion_time_histogram = meter.create_histogram(
241
+ # name="gen_ai.client.ioa.agent.end_to_end_chain_completion_time",
242
+ # description="Records the end-to-end chain completion time for a single agent",
243
+ # unit="s",
244
+ # )
245
245
  obj.agent_execution_success_rate = meter.create_observable_gauge(
246
246
  name="gen_ai.client.ioa.agent.execution_success_rate",
247
247
  description="Success rate of agent executions",
@@ -363,7 +363,7 @@ class TracerWrapper(object):
363
363
  self._processed_spans.add(span_id)
364
364
 
365
365
  determine_reliability_score(span)
366
- start_time = span.attributes.get("ioa_start_time")
366
+ # start_time = span.attributes.get("ioa_start_time")
367
367
 
368
368
  # Apply transformations if enabled
369
369
  apply_transform = get_value("apply_transform")
@@ -382,9 +382,9 @@ class TracerWrapper(object):
382
382
  except Exception as e:
383
383
  logging.error(f"Error applying span transformation: {e}")
384
384
 
385
- if start_time is not None:
386
- latency = (time.time() - start_time) * 1000
387
- self.response_latency_histogram.record(latency, attributes=span.attributes)
385
+ # if start_time is not None:
386
+ # latency = (time.time() - start_time) * 1000
387
+ # self.response_latency_histogram.record(latency, attributes=span.attributes)
388
388
 
389
389
  # Call original on_end method if it exists
390
390
  if (
@@ -684,28 +684,24 @@ def set_session_id(session_id: str, traceparent: str = None) -> None:
684
684
 
685
685
  # Check if we have an active span first
686
686
  current_span = trace.get_current_span()
687
+ extracted_traceparent = None
687
688
 
688
689
  if current_span.is_recording():
689
690
  # We have an active span, use its context
690
691
  carrier = {}
691
692
  TraceContextTextMapPropagator().inject(carrier)
692
693
  extracted_traceparent = carrier.get("traceparent")
693
- else:
694
- # Only create new span if absolutely necessary (no existing context)
695
- tracer = trace.get_tracer(__name__)
696
- with tracer.start_as_current_span("set_session_id"):
697
- carrier = {}
698
- TraceContextTextMapPropagator().inject(carrier)
699
- extracted_traceparent = carrier.get("traceparent")
700
-
701
- # Store execution ID with traceparent as key
694
+ # Store execution ID with traceparent as key
695
+ if extracted_traceparent:
696
+ kv_key = f"execution.{extracted_traceparent}"
697
+ if kv_store.get(kv_key) is None:
698
+ kv_store.set(kv_key, session_id)
699
+ # If there is no active span, we do nothing now and when we need the traceparent
700
+ # e.g. when propagating context, we update the kv_store with the session_id then
701
+
702
+ # Finally, attach to context session.id and current_traceparent if present
703
+ attach(set_value("session.id", session_id))
702
704
  if extracted_traceparent:
703
- kv_key = f"execution.{extracted_traceparent}"
704
- if kv_store.get(kv_key) is None:
705
- kv_store.set(kv_key, session_id)
706
-
707
- # Also store in OpenTelemetry context
708
- attach(set_value("session.id", session_id))
709
705
  attach(set_value("current_traceparent", extracted_traceparent))
710
706
 
711
707
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ioa-observe-sdk
3
- Version: 1.0.24
3
+ Version: 1.0.26
4
4
  Summary: IOA Observability SDK
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -76,6 +76,37 @@ Alternatively, to download the SDK from git, you could also use the following co
76
76
  uv add "git+https://github.com/agntcy/observe"
77
77
  ```
78
78
 
79
+ ### Quick Start
80
+
81
+ After installation, import and initialize the SDK:
82
+
83
+ ```python
84
+ import os
85
+ from ioa_observe.sdk import Observe
86
+ from ioa_observe.sdk.decorators import agent
87
+ from ioa_observe.sdk.tracing import session_start
88
+
89
+ # Initialize Observe
90
+ Observe.init(
91
+ app_name="your_app_name",
92
+ api_endpoint=os.getenv("OTLP_HTTP_ENDPOINT", "http://localhost:4318")
93
+ )
94
+
95
+ # Use decorators to instrument your agents
96
+ @agent(name="my_agent", description="Example agent")
97
+ def my_agent_function(state):
98
+ # Your agent logic here
99
+ return {"result": "success"}
100
+
101
+ # Start a session for tracking
102
+ with session_start() as session_id:
103
+ result = my_agent_function({"input": "data"})
104
+ ```
105
+
106
+ **Note:** The package name for installation is `ioa_observe_sdk`, but imports use `ioa_observe` (underscore, not hyphen).
107
+
108
+ For comprehensive integration examples with LangGraph, LlamaIndex, and other frameworks, see the [Getting Started Guide](GETTING-STARTED.md).
109
+
79
110
  ## Schema
80
111
 
81
112
  The AGNTCY observability schema is an extension of the OTel LLM Semantic Conventions for Generative AI systems.
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "ioa-observe-sdk"
8
- version = "1.0.24"
8
+ version = "1.0.26"
9
9
  license = "Apache-2.0"
10
10
  description = "IOA Observability SDK"
11
11
  readme = "README.md"