paid-python 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -217,7 +217,8 @@ def _instrument_langchain() -> None:
217
217
  return
218
218
 
219
219
  # Instrument LangChain with Paid's tracer provider
220
- LangchainInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
220
+ LangchainInstrumentor(disable_trace_context_propagation=True).instrument(tracer_provider=tracing.paid_tracer_provider)
221
+
221
222
 
222
223
  _initialized_instrumentors.append("langchain")
223
224
  logger.info("LangChain auto-instrumentation enabled")
@@ -0,0 +1,60 @@
1
+ import contextvars
2
+ from typing import Any, Optional
3
+
4
+ from paid.logger import logger
5
+
6
+
7
+ # this class is used like a namespace, it's not for instantiation
8
+ class ContextData:
9
+ _EXTERNAL_CUSTOMER_ID = contextvars.ContextVar[Optional[str]]("external_customer_id", default=None)
10
+ _EXTERNAL_AGENT_ID = contextvars.ContextVar[Optional[str]]("external_agent_id", default=None)
11
+ _TRACE_ID = contextvars.ContextVar[Optional[int]]("trace_id", default=None)
12
+ _STORE_PROMPT = contextvars.ContextVar[Optional[bool]]("store_prompt", default=False)
13
+ _USER_METADATA = contextvars.ContextVar[Optional[dict[str, Any]]]("user_metadata", default=None)
14
+
15
+ _context: dict[str, contextvars.ContextVar] = {
16
+ "external_customer_id": _EXTERNAL_CUSTOMER_ID,
17
+ "external_agent_id": _EXTERNAL_AGENT_ID,
18
+ "trace_id": _TRACE_ID,
19
+ "store_prompt": _STORE_PROMPT,
20
+ "user_metadata": _USER_METADATA,
21
+ }
22
+
23
+ # Use ContextVar for reset tokens to avoid race conditions in async/concurrent scenarios
24
+ _reset_tokens: contextvars.ContextVar[Optional[dict[str, Any]]] = contextvars.ContextVar(
25
+ "reset_tokens", default=None
26
+ )
27
+
28
+ @classmethod
29
+ def _get_or_create_reset_tokens(cls) -> dict[str, Any]:
30
+ """Get the reset tokens dict for this context, creating a new one if needed."""
31
+ reset_tokens = cls._reset_tokens.get()
32
+ if reset_tokens is None:
33
+ reset_tokens = {}
34
+ cls._reset_tokens.set(reset_tokens)
35
+ return reset_tokens
36
+
37
+ @classmethod
38
+ def get_context(cls) -> dict[str, Any]:
39
+ return {key: var.get() for key, var in cls._context.items()}
40
+
41
+ @classmethod
42
+ def get_context_key(cls, key: str) -> Any:
43
+ return cls._context[key].get() if key in cls._context else None
44
+
45
+ @classmethod
46
+ def set_context_key(cls, key: str, value: Any) -> None:
47
+ if key not in cls._context:
48
+ logger.warning(f"Invalid context key: {key}")
49
+ return
50
+ reset_token = cls._context[key].set(value)
51
+ reset_tokens = cls._get_or_create_reset_tokens()
52
+ reset_tokens[key] = reset_token
53
+
54
+ @classmethod
55
+ def reset_context(cls) -> None:
56
+ reset_tokens = cls._reset_tokens.get()
57
+ if reset_tokens:
58
+ for key, reset_token in reset_tokens.items():
59
+ cls._context[key].reset(reset_token)
60
+ reset_tokens.clear()
@@ -1,9 +1,9 @@
1
1
  import asyncio
2
- import contextvars
3
2
  import functools
4
- from typing import Any, Callable, Dict, Optional, Tuple
3
+ from typing import Any, Callable, Dict, Optional
5
4
 
6
5
  from . import distributed_tracing, tracing
6
+ from .context_data import ContextData
7
7
  from .tracing import get_paid_tracer, get_token, initialize_tracing, trace_async_, trace_sync_
