ioa-observe-sdk 1.0.28__py3-none-any.whl → 1.0.30__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.
@@ -30,8 +30,15 @@ from opentelemetry.sdk.trace.export import (
30
30
  SimpleSpanProcessor,
31
31
  BatchSpanProcessor,
32
32
  )
33
- from opentelemetry.trace import get_tracer_provider, ProxyTracerProvider
33
+ from opentelemetry.trace import (
34
+ get_tracer_provider,
35
+ ProxyTracerProvider,
36
+ SpanContext,
37
+ TraceFlags,
38
+ NonRecordingSpan,
39
+ )
34
40
  from opentelemetry.context import get_value, attach, set_value
41
+ from opentelemetry.trace import set_span_in_context
35
42
  from opentelemetry.instrumentation.threading import ThreadingInstrumentor
36
43
  from opentelemetry.metrics import get_meter
37
44
  from ioa_observe.sdk.metrics.agents.agent_connections import connection_reliability
@@ -137,9 +144,12 @@ class TracerWrapper(object):
137
144
  if not TracerWrapper.endpoint:
138
145
  return obj
139
146
 
140
- # session activity tracking
141
- obj._session_last_activity: dict[str, float] = {}
147
+ # session activity tracking: {session_id: (last_activity_time, trace_id)}
148
+ obj._session_last_activity: dict[str, tuple[float, int]] = {}
142
149
  obj._session_lock = threading.Lock()
150
+ # Track sessions that have already been ended to prevent duplicates
151
+ obj._ended_sessions: set[str] = set()
152
+ obj._ended_sessions_lock = threading.Lock()
143
153
  obj.__image_uploader = image_uploader
144
154
  # {(agent_name): [success_count, total_count]}
145
155
  obj._agent_execution_counts = {}
@@ -300,13 +310,15 @@ class TracerWrapper(object):
300
310
  while True:
301
311
  time.sleep(SESSION_WATCHER_INTERVAL_SECONDS)
302
312
  now = time.time()
303
- expired: dict[str, float] = {}
313
+ expired: dict[str, tuple[float, int]] = {}
304
314
 
305
315
  # Find idle sessions and remove them from _session_last_activity
306
316
  with self._session_lock:
307
- for session_id, last_ts in list(self._session_last_activity.items()):
317
+ for session_id, (last_ts, trace_id) in list(
318
+ self._session_last_activity.items()
319
+ ):
308
320
  if now - last_ts > SESSION_IDLE_TIMEOUT_SECONDS:
309
- expired[session_id] = last_ts
321
+ expired[session_id] = (last_ts, trace_id)
310
322
  del self._session_last_activity[session_id]
311
323
 
312
324
  # Periodic cleanup of processed spans to prevent unbounded memory growth
@@ -314,19 +326,42 @@ class TracerWrapper(object):
314
326
  if len(self._processed_spans) > MAX_PROCESSED_SPANS_SIZE:
315
327
  self._processed_spans.clear()
316
328
 
329
+ # Periodic cleanup of ended sessions to prevent unbounded memory growth
330
+ with self._ended_sessions_lock:
331
+ if len(self._ended_sessions) > MAX_PROCESSED_SPANS_SIZE:
332
+ self._ended_sessions.clear()
333
+
317
334
  if not expired:
318
335
  continue
319
336
 
320
337
  tracer = self.get_tracer()
321
338
 
322
339
  # Iterate over a snapshot and do *not* modify `expired` in the loop
323
- for session_id, _last_ts in list(expired.items()):
324
- with tracer.start_as_current_span("session.end") as span:
340
+ for session_id, (last_ts, trace_id) in list(expired.items()):
341
+ # Check if this session has already been ended to prevent duplicates
342
+ with self._ended_sessions_lock:
343
+ if session_id in self._ended_sessions:
344
+ continue
345
+ self._ended_sessions.add(session_id)
346
+
347
+ # Create a parent context from the stored trace_id to keep session.end under the same trace
348
+ parent_span_context = SpanContext(
349
+ trace_id=trace_id,
350
+ span_id=0, # Use 0 as we don't have a specific parent span
351
+ is_remote=False,
352
+ trace_flags=TraceFlags(TraceFlags.SAMPLED),
353
+ )
354
+ parent_span = NonRecordingSpan(parent_span_context)
355
+ parent_context = set_span_in_context(parent_span)
356
+
357
+ with tracer.start_as_current_span(
358
+ "session.end", context=parent_context
359
+ ) as span:
325
360
  span.set_attribute("session.id", session_id)
