paid-python 1.2.0__tar.gz → 1.3.0__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 (125) hide show
  1. {paid_python-1.2.0 → paid_python-1.3.0}/PKG-INFO +1 -1
  2. {paid_python-1.2.0 → paid_python-1.3.0}/pyproject.toml +1 -1
  3. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/client_wrapper.py +2 -2
  4. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/autoinstrumentation.py +3 -0
  5. paid_python-1.3.0/src/paid/tracing/openai_agents_patches/__init__.py +15 -0
  6. paid_python-1.3.0/src/paid/tracing/openai_agents_patches/patches.py +189 -0
  7. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/tracing.py +39 -9
  8. {paid_python-1.2.0 → paid_python-1.3.0}/LICENSE +0 -0
  9. {paid_python-1.2.0 → paid_python-1.3.0}/README.md +0 -0
  10. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/__init__.py +0 -0
  11. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/client.py +0 -0
  12. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/contacts/__init__.py +0 -0
  13. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/contacts/client.py +0 -0
  14. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/contacts/raw_client.py +0 -0
  15. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/contacts/types/__init__.py +0 -0
  16. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/contacts/types/create_contact_request_roles_item.py +0 -0
  17. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/__init__.py +0 -0
  18. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/api_error.py +0 -0
  19. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/datetime_utils.py +0 -0
  20. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/file.py +0 -0
  21. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/force_multipart.py +0 -0
  22. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_client.py +0 -0
  23. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_response.py +0 -0
  24. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_sse/__init__.py +0 -0
  25. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_sse/_api.py +0 -0
  26. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_sse/_decoders.py +0 -0
  27. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_sse/_exceptions.py +0 -0
  28. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/http_sse/_models.py +0 -0
  29. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/jsonable_encoder.py +0 -0
  30. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/pydantic_utilities.py +0 -0
  31. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/query_encoder.py +0 -0
  32. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/remove_none_from_dict.py +0 -0
  33. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/request_options.py +0 -0
  34. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/core/serialization.py +0 -0
  35. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/customers/__init__.py +0 -0
  36. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/customers/client.py +0 -0
  37. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/customers/raw_client.py +0 -0
  38. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/environment.py +0 -0
  39. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/errors/__init__.py +0 -0
  40. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/errors/bad_request_error.py +0 -0
  41. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/errors/forbidden_error.py +0 -0
  42. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/errors/internal_server_error.py +0 -0
  43. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/errors/not_found_error.py +0 -0
  44. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/invoices/__init__.py +0 -0
  45. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/invoices/client.py +0 -0
  46. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/invoices/raw_client.py +0 -0
  47. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/logger.py +0 -0
  48. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/orders/__init__.py +0 -0
  49. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/orders/client.py +0 -0
  50. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/orders/raw_client.py +0 -0
  51. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/products/__init__.py +0 -0
  52. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/products/client.py +0 -0
  53. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/products/raw_client.py +0 -0
  54. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/py.typed +0 -0
  55. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/signals/__init__.py +0 -0
  56. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/signals/client.py +0 -0
  57. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/signals/raw_client.py +0 -0
  58. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/__init__.py +0 -0
  59. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/anthropic_patches/__init__.py +0 -0
  60. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/anthropic_patches/patches.py +0 -0
  61. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/context_data.py +0 -0
  62. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/context_manager.py +0 -0
  63. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/distributed_tracing.py +0 -0
  64. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/gemini_patches/__init__.py +0 -0
  65. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/gemini_patches/patches.py +0 -0
  66. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/signal.py +0 -0
  67. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/__init__.py +0 -0
  68. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/anthropic/__init__.py +0 -0
  69. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/anthropic/anthropicWrapper.py +0 -0
  70. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/bedrock/__init__.py +0 -0
  71. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/bedrock/bedrockWrapper.py +0 -0
  72. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/gemini/__init__.py +0 -0
  73. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/gemini/geminiWrapper.py +0 -0
  74. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/langchain/__init__.py +0 -0
  75. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/langchain/paidLangChainCallback.py +0 -0
  76. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/mistral/__init__.py +0 -0
  77. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/mistral/mistralWrapper.py +0 -0
  78. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/openai/__init__.py +0 -0
  79. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/openai/openAiWrapper.py +0 -0
  80. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/openai_agents/__init__.py +0 -0
  81. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/openai_agents/openaiAgentsHook.py +0 -0
  82. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/tracing/wrappers/utils.py +0 -0
  83. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/__init__.py +0 -0
  84. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/attribution.py +0 -0
  85. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/bulk_signals_response.py +0 -0
  86. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/contact.py +0 -0
  87. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/contact_billing_address.py +0 -0
  88. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/contact_list_response.py +0 -0
  89. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/contact_roles_item.py +0 -0
  90. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/create_order_line_attribute_request.py +0 -0
  91. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/create_order_line_request.py +0 -0
  92. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer.py +0 -0
  93. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer_attribution.py +0 -0
  94. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer_billing_address.py +0 -0
  95. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer_by_external_id.py +0 -0
  96. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer_by_id.py +0 -0
  97. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer_creation_state.py +0 -0
  98. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/customer_list_response.py +0 -0
  99. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/empty_response.py +0 -0
  100. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/error_response.py +0 -0
  101. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice.py +0 -0
  102. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_line.py +0 -0
  103. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_line_payment_status.py +0 -0
  104. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_lines_response.py +0 -0
  105. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_list_response.py +0 -0
  106. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_payment_status.py +0 -0
  107. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_source.py +0 -0
  108. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_status.py +0 -0
  109. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/invoice_tax_status.py +0 -0
  110. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/order.py +0 -0
  111. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/order_creation_state.py +0 -0
  112. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/order_line.py +0 -0
  113. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/order_lines_response.py +0 -0
  114. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/order_list_response.py +0 -0
  115. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/pagination.py +0 -0
  116. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/product.py +0 -0
  117. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/product_by_external_id.py +0 -0
  118. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/product_by_id.py +0 -0
  119. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/product_list_response.py +0 -0
  120. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/signal.py +0 -0
  121. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/update_contact_request.py +0 -0
  122. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/update_contact_request_roles_item.py +0 -0
  123. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/update_customer_request.py +0 -0
  124. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/types/update_product_request.py +0 -0
  125. {paid_python-1.2.0 → paid_python-1.3.0}/src/paid/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: paid-python
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary:
5
5
  Requires-Python: >=3.10,<3.14