8
8
  from opentelemetry import trace
9
9
  from opentelemetry.context import Context
@@ -78,14 +78,6 @@ class paid_tracing:
78
78
  self.metadata = metadata
79
79
  self.span: Optional[Span] = None
80
80
  self.span_ctx: Optional[Any] = None # Context manager for the span
81
- self.reset_tokens: Optional[
82
- Tuple[
83
- contextvars.Token[Optional[str]], # external_customer_id
84
- contextvars.Token[Optional[str]], # external_agent_id
85
- contextvars.Token[Optional[bool]], # store_prompt
86
- contextvars.Token[Optional[Dict[str, Any]]], # metadata
87
- ]
88
- ] = None
89
81
 
90
82
  if not get_token():
91
83
  initialize_tracing(None, self.collector_endpoint)
@@ -94,23 +86,15 @@ class paid_tracing:
94
86
  """Set up context variables and return OTEL context if needed."""
95
87
 
96
88
  # Set context variables
97
- reset_customer_id_ctx_token = tracing.paid_external_customer_id_var.set(self.external_customer_id)
98
- reset_agent_id_ctx_token = tracing.paid_external_agent_id_var.set(self.external_agent_id)
99
- reset_store_prompt_ctx_token = tracing.paid_store_prompt_var.set(self.store_prompt)
100
- reset_user_metadata_ctx_token = tracing.paid_user_metadata_var.set(self.metadata)
101
-
102
- # Store reset tokens for cleanup
103
- self.reset_tokens = (
104
- reset_customer_id_ctx_token,
105
- reset_agent_id_ctx_token,
106
- reset_store_prompt_ctx_token,
107
- reset_user_metadata_ctx_token,
108
- )
89
+ ContextData.set_context_key("external_customer_id", self.external_customer_id)
90
+ ContextData.set_context_key("external_agent_id", self.external_agent_id)
91
+ ContextData.set_context_key("store_prompt", self.store_prompt)
92
+ ContextData.set_context_key("user_metadata", self.metadata)
109
93
 
110
94
  # Handle distributed tracing token
111
95
  override_trace_id = self.tracing_token
112
96
  if not override_trace_id:
113
- override_trace_id = tracing.paid_trace_id_var.get()
97
+ override_trace_id = ContextData.get_context_key("trace_id")
114
98
 
115
99
  ctx: Optional[Context] = None
116
100
  if override_trace_id is not None:
@@ -126,18 +110,7 @@ class paid_tracing:
126
110
 
127
111
  def _cleanup_context(self):
128
112
  """Reset all context variables."""
129
- if self.reset_tokens:
130
- (
131
- reset_customer_id_ctx_token,
132
- reset_agent_id_ctx_token,
133
- reset_store_prompt_ctx_token,
134
- reset_user_metadata_ctx_token,
135
- ) = self.reset_tokens
136
- tracing.paid_external_customer_id_var.reset(reset_customer_id_ctx_token)
137
- tracing.paid_external_agent_id_var.reset(reset_agent_id_ctx_token)
138
- tracing.paid_store_prompt_var.reset(reset_store_prompt_ctx_token)
139
- tracing.paid_user_metadata_var.reset(reset_user_metadata_ctx_token)
140
- self.reset_tokens = None
113
+ ContextData.reset_context()
141
114
 
142
115
  # Context manager methods for sync
143
116
  def __enter__(self):
@@ -1,6 +1,6 @@
1
1
  import warnings
2
2
 
3
- from . import tracing
3
+ from .context_data import ContextData
4
4
  from opentelemetry.sdk.trace.id_generator import RandomIdGenerator
5
5
 
6
6
  otel_id_generator = RandomIdGenerator()
@@ -85,7 +85,7 @@ def set_tracing_token(token: int):
85
85
  DeprecationWarning,
86
86
  stacklevel=2,
87
87
  )