326
361
  workflow_name = get_value("workflow_name")
327
362
  if workflow_name:
328
363
  span.set_attribute(OBSERVE_WORKFLOW_NAME, workflow_name)
329
- span.set_attribute("session.ended_at", _last_ts)
364
+ span.set_attribute("session.ended_at", last_ts)
330
365
 
331
366
  # ensure end spans are exported reasonably fast
332
367
  self.flush()
@@ -337,11 +372,29 @@ class TracerWrapper(object):
337
372
  tracer = self.get_tracer()
338
373
 
339
374
  with self._session_lock:
340
- remaining_ids = list(self._session_last_activity.keys())
375
+ remaining_sessions = dict(self._session_last_activity)
341
376
  self._session_last_activity.clear()
342
377
 
343
- for session_id in remaining_ids:
344
- with tracer.start_as_current_span("session.end") as span:
378
+ for session_id, (_, trace_id) in remaining_sessions.items():
379
+ # Check if this session has already been ended to prevent duplicates
380
+ with self._ended_sessions_lock:
381
+ if session_id in self._ended_sessions:
382
+ continue
383
+ self._ended_sessions.add(session_id)
384
+
385
+ # Create a parent context from the stored trace_id to keep session.end under the same trace
386
+ parent_span_context = SpanContext(
387
+ trace_id=trace_id,
388
+ span_id=0, # Use 0 as we don't have a specific parent span
389
+ is_remote=False,
390
+ trace_flags=TraceFlags(TraceFlags.SAMPLED),
391
+ )
392
+ parent_span = NonRecordingSpan(parent_span_context)
393
+ parent_context = set_span_in_context(parent_span)
394
+
395
+ with tracer.start_as_current_span(
396
+ "session.end", context=parent_context
397
+ ) as span:
345
398
  span.set_attribute("session.id", session_id)
346
399
  workflow_name = get_value("workflow_name")
347
400
  if workflow_name:
@@ -441,7 +494,11 @@ class TracerWrapper(object):
441
494
  session_id = span.attributes.get("session.id")
442
495
  if session_id and span.name != "session.end":
443
496
  with self._session_lock:
444
- self._session_last_activity[session_id] = time.time()
497
+ # Store both the last activity time and the trace_id for this session
498
+ self._session_last_activity[session_id] = (
499
+ time.time(),
500
+ span.context.trace_id,
501
+ )
445
502
 
446
503
  determine_reliability_score(span)
447
504
  # start_time = span.attributes.get("ioa_start_time")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ioa-observe-sdk
3
- Version: 1.0.28
3
+ Version: 1.0.30
4
4
  Summary: IOA Observability SDK
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.10
@@ -8,37 +8,37 @@ Description-Content-Type: text/markdown
8
8
  License-File: LICENSE.md
9
9
  Requires-Dist: colorama==0.4.6
10
10
  Requires-Dist: requests>=2.32.3
11
- Requires-Dist: opentelemetry-api==1.37.0
11
+ Requires-Dist: opentelemetry-api==1.39.1
12
12
  Requires-Dist: opentelemetry-distro
13
- Requires-Dist: opentelemetry-exporter-otlp==1.37.0
14
- Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.37.0
15
- Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.37.0
16
- Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.37.0
13
+ Requires-Dist: opentelemetry-exporter-otlp==1.39.1
14
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.39.1
15
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.39.1
16
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.39.1
17
17
  Requires-Dist: opentelemetry-instrumentation
