posthoganalytics 6.7.0__py3-none-any.whl → 6.7.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,9 +12,16 @@ except ImportError:
12
12
  from posthoganalytics.ai.utils import (
13
13
  call_llm_and_track_usage,
14
14
  extract_available_tool_calls,
15
- get_model_params,
15
+ merge_usage_stats,
16
16
  with_privacy_mode,
17
17
  )
18
+ from posthoganalytics.ai.openai.openai_converter import (
19
+ extract_openai_usage_from_chunk,
20
+ extract_openai_content_from_chunk,
21
+ extract_openai_tool_calls_from_chunk,
22
+ accumulate_openai_tool_calls,
23
+ )
24
+ from posthoganalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
18
25
  from posthoganalytics.client import Client as PostHogClient
19
26
  from posthoganalytics import setup
20
27
 
@@ -33,6 +40,7 @@ class OpenAI(openai.OpenAI):
33
40
  posthog_client: If provided, events will be captured via this client instead of the global `posthog`.
34
41
  **openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
35
42
  """
43
+
36
44
  super().__init__(**kwargs)
37
45
  self._ph_client = posthog_client or setup()
38
46
 
@@ -122,35 +130,17 @@ class WrappedResponses:
122
130
 
123
131
  try:
124
132
  for chunk in response:
125
- if hasattr(chunk, "type") and chunk.type == "response.completed":
126
- res = chunk.response
127
- if res.output and len(res.output) > 0:
128
- final_content.append(res.output[0])
129
-
130
- if hasattr(chunk, "usage") and chunk.usage:
131
- usage_stats = {
132
- k: getattr(chunk.usage, k, 0)
133
- for k in [
134
- "input_tokens",
135
- "output_tokens",
136
- "total_tokens",
137
- ]
138
- }
139
-
140
- # Add support for cached tokens
141
- if hasattr(chunk.usage, "output_tokens_details") and hasattr(
142
- chunk.usage.output_tokens_details, "reasoning_tokens"
143
- ):
144
- usage_stats["reasoning_tokens"] = (
145
- chunk.usage.output_tokens_details.reasoning_tokens
146
- )
147
-
148
- if hasattr(chunk.usage, "input_tokens_details") and hasattr(
149
- chunk.usage.input_tokens_details, "cached_tokens"
150
- ):
151
- usage_stats["cache_read_input_tokens"] = (
152
- chunk.usage.input_tokens_details.cached_tokens
153
- )
133
+ # Extract usage stats from chunk
134
+ chunk_usage = extract_openai_usage_from_chunk(chunk, "responses")
135
+
136
+ if chunk_usage:
137
+ merge_usage_stats(usage_stats, chunk_usage)
138
+
139
+ # Extract content from chunk
140
+ content = extract_openai_content_from_chunk(chunk, "responses")
141
+
142
+ if content is not None:
143
+ final_content.append(content)
154
144
 
155
145
  yield chunk
156
146
 
@@ -168,7 +158,7 @@ class WrappedResponses:
168
158
  usage_stats,
169
159
  latency,
170
160
  output,
171
- extract_available_tool_calls("openai", kwargs),
161
+ None, # Responses API doesn't have tools
172
162
  )
173
163
 
174
164
  return generator()
@@ -186,47 +176,36 @@ class WrappedResponses:
186
176
  output: Any,
187
177
  available_tool_calls: Optional[List[Dict[str, Any]]] = None,
188
178
  ):
189
- if posthog_trace_id is None:
190
- posthog_trace_id = str(uuid.uuid4())
191
-
192
- event_properties = {
193
- "$ai_provider": "openai",
194
- "$ai_model": kwargs.get("model"),
195
- "$ai_model_parameters": get_model_params(kwargs),
196
- "$ai_input": with_privacy_mode(
197
- self._client._ph_client, posthog_privacy_mode, kwargs.get("input")
198
- ),
199
- "$ai_output_choices": with_privacy_mode(
200
- self._client._ph_client,
201
- posthog_privacy_mode,
202
- output,
203
- ),
204
- "$ai_http_status": 200,
205
- "$ai_input_tokens": usage_stats.get("input_tokens", 0),
206
- "$ai_output_tokens": usage_stats.get("output_tokens", 0),
207
- "$ai_cache_read_input_tokens": usage_stats.get(
208
- "cache_read_input_tokens", 0
209
- ),
210
- "$ai_reasoning_tokens": usage_stats.get("reasoning_tokens", 0),
211
- "$ai_latency": latency,
212
- "$ai_trace_id": posthog_trace_id,
213
- "$ai_base_url": str(self._client.base_url),
214
- **(posthog_properties or {}),
215
- }
216
-
217
- if available_tool_calls:
218
- event_properties["$ai_tools"] = available_tool_calls
179
+ from posthoganalytics.ai.types import StreamingEventData
180
+ from posthoganalytics.ai.openai.openai_converter import (
181
+ standardize_openai_usage,
182
+ format_openai_streaming_input,
183
+ format_openai_streaming_output,
184
+ )
185
+ from posthoganalytics.ai.utils import capture_streaming_event
186
+
187
+ # Prepare standardized event data
188
+ formatted_input = format_openai_streaming_input(kwargs, "responses")
189
+ sanitized_input = sanitize_openai_response(formatted_input)
190
+
191
+ event_data = StreamingEventData(
192
+ provider="openai",
193
+ model=kwargs.get("model", "unknown"),
194
+ base_url=str(self._client.base_url),
195
+ kwargs=kwargs,
196
+ formatted_input=sanitized_input,
197
+ formatted_output=format_openai_streaming_output(output, "responses"),
198
+ usage_stats=standardize_openai_usage(usage_stats, "responses"),
199
+ latency=latency,
200
+ distinct_id=posthog_distinct_id,
201
+ trace_id=posthog_trace_id,
202
+ properties=posthog_properties,
203
+ privacy_mode=posthog_privacy_mode,
204
+ groups=posthog_groups,
205
+ )
219
206
 
220
- if posthog_distinct_id is None:
221
- event_properties["$process_person_profile"] = False
222
-
223
- if hasattr(self._client._ph_client, "capture"):
224
- self._client._ph_client.capture(
225
- distinct_id=posthog_distinct_id or posthog_trace_id,
226
- event="$ai_generation",
227
- properties=event_properties,
228
- groups=posthog_groups,
229
- )
207
+ # Use the common capture function
208
+ capture_streaming_event(self._client._ph_client, event_data)
230
209
 
231
210
  def parse(
232
211
  self,
@@ -339,6 +318,7 @@ class WrappedCompletions:
339
318
  start_time = time.time()
340
319
  usage_stats: Dict[str, int] = {}
341
320
  accumulated_content = []
321
+ accumulated_tool_calls: Dict[int, Dict[str, Any]] = {}
342
322
  if "stream_options" not in kwargs:
343
323
  kwargs["stream_options"] = {}
344
324
  kwargs["stream_options"]["include_usage"] = True
@@ -347,50 +327,42 @@ class WrappedCompletions:
347
327
  def generator():
348
328
  nonlocal usage_stats
349
329
  nonlocal accumulated_content # noqa: F824
330
+ nonlocal accumulated_tool_calls
350
331
 
351
332
  try:
352
333
  for chunk in response:
353
- if hasattr(chunk, "usage") and chunk.usage:
354
- usage_stats = {
355
- k: getattr(chunk.usage, k, 0)
356
- for k in [
357
- "prompt_tokens",
358
- "completion_tokens",
359
- "total_tokens",
360
- ]
361
- }
362
-
363
- # Add support for cached tokens
364
- if hasattr(chunk.usage, "prompt_tokens_details") and hasattr(
365
- chunk.usage.prompt_tokens_details, "cached_tokens"
366
- ):
367
- usage_stats["cache_read_input_tokens"] = (
368
- chunk.usage.prompt_tokens_details.cached_tokens
369
- )
370
-
371
- if hasattr(chunk.usage, "output_tokens_details") and hasattr(
372
- chunk.usage.output_tokens_details, "reasoning_tokens"
373
- ):
374
- usage_stats["reasoning_tokens"] = (
375
- chunk.usage.output_tokens_details.reasoning_tokens
376
- )
377
-
378
- if (
379
- hasattr(chunk, "choices")
380
- and chunk.choices
381
- and len(chunk.choices) > 0
382
- ):
383
- if chunk.choices[0].delta and chunk.choices[0].delta.content:
384
- content = chunk.choices[0].delta.content
385
- if content:
386
- accumulated_content.append(content)
334
+ # Extract usage stats from chunk
335
+ chunk_usage = extract_openai_usage_from_chunk(chunk, "chat")
336
+
337
+ if chunk_usage:
338
+ merge_usage_stats(usage_stats, chunk_usage)
339
+
340
+ # Extract content from chunk
341
+ content = extract_openai_content_from_chunk(chunk, "chat")
342
+
343
+ if content is not None:
344
+ accumulated_content.append(content)
345
+
346
+ # Extract and accumulate tool calls from chunk
347
+ chunk_tool_calls = extract_openai_tool_calls_from_chunk(chunk)
348
+ if chunk_tool_calls:
349
+ accumulate_openai_tool_calls(
350
+ accumulated_tool_calls, chunk_tool_calls
351
+ )
387
352
 
388
353
  yield chunk
389
354
 
390
355
  finally:
391
356
  end_time = time.time()
392
357
  latency = end_time - start_time
393
- output = "".join(accumulated_content)
358
+
359
+ # Convert accumulated tool calls dict to list
360
+ tool_calls_list = (
361
+ list(accumulated_tool_calls.values())
362
+ if accumulated_tool_calls
363
+ else None
364
+ )
365
+
394
366
  self._capture_streaming_event(
395
367
  posthog_distinct_id,
396
368
  posthog_trace_id,
@@ -400,7 +372,8 @@ class WrappedCompletions:
400
372
  kwargs,
401
373
  usage_stats,
402
374
  latency,
403
- output,
375
+ accumulated_content,
376
+ tool_calls_list,
404
377
  extract_available_tool_calls("openai", kwargs),
405
378
  )
406
379
 
@@ -417,49 +390,39 @@ class WrappedCompletions:
417
390
  usage_stats: Dict[str, int],
418
391
  latency: float,
419
392
  output: Any,
393
+ tool_calls: Optional[List[Dict[str, Any]]] = None,
420
394
  available_tool_calls: Optional[List[Dict[str, Any]]] = None,
421
395
  ):
422
- if posthog_trace_id is None:
423
- posthog_trace_id = str(uuid.uuid4())
424
-
425
- event_properties = {
426
- "$ai_provider": "openai",
427
- "$ai_model": kwargs.get("model"),
428
- "$ai_model_parameters": get_model_params(kwargs),
429
- "$ai_input": with_privacy_mode(
430
- self._client._ph_client, posthog_privacy_mode, kwargs.get("messages")
431
- ),
432
- "$ai_output_choices": with_privacy_mode(
433
- self._client._ph_client,
434
- posthog_privacy_mode,
435
- [{"content": output, "role": "assistant"}],
436
- ),
437
- "$ai_http_status": 200,
438
- "$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
439
- "$ai_output_tokens": usage_stats.get("completion_tokens", 0),
440
- "$ai_cache_read_input_tokens": usage_stats.get(
441
- "cache_read_input_tokens", 0
442
- ),
443
- "$ai_reasoning_tokens": usage_stats.get("reasoning_tokens", 0),
444
- "$ai_latency": latency,
445
- "$ai_trace_id": posthog_trace_id,
446
- "$ai_base_url": str(self._client.base_url),
447
- **(posthog_properties or {}),
448
- }
396
+ from posthoganalytics.ai.types import StreamingEventData
397
+ from posthoganalytics.ai.openai.openai_converter import (
398
+ standardize_openai_usage,
399
+ format_openai_streaming_input,
400
+ format_openai_streaming_output,
401
+ )
402
+ from posthoganalytics.ai.utils import capture_streaming_event
403
+
404
+ # Prepare standardized event data
405
+ formatted_input = format_openai_streaming_input(kwargs, "chat")
406
+ sanitized_input = sanitize_openai(formatted_input)
407
+
408
+ event_data = StreamingEventData(
409
+ provider="openai",
410
+ model=kwargs.get("model", "unknown"),
411
+ base_url=str(self._client.base_url),
412
+ kwargs=kwargs,
413
+ formatted_input=sanitized_input,
414
+ formatted_output=format_openai_streaming_output(output, "chat", tool_calls),
415
+ usage_stats=standardize_openai_usage(usage_stats, "chat"),
416
+ latency=latency,
417
+ distinct_id=posthog_distinct_id,
418
+ trace_id=posthog_trace_id,
419
+ properties=posthog_properties,
420
+ privacy_mode=posthog_privacy_mode,
421
+ groups=posthog_groups,
422
+ )
449
423
 
450
- if available_tool_calls:
451
- event_properties["$ai_tools"] = available_tool_calls
452
-
453
- if posthog_distinct_id is None:
454
- event_properties["$process_person_profile"] = False
455
-
456
- if hasattr(self._client._ph_client, "capture"):
457
- self._client._ph_client.capture(
458
- distinct_id=posthog_distinct_id or posthog_trace_id,
459
- event="$ai_generation",
460
- properties=event_properties,
461
- groups=posthog_groups,
462
- )
424
+ # Use the common capture function
425
+ capture_streaming_event(self._client._ph_client, event_data)
463
426
 
464
427
 
465
428
  class WrappedEmbeddings:
@@ -496,6 +459,7 @@ class WrappedEmbeddings:
496
459
  Returns:
497
460
  The response from OpenAI's embeddings.create call.
498
461
  """
