paid-python 1.0.4__tar.gz → 1.0.6__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.4 → paid_python-1.0.6}/PKG-INFO +1 -1
- {paid_python-1.0.4 → paid_python-1.0.6}/pyproject.toml +1 -1
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/client_wrapper.py +2 -2
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/anthropic_patches/patches.py +106 -5
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/autoinstrumentation.py +8 -0
- paid_python-1.0.6/src/paid/tracing/gemini_patches/__init__.py +15 -0
- paid_python-1.0.6/src/paid/tracing/gemini_patches/patches.py +101 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/tracing.py +5 -3
- paid_python-1.0.6/src/paid/version.py +6 -0
- paid_python-1.0.4/src/paid/version.py +0 -3
- {paid_python-1.0.4 → paid_python-1.0.6}/LICENSE +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/README.md +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/contacts/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/contacts/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/contacts/raw_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/api_error.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/datetime_utils.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/file.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/force_multipart.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_sse/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_sse/_api.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_sse/_decoders.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_sse/_exceptions.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/http_sse/_models.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/jsonable_encoder.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/pydantic_utilities.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/query_encoder.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/remove_none_from_dict.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/request_options.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/core/serialization.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/customers/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/customers/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/customers/raw_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/environment.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/errors/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/errors/bad_request_error.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/errors/forbidden_error.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/errors/internal_server_error.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/errors/not_found_error.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/invoices/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/invoices/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/invoices/raw_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/logger.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/orders/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/orders/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/orders/raw_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/products/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/products/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/products/raw_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/py.typed +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/signals/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/signals/client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/signals/raw_client.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/anthropic_patches/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/context_data.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/context_manager.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/distributed_tracing.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/signal.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/anthropic/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/anthropic/anthropicWrapper.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/bedrock/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/bedrock/bedrockWrapper.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/gemini/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/gemini/geminiWrapper.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/langchain/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/langchain/paidLangChainCallback.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/mistral/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/mistral/mistralWrapper.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/openai/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/openai/openAiWrapper.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/openai_agents/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/openai_agents/openaiAgentsHook.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/utils.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/__init__.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/attribution.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/bulk_signals_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/contact.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/contact_billing_address.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/contact_list_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer_attribution.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer_billing_address.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer_by_external_id.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer_by_id.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer_creation_state.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/customer_list_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/empty_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/error_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_line.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_line_payment_status.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_lines_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_list_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_payment_status.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_source.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_status.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/invoice_tax_status.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/order.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/order_creation_state.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/order_line.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/order_lines_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/order_list_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/pagination.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/product.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/product_by_external_id.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/product_by_id.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/product_list_response.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/signal.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/update_contact_request.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/update_customer_request.py +0 -0
- {paid_python-1.0.4 → paid_python-1.0.6}/src/paid/types/update_product_request.py +0 -0
|
@@ -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.0.
|
|
27
|
+
"User-Agent": "paid-python/1.0.6",
|
|
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.0.
|
|
32
|
+
"X-Fern-SDK-Version": "1.0.6",
|
|
33
33
|
**(self.get_custom_headers() or {}),
|
|
34
34
|
}
|
|
35
35
|
headers["Authorization"] = f"Bearer {self._get_token()}"
|
|
@@ -15,8 +15,13 @@ _original_async_messages_stream = None
|
|
|
15
15
|
# Originals for the beta path, keyed by method name.
|
|
16
16
|
_beta_originals: Dict[str, Any] = {}
|
|
17
17
|
|
|
18
|
+
# Originals for response-ID patches.
|
|
19
|
+
_response_id_originals: Dict[str, Any] = {}
|
|
20
|
+
|
|
18
21
|
_BETA_MODULE = "anthropic.resources.beta.messages.messages"
|
|
19
22
|
|
|
23
|
+
_ATTR_RESPONSE_ID = "gen_ai.response.id"
|
|
24
|
+
|
|
20
25
|
|
|
21
26
|
def instrument_anthropic() -> None:
|
|
22
27
|
"""Apply all Anthropic patches. Call after AnthropicInstrumentor().instrument()."""
|
|
@@ -24,6 +29,8 @@ def instrument_anthropic() -> None:
|
|
|
24
29
|
_patch_message_stream_manager()
|
|
25
30
|
_wrap_async_messages_stream()
|
|
26
31
|
_patch_response_accumulator_for_beta()
|
|
32
|
+
_patch_response_id_extraction()
|
|
33
|
+
_patch_streaming_response_id_extraction()
|
|
27
34
|
_wrap_beta_messages()
|
|
28
35
|
|
|
29
36
|
|
|
@@ -38,6 +45,7 @@ def uninstrument_anthropic() -> None:
|
|
|
38
45
|
_original_async_messages_stream = None
|
|
39
46
|
|
|
40
47
|
_uninstrument_beta_messages()
|
|
48
|
+
_uninstrument_response_id_patches()
|
|
41
49
|
|
|
42
50
|
def _patch_stream_context_managers() -> None:
|
|
43
51
|
try:
|
|
@@ -120,14 +128,26 @@ class _AsyncMessageStreamManagerProxy(ObjectProxy): # type: ignore[misc]
|
|
|
120
128
|
def __init__(self, manager: Any, span: trace_api.Span) -> None:
|
|
121
129
|
super().__init__(manager)
|
|
122
130
|
self._self_span = span
|
|
131
|
+
self._self_stream: Any = None
|
|
123
132
|
|
|
124
133
|
async def __aenter__(self): # type: ignore[misc]
|
|
125
|
-
|
|
134
|
+
stream = await self.__wrapped__.__aenter__()
|
|
135
|
+
self._self_stream = stream
|
|
136
|
+
return stream
|
|
126
137
|
|
|
127
138
|
async def __aexit__(self, exc_type, exc_val, exc_tb): # type: ignore[misc]
|
|
128
139
|
try:
|
|
129
140
|
return await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb)
|
|
130
141
|
finally:
|
|
142
|
+
try:
|
|
143
|
+
# Try to capture response ID from the stream's final message snapshot
|
|
144
|
+
stream = self._self_stream
|
|
145
|
+
if stream is not None:
|
|
146
|
+
final_msg = getattr(stream, "_MessageStream__final_message_snapshot", None)
|
|
147
|
+
if final_msg is not None and hasattr(final_msg, "id") and final_msg.id:
|
|
148
|
+
self._self_span.set_attribute(_ATTR_RESPONSE_ID, final_msg.id)
|
|
149
|
+
except Exception:
|
|
150
|
+
logger.debug("Failed to capture response ID from async stream", exc_info=True)
|
|
131
151
|
try:
|
|
132
152
|
if exc_type:
|
|
133
153
|
self._self_span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc_val)))
|
|
@@ -178,7 +198,7 @@ def _wrap_async_messages_stream() -> None:
|
|
|
178
198
|
|
|
179
199
|
|
|
180
200
|
def _patch_response_accumulator_for_beta() -> None:
|
|
181
|
-
"""Extend _MessageResponseAccumulator.process_chunk to handle beta event types."""
|
|
201
|
+
"""Extend _MessageResponseAccumulator.process_chunk to handle beta event types and capture response IDs."""
|
|
182
202
|
try:
|
|
183
203
|
from openinference.instrumentation.anthropic._stream import _MessageResponseAccumulator
|
|
184
204
|
except ImportError:
|
|
@@ -196,13 +216,21 @@ def _patch_response_accumulator_for_beta() -> None:
|
|
|
196
216
|
logger.debug("Could not import beta event types, skipping beta accumulator patch")
|
|
197
217
|
return
|
|
198
218
|
|
|
219
|
+
try:
|
|
220
|
+
from anthropic.types.raw_message_start_event import RawMessageStartEvent
|
|
221
|
+
except ImportError:
|
|
222
|
+
RawMessageStartEvent = None # type: ignore[assignment,misc]
|
|
223
|
+
|
|
199
224
|
_original_process_chunk = _MessageResponseAccumulator.process_chunk
|
|
200
225
|
|
|
201
226
|
def _process_chunk_with_beta(self, chunk): # type: ignore[misc]
|
|
202
|
-
"""Handles both regular and beta event types."""
|
|
227
|
+
"""Handles both regular and beta event types, and captures response IDs."""
|
|
203
228
|
if isinstance(chunk, BetaRawMessageStartEvent):
|
|
204
229
|
self._is_null = False
|
|
205
230
|
self._current_message_idx += 1
|
|
231
|
+
# Capture response ID from beta message start
|
|
232
|
+
if hasattr(chunk.message, "id") and chunk.message.id:
|
|
233
|
+
self._values += {"response_id": chunk.message.id}
|
|
206
234
|
value = {
|
|
207
235
|
"messages": {
|
|
208
236
|
"index": str(self._current_message_idx),
|
|
@@ -254,11 +282,84 @@ def _patch_response_accumulator_for_beta() -> None:
|
|
|
254
282
|
}
|
|
255
283
|
self._values += value
|
|
256
284
|
else:
|
|
257
|
-
# Non-beta event —
|
|
285
|
+
# Non-beta event — capture response ID from regular RawMessageStartEvent
|
|
286
|
+
# before delegating to the original handler.
|
|
287
|
+
if RawMessageStartEvent is not None and isinstance(chunk, RawMessageStartEvent):
|
|
288
|
+
if hasattr(chunk.message, "id") and chunk.message.id:
|
|
289
|
+
self._values += {"response_id": chunk.message.id}
|
|
258
290
|
return _original_process_chunk(self, chunk)
|
|
259
291
|
|
|
260
292
|
_MessageResponseAccumulator.process_chunk = _process_chunk_with_beta # type: ignore[method-assign]
|
|
261
|
-
logger.debug("Patched _MessageResponseAccumulator.process_chunk for beta event types")
|
|
293
|
+
logger.debug("Patched _MessageResponseAccumulator.process_chunk for beta event types and response IDs")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _patch_response_id_extraction() -> None:
|
|
297
|
+
"""Patch openinference to extract response.id from non-streaming Anthropic messages."""
|
|
298
|
+
try:
|
|
299
|
+
from openinference.instrumentation.anthropic import _wrappers
|
|
300
|
+
except ImportError:
|
|
301
|
+
logger.debug("Could not import openinference anthropic _wrappers, skipping response ID patch")
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
_original = _wrappers._get_llm_model_name_from_response
|
|
305
|
+
_response_id_originals["_get_llm_model_name_from_response"] = _original
|
|
306
|
+
|
|
307
|
+
def _patched(message): # type: ignore[misc]
|
|
308
|
+
yield from _original(message)
|
|
309
|
+
if response_id := getattr(message, "id", None):
|
|
310
|
+
yield _ATTR_RESPONSE_ID, response_id
|
|
311
|
+
|
|
312
|
+
_wrappers._get_llm_model_name_from_response = _patched # type: ignore[attr-defined]
|
|
313
|
+
logger.debug("Patched _get_llm_model_name_from_response to also yield response ID")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _patch_streaming_response_id_extraction() -> None:
|
|
317
|
+
"""Patch openinference _MessageResponseExtractor to yield response ID from accumulated data."""
|
|
318
|
+
try:
|
|
319
|
+
from openinference.instrumentation.anthropic._stream import _MessageResponseExtractor
|
|
320
|
+
except ImportError:
|
|
321
|
+
logger.debug("Could not import _MessageResponseExtractor, skipping streaming response ID patch")
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
_original_get_extra = _MessageResponseExtractor.get_extra_attributes
|
|
325
|
+
_response_id_originals["get_extra_attributes"] = _original_get_extra
|
|
326
|
+
|
|
327
|
+
def _get_extra_with_response_id(self): # type: ignore[misc]
|
|
328
|
+
yield from _original_get_extra(self)
|
|
329
|
+
try:
|
|
330
|
+
result = self._response_accumulator._result()
|
|
331
|
+
if result and (response_id := result.get("response_id")):
|
|
332
|
+
yield _ATTR_RESPONSE_ID, response_id
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
_MessageResponseExtractor.get_extra_attributes = _get_extra_with_response_id # type: ignore[method-assign]
|
|
337
|
+
logger.debug("Patched _MessageResponseExtractor.get_extra_attributes for response IDs")
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _uninstrument_response_id_patches() -> None:
|
|
341
|
+
"""Restore original methods patched by _patch_response_id_extraction / _patch_streaming_response_id_extraction."""
|
|
342
|
+
if "_get_llm_model_name_from_response" in _response_id_originals:
|
|
343
|
+
try:
|
|
344
|
+
from openinference.instrumentation.anthropic import _wrappers
|
|
345
|
+
|
|
346
|
+
_wrappers._get_llm_model_name_from_response = _response_id_originals.pop( # type: ignore[attr-defined]
|
|
347
|
+
"_get_llm_model_name_from_response"
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
if "get_extra_attributes" in _response_id_originals:
|
|
353
|
+
try:
|
|
354
|
+
from openinference.instrumentation.anthropic._stream import _MessageResponseExtractor
|
|
355
|
+
|
|
356
|
+
_MessageResponseExtractor.get_extra_attributes = _response_id_originals.pop( # type: ignore[method-assign]
|
|
357
|
+
"get_extra_attributes"
|
|
358
|
+
)
|
|
359
|
+
except Exception:
|
|
360
|
+
pass
|
|
361
|
+
|
|
362
|
+
_response_id_originals.clear()
|
|
262
363
|
|
|
263
364
|
|
|
264
365
|
# ---------------------------------------------------------------------------
|
|
@@ -249,14 +249,22 @@ def _instrument_langchain() -> None:
|
|
|
249
249
|
def _instrument_google_genai() -> None:
|
|
250
250
|
"""
|
|
251
251
|
Instrument Google GenAI using openinference-instrumentation-google-genai.
|
|
252
|
+
|
|
253
|
+
Applies patches on top of the base instrumentor to capture response IDs — see
|
|
254
|
+
``paid.tracing.gemini_patches`` for full details.
|
|
252
255
|
"""
|
|
253
256
|
if not GOOGLE_GENAI_AVAILABLE:
|
|
254
257
|
logger.warning("Google GenAI instrumentation library not available, skipping instrumentation")
|
|
255
258
|
return
|
|
256
259
|
|
|
260
|
+
from .gemini_patches import instrument_google_genai
|
|
261
|
+
|
|
257
262
|
logger.debug("[paid:autoinstrument] Instrumenting google-genai with GoogleGenAIInstrumentor, provider=%s",
|
|
258
263
|
type(tracing.paid_tracer_provider).__name__)
|
|
259
264
|
GoogleGenAIInstrumentor().instrument(tracer_provider=tracing.paid_tracer_provider)
|
|
265
|
+
|
|
266
|
+
instrument_google_genai()
|
|
267
|
+
|
|
260
268
|
_initialized_instrumentors.append("google-genai")
|
|
261
269
|
logger.info("Google GenAI auto-instrumentation enabled")
|
|
262
270
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Patches for openinference-instrumentation-google-genai.
|
|
2
|
+
|
|
3
|
+
Adds response ID extraction (gen_ai.response.id) to both non-streaming
|
|
4
|
+
and streaming Google GenAI spans.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .patches import (
|
|
8
|
+
instrument_google_genai,
|
|
9
|
+
uninstrument_google_genai,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"instrument_google_genai",
|
|
14
|
+
"uninstrument_google_genai",
|
|
15
|
+
]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Monkey-patches for openinference-instrumentation-google-genai.
|
|
2
|
+
|
|
3
|
+
Adds response ID extraction to both non-streaming and streaming spans.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from paid.logger import logger
|
|
9
|
+
|
|
10
|
+
_ATTR_RESPONSE_ID = "gen_ai.response.id"
|
|
11
|
+
|
|
12
|
+
# Store originals for uninstrumentation
|
|
13
|
+
_originals: dict = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def instrument_google_genai() -> None:
|
|
17
|
+
"""Apply all Google GenAI patches. Call after GoogleGenAIInstrumentor().instrument()."""
|
|
18
|
+
_patch_response_id_extraction()
|
|
19
|
+
_patch_streaming_response_id_extraction()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def uninstrument_google_genai() -> None:
|
|
23
|
+
"""Restore original Google GenAI methods."""
|
|
24
|
+
if "get_attributes_from_generate_content" in _originals:
|
|
25
|
+
try:
|
|
26
|
+
from openinference.instrumentation.google_genai import (
|
|
27
|
+
_response_attributes_extractor as mod,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
mod._ResponseAttributesExtractor._get_attributes_from_generate_content = _originals.pop( # type: ignore[method-assign]
|
|
31
|
+
"get_attributes_from_generate_content"
|
|
32
|
+
)
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
if "stream_get_extra_attributes" in _originals:
|
|
37
|
+
try:
|
|
38
|
+
from openinference.instrumentation.google_genai import _stream as stream_mod
|
|
39
|
+
|
|
40
|
+
stream_mod._ResponseExtractor.get_extra_attributes = _originals.pop( # type: ignore[method-assign]
|
|
41
|
+
"stream_get_extra_attributes"
|
|
42
|
+
)
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
_originals.clear()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _patch_response_id_extraction() -> None:
|
|
50
|
+
"""Patch openinference to extract response_id from non-streaming Gemini responses."""
|
|
51
|
+
try:
|
|
52
|
+
from openinference.instrumentation.google_genai import (
|
|
53
|
+
_response_attributes_extractor as mod,
|
|
54
|
+
)
|
|
55
|
+
except ImportError:
|
|
56
|
+
logger.debug(
|
|
57
|
+
"Could not import openinference google_genai _response_attributes_extractor, "
|
|
58
|
+
"skipping response ID patch"
|
|
59
|
+
)
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
_original = mod._ResponseAttributesExtractor._get_attributes_from_generate_content
|
|
63
|
+
_originals["get_attributes_from_generate_content"] = _original
|
|
64
|
+
|
|
65
|
+
def _patched(self, response, request_parameters): # type: ignore[misc]
|
|
66
|
+
yield from _original(self, response=response, request_parameters=request_parameters)
|
|
67
|
+
if response_id := getattr(response, "response_id", None):
|
|
68
|
+
yield _ATTR_RESPONSE_ID, response_id
|
|
69
|
+
|
|
70
|
+
mod._ResponseAttributesExtractor._get_attributes_from_generate_content = _patched # type: ignore[method-assign]
|
|
71
|
+
logger.debug(
|
|
72
|
+
"Patched _ResponseAttributesExtractor._get_attributes_from_generate_content "
|
|
73
|
+
"to also yield response ID"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _patch_streaming_response_id_extraction() -> None:
|
|
78
|
+
"""Patch openinference _ResponseExtractor to yield response_id from accumulated streaming data."""
|
|
79
|
+
try:
|
|
80
|
+
from openinference.instrumentation.google_genai import _stream as stream_mod
|
|
81
|
+
except ImportError:
|
|
82
|
+
logger.debug(
|
|
83
|
+
"Could not import openinference google_genai _stream, "
|
|
84
|
+
"skipping streaming response ID patch"
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
_original_get_extra = stream_mod._ResponseExtractor.get_extra_attributes
|
|
89
|
+
_originals["stream_get_extra_attributes"] = _original_get_extra
|
|
90
|
+
|
|
91
|
+
def _get_extra_with_response_id(self): # type: ignore[misc]
|
|
92
|
+
yield from _original_get_extra(self)
|
|
93
|
+
try:
|
|
94
|
+
result = self._response_accumulator._result()
|
|
95
|
+
if result and (response_id := result.get("response_id")):
|
|
96
|
+
yield _ATTR_RESPONSE_ID, response_id
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
stream_mod._ResponseExtractor.get_extra_attributes = _get_extra_with_response_id # type: ignore[method-assign]
|
|
101
|
+
logger.debug("Patched _ResponseExtractor.get_extra_attributes for response IDs")
|
|
@@ -19,6 +19,7 @@ from opentelemetry.trace import NonRecordingSpan, NoOpTracerProvider, SpanContex
|
|
|
19
19
|
from opentelemetry.util.types import Attributes
|
|
20
20
|
|
|
21
21
|
from paid.logger import logger
|
|
22
|
+
from paid.version import __version__ as _paid_version
|
|
22
23
|
|
|
23
24
|
DEFAULT_COLLECTOR_ENDPOINT = (
|
|
24
25
|
os.environ.get("PAID_OTEL_COLLECTOR_ENDPOINT") or "https://collector.agentpaid.io:4318/v1/traces"
|
|
@@ -223,6 +224,9 @@ class PaidSpanProcessor(SpanProcessor):
|
|
|
223
224
|
if agent_id:
|
|
224
225
|
span.set_attribute("external_agent_id", agent_id)
|
|
225
226
|
|
|
227
|
+
# Always stamp the SDK version so the backend can identify the source
|
|
228
|
+
span.set_attribute("paid.sdk.version", f"python-{_paid_version}")
|
|
229
|
+
|
|
226
230
|
logger.debug(
|
|
227
231
|
"[paid:span] on_start: name=%s, customer_id=%s, agent_id=%s",
|
|
228
232
|
span.name, customer_id, agent_id,
|
|
@@ -445,13 +449,11 @@ def initialize_tracing(
|
|
|
445
449
|
|
|
446
450
|
set_token(api_key)
|
|
447
451
|
|
|
448
|
-
resource = Resource(attributes={"api.key": api_key})
|
|
449
452
|
# Create isolated tracer provider for Paid - don't use or modify global provider
|
|
450
453
|
# Pass explicit sampler and span_limits to avoid inheriting from OTEL env vars
|
|
451
454
|
# (OTEL_TRACES_SAMPLER=always_off or OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=1
|
|
452
455
|
# set by the client app would silently break this)
|
|
453
456
|
paid_tracer_provider = TracerProvider(
|
|
454
|
-
resource=resource,
|
|
455
457
|
sampler=ALWAYS_ON,
|
|
456
458
|
span_limits=SpanLimits(
|
|
457
459
|
max_span_attributes=128,
|
|
@@ -482,7 +484,7 @@ def initialize_tracing(
|
|
|
482
484
|
# client OTEL env vars (e.g. OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT)
|
|
483
485
|
otlp_exporter = OTLPSpanExporter(
|
|
484
486
|
endpoint=collector_endpoint,
|
|
485
|
-
headers={"
|
|
487
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
486
488
|
timeout=10, # Explicit timeout to prevent env var OTEL_EXPORTER_OTLP_TIMEOUT override
|
|
487
489
|
)
|
|
488
490
|
|
|
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
|
|
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.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/anthropic/anthropicWrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/langchain/paidLangChainCallback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paid_python-1.0.4 → paid_python-1.0.6}/src/paid/tracing/wrappers/openai_agents/openaiAgentsHook.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
|