88
- _ = tracing.paid_trace_id_var.set(token)
88
+ ContextData.set_context_key("trace_id", token)
89
89
 
90
90
 
91
91
  def unset_tracing_token():
@@ -110,4 +110,4 @@ def unset_tracing_token():
110
110
  DeprecationWarning,
111
111
  stacklevel=2,
112
112
  )
113
- _ = tracing.paid_trace_id_var.set(None)
113
+ ContextData.set_context_key("trace_id", None)
paid/tracing/tracing.py CHANGED
@@ -1,13 +1,13 @@
1
1
  # Initializing tracing for OTLP
2
2
  import asyncio
3
3
  import atexit
4
- import contextvars
5
4
  import os
6
5
  import signal
7
6
  from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, TypeVar, Union
8
7
 
9
8
  import dotenv
10
9
  from . import distributed_tracing
10
+ from .context_data import ContextData
11
11
  from opentelemetry import trace
12
12
  from opentelemetry.context import Context
13
13
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
@@ -23,24 +23,6 @@ DEFAULT_COLLECTOR_ENDPOINT = (
23
23
  os.environ.get("PAID_OTEL_COLLECTOR_ENDPOINT") or "https://collector.agentpaid.io:4318/v1/traces"
24
24
  )
25
25
 
26
- # Context variables for passing data to nested spans (e.g., in openAiWrapper)
27
- paid_external_customer_id_var: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
28
- "paid_external_customer_id", default=None
29
- )
30
- paid_external_agent_id_var: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
31
- "paid_external_agent_id", default=None
32
- )
33
- # trace id storage (generated from token)
34
- paid_trace_id_var: contextvars.ContextVar[Optional[int]] = contextvars.ContextVar("paid_trace_id", default=None)
35
- # flag to enable storing prompt contents
36
- paid_store_prompt_var: contextvars.ContextVar[Optional[bool]] = contextvars.ContextVar(
37
- "paid_store_prompt", default=False
38
- )
39
- # user metadata
40
- paid_user_metadata_var: contextvars.ContextVar[Optional[Dict[str, Any]]] = contextvars.ContextVar(
41
- "paid_user_metadata", default=None
42
- )
43
-
44
26
  T = TypeVar("T")
45
27
 
46
28
 
@@ -102,15 +84,15 @@ class PaidSpanProcessor(SpanProcessor):
102
84
  span.update_name(f"{self.SPAN_NAME_PREFIX}{span.name}")
103
85
 
104
86
  # Add customer and agent IDs from context
105
- customer_id = paid_external_customer_id_var.get()
87
+ customer_id = ContextData.get_context_key("external_customer_id")
106
88
  if customer_id:
107
89
  span.set_attribute("external_customer_id", customer_id)
108
90
 
109
- agent_id = paid_external_agent_id_var.get()
91
+ agent_id = ContextData.get_context_key("external_agent_id")
110
92
  if agent_id:
111
93
  span.set_attribute("external_agent_id", agent_id)
112
94
 
113
- metadata = paid_user_metadata_var.get()
95
+ metadata = ContextData.get_context_key("user_metadata")
114
96
  if metadata:
115
97
  metadata_attributes: dict[str, Any] = {}
116
98
 
@@ -131,7 +113,7 @@ class PaidSpanProcessor(SpanProcessor):
131
113
 
132
114
  def on_end(self, span: ReadableSpan) -> None:
133
115
  """Filter out prompt and response contents unless explicitly asked to store"""
134
- store_prompt = paid_store_prompt_var.get()
116
+ store_prompt = ContextData.get_context_key("store_prompt")
135
117
  if store_prompt:
136
118
  return
137
119
 
