paid-python 1.0.0a4__tar.gz → 1.0.2__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.
- {paid_python-1.0.0a4 → paid_python-1.0.2}/PKG-INFO +67 -73
- {paid_python-1.0.0a4 → paid_python-1.0.2}/README.md +68 -73
- {paid_python-1.0.0a4 → paid_python-1.0.2}/pyproject.toml +2 -3
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/client_wrapper.py +2 -2
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/logger.py +0 -3
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/autoinstrumentation.py +21 -1
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/context_data.py +3 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/context_manager.py +19 -2
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/tracing.py +88 -12
- {paid_python-1.0.0a4 → paid_python-1.0.2}/LICENSE +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/contacts/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/contacts/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/contacts/raw_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/api_error.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/datetime_utils.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/file.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/force_multipart.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/http_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/http_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/jsonable_encoder.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/pydantic_utilities.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/query_encoder.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/remove_none_from_dict.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/request_options.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/core/serialization.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/customers/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/customers/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/customers/raw_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/environment.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/errors/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/errors/bad_request_error.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/errors/forbidden_error.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/errors/internal_server_error.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/errors/not_found_error.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/invoices/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/invoices/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/invoices/raw_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/orders/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/orders/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/orders/raw_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/products/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/products/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/products/raw_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/py.typed +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/signals/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/signals/client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/signals/raw_client.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/distributed_tracing.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/signal.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/anthropic/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/anthropic/anthropicWrapper.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/bedrock/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/bedrock/bedrockWrapper.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/gemini/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/gemini/geminiWrapper.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/langchain/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/langchain/paidLangChainCallback.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/mistral/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/mistral/mistralWrapper.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/openai/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/openai/openAiWrapper.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/openai_agents/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/openai_agents/openaiAgentsHook.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/utils.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/__init__.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/attribution.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/bulk_signals_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/contact.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/contact_billing_address.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/contact_list_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer_attribution.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer_billing_address.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer_by_external_id.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer_by_id.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer_creation_state.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/customer_list_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/empty_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/error_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_line.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_line_payment_status.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_lines_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_list_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_payment_status.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_source.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_status.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/invoice_tax_status.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/order.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/order_creation_state.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/order_line.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/order_lines_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/order_list_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/pagination.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/product.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/product_by_external_id.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/product_by_id.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/product_list_response.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/signal.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/update_contact_request.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/update_customer_request.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/types/update_product_request.py +0 -0
- {paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: paid-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary:
|
|
5
5
|
Requires-Python: >=3.9,<3.14
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -20,6 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Dist: httpx (>=0.21.2)
|
|
22
22
|
Requires-Dist: mutagen (>=1.47.0)
|
|
23
|
+
Requires-Dist: openinference-instrumentation-anthropic (>=0.1.20)
|
|
23
24
|
Requires-Dist: openinference-instrumentation-bedrock (>=0.1.0)
|
|
24
25
|
Requires-Dist: openinference-instrumentation-google-genai (>=0.1.8)
|
|
25
26
|
Requires-Dist: openinference-instrumentation-instructor (>=0.1.0)
|
|
@@ -28,11 +29,9 @@ Requires-Dist: openinference-instrumentation-openai (>=0.1.40)
|
|
|
28
29
|
Requires-Dist: openinference-instrumentation-openai-agents (>=1.0.0)
|
|
29
30
|
Requires-Dist: opentelemetry-api (>=1.23.0)
|
|
30
31
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.23.0)
|
|
31
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.47.0)
|
|
32
32
|
Requires-Dist: opentelemetry-sdk (>=1.23.0)
|
|
33
33
|
Requires-Dist: pydantic (>=1.9.2)
|
|
34
34
|
Requires-Dist: pydantic-core (>=2.18.2,<3.0.0)
|
|
35
|
-
Requires-Dist: python-dotenv (>=0.15.0)
|
|
36
35
|
Requires-Dist: typing_extensions (>=4.0.0)
|
|
37
36
|
Description-Content-Type: text/markdown
|
|
38
37
|
|
|
@@ -281,83 +280,78 @@ image_generate()
|
|
|
281
280
|
|
|
282
281
|
You can attach custom metadata to your traces by passing a `metadata` dictionary to the `paid_tracing()` decorator or context manager. This metadata will be stored with the trace and can be used to filter and query traces later.
|
|
283
282
|
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
```python
|
|
287
|
-
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
288
|
-
from paid.tracing.wrappers import PaidOpenAI
|
|
289
|
-
from openai import OpenAI
|
|
283
|
+
<details>
|
|
284
|
+
<summary><strong>Python - Decorator</strong></summary>
|
|
290
285
|
|
|
291
|
-
|
|
286
|
+
```python
|
|
287
|
+
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
288
|
+
from paid.tracing.wrappers import PaidOpenAI
|
|
289
|
+
from openai import OpenAI
|
|
292
290
|
|
|
293
|
-
|
|
291
|
+
initialize_tracing()
|
|
294
292
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
293
|
+
openai_client = PaidOpenAI(OpenAI(api_key="<OPENAI_API_KEY>"))
|
|
294
|
+
|
|
295
|
+
@paid_tracing(
|
|
296
|
+
"customer_123",
|
|
297
|
+
external_product_id="product_123",
|
|
298
|
+
metadata={
|
|
299
|
+
"campaign_id": "campaign_456",
|
|
300
|
+
"environment": "production",
|
|
301
|
+
"user_tier": "enterprise"
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
def process_event(event):
|
|
305
|
+
"""Process event with custom metadata"""
|
|
306
|
+
response = openai_client.chat.completions.create(
|
|
307
|
+
model="gpt-4",
|
|
308
|
+
messages=[{"role": "user", "content": event.content}]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
signal("event_processed", enable_cost_tracing=True)
|
|
312
|
+
return response
|
|
313
|
+
|
|
314
|
+
process_event(incoming_event)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
</details>
|
|
318
|
+
|
|
319
|
+
<details>
|
|
320
|
+
<summary><strong>Python - Context Manager</strong></summary>
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
324
|
+
from paid.tracing.wrappers import PaidOpenAI
|
|
325
|
+
from openai import OpenAI
|
|
326
|
+
|
|
327
|
+
initialize_tracing()
|
|
328
|
+
|
|
329
|
+
openai_client = PaidOpenAI(OpenAI(api_key="<OPENAI_API_KEY>"))
|
|
330
|
+
|
|
331
|
+
def process_event(event):
|
|
332
|
+
"""Process event with custom metadata"""
|
|
333
|
+
response = openai_client.chat.completions.create(
|
|
334
|
+
model="gpt-4",
|
|
335
|
+
messages=[{"role": "user", "content": event.content}]
|
|
303
336
|
)
|
|
304
|
-
def process_event(event):
|
|
305
|
-
"""Process event with custom metadata"""
|
|
306
|
-
response = openai_client.chat.completions.create(
|
|
307
|
-
model="gpt-4",
|
|
308
|
-
messages=[{"role": "user", "content": event.content}]
|
|
309
|
-
)
|
|
310
337
|
|
|
311
|
-
|
|
312
|
-
|
|
338
|
+
signal("event_processed", enable_cost_tracing=True)
|
|
339
|
+
return response
|
|
313
340
|
|
|
341
|
+
# Pass metadata to context manager
|
|
342
|
+
with paid_tracing(
|
|
343
|
+
"customer_123",
|
|
344
|
+
external_product_id="product_123",
|
|
345
|
+
metadata={
|
|
346
|
+
"campaign_id": "campaign_456",
|
|
347
|
+
"environment": "production",
|
|
348
|
+
"user_tier": "enterprise"
|
|
349
|
+
}
|
|
350
|
+
):
|
|
314
351
|
process_event(incoming_event)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<Tab title="Python - Context Manager">
|
|
320
|
-
```python
|
|
321
|
-
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
322
|
-
from paid.tracing.wrappers import PaidOpenAI
|
|
323
|
-
from openai import OpenAI
|
|
324
|
-
|
|
325
|
-
initialize_tracing()
|
|
326
|
-
|
|
327
|
-
openai_client = PaidOpenAI(OpenAI(api_key="<OPENAI_API_KEY>"))
|
|
328
|
-
|
|
329
|
-
def process_event(event):
|
|
330
|
-
"""Process event with custom metadata"""
|
|
331
|
-
response = openai_client.chat.completions.create(
|
|
332
|
-
model="gpt-4",
|
|
333
|
-
messages=[{"role": "user", "content": event.content}]
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
signal("event_processed", enable_cost_tracing=True)
|
|
337
|
-
return response
|
|
338
|
-
|
|
339
|
-
# Pass metadata to context manager
|
|
340
|
-
with paid_tracing(
|
|
341
|
-
"customer_123",
|
|
342
|
-
external_product_id="product_123",
|
|
343
|
-
metadata={
|
|
344
|
-
"campaign_id": "campaign_456",
|
|
345
|
-
"environment": "production",
|
|
346
|
-
"user_tier": "enterprise"
|
|
347
|
-
}
|
|
348
|
-
):
|
|
349
|
-
process_event(incoming_event)
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
</Tab>
|
|
353
|
-
|
|
354
|
-
<Tab title="Node.js">
|
|
355
|
-
```typescript
|
|
356
|
-
// Metadata support is not yet available in the Node.js SDK.
|
|
357
|
-
// Please use Python for passing custom metadata to traces.
|
|
358
|
-
```
|
|
359
|
-
</Tab>
|
|
360
|
-
</Tabs>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
</details>
|
|
361
355
|
|
|
362
356
|
#### Querying Traces by Metadata
|
|
363
357
|
|
|
@@ -243,83 +243,78 @@ image_generate()
|
|
|
243
243
|
|
|
244
244
|
You can attach custom metadata to your traces by passing a `metadata` dictionary to the `paid_tracing()` decorator or context manager. This metadata will be stored with the trace and can be used to filter and query traces later.
|
|
245
245
|
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
246
|
+
<details>
|
|
247
|
+
<summary><strong>Python - Decorator</strong></summary>
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
251
|
+
from paid.tracing.wrappers import PaidOpenAI
|
|
252
|
+
from openai import OpenAI
|
|
253
|
+
|
|
254
|
+
initialize_tracing()
|
|
255
|
+
|
|
256
|
+
openai_client = PaidOpenAI(OpenAI(api_key="<OPENAI_API_KEY>"))
|
|
257
|
+
|
|
258
|
+
@paid_tracing(
|
|
259
|
+
"customer_123",
|
|
260
|
+
external_product_id="product_123",
|
|
261
|
+
metadata={
|
|
262
|
+
"campaign_id": "campaign_456",
|
|
263
|
+
"environment": "production",
|
|
264
|
+
"user_tier": "enterprise"
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
def process_event(event):
|
|
268
|
+
"""Process event with custom metadata"""
|
|
269
|
+
response = openai_client.chat.completions.create(
|
|
270
|
+
model="gpt-4",
|
|
271
|
+
messages=[{"role": "user", "content": event.content}]
|
|
265
272
|
)
|
|
266
|
-
def process_event(event):
|
|
267
|
-
"""Process event with custom metadata"""
|
|
268
|
-
response = openai_client.chat.completions.create(
|
|
269
|
-
model="gpt-4",
|
|
270
|
-
messages=[{"role": "user", "content": event.content}]
|
|
271
|
-
)
|
|
272
273
|
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
signal("event_processed", enable_cost_tracing=True)
|
|
275
|
+
return response
|
|
276
|
+
|
|
277
|
+
process_event(incoming_event)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
</details>
|
|
281
|
+
|
|
282
|
+
<details>
|
|
283
|
+
<summary><strong>Python - Context Manager</strong></summary>
|
|
275
284
|
|
|
285
|
+
```python
|
|
286
|
+
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
287
|
+
from paid.tracing.wrappers import PaidOpenAI
|
|
288
|
+
from openai import OpenAI
|
|
289
|
+
|
|
290
|
+
initialize_tracing()
|
|
291
|
+
|
|
292
|
+
openai_client = PaidOpenAI(OpenAI(api_key="<OPENAI_API_KEY>"))
|
|
293
|
+
|
|
294
|
+
def process_event(event):
|
|
295
|
+
"""Process event with custom metadata"""
|
|
296
|
+
response = openai_client.chat.completions.create(
|
|
297
|
+
model="gpt-4",
|
|
298
|
+
messages=[{"role": "user", "content": event.content}]
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
signal("event_processed", enable_cost_tracing=True)
|
|
302
|
+
return response
|
|
303
|
+
|
|
304
|
+
# Pass metadata to context manager
|
|
305
|
+
with paid_tracing(
|
|
306
|
+
"customer_123",
|
|
307
|
+
external_product_id="product_123",
|
|
308
|
+
metadata={
|
|
309
|
+
"campaign_id": "campaign_456",
|
|
310
|
+
"environment": "production",
|
|
311
|
+
"user_tier": "enterprise"
|
|
312
|
+
}
|
|
313
|
+
):
|
|
276
314
|
process_event(incoming_event)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
<Tab title="Python - Context Manager">
|
|
282
|
-
```python
|
|
283
|
-
from paid.tracing import paid_tracing, signal, initialize_tracing
|
|
284
|
-
from paid.tracing.wrappers import PaidOpenAI
|
|
285
|
-
from openai import OpenAI
|
|
286
|
-
|
|
287
|
-
initialize_tracing()
|
|
288
|
-
|
|
289
|
-
openai_client = PaidOpenAI(OpenAI(api_key="<OPENAI_API_KEY>"))
|
|
290
|
-
|
|
291
|
-
def process_event(event):
|
|
292
|
-
"""Process event with custom metadata"""
|
|
293
|
-
response = openai_client.chat.completions.create(
|
|
294
|
-
model="gpt-4",
|
|
295
|
-
messages=[{"role": "user", "content": event.content}]
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
signal("event_processed", enable_cost_tracing=True)
|
|
299
|
-
return response
|
|
300
|
-
|
|
301
|
-
# Pass metadata to context manager
|
|
302
|
-
with paid_tracing(
|
|
303
|
-
"customer_123",
|
|
304
|
-
external_product_id="product_123",
|
|
305
|
-
metadata={
|
|
306
|
-
"campaign_id": "campaign_456",
|
|
307
|
-
"environment": "production",
|
|
308
|
-
"user_tier": "enterprise"
|
|
309
|
-
}
|
|
310
|
-
):
|
|
311
|
-
process_event(incoming_event)
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
</Tab>
|
|
315
|
-
|
|
316
|
-
<Tab title="Node.js">
|
|
317
|
-
```typescript
|
|
318
|
-
// Metadata support is not yet available in the Node.js SDK.
|
|
319
|
-
// Please use Python for passing custom metadata to traces.
|
|
320
|
-
```
|
|
321
|
-
</Tab>
|
|
322
|
-
</Tabs>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
</details>
|
|
323
318
|
|
|
324
319
|
#### Querying Traces by Metadata
|
|
325
320
|
|
|
@@ -3,7 +3,7 @@ name = "paid-python"
|
|
|
3
3
|
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "paid-python"
|
|
6
|
-
version = "1.0.
|
|
6
|
+
version = "1.0.2"
|
|
7
7
|
description = ""
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = []
|
|
@@ -37,6 +37,7 @@ Repository = 'https://github.com/paid-ai/paid-python'
|
|
|
37
37
|
python = ">=3.9,<3.14"
|
|
38
38
|
httpx = ">=0.21.2"
|
|
39
39
|
mutagen = ">=1.47.0"
|
|
40
|
+
openinference-instrumentation-anthropic = ">=0.1.20"
|
|
40
41
|
openinference-instrumentation-bedrock = ">=0.1.0"
|
|
41
42
|
openinference-instrumentation-google-genai = ">=0.1.8"
|
|
42
43
|
openinference-instrumentation-instructor = ">=0.1.0"
|
|
@@ -45,11 +46,9 @@ openinference-instrumentation-openai = ">=0.1.40"
|
|
|
45
46
|
openinference-instrumentation-openai-agents = ">=1.0.0"
|
|
46
47
|
opentelemetry-api = ">=1.23.0"
|
|
47
48
|
opentelemetry-exporter-otlp-proto-http = ">=1.23.0"
|
|
48
|
-
opentelemetry-instrumentation-anthropic = ">=0.47.0"
|
|
49
49
|
opentelemetry-sdk = ">=1.23.0"
|
|
50
50
|
pydantic = ">= 1.9.2"
|
|
51
51
|
pydantic-core = "^2.18.2"
|
|
52
|
-
python-dotenv = ">=0.15.0"
|
|
53
52
|
typing_extensions = ">= 4.0.0"
|
|
54
53
|
|
|
55
54
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -20,10 +20,10 @@ class BaseClientWrapper:
|
|
|
20
20
|
|
|
21
21
|
def get_headers(self) -> typing.Dict[str, str]:
|
|
22
22
|
headers: typing.Dict[str, str] = {
|
|
23
|
-
"User-Agent": "paid-python/1.0.
|
|
23
|
+
"User-Agent": "paid-python/1.0.2",
|
|
24
24
|
"X-Fern-Language": "Python",
|
|
25
25
|
"X-Fern-SDK-Name": "paid-python",
|
|
26
|
-
"X-Fern-SDK-Version": "1.0.
|
|
26
|
+
"X-Fern-SDK-Version": "1.0.2",
|
|
27
27
|
}
|
|
28
28
|
headers["Authorization"] = f"Bearer {self._get_token()}"
|
|
29
29
|
return headers
|
|
@@ -5,10 +5,7 @@ This file exports `logger` object for unified logging across the Paid SDK.
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
-
import dotenv
|
|
9
|
-
|
|
10
8
|
# Configure logging
|
|
11
|
-
_ = dotenv.load_dotenv()
|
|
12
9
|
logger = logging.getLogger(__name__)
|
|
13
10
|
|
|
14
11
|
# Set default log level to ERROR, allow override via PAID_LOG_LEVEL environment variable
|
|
@@ -15,7 +15,7 @@ from paid.logger import logger
|
|
|
15
15
|
|
|
16
16
|
# Safe imports for instrumentation libraries
|
|
17
17
|
try:
|
|
18
|
-
from
|
|
18
|
+
from openinference.instrumentation.anthropic import AnthropicInstrumentor
|
|
19
19
|
|
|
20
20
|
ANTHROPIC_AVAILABLE = True
|
|
21
21
|
except Exception:
|
|
@@ -118,9 +118,15 @@ def paid_autoinstrument(libraries: Optional[List[str]] = None) -> None:
|
|
|
118
118
|
logger.info("Tracing not initialized, initializing automatically")
|
|
119
119
|
initialize_tracing()
|
|
120
120
|
|
|
121
|
+
logger.debug(
|
|
122
|
+
"[paid:autoinstrument] tracer_provider type=%s, requested libraries=%s",
|
|
123
|
+
type(tracing.paid_tracer_provider).__name__, libraries,
|
|
124
|
+
)
|
|
125
|
+
|
|
121
126
|
# Default to all supported libraries if none specified
|
|
122
127
|
if libraries is None:
|
|
123
128
|
libraries = ["anthropic", "openai", "openai-agents", "bedrock", "langchain", "google-genai", "instructor"]
|
|
129
|
+
logger.debug("[paid:autoinstrument] No libraries specified, defaulting to all: %s", libraries)
|
|
124
130
|
|
|
125
131
|
for library in libraries:
|
|
126
132
|
if library in _initialized_instrumentors:
|
|
@@ -157,6 +163,8 @@ def _instrument_anthropic() -> None:
|
|
|
157
163
|
logger.warning("Anthropic library not available, skipping instrumentation")
|
|
158
164
|
return
|
|
159
165
|
|
|
166
|
+
logger.debug("[paid:autoinstrument] Instrumenting anthropic with AnthropicInstrumentor, provider=%s",
|
|
167
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
160
168
|
# Instrument Anthropic with Paid's tracer provider
|
|
161
169
|
AnthropicInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
162
170
|
|
|
@@ -172,6 +180,8 @@ def _instrument_openai() -> None:
|
|
|
172
180
|
logger.warning("OpenAI library not available, skipping instrumentation")
|
|
173
181
|
return
|
|
174
182
|
|
|
183
|
+
logger.debug("[paid:autoinstrument] Instrumenting openai with OpenAIInstrumentor, provider=%s",
|
|
184
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
175
185
|
# Instrument OpenAI with Paid's tracer provider
|
|
176
186
|
OpenAIInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
177
187
|
|
|
@@ -187,6 +197,8 @@ def _instrument_openai_agents() -> None:
|
|
|
187
197
|
logger.warning("OpenAI Agents library not available, skipping instrumentation")
|
|
188
198
|
return
|
|
189
199
|
|
|
200
|
+
logger.debug("[paid:autoinstrument] Instrumenting openai-agents with OpenAIAgentsInstrumentor, provider=%s",
|
|
201
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
190
202
|
# Instrument OpenAI Agents with Paid's tracer provider
|
|
191
203
|
OpenAIAgentsInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
192
204
|
|
|
@@ -202,6 +214,8 @@ def _instrument_bedrock() -> None:
|
|
|
202
214
|
logger.warning("Bedrock instrumentation library not available, skipping instrumentation")
|
|
203
215
|
return
|
|
204
216
|
|
|
217
|
+
logger.debug("[paid:autoinstrument] Instrumenting bedrock with BedrockInstrumentor, provider=%s",
|
|
218
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
205
219
|
# Instrument Bedrock with Paid's tracer provider
|
|
206
220
|
BedrockInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
207
221
|
|
|
@@ -217,6 +231,8 @@ def _instrument_langchain() -> None:
|
|
|
217
231
|
logger.warning("LangChain instrumentation library not available, skipping instrumentation")
|
|
218
232
|
return
|
|
219
233
|
|
|
234
|
+
logger.debug("[paid:autoinstrument] Instrumenting langchain with LangChainInstrumentor, provider=%s",
|
|
235
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
220
236
|
# Instrument LangChain with Paid's tracer provider
|
|
221
237
|
LangChainInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
222
238
|
|
|
@@ -232,6 +248,8 @@ def _instrument_google_genai() -> None:
|
|
|
232
248
|
logger.warning("Google GenAI instrumentation library not available, skipping instrumentation")
|
|
233
249
|
return
|
|
234
250
|
|
|
251
|
+
logger.debug("[paid:autoinstrument] Instrumenting google-genai with GoogleGenAIInstrumentor, provider=%s",
|
|
252
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
235
253
|
GoogleGenAIInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
236
254
|
_initialized_instrumentors.append("google-genai")
|
|
237
255
|
logger.info("Google GenAI auto-instrumentation enabled")
|
|
@@ -245,6 +263,8 @@ def _instrument_instructor() -> None:
|
|
|
245
263
|
logger.warning("Instructor library not available, skipping instrumentation")
|
|
246
264
|
return
|
|
247
265
|
|
|
266
|
+
logger.debug("[paid:autoinstrument] Instrumenting instructor with InstructorInstrumentor, provider=%s",
|
|
267
|
+
type(tracing.paid_tracer_provider).__name__)
|
|
248
268
|
InstructorInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
249
269
|
_initialized_instrumentors.append("instructor")
|
|
250
270
|
logger.info("Instructor auto-instrumentation enabled")
|
|
@@ -52,6 +52,7 @@ class ContextData:
|
|
|
52
52
|
reset_token = cls._context[key].set(value)
|
|
53
53
|
reset_tokens = cls._get_or_create_reset_tokens()
|
|
54
54
|
reset_tokens[key] = reset_token
|
|
55
|
+
logger.debug("[paid:ctx] set_context_key: %s (type=%s)", key, type(value).__name__)
|
|
55
56
|
|
|
56
57
|
@classmethod
|
|
57
58
|
def unset_context_key(cls, key: str) -> None:
|
|
@@ -59,6 +60,7 @@ class ContextData:
|
|
|
59
60
|
if key not in cls._context:
|
|
60
61
|
logger.warning(f"Invalid context key: {key}")
|
|
61
62
|
return
|
|
63
|
+
logger.debug("[paid:ctx] unset_context_key: %s", key)
|
|
62
64
|
reset_tokens = cls._reset_tokens.get()
|
|
63
65
|
if reset_tokens:
|
|
64
66
|
_ = cls._context[key].set(None)
|
|
@@ -69,6 +71,7 @@ class ContextData:
|
|
|
69
71
|
def reset_context(cls) -> None:
|
|
70
72
|
reset_tokens = cls._reset_tokens.get()
|
|
71
73
|
if reset_tokens:
|
|
74
|
+
logger.debug("[paid:ctx] reset_context: resetting keys=%s", list(reset_tokens.keys()))
|
|
72
75
|
for key, reset_token in reset_tokens.items():
|
|
73
76
|
cls._context[key].reset(reset_token)
|
|
74
77
|
reset_tokens.clear()
|
|
@@ -80,7 +80,14 @@ class paid_tracing:
|
|
|
80
80
|
self.span: Optional[Span] = None
|
|
81
81
|
self.span_ctx: Optional[Any] = None # Context manager for the span
|
|
82
82
|
|
|
83
|
+
logger.debug(
|
|
84
|
+
"[paid:ctx] paid_tracing.__init__: customer_id=%s, product_id=%s, tracing_token=%s, store_prompt=%s",
|
|
85
|
+
external_customer_id, external_product_id,
|
|
86
|
+
"present" if tracing_token else "none", store_prompt,
|
|
87
|
+
)
|
|
88
|
+
|
|
83
89
|
if not get_token():
|
|
90
|
+
logger.debug("[paid:ctx] paid_tracing.__init__: token not set, auto-initializing tracing")
|
|
84
91
|
initialize_tracing(None, self.collector_endpoint)
|
|
85
92
|
|
|
86
93
|
def _setup_context(self) -> Optional[Context]:
|
|
@@ -106,6 +113,12 @@ class paid_tracing:
|
|
|
106
113
|
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
107
114
|
)
|
|
108
115
|
ctx = trace.set_span_in_context(NonRecordingSpan(span_context))
|
|
116
|
+
token_source = "explicit" if self.tracing_token else "context-inherited"
|
|
117
|
+
logger.debug("[paid:distributed] _setup_context: trace_id=%s (source=%s)",
|
|
118
|
+
format(override_trace_id, '032x') if isinstance(override_trace_id, int) else str(override_trace_id),
|
|
119
|
+
token_source)
|
|
120
|
+
else:
|
|
121
|
+
logger.debug("[paid:distributed] _setup_context: no override trace_id, using auto-generated")
|
|
109
122
|
|
|
110
123
|
return ctx
|
|
111
124
|
|
|
@@ -130,9 +143,11 @@ class paid_tracing:
|
|
|
130
143
|
def _enter_ctx(self):
|
|
131
144
|
ctx = self._setup_context()
|
|
132
145
|
tracer = get_paid_tracer()
|
|
133
|
-
logger.
|
|
146
|
+
logger.debug("[paid:ctx] _enter_ctx: creating parent_span for customer_id=%s", self.external_customer_id)
|
|
134
147
|
self.span_ctx = tracer.start_as_current_span("parent_span", context=ctx)
|
|
135
148
|
self.span = self.span_ctx.__enter__()
|
|
149
|
+
logger.debug("[paid:ctx] _enter_ctx: span created, trace_id=%s",
|
|
150
|
+
format(self.span.get_span_context().trace_id, '032x') if isinstance(self.span.get_span_context().trace_id, int) else str(self.span.get_span_context().trace_id))
|
|
136
151
|
return self
|
|
137
152
|
|
|
138
153
|
def _exit_ctx(self, exc_type, exc_val, exc_tb):
|
|
@@ -141,9 +156,10 @@ class paid_tracing:
|
|
|
141
156
|
if self.span and self.span_ctx:
|
|
142
157
|
if exc_type is not None:
|
|
143
158
|
self.span.set_status(Status(StatusCode.ERROR, str(exc_val)))
|
|
159
|
+
logger.debug("[paid:ctx] _exit_ctx: span ended with ERROR: %s", exc_val)
|
|
144
160
|
else:
|
|
145
161
|
self.span.set_status(Status(StatusCode.OK))
|
|
146
|
-
logger.
|
|
162
|
+
logger.debug("[paid:ctx] _exit_ctx: span ended with OK")
|
|
147
163
|
|
|
148
164
|
self.span_ctx.__exit__(exc_type, exc_val, exc_tb)
|
|
149
165
|
self.span_ctx = None
|
|
@@ -157,6 +173,7 @@ class paid_tracing:
|
|
|
157
173
|
# Decorator functionality
|
|
158
174
|
def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
|
|
159
175
|
"""Use as a decorator."""
|
|
176
|
+
logger.debug("[paid:ctx] __call__: wrapping fn=%s (async=%s)", getattr(func, '__name__', repr(func)), asyncio.iscoroutinefunction(func))
|
|
160
177
|
if asyncio.iscoroutinefunction(func):
|
|
161
178
|
|
|
162
179
|
@functools.wraps(func)
|
|
@@ -6,21 +6,20 @@ import signal
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, TypeVar, Union
|
|
8
8
|
|
|
9
|
-
import dotenv
|
|
10
9
|
from . import distributed_tracing
|
|
11
10
|
from .context_data import ContextData
|
|
12
11
|
from opentelemetry import trace
|
|
13
12
|
from opentelemetry.context import Context
|
|
14
13
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
15
14
|
from opentelemetry.sdk.resources import Resource
|
|
16
|
-
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor, TracerProvider
|
|
15
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanLimits, SpanProcessor, TracerProvider
|
|
17
16
|
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
17
|
+
from opentelemetry.sdk.trace.sampling import ALWAYS_ON
|
|
18
18
|
from opentelemetry.trace import NonRecordingSpan, NoOpTracerProvider, SpanContext, Status, StatusCode, TraceFlags
|
|
19
19
|
from opentelemetry.util.types import Attributes
|
|
20
20
|
|
|
21
21
|
from paid.logger import logger
|
|
22
22
|
|
|
23
|
-
_ = dotenv.load_dotenv()
|
|
24
23
|
DEFAULT_COLLECTOR_ENDPOINT = (
|
|
25
24
|
os.environ.get("PAID_OTEL_COLLECTOR_ENDPOINT") or "https://collector.agentpaid.io:4318/v1/traces"
|
|
26
25
|
)
|
|
@@ -208,7 +207,7 @@ class PaidSpanProcessor(SpanProcessor):
|
|
|
208
207
|
# Langchain instrumentation creates spans, that are created by other instrumentations (ex. OpenAI, Anthropic).
|
|
209
208
|
# Not all spans need filtering (ex. ChatGoogleGenerativeAI), so first test actual telemetry before adding filters.
|
|
210
209
|
# TODO: maybe consider a dropping sampler for such spans instead of raising an exception?
|
|
211
|
-
logger.debug(
|
|
210
|
+
logger.debug("[paid:span] Dropping duplicate LangChain span: %s", span.name)
|
|
212
211
|
raise Exception(f"Dropping Langchain span: {span.name}")
|
|
213
212
|
|
|
214
213
|
# Prefix the span name
|
|
@@ -224,10 +223,29 @@ class PaidSpanProcessor(SpanProcessor):
|
|
|
224
223
|
if agent_id:
|
|
225
224
|
span.set_attribute("external_agent_id", agent_id)
|
|
226
225
|
|
|
226
|
+
logger.debug(
|
|
227
|
+
"[paid:span] on_start: name=%s, customer_id=%s, agent_id=%s",
|
|
228
|
+
span.name, customer_id, agent_id,
|
|
229
|
+
)
|
|
230
|
+
|
|
227
231
|
metadata = ContextData.get_context_key("user_metadata")
|
|
228
232
|
if metadata:
|
|
229
233
|
metadata_attributes: dict[str, Any] = {}
|
|
230
234
|
|
|
235
|
+
# OTEL attributes only accept: bool, str, bytes, int, float
|
|
236
|
+
_OTEL_SAFE_TYPES = (bool, str, bytes, int, float)
|
|
237
|
+
|
|
238
|
+
def _sanitize_value(v: Any) -> Any:
|
|
239
|
+
"""Convert non-OTEL-safe values to str."""
|
|
240
|
+
if isinstance(v, _OTEL_SAFE_TYPES):
|
|
241
|
+
return v
|
|
242
|
+
if isinstance(v, (list, tuple)):
|
|
243
|
+
return [
|
|
244
|
+
el if isinstance(el, _OTEL_SAFE_TYPES) else str(el)
|
|
245
|
+
for el in v
|
|
246
|
+
]
|
|
247
|
+
return str(v)
|
|
248
|
+
|
|
231
249
|
def flatten_dict(d: dict[str, Any], parent_key: str = "") -> None:
|
|
232
250
|
"""Recursively flatten nested dictionaries into dot-notation keys."""
|
|
233
251
|
for k, v in d.items():
|
|
@@ -235,13 +253,14 @@ class PaidSpanProcessor(SpanProcessor):
|
|
|
235
253
|
if isinstance(v, dict):
|
|
236
254
|
flatten_dict(v, new_key)
|
|
237
255
|
else:
|
|
238
|
-
metadata_attributes[new_key] = v
|
|
256
|
+
metadata_attributes[new_key] = _sanitize_value(v)
|
|
239
257
|
|
|
240
258
|
flatten_dict(metadata)
|
|
241
259
|
|
|
242
260
|
# Add all flattened metadata attributes to the span
|
|
243
261
|
for key, value in metadata_attributes.items():
|
|
244
262
|
span.set_attribute(f"metadata.{key}", value)
|
|
263
|
+
logger.debug("[paid:span] on_start: attached metadata keys=%s", list(metadata_attributes.keys()))
|
|
245
264
|
|
|
246
265
|
def on_end(self, span: ReadableSpan) -> None:
|
|
247
266
|
if span.name and not span.name.startswith(self.SPAN_NAME_PREFIX):
|
|
@@ -251,6 +270,7 @@ class PaidSpanProcessor(SpanProcessor):
|
|
|
251
270
|
"""Filter out prompt and response contents unless explicitly asked to store"""
|
|
252
271
|
store_prompt = ContextData.get_context_key("store_prompt")
|
|
253
272
|
if store_prompt:
|
|
273
|
+
logger.debug("[paid:span] on_end: name=%s, store_prompt=True, keeping all attributes", span.name)
|
|
254
274
|
return
|
|
255
275
|
|
|
256
276
|
original_attributes = span.attributes
|
|
@@ -262,6 +282,12 @@ class PaidSpanProcessor(SpanProcessor):
|
|
|
262
282
|
for k, v in original_attributes.items()
|
|
263
283
|
if not any(substr in k for substr in self.PROMPT_ATTRIBUTES_SUBSTRINGS)
|
|
264
284
|
}
|
|
285
|
+
filtered_count = len(original_attributes) - len(filtered_attrs)
|
|
286
|
+
if filtered_count > 0:
|
|
287
|
+
logger.debug(
|
|
288
|
+
"[paid:span] on_end: name=%s, filtered %d prompt attribute(s)",
|
|
289
|
+
span.name, filtered_count,
|
|
290
|
+
)
|
|
265
291
|
# This works because the exporter reads attributes during serialization
|
|
266
292
|
object.__setattr__(span, "_attributes", filtered_attrs)
|
|
267
293
|
|
|
@@ -303,6 +329,7 @@ class PydanticSpanProcessor(SpanProcessor):
|
|
|
303
329
|
settings = _PydanticSettingsRegistry.get(scope_name)
|
|
304
330
|
if settings is None or settings.track_usage:
|
|
305
331
|
# No filtering needed - keep usage/cost data
|
|
332
|
+
logger.debug("[paid:span] PydanticSpanProcessor on_end: scope=%s, track_usage=True, keeping usage data", scope_name)
|
|
306
333
|
return
|
|
307
334
|
|
|
308
335
|
original_attributes = span.attributes
|
|
@@ -314,6 +341,11 @@ class PydanticSpanProcessor(SpanProcessor):
|
|
|
314
341
|
for k, v in original_attributes.items()
|
|
315
342
|
if not any(substr in k for substr in self.USAGE_ATTRIBUTES_SUBSTRINGS)
|
|
316
343
|
}
|
|
344
|
+
filtered_count = len(original_attributes) - len(filtered_attrs)
|
|
345
|
+
logger.debug(
|
|
346
|
+
"[paid:span] PydanticSpanProcessor on_end: scope=%s, filtered %d usage/cost attribute(s)",
|
|
347
|
+
scope_name, filtered_count,
|
|
348
|
+
)
|
|
317
349
|
# This works because the exporter reads attributes during serialization
|
|
318
350
|
object.__setattr__(span, "_attributes", filtered_attrs)
|
|
319
351
|
|
|
@@ -355,6 +387,7 @@ def setup_graceful_termination(paid_tracer_provider: TracerProvider):
|
|
|
355
387
|
# signal handlers
|
|
356
388
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
357
389
|
signal.signal(sig, create_chained_signal_handler(sig))
|
|
390
|
+
logger.debug("[paid:init] Registered atexit and signal handlers (SIGINT, SIGTERM)")
|
|
358
391
|
except Exception as e:
|
|
359
392
|
logger.warning(
|
|
360
393
|
f"Could not set up termination handlers: {e}"
|
|
@@ -385,6 +418,9 @@ def initialize_tracing(
|
|
|
385
418
|
if not collector_endpoint:
|
|
386
419
|
collector_endpoint = DEFAULT_COLLECTOR_ENDPOINT
|
|
387
420
|
|
|
421
|
+
logger.debug("[paid:init] initialize_tracing called, endpoint=%s, api_key=%s",
|
|
422
|
+
collector_endpoint, "provided" if api_key else "not provided (will check env)")
|
|
423
|
+
|
|
388
424
|
try:
|
|
389
425
|
# Check if tracing is disabled via environment variable
|
|
390
426
|
paid_enabled = os.environ.get("PAID_ENABLED", "true").lower()
|
|
@@ -403,27 +439,51 @@ def initialize_tracing(
|
|
|
403
439
|
logger.error("API key must be provided via PAID_API_KEY environment variable")
|
|
404
440
|
# don't throw - tracing should not break the app
|
|
405
441
|
return
|
|
442
|
+
logger.debug("[paid:init] API key resolved from PAID_API_KEY environment variable")
|
|
443
|
+
else:
|
|
444
|
+
logger.debug("[paid:init] API key provided via parameter")
|
|
406
445
|
|
|
407
446
|
set_token(api_key)
|
|
408
447
|
|
|
409
448
|
resource = Resource(attributes={"api.key": api_key})
|
|
410
449
|
# Create isolated tracer provider for Paid - don't use or modify global provider
|
|
411
|
-
|
|
450
|
+
# Pass explicit sampler and span_limits to avoid inheriting from OTEL env vars
|
|
451
|
+
# (OTEL_TRACES_SAMPLER=always_off or OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=1
|
|
452
|
+
# set by the client app would silently break this)
|
|
453
|
+
paid_tracer_provider = TracerProvider(
|
|
454
|
+
resource=resource,
|
|
455
|
+
sampler=ALWAYS_ON,
|
|
456
|
+
span_limits=SpanLimits(
|
|
457
|
+
max_span_attributes=128,
|
|
458
|
+
max_event_attributes=128,
|
|
459
|
+
max_link_attributes=128,
|
|
460
|
+
max_events=128,
|
|
461
|
+
max_links=128,
|
|
462
|
+
),
|
|
463
|
+
)
|
|
464
|
+
# Override OTEL_SDK_DISABLED - the client may set it to disable their own OTEL
|
|
465
|
+
paid_tracer_provider._disabled = False
|
|
466
|
+
logger.debug("[paid:init] TracerProvider created (sampler=ALWAYS_ON, OTEL_SDK_DISABLED overridden)")
|
|
412
467
|
|
|
413
468
|
# Add span processor to prefix span names and add customer/agent ID attributes
|
|
414
469
|
paid_tracer_provider.add_span_processor(PaidSpanProcessor())
|
|
470
|
+
logger.debug("[paid:init] Added PaidSpanProcessor")
|
|
415
471
|
|
|
416
472
|
# Add Pydantic span processor - it self-filters by scope using the settings registry
|
|
417
473
|
paid_tracer_provider.add_span_processor(PydanticSpanProcessor())
|
|
474
|
+
logger.debug("[paid:init] Added PydanticSpanProcessor")
|
|
418
475
|
|
|
419
476
|
# Legacy support: if processor_settings.pydantic is provided, register it for the default scope
|
|
420
477
|
if processor_settings and processor_settings.pydantic:
|
|
421
478
|
_PydanticSettingsRegistry.register(PYDANTIC_SCOPE_NAME, processor_settings.pydantic)
|
|
479
|
+
logger.debug("[paid:init] Registered legacy PydanticProcessorSettings for scope=%s", PYDANTIC_SCOPE_NAME)
|
|
422
480
|
|
|
423
|
-
# Set up OTLP exporter
|
|
481
|
+
# Set up OTLP exporter with explicit settings to avoid inheriting from
|
|
482
|
+
# client OTEL env vars (e.g. OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT)
|
|
424
483
|
otlp_exporter = OTLPSpanExporter(
|
|
425
484
|
endpoint=collector_endpoint,
|
|
426
|
-
headers={}, #
|
|
485
|
+
headers={"_paid": "1"}, # Non-empty to prevent env var OTEL_EXPORTER_OTLP_HEADERS leak (empty dict is falsy)
|
|
486
|
+
timeout=10, # Explicit timeout to prevent env var OTEL_EXPORTER_OTLP_TIMEOUT override
|
|
427
487
|
)
|
|
428
488
|
|
|
429
489
|
# Use SimpleSpanProcessor for immediate span export.
|
|
@@ -431,6 +491,7 @@ def initialize_tracing(
|
|
|
431
491
|
# Airflow terminates processes before the batch is sent, losing traces.
|
|
432
492
|
span_processor = SimpleSpanProcessor(otlp_exporter)
|
|
433
493
|
paid_tracer_provider.add_span_processor(span_processor)
|
|
494
|
+
logger.debug("[paid:init] Added SimpleSpanProcessor with OTLPSpanExporter -> %s", collector_endpoint)
|
|
434
495
|
|
|
435
496
|
setup_graceful_termination(paid_tracer_provider) # doesn't throw
|
|
436
497
|
|
|
@@ -454,6 +515,7 @@ def get_paid_tracer() -> trace.Tracer:
|
|
|
454
515
|
Tracing is automatically initialized when using @paid_tracing decorator or context manager.
|
|
455
516
|
"""
|
|
456
517
|
global paid_tracer_provider
|
|
518
|
+
logger.debug("[paid:init] get_paid_tracer: provider_type=%s", type(paid_tracer_provider).__name__)
|
|
457
519
|
return paid_tracer_provider.get_tracer("paid.python")
|
|
458
520
|
|
|
459
521
|
|
|
@@ -553,15 +615,22 @@ def trace_sync_(
|
|
|
553
615
|
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
554
616
|
)
|
|
555
617
|
ctx = trace.set_span_in_context(NonRecordingSpan(span_context))
|
|
618
|
+
logger.debug("[paid:distributed] trace_sync_ using override trace_id=%s",
|
|
619
|
+
format(override_trace_id, '032x') if isinstance(override_trace_id, int) else str(override_trace_id))
|
|
620
|
+
else:
|
|
621
|
+
logger.debug("[paid:distributed] trace_sync_ no override trace_id, using auto-generated")
|
|
556
622
|
|
|
557
623
|
try:
|
|
558
624
|
tracer = get_paid_tracer()
|
|
559
|
-
logger.
|
|
625
|
+
logger.debug("[paid:span] trace_sync_ creating parent_span for customer_id=%s, agent_id=%s, fn=%s",
|
|
626
|
+
external_customer_id, external_agent_id, getattr(fn, '__name__', repr(fn)))
|
|
560
627
|
with tracer.start_as_current_span("parent_span", context=ctx) as span:
|
|
628
|
+
logger.debug("[paid:span] trace_sync_ span created, trace_id=%s",
|
|
629
|
+
format(span.get_span_context().trace_id, '032x') if isinstance(span.get_span_context().trace_id, int) else str(span.get_span_context().trace_id))
|
|
561
630
|
try:
|
|
562
631
|
result = fn(*args, **kwargs)
|
|
563
632
|
span.set_status(Status(StatusCode.OK))
|
|
564
|
-
logger.
|
|
633
|
+
logger.debug("[paid:span] trace_sync_ fn=%s completed successfully", getattr(fn, '__name__', repr(fn)))
|
|
565
634
|
return result
|
|
566
635
|
except Exception as error:
|
|
567
636
|
span.set_status(Status(StatusCode.ERROR, str(error)))
|
|
@@ -624,18 +693,25 @@ async def trace_async_(
|
|
|
624
693
|
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
625
694
|
)
|
|
626
695
|
ctx = trace.set_span_in_context(NonRecordingSpan(span_context))
|
|
696
|
+
logger.debug("[paid:distributed] trace_async_ using override trace_id=%s",
|
|
697
|
+
format(override_trace_id, '032x') if isinstance(override_trace_id, int) else str(override_trace_id))
|
|
698
|
+
else:
|
|
699
|
+
logger.debug("[paid:distributed] trace_async_ no override trace_id, using auto-generated")
|
|
627
700
|
|
|
628
701
|
try:
|
|
629
702
|
tracer = get_paid_tracer()
|
|
630
|
-
logger.
|
|
703
|
+
logger.debug("[paid:span] trace_async_ creating parent_span for customer_id=%s, agent_id=%s, fn=%s",
|
|
704
|
+
external_customer_id, external_agent_id, getattr(fn, '__name__', repr(fn)))
|
|
631
705
|
with tracer.start_as_current_span("parent_span", context=ctx) as span:
|
|
706
|
+
logger.debug("[paid:span] trace_async_ span created, trace_id=%s",
|
|
707
|
+
format(span.get_span_context().trace_id, '032x') if isinstance(span.get_span_context().trace_id, int) else str(span.get_span_context().trace_id))
|
|
632
708
|
try:
|
|
633
709
|
if asyncio.iscoroutinefunction(fn):
|
|
634
710
|
result = await fn(*args, **kwargs)
|
|
635
711
|
else:
|
|
636
712
|
result = fn(*args, **kwargs)
|
|
637
713
|
span.set_status(Status(StatusCode.OK))
|
|
638
|
-
logger.
|
|
714
|
+
logger.debug("[paid:span] trace_async_ fn=%s completed successfully", getattr(fn, '__name__', repr(fn)))
|
|
639
715
|
return result
|
|
640
716
|
except Exception as error:
|
|
641
717
|
span.set_status(Status(StatusCode.ERROR, str(error)))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/anthropic/anthropicWrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/bedrock/bedrockWrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/mistral/mistralWrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paid_python-1.0.0a4 → paid_python-1.0.2}/src/paid/tracing/wrappers/openai_agents/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|