18
- Requires-Dist: opentelemetry-instrumentation-logging==0.58b0
19
- Requires-Dist: opentelemetry-instrumentation-openai==0.43.1
20
- Requires-Dist: opentelemetry-instrumentation-llamaindex==0.43.1
21
- Requires-Dist: opentelemetry-instrumentation-ollama==0.43.1
22
- Requires-Dist: opentelemetry-instrumentation-anthropic==0.43.1
23
- Requires-Dist: opentelemetry-instrumentation-langchain==0.43.1
24
- Requires-Dist: opentelemetry-instrumentation-bedrock==0.43.1
25
- Requires-Dist: opentelemetry-instrumentation-cohere==0.43.1
26
- Requires-Dist: opentelemetry-instrumentation-crewai==0.43.1
27
- Requires-Dist: opentelemetry-instrumentation-google-generativeai==0.43.1
28
- Requires-Dist: opentelemetry-instrumentation-groq==0.43.1
29
- Requires-Dist: opentelemetry-instrumentation-mistralai==0.43.1
30
- Requires-Dist: opentelemetry-instrumentation-requests==0.58b0
31
- Requires-Dist: opentelemetry-instrumentation-sagemaker==0.43.1
32
- Requires-Dist: opentelemetry-instrumentation-threading==0.58b0
33
- Requires-Dist: opentelemetry-instrumentation-together==0.43.1
34
- Requires-Dist: opentelemetry-instrumentation-transformers==0.43.1
35
- Requires-Dist: opentelemetry-instrumentation-urllib3==0.58b0
36
- Requires-Dist: opentelemetry-instrumentation-vertexai==0.43.1
37
- Requires-Dist: opentelemetry-proto==1.37.0
38
- Requires-Dist: opentelemetry-sdk==1.37.0
39
- Requires-Dist: opentelemetry-semantic-conventions==0.58b0
18
+ Requires-Dist: opentelemetry-instrumentation-logging==0.60b1
19
+ Requires-Dist: opentelemetry-instrumentation-openai==0.52.1
20
+ Requires-Dist: opentelemetry-instrumentation-llamaindex==0.52.1
21
+ Requires-Dist: opentelemetry-instrumentation-ollama==0.52.1
22
+ Requires-Dist: opentelemetry-instrumentation-anthropic==0.52.1
23
+ Requires-Dist: opentelemetry-instrumentation-langchain==0.52.1
24
+ Requires-Dist: opentelemetry-instrumentation-bedrock==0.52.1
25
+ Requires-Dist: opentelemetry-instrumentation-cohere==0.52.1
26
+ Requires-Dist: opentelemetry-instrumentation-crewai==0.52.1
27
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai==0.52.1
28
+ Requires-Dist: opentelemetry-instrumentation-groq==0.52.1
29
+ Requires-Dist: opentelemetry-instrumentation-mistralai==0.52.1
30
+ Requires-Dist: opentelemetry-instrumentation-requests==0.60b1
31
+ Requires-Dist: opentelemetry-instrumentation-sagemaker==0.52.1
32
+ Requires-Dist: opentelemetry-instrumentation-threading==0.60b1
33
+ Requires-Dist: opentelemetry-instrumentation-together==0.52.1
34
+ Requires-Dist: opentelemetry-instrumentation-transformers==0.52.1
35
+ Requires-Dist: opentelemetry-instrumentation-urllib3==0.60b1
36
+ Requires-Dist: opentelemetry-instrumentation-vertexai==0.52.1
37
+ Requires-Dist: opentelemetry-proto==1.39.1
38
+ Requires-Dist: opentelemetry-sdk==1.39.1
39
+ Requires-Dist: opentelemetry-semantic-conventions==0.60b1
40
40
  Requires-Dist: opentelemetry-semantic-conventions-ai>=0.4.11
41
- Requires-Dist: opentelemetry-util-http==0.58b0
41
+ Requires-Dist: opentelemetry-util-http==0.60b1
42
42
  Requires-Dist: langgraph>=0.3.2
43
43
  Requires-Dist: langchain>=0.3.19
44
44
  Requires-Dist: langchain-openai>=0.3.8
@@ -14,10 +14,10 @@ ioa_observe/sdk/decorators/base.py,sha256=gezoLMLOo-CWsxzsXbQMsj5HxmchUEmlhTspbq
14
14
  ioa_observe/sdk/decorators/helpers.py,sha256=I9HXMBivkZpGDtPe9Ad_UU35p_m_wEPate4r_fU0oOA,2705