@@ -299,15 +281,15 @@ def trace_sync_(
299
281
  kwargs = kwargs or {}
300
282
 
301
283
  # Set context variables for access by nested spans
302
- reset_customer_id_ctx_token = paid_external_customer_id_var.set(external_customer_id)
303
- reset_agent_id_ctx_token = paid_external_agent_id_var.set(external_agent_id)
304
- reset_store_prompt_ctx_token = paid_store_prompt_var.set(store_prompt)
305
- reset_user_metadata_ctx_token = paid_user_metadata_var.set(metadata)
284
+ ContextData.set_context_key("external_customer_id", external_customer_id)
285
+ ContextData.set_context_key("external_agent_id", external_agent_id)
286
+ ContextData.set_context_key("store_prompt", store_prompt)
287
+ ContextData.set_context_key("user_metadata", metadata)
306
288
 
307
289
  # If user set trace context manually
308
290
  override_trace_id = tracing_token
309
291
  if not override_trace_id:
310
- override_trace_id = paid_trace_id_var.get()
292
+ override_trace_id = ContextData.get_context_key("trace_id")
311
293
  ctx: Optional[Context] = None
312
294
  if override_trace_id is not None:
313
295
  span_context = SpanContext(
@@ -331,10 +313,7 @@ def trace_sync_(
331
313
  span.set_status(Status(StatusCode.ERROR, str(error)))
332
314
  raise
333
315
  finally:
334
- paid_external_customer_id_var.reset(reset_customer_id_ctx_token)
335
- paid_external_agent_id_var.reset(reset_agent_id_ctx_token)
336
- paid_store_prompt_var.reset(reset_store_prompt_ctx_token)
337
- paid_user_metadata_var.reset(reset_user_metadata_ctx_token)
316
+ ContextData.reset_context()
338
317
 
339
318
 
340
319
  async def trace_async_(
@@ -373,15 +352,15 @@ async def trace_async_(
373
352
  kwargs = kwargs or {}
374
353
 
375
354
  # Set context variables for access by nested spans
376
- reset_customer_id_ctx_token = paid_external_customer_id_var.set(external_customer_id)
377
- reset_agent_id_ctx_token = paid_external_agent_id_var.set(external_agent_id)
378
- reset_store_prompt_ctx_token = paid_store_prompt_var.set(store_prompt)
379
- reset_user_metadata_ctx_token = paid_user_metadata_var.set(metadata)
355
+ ContextData.set_context_key("external_customer_id", external_customer_id)
356
+ ContextData.set_context_key("external_agent_id", external_agent_id)
357
+ ContextData.set_context_key("store_prompt", store_prompt)
358
+ ContextData.set_context_key("user_metadata", metadata)
380
359
 
381
360
  # If user set trace context manually
382
361
  override_trace_id = tracing_token
383
362
  if not override_trace_id:
384
- override_trace_id = paid_trace_id_var.get()
363
+ override_trace_id = ContextData.get_context_key("trace_id")
385
364
  ctx: Optional[Context] = None
386
365
  if override_trace_id is not None:
387
366
  span_context = SpanContext(
@@ -408,7 +387,4 @@ async def trace_async_(
408
387
  span.set_status(Status(StatusCode.ERROR, str(error)))
409
388
  raise
410
389
  finally:
411
- paid_external_customer_id_var.reset(reset_customer_id_ctx_token)
412
- paid_external_agent_id_var.reset(reset_agent_id_ctx_token)
413
- paid_store_prompt_var.reset(reset_store_prompt_ctx_token)
414
- paid_user_metadata_var.reset(reset_user_metadata_ctx_token)
390
+ ContextData.reset_context()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: paid-python
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary:
5
5
  Requires-Python: >=3.9,<3.14
6
6
  Classifier: Intended Audience :: Developers
@@ -37,11 +37,12 @@ paid/orders/lines/raw_client.py,sha256=KZN_yBokCOkf1lUb4ZJtX_NZbqmTqCdJNoaIOdWar
37
37
  paid/orders/raw_client.py,sha256=650e1Sj2vi9KVJc15M3ENXIKYoth0qMz66dzvXy1Sb4,16245
38
38
  paid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  paid/tracing/__init__.py,sha256=Pe55koIwqJ6Vv5-9Wqi8xIdwCS2BbxZds-MK5fD-F5Y,506
40
- paid/tracing/autoinstrumentation.py,sha256=qlLLiP9cWNIXX226dLDLvvlioLCPO-0F81mU_0fBb9s,7604
41
- paid/tracing/context_manager.py,sha256=Qtl59mjDsacX53LPFFAVXsfeGCEA6CIfxPsvjO-Kmx8,9729
42
- paid/tracing/distributed_tracing.py,sha256=CpUWpHai-4LxLLHbGxz41r9h5wLG-dC83YL5Vg29OBI,3967
40
+ paid/tracing/autoinstrumentation.py,sha256=NUVvzYwbrI9rDs0sWEgOk0jYYLQPtjo3Cfk_OsZX6-A,7643
41
+ paid/tracing/context_data.py,sha256=UFCZxX6zGa9w5lGLQK5tXdMM69aV52nReUkxwfN9S6A,2378
42
+ paid/tracing/context_manager.py,sha256=ZQtsJ9JPxTwn2t4AW26WpYboaOEZdI2T1Sw0Rwsbf-E,8470
43
+ paid/tracing/distributed_tracing.py,sha256=XCUGFBB2lksrR19Mry10cTZRFSCSztUbUjE_sKCwjX8,3995
43
44
  paid/tracing/signal.py,sha256=PfYxF6EFQS8j7RY5_C5NXrCBVu9Hq2E2tyG4fdQScJk,3252
44
- paid/tracing/tracing.py,sha256=w07I7KQ4O8yJdvLs9RDAQVVofVWeR4xNei3zC5-EE6c,16080
45
+ paid/tracing/tracing.py,sha256=CxumDRwQHJ5S4FnzyTfUi_s557Ka7LDxVYSId0jStSc,14783
45
46
  paid/tracing/wrappers/__init__.py,sha256=IIleLB_JUbzLw7FshrU2VHZAKF3dZHMGy1O5zCBwwqM,1588
46
47
  paid/tracing/wrappers/anthropic/__init__.py,sha256=_x1fjySAQxuT5cIGO_jU09LiGcZH-WQLqKg8mUFAu2w,115
47
48
  paid/tracing/wrappers/anthropic/anthropicWrapper.py,sha256=pGchbOb41CbTxc7H8xXoM-LjR085spqrzXqCVC_rrFk,4913
@@ -98,7 +99,7 @@ paid/usage/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUzqhXJa4,85
98
99
  paid/usage/client.py,sha256=280WJuepoovk3BAVbAx2yN2Q_qBdvx3CcPkLu8lXslc,3030
99
100
  paid/usage/raw_client.py,sha256=2acg5C4lxuZodZjepU9QYF0fmBxgG-3ZgXs1zUJG-wM,3709
100
101
  paid/version.py,sha256=QIpDFnOrxMxrs86eL0iNH0mSZ1DO078wWHYY9TYAoew,78
101
- paid_python-0.2.0.dist-info/LICENSE,sha256=Nz4baY1zvv0Qy7lqrQtbaiMhmEeGr2Q7A93aqzpml4c,1071
102
- paid_python-0.2.0.dist-info/METADATA,sha256=tFvHkGpKqlXPnxZhDhxFm-vETz6RbVihbJZOEdjGPUw,22343
103
- paid_python-0.2.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
104
- paid_python-0.2.0.dist-info/RECORD,,
102
+ paid_python-0.2.1.dist-info/LICENSE,sha256=Nz4baY1zvv0Qy7lqrQtbaiMhmEeGr2Q7A93aqzpml4c,1071
103
+ paid_python-0.2.1.dist-info/METADATA,sha256=kmG2tSGACEXkk1jBxsOu8DePxUxfu4JKq6aXGe_worE,22343
104
+ paid_python-0.2.1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
105
+ paid_python-0.2.1.dist-info/RECORD,,