6
6
  Classifier: Intended Audience :: Developers
@@ -4,7 +4,7 @@ dynamic = ["version"]
4
4
 
5
5
  [tool.poetry]
6
6
  name = "paid-python"
7
- version = "1.2.0"
7
+ version = "1.3.0"
8
8
  description = ""
9
9
  readme = "README.md"
10
10
  authors = []
@@ -24,12 +24,12 @@ class BaseClientWrapper:
24
24
  import platform
25
25
 
26
26
  headers: typing.Dict[str, str] = {
27
- "User-Agent": "paid-python/1.2.0",
27
+ "User-Agent": "paid-python/1.3.0",
28
28
  "X-Fern-Language": "Python",
29
29
  "X-Fern-Runtime": f"python/{platform.python_version()}",
30
30
  "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
31
31
  "X-Fern-SDK-Name": "paid-python",
32
- "X-Fern-SDK-Version": "1.2.0",
32
+ "X-Fern-SDK-Version": "1.3.0",
33
33
  **(self.get_custom_headers() or {}),
34
34
  }
35
35
  headers["Authorization"] = f"Bearer {self._get_token()}"
@@ -203,10 +203,13 @@ def _instrument_openai_agents() -> None:
203
203
  logger.warning("OpenAI Agents library not available, skipping instrumentation")
204
204
  return
205
205
 
206
+ from .openai_agents_patches import instrument_openai_agents
207
+
206
208
  logger.debug("[paid:autoinstrument] Instrumenting openai-agents with OpenAIAgentsInstrumentor, provider=%s",
207
209
  type(tracing.paid_tracer_provider).__name__)