15
15
  ioa_observe/sdk/decorators/util.py,sha256=IebvH9gwZN1en3LblYJUh4bAV2STl6xmp8WpZzBDH2g,30068
16
16
  ioa_observe/sdk/instrumentations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- ioa_observe/sdk/instrumentations/a2a.py,sha256=LHvvQUluPtmvbRNuoqf-EkWlZJa_NicerTpM3t1UZws,15299
17
+ ioa_observe/sdk/instrumentations/a2a.py,sha256=smPMHhFONi9B7r2qVHe-YWoFctjHWehO_MR0kY_DgVI,27897
18
18
  ioa_observe/sdk/instrumentations/mcp.py,sha256=UG82fWitFT-K-ecnkTKZa-uFTDAs-j2HymvhUwCBRLY,18721
19
19
  ioa_observe/sdk/instrumentations/nats.py,sha256=MEYCFqHyRK5HtkjrbGVV5qIWplZ1ZjJjFCB8vtQl6qE,14736
20
- ioa_observe/sdk/instrumentations/slim.py,sha256=c_Q1cb6pdjZLOw1MbclUf1eRFrc_sUEe9pQJvAyoD1Y,45712
20
+ ioa_observe/sdk/instrumentations/slim.py,sha256=CE0wUGlRyxbAgP9oI69kGQ9M2yu9HmufrQNua4L0jTE,19510
21
21
  ioa_observe/sdk/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  ioa_observe/sdk/logging/logging.py,sha256=HZxW9s8Due7jgiNkdI38cIjv5rC9D-Flta3RQMOnpow,2891
23
23
  ioa_observe/sdk/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -35,15 +35,15 @@ ioa_observe/sdk/tracing/content_allow_list.py,sha256=1fAkpIwUQ7vDwCTkIVrqeltWQtr
35
35
  ioa_observe/sdk/tracing/context_manager.py,sha256=O0JEXYa9h8anhW78R8KKBuqS0j4by1E1KXxNIMPnLr8,400
36
36
  ioa_observe/sdk/tracing/context_utils.py,sha256=-sYS9vPLI87davV9ubneq5xqbV583CC_c0SmOQS1TAs,2933
37
37
  ioa_observe/sdk/tracing/manual.py,sha256=KS6WN-zw9vAACzXYmnMoJm9d1fenYMfvzeK1GrGDPDE,1937
38
- ioa_observe/sdk/tracing/tracing.py,sha256=ywvT5Z1DcaLYZQvUP30YKz6m6v76Idlvb2qszupcxS4,50774
38
+ ioa_observe/sdk/tracing/tracing.py,sha256=pGiIa1S3562QRlTG5mANtU94wkFjKspnX21N_bJ7SfY,53359
39
39
  ioa_observe/sdk/tracing/transform_span.py,sha256=XTApi_gJxum7ynvhtcoCfDyK8VVOj91Q1DT6hAeLHA8,8419
40
40
  ioa_observe/sdk/utils/__init__.py,sha256=UPn182U-UblF_XwXaFpx8F-TmQTbm1LYf9y89uSp5Hw,704
41
41
  ioa_observe/sdk/utils/const.py,sha256=d67dUTAH9UpWvUV9GLBUqn1Sc2knJ55dy-e6YoLrvSo,1318
42
42
  ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
43
43
  ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
44
44
  ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
45
- ioa_observe_sdk-1.0.28.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
46
- ioa_observe_sdk-1.0.28.dist-info/METADATA,sha256=xEbYDkX8xS0u72TgyF-DvEM9R7BxEA8wp3khv4MWHAE,7997
47
- ioa_observe_sdk-1.0.28.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
- ioa_observe_sdk-1.0.28.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
49
- ioa_observe_sdk-1.0.28.dist-info/RECORD,,
45
+ ioa_observe_sdk-1.0.30.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
46
+ ioa_observe_sdk-1.0.30.dist-info/METADATA,sha256=KxVA9-iwTZF37H5BMdAeRxk_HeJ_uSJXRzVcsbeMc7c,7997
47
+ ioa_observe_sdk-1.0.30.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
+ ioa_observe_sdk-1.0.30.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
49
+ ioa_observe_sdk-1.0.30.dist-info/RECORD,,