462
+
499
463
  if posthog_trace_id is None:
500
464
  posthog_trace_id = str(uuid.uuid4())
501
465
 
@@ -518,7 +482,9 @@ class WrappedEmbeddings:
518
482
  "$ai_provider": "openai",
519
483
  "$ai_model": kwargs.get("model"),
520
484
  "$ai_input": with_privacy_mode(
521
- self._client._ph_client, posthog_privacy_mode, kwargs.get("input")
485
+ self._client._ph_client,
486
+ posthog_privacy_mode,
487
+ sanitize_openai_response(kwargs.get("input")),
522
488
  ),
523
489
  "$ai_http_status": 200,
524
490
  "$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
@@ -14,8 +14,17 @@ from posthoganalytics.ai.utils import (
14
14
  call_llm_and_track_usage_async,
15
15
  extract_available_tool_calls,
16
16
  get_model_params,
17
+ merge_usage_stats,
17
18
  with_privacy_mode,
18
19
  )
20
+ from posthoganalytics.ai.openai.openai_converter import (
21
+ extract_openai_usage_from_chunk,
22
+ extract_openai_content_from_chunk,
23
+ extract_openai_tool_calls_from_chunk,
24
+ accumulate_openai_tool_calls,
25
+ format_openai_streaming_output,
26
+ )
27
+ from posthoganalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
19
28
  from posthoganalytics.client import Client as PostHogClient