208
210
  # Instrument OpenAI Agents with Paid's tracer provider
209
211
  OpenAIAgentsInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
212
+ instrument_openai_agents()
210
213
 
211
214
  _initialized_instrumentors.append("openai-agents")
212
215
  logger.info("OpenAI Agents auto-instrumentation enabled")
@@ -0,0 +1,15 @@
1
+ """Patches for openinference-instrumentation-openai-agents.
2
+
3
+ Adds tool metadata to function/tool spans by carrying schema details from
4
+ response spans onto the later function execution spans.
5
+ """
6
+
7
+ from .patches import (
8
+ instrument_openai_agents,
9
+ uninstrument_openai_agents,
10
+ )
11
+
12
+ __all__ = [
13
+ "instrument_openai_agents",
14
+ "uninstrument_openai_agents",
15
+ ]
@@ -0,0 +1,189 @@
1
+ """Monkey-patches for openinference-instrumentation-openai-agents.
2
+
3
+ Upstream records full tool schemas on response spans, but function/tool spans only
4
+ get `tool.name`. This patch caches function tool metadata per trace when the
5
+ response span ends, then applies that metadata to subsequent function spans.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from datetime import datetime
11
+ from typing import Any, Optional
12
+
13
+ from agents.tracing.span_data import FunctionSpanData, ResponseSpanData
14
+ from openai.types.responses import FunctionTool, Response
15
+ from openinference.instrumentation import safe_json_dumps
16
+ from opentelemetry.context import detach
17
+ from opentelemetry.trace import Status, StatusCode
18
+ from opentelemetry.util.types import AttributeValue
19
+
20
+ from paid.logger import logger
21
+
22
+ _originals: dict[str, Any] = {}
23
+ _SEMCONV_FALLBACKS = {
24
+ "TOOL_DESCRIPTION": "tool.description",
25
+ "TOOL_PARAMETERS": "tool.parameters",
26
+ "TOOL_JSON_SCHEMA": "tool.json_schema",
27
+ }
28
+
29
+
30
+ def instrument_openai_agents() -> None:
31
+ """Apply all OpenAI Agents patches. Call after instrumenting the SDK."""
32
+ _patch_tracing_processor()
33
+
34
+
35
+ def uninstrument_openai_agents() -> None:
36
+ """Restore original OpenAI Agents methods."""
37
+ try:
38
+ from openinference.instrumentation.openai_agents import _processor as processor_mod
39
+ except ImportError:
40
+ _originals.clear()
41
+ return
42
+
43
+ processor = processor_mod.OpenInferenceTracingProcessor
44
+ if "on_span_end" in _originals:
45
+ processor.on_span_end = _originals.pop("on_span_end") # type: ignore[method-assign]
46
+ if "on_trace_end" in _originals:
47
+ processor.on_trace_end = _originals.pop("on_trace_end") # type: ignore[method-assign]
48
+ _originals.clear()
49
+
50
+
51
+ def _patch_tracing_processor() -> None:
52
+ try:
53
+ from openinference.instrumentation.openai_agents import _processor as processor_mod
54
+ except ImportError:
55
+ logger.debug("Could not import openai-agents tracing processor, skipping patch")
56
+ return
57
+
58
+ processor = processor_mod.OpenInferenceTracingProcessor
59
+ if "on_span_end" in _originals:
60
+ return
61
+
62
+ _originals["on_span_end"] = processor.on_span_end
63
+ _originals["on_trace_end"] = processor.on_trace_end
64
+
65
+ def _patched_on_trace_end(self, trace): # type: ignore[misc]
66
+ try:
67
+ return _originals["on_trace_end"](self, trace)
68
+ finally:
69
+ tool_cache = getattr(self, "_paid_tool_schemas_by_trace", None)
70
+ if isinstance(tool_cache, dict):
71
+ tool_cache.pop(trace.trace_id, None)
72
+
73
+ def _patched_on_span_end(self, span): # type: ignore[misc]
74
+ if token := self._tokens.pop(span.span_id, None):
75
+ detach(token) # type: ignore[arg-type]
76
+ if not (otel_span := self._otel_spans.pop(span.span_id, None)):
77
+ return
78
+
79
+ processing_error: Exception | None = None
80
+ end_time: Optional[int] = None
81
+ try:
82
+ otel_span.update_name(processor_mod._get_span_name(span))
83
+ data = span.span_data
84
+ if isinstance(data, ResponseSpanData):
85
+ if hasattr(data, "response") and isinstance(response := data.response, Response):
86
+ _cache_tool_schemas(self, span.trace_id, response)
87
+ otel_span.set_attribute(processor_mod.OUTPUT_MIME_TYPE, processor_mod.JSON)
88
+ otel_span.set_attribute(processor_mod.OUTPUT_VALUE, response.model_dump_json())
89
+ for k, v in processor_mod._get_attributes_from_response(response):
90
+ otel_span.set_attribute(k, v)
91
+ if hasattr(data, "input") and (input := data.input):
92
+ if isinstance(input, str):
93
+ otel_span.set_attribute(processor_mod.INPUT_VALUE, input)
94
+ elif isinstance(input, list):
95
+ otel_span.set_attribute(processor_mod.INPUT_MIME_TYPE, processor_mod.JSON)
96
+ otel_span.set_attribute(processor_mod.INPUT_VALUE, safe_json_dumps(input))
97
+ for k, v in processor_mod._get_attributes_from_input(input):
98
+ otel_span.set_attribute(k, v)
99
+ elif isinstance(data, FunctionSpanData):
100
+ for k, v in processor_mod._get_attributes_from_function_span_data(data):
101
+ otel_span.set_attribute(k, v)
102
+ for k, v in _get_enriched_function_attributes(self, span.trace_id, data.name):
103
+ otel_span.set_attribute(k, v)
104
+ elif isinstance(data, processor_mod.MCPListToolsSpanData):
105
+ for k, v in processor_mod._get_attributes_from_mcp_list_tool_span_data(data):
106
+ otel_span.set_attribute(k, v)
107
+ elif isinstance(data, processor_mod.HandoffSpanData):
108
+ if data.to_agent and data.from_agent:
109
+ key = f"{data.to_agent}:{span.trace_id}"
110
+ self._reverse_handoffs_dict[key] = data.from_agent
111
+ while len(self._reverse_handoffs_dict) > self._MAX_HANDOFFS_IN_FLIGHT:
112
+ self._reverse_handoffs_dict.popitem(last=False)
113
+ elif isinstance(data, processor_mod.AgentSpanData):
114
+ otel_span.set_attribute(processor_mod.GRAPH_NODE_ID, data.name)
115
+ key = f"{data.name}:{span.trace_id}"
116
+ if parent_node := self._reverse_handoffs_dict.pop(key, None):
117
+ otel_span.set_attribute(processor_mod.GRAPH_NODE_PARENT_ID, parent_node)
118
+ elif isinstance(data, processor_mod.GenerationSpanData):
119
+ for k, v in processor_mod._get_attributes_from_generation_span_data(data):
120
+ otel_span.set_attribute(k, v)
121
+ elif isinstance(data, (processor_mod.CustomSpanData, processor_mod.GuardrailSpanData)):
122
+ for k, v in processor_mod._flatten(data.export()):
123
+ otel_span.set_attribute(k, v)
124
+ except Exception as exc:
125
+ processing_error = exc
126
+ logger.debug("Failed to enrich openai-agents span", exc_info=True)
127
+ try:
128
+ otel_span.record_exception(exc)
129
+ except Exception:
130
+ logger.debug("Failed to record openai-agents patch exception", exc_info=True)
131
+ finally:
132
+ if span.ended_at:
133
+ try:
134
+ end_time = processor_mod._as_utc_nano(datetime.fromisoformat(span.ended_at))
135
+ except ValueError:
136
+ pass
137
+ if processing_error is not None:
138
+ otel_span.set_status(Status(StatusCode.ERROR, str(processing_error)))
139
+ else:
140
+ otel_span.set_status(status=processor_mod._get_span_status(span))
141
+ otel_span.end(end_time)
142
+
143
+ processor.on_trace_end = _patched_on_trace_end # type: ignore[method-assign]
144
+ processor.on_span_end = _patched_on_span_end # type: ignore[method-assign]
145
+ logger.debug("Patched OpenInferenceTracingProcessor to enrich openai-agents tool spans")
146
+
147
+
148
+ def _cache_tool_schemas(processor: Any, trace_id: str, response: Response) -> None:
149
+ per_trace = getattr(processor, "_paid_tool_schemas_by_trace", None)
150
+ if not isinstance(per_trace, dict):
151
+ per_trace = {}
152
+ setattr(processor, "_paid_tool_schemas_by_trace", per_trace)
153
+
154
+ tool_map = per_trace.setdefault(trace_id, {})
155
+ for tool in response.tools or []:
156
+ if not isinstance(tool, FunctionTool):
157
+ continue
158
+ attrs: dict[str, AttributeValue] = {}
159
+ if tool.description:
160
+ attrs[processor_mod_attr("TOOL_DESCRIPTION")] = tool.description
161
+ if tool.parameters is not None:
162
+ attrs[processor_mod_attr("TOOL_PARAMETERS")] = safe_json_dumps(tool.parameters)
163
+ attrs[processor_mod_attr("TOOL_JSON_SCHEMA")] = safe_json_dumps(
164
+ {
165
+ "type": "function",
166
+ "function": {
167
+ "name": tool.name,
168
+ "description": tool.description,
169
+ "parameters": tool.parameters,
170
+ "strict": tool.strict,
171
+ },
172
+ }
173
+ )
174
+ tool_map[tool.name] = attrs
175
+
176
+
177
+ def _get_enriched_function_attributes(
178
+ processor: Any,
179
+ trace_id: str,
180
+ tool_name: str,
181
+ ):
182
+ tool_map = getattr(processor, "_paid_tool_schemas_by_trace", {}).get(trace_id, {})
183
+ yield from tool_map.get(tool_name, {}).items()
184
+
185
+
186
+ def processor_mod_attr(name: str) -> str:
187
+ from openinference.instrumentation.openai_agents import _processor as processor_mod
188
+
189
+ return getattr(processor_mod, name, _SEMCONV_FALLBACKS.get(name, name.lower()))
@@ -5,7 +5,7 @@ import os
5
5
  import random
6
6
  import signal
7
7
  from dataclasses import dataclass
8
- from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, TypeVar, Union
8
+ from typing import Any, Awaitable, Callable, Dict, Literal, Optional, Tuple, TypeVar, Union
9
9
 
10
10
  from . import distributed_tracing
11
11
  from .context_data import ContextData
@@ -13,7 +13,7 @@ from opentelemetry import trace
13
13
  from opentelemetry.context import Context
14
14
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
15
15
  from opentelemetry.sdk.trace import ReadableSpan, Span, SpanLimits, SpanProcessor, TracerProvider
16
- from opentelemetry.sdk.trace.export import SimpleSpanProcessor
16
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
17
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
@@ -43,6 +43,9 @@ class ProcessorSettings:
43
43
  pydantic: Optional[PydanticProcessorSettings] = None
44
44
  """Settings for Pydantic AI span processing. If provided, enables Pydantic-specific filtering."""
45
45
 
46
+ export_mode: Literal["batch", "simple"] = "simple"
47
+ """Span export strategy. Defaults to simple."""
48
+
46
49
 
47
50
  # Scope name constants for library-specific tracers
48
51
  PYDANTIC_SCOPE_NAME = "paid.pydantic"
@@ -201,6 +204,10 @@ class PaidSpanProcessor(SpanProcessor):
201
204
  "logfire.json_schema",
202
205
  }
203
206
 
207
+ def __init__(self, span_processor_mode: Literal["batch", "simple"] = "simple") -> None:
208
+ """Initialize processor with the export mode used by the OTLP span processor."""
209
+ self._span_processor_mode = span_processor_mode
210
+
204
211
  def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
205
212
  """Called when a span is started. Prefix the span name and add attributes."""
206
213
 
@@ -232,6 +239,7 @@ class PaidSpanProcessor(SpanProcessor):
232
239
 
233
240
  # Always stamp the SDK version so the backend can identify the source
234
241
  span.set_attribute("paid.sdk.version", f"python-{_paid_version}")
242
+ span.set_attribute("paid_span_processor_mode", self._span_processor_mode)
235
243
 
236
244
  logger.debug(
237
245
  "[paid:span] on_start: name=%s, customer_id=%s, agent_id=%s",
@@ -419,7 +427,8 @@ def initialize_tracing(
419
427
  Args:
420
428
  api_key: The API key for authentication. If not provided, will try to get from PAID_API_KEY environment variable.
421
429
  collector_endpoint: The OTLP collector endpoint URL.
422
- processor_settings: Optional configuration for span processors (deprecated for Pydantic - use get_paid_tracer_pydantic instead).
430
+ processor_settings: Optional configuration for span processors. Pydantic settings here are deprecated;
431
+ use get_paid_tracer_pydantic instead.
423
432
 
424
433
  Example:
425
434
  # Basic initialization
@@ -479,8 +488,10 @@ def initialize_tracing(
479
488
  paid_tracer_provider._disabled = False
480
489
  logger.debug("[paid:init] TracerProvider created (sampler=ALWAYS_ON, OTEL_SDK_DISABLED overridden)")
481
490
 
491
+ processor_settings = processor_settings or ProcessorSettings()
492
+
482
493
  # Add span processor to prefix span names and add customer/agent ID attributes
483
- paid_tracer_provider.add_span_processor(PaidSpanProcessor())
494
+ paid_tracer_provider.add_span_processor(PaidSpanProcessor(span_processor_mode=processor_settings.export_mode))
484
495
  logger.debug("[paid:init] Added PaidSpanProcessor")
485
496
 
486
497
  # Add Pydantic span processor - it self-filters by scope using the settings registry
@@ -500,12 +511,31 @@ def initialize_tracing(
500
511
  timeout=10, # Explicit timeout to prevent env var OTEL_EXPORTER_OTLP_TIMEOUT override
501
512
  )
502
513
 
503
- # Use SimpleSpanProcessor for immediate span export.
504
- # There are problems with BatchSpanProcessor in some environments - ex. Airflow.
505
- # Airflow terminates processes before the batch is sent, losing traces.
506
- span_processor = SimpleSpanProcessor(otlp_exporter)
514
+ span_processor: SpanProcessor
515
+
516
+ if processor_settings.export_mode == "batch":
517
+ # BatchSpanProcessor exports from a worker thread so collector outages do not
518
+ # block user logic on span end.
519
+ span_processor = BatchSpanProcessor(
520
+ otlp_exporter,
521
+ schedule_delay_millis=500,
522
+ max_export_batch_size=64,
523
+ max_queue_size=2048,
524
+ export_timeout_millis=10_000,
525
+ )
526
+ logger.debug("[paid:init] Using BatchSpanProcessor for non-blocking span export")
527
+ else:
528
+ # SimpleSpanProcessor exports inline. Keep it as an opt-in for short-lived
529
+ # environments where losing spans on process exit is worse than blocking.
530
+ span_processor = SimpleSpanProcessor(otlp_exporter)
531
+ logger.debug("[paid:init] Using SimpleSpanProcessor for immediate span export")
532
+
507
533
  paid_tracer_provider.add_span_processor(span_processor)
508
- logger.debug("[paid:init] Added SimpleSpanProcessor with OTLPSpanExporter -> %s", collector_endpoint)
534
+ logger.debug(
535
+ "[paid:init] Added %s with OTLPSpanExporter -> %s",
536
+ type(span_processor).__name__,
537
+ collector_endpoint,
538
+ )
509
539
 
510
540
  setup_graceful_termination(paid_tracer_provider) # doesn't throw
511
541
 
File without changes
File without changes