20
29
 
21
30
 
@@ -34,6 +43,7 @@ class AsyncOpenAI(openai.AsyncOpenAI):
34
43
  of the global posthog.
35
44
  **openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
36
45
  """
46
+
37
47
  super().__init__(**kwargs)
38
48
  self._ph_client = posthog_client or setup()
39
49
 
@@ -66,6 +76,7 @@ class WrappedResponses:
66
76
 
67
77
  def __getattr__(self, name):
68
78
  """Fallback to original responses object for any methods we don't explicitly handle."""
79
+
69
80
  return getattr(self._original, name)
70
81
 
71
82
  async def create(
@@ -115,7 +126,7 @@ class WrappedResponses:
115
126
  start_time = time.time()
116
127
  usage_stats: Dict[str, int] = {}
117
128
  final_content = []
118
- response = await self._original.create(**kwargs)
129
+ response = self._original.create(**kwargs)
119
130
 
120
131
  async def async_generator():
121
132
  nonlocal usage_stats
@@ -123,35 +134,17 @@ class WrappedResponses:
123
134
 
124
135
  try:
125
136
  async for chunk in response:
126
- if hasattr(chunk, "type") and chunk.type == "response.completed":
127
- res = chunk.response
128
- if res.output and len(res.output) > 0:
129
- final_content.append(res.output[0])
130
-
131
- if hasattr(chunk, "usage") and chunk.usage:
132
- usage_stats = {
133
- k: getattr(chunk.usage, k, 0)
134
- for k in [
135
- "input_tokens",
136
- "output_tokens",
137
- "total_tokens",
138
- ]
139
- }
140
-
141
- # Add support for cached tokens
142
- if hasattr(chunk.usage, "output_tokens_details") and hasattr(
143
- chunk.usage.output_tokens_details, "reasoning_tokens"
144
- ):
145
- usage_stats["reasoning_tokens"] = (
146
- chunk.usage.output_tokens_details.reasoning_tokens
147
- )
148
-
149
- if hasattr(chunk.usage, "input_tokens_details") and hasattr(
150
- chunk.usage.input_tokens_details, "cached_tokens"
151
- ):
152
- usage_stats["cache_read_input_tokens"] = (
153
- chunk.usage.input_tokens_details.cached_tokens
154
- )
137
+ # Extract usage stats from chunk
138
+ chunk_usage = extract_openai_usage_from_chunk(chunk, "responses")
139
+
140
+ if chunk_usage:
141
+ merge_usage_stats(usage_stats, chunk_usage)
142
+
143
+ # Extract content from chunk
144
+ content = extract_openai_content_from_chunk(chunk, "responses")
145
+
146
+ if content is not None:
147
+ final_content.append(content)
155
148
 
156
149
  yield chunk
157
150
 
@@ -159,6 +152,7 @@ class WrappedResponses:
159
152
  end_time = time.time()
160
153
  latency = end_time - start_time
161
154
  output = final_content
155
+
162
156
  await self._capture_streaming_event(
163
157
  posthog_distinct_id,
164
158
  posthog_trace_id,
@@ -195,12 +189,14 @@ class WrappedResponses:
195
189
  "$ai_model": kwargs.get("model"),
196
190
  "$ai_model_parameters": get_model_params(kwargs),
197
191
  "$ai_input": with_privacy_mode(
198
- self._client._ph_client, posthog_privacy_mode, kwargs.get("input")
192
+ self._client._ph_client,
193
+ posthog_privacy_mode,
194
+ sanitize_openai_response(kwargs.get("input")),
199
195
  ),
200
196
  "$ai_output_choices": with_privacy_mode(
201
197
  self._client._ph_client,
202
198
  posthog_privacy_mode,
203
- output,
199
+ format_openai_streaming_output(output, "responses"),
204
200
  ),
205
201
  "$ai_http_status": 200,
206
202
  "$ai_input_tokens": usage_stats.get("input_tokens", 0),
@@ -342,59 +338,50 @@ class WrappedCompletions:
342
338
  start_time = time.time()
343
339
  usage_stats: Dict[str, int] = {}
344
340
  accumulated_content = []
341
+ accumulated_tool_calls: Dict[int, Dict[str, Any]] = {}
345
342
 
346
343
  if "stream_options" not in kwargs:
347
344
  kwargs["stream_options"] = {}
348
345
  kwargs["stream_options"]["include_usage"] = True
349
- response = await self._original.create(**kwargs)
346
+ response = self._original.create(**kwargs)
350
347
 
351
348
  async def async_generator():
352
349
  nonlocal usage_stats
353
350
  nonlocal accumulated_content # noqa: F824
351
+ nonlocal accumulated_tool_calls
354
352
 
355
353
  try:
356
354
  async for chunk in response:
357
- if hasattr(chunk, "usage") and chunk.usage:
358
- usage_stats = {
359
- k: getattr(chunk.usage, k, 0)
360
- for k in [
361
- "prompt_tokens",
362
- "completion_tokens",
363
- "total_tokens",
364
- ]
365
- }
366
-
367
- # Add support for cached tokens
368
- if hasattr(chunk.usage, "prompt_tokens_details") and hasattr(
369
- chunk.usage.prompt_tokens_details, "cached_tokens"
370
- ):
371
- usage_stats["cache_read_input_tokens"] = (
372
- chunk.usage.prompt_tokens_details.cached_tokens
373
- )
374
-
375
- if hasattr(chunk.usage, "output_tokens_details") and hasattr(
376
- chunk.usage.output_tokens_details, "reasoning_tokens"
377
- ):
378
- usage_stats["reasoning_tokens"] = (
379
- chunk.usage.output_tokens_details.reasoning_tokens
380
- )
381
-
382
- if (
383
- hasattr(chunk, "choices")
384
- and chunk.choices
385
- and len(chunk.choices) > 0
386
- ):
387
- if chunk.choices[0].delta and chunk.choices[0].delta.content:
388
- content = chunk.choices[0].delta.content
389
- if content:
390
- accumulated_content.append(content)
355
+ # Extract usage stats from chunk
356
+ chunk_usage = extract_openai_usage_from_chunk(chunk, "chat")
357
+ if chunk_usage:
358
+ merge_usage_stats(usage_stats, chunk_usage)
359
+
360
+ # Extract content from chunk
361
+ content = extract_openai_content_from_chunk(chunk, "chat")
362
+ if content is not None:
363
+ accumulated_content.append(content)
364
+
365
+ # Extract and accumulate tool calls from chunk
366
+ chunk_tool_calls = extract_openai_tool_calls_from_chunk(chunk)
367
+ if chunk_tool_calls:
368
+ accumulate_openai_tool_calls(
369
+ accumulated_tool_calls, chunk_tool_calls
370
+ )
391
371
 
392
372
  yield chunk
393
373
 
394
374
  finally:
395
375
  end_time = time.time()
396
376
  latency = end_time - start_time
397
- output = "".join(accumulated_content)
377
+
378
+ # Convert accumulated tool calls dict to list
379
+ tool_calls_list = (
380
+ list(accumulated_tool_calls.values())
381
+ if accumulated_tool_calls
382
+ else None
383
+ )
384
+
398
385
  await self._capture_streaming_event(
399
386
  posthog_distinct_id,
400
387
  posthog_trace_id,
@@ -404,7 +391,8 @@ class WrappedCompletions:
404
391
  kwargs,
405
392
  usage_stats,
406
393
  latency,
407
- output,
394
+ accumulated_content,
395
+ tool_calls_list,
408
396
  extract_available_tool_calls("openai", kwargs),
409
397
  )
410
398
 
@@ -421,6 +409,7 @@ class WrappedCompletions:
421
409
  usage_stats: Dict[str, int],
422
410
  latency: float,
423
411
  output: Any,
412
+ tool_calls: Optional[List[Dict[str, Any]]] = None,
424
413
  available_tool_calls: Optional[List[Dict[str, Any]]] = None,
425
414
  ):
426
415
  if posthog_trace_id is None:
@@ -431,12 +420,14 @@ class WrappedCompletions:
431
420
  "$ai_model": kwargs.get("model"),
432
421
  "$ai_model_parameters": get_model_params(kwargs),
433
422
  "$ai_input": with_privacy_mode(
434
- self._client._ph_client, posthog_privacy_mode, kwargs.get("messages")
423
+ self._client._ph_client,
424
+ posthog_privacy_mode,
425
+ sanitize_openai(kwargs.get("messages")),
435
426
  ),
436
427
  "$ai_output_choices": with_privacy_mode(
437
428
  self._client._ph_client,
438
429
  posthog_privacy_mode,
439
- [{"content": output, "role": "assistant"}],
430
+ format_openai_streaming_output(output, "chat", tool_calls),
440
431
  ),
441
432
  "$ai_http_status": 200,
442
433
  "$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
@@ -475,6 +466,7 @@ class WrappedEmbeddings:
475
466
 
476
467
  def __getattr__(self, name):
477
468
  """Fallback to original embeddings object for any methods we don't explicitly handle."""
469
+
478
470
  return getattr(self._original, name)
479
471
 
480
472
  async def create(
@@ -500,15 +492,17 @@ class WrappedEmbeddings:
500
492
  Returns:
501
493
  The response from OpenAI's embeddings.create call.
502
494
  """
495
+
503
496
  if posthog_trace_id is None:
504
497
  posthog_trace_id = str(uuid.uuid4())
505
498
 
506
499
  start_time = time.time()
507
- response = await self._original.create(**kwargs)
500
+ response = self._original.create(**kwargs)
508
501
  end_time = time.time()
509
502
 
510
503
  # Extract usage statistics if available
511
504
  usage_stats = {}
505
+
512
506
  if hasattr(response, "usage") and response.usage:
513
507
  usage_stats = {
514
508
  "prompt_tokens": getattr(response.usage, "prompt_tokens", 0),
@@ -522,7 +516,9 @@ class WrappedEmbeddings:
522
516
  "$ai_provider": "openai",
523
517
  "$ai_model": kwargs.get("model"),
524
518
  "$ai_input": with_privacy_mode(
525
- self._client._ph_client, posthog_privacy_mode, kwargs.get("input")
519
+ self._client._ph_client,
520
+ posthog_privacy_mode,
521
+ sanitize_openai_response(kwargs.get("input")),
526
522
  ),
527
523
  "$ai_http_status": 200,
528
524
  "$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
@@ -556,6 +552,7 @@ class WrappedBeta:
556
552
 
557
553
  def __getattr__(self, name):
558
554
  """Fallback to original beta object for any methods we don't explicitly handle."""
555
+
559
556
  return getattr(self._original, name)
560
557
 
561
558
  @property
@@ -572,6 +569,7 @@ class WrappedBetaChat:
572
569
 
573
570
  def __getattr__(self, name):
574
571
  """Fallback to original beta chat object for any methods we don't explicitly handle."""
572
+
575
573
  return getattr(self._original, name)
576
574
 
577
575
  @property
@@ -588,6 +586,7 @@ class WrappedBetaCompletions:
588
586
 
589
587
  def __getattr__(self, name):
590
588
  """Fallback to original beta completions object for any methods we don't explicitly handle."""
589
+
591
590
  return getattr(self._original, name)
592
591
 
593
592
  async def parse(