Skip to content

Commit ec7f8f1

Browse files
feat(audio): add tracking for audio transcriptions in OpenAI client
1 parent 7ac63e1 commit ec7f8f1

File tree

3 files changed

+441
-0
lines changed

3 files changed

+441
-0
lines changed

posthog/ai/openai/openai.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
5151
self._original_embeddings = getattr(self, "embeddings", None)
5252
self._original_beta = getattr(self, "beta", None)
5353
self._original_responses = getattr(self, "responses", None)
54+
self._original_audio = getattr(self, "audio", None)
5455

5556
# Replace with wrapped versions (only if originals exist)
5657
if self._original_chat is not None:
@@ -65,6 +66,9 @@ def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
6566
if self._original_responses is not None:
6667
self.responses = WrappedResponses(self, self._original_responses)
6768

69+
if self._original_audio is not None:
70+
self.audio = WrappedAudio(self, self._original_audio)
71+
6872

6973
class WrappedResponses:
7074
"""Wrapper for OpenAI responses that tracks usage in PostHog."""
@@ -534,6 +538,119 @@ def create(
534538
return response
535539

536540

541+
class WrappedAudio:
542+
"""Wrapper for OpenAI audio that tracks usage in PostHog."""
543+
544+
def __init__(self, client: OpenAI, original_audio):
545+
self._client = client
546+
self._original = original_audio
547+
548+
def __getattr__(self, name):
549+
"""Fallback to original audio object for any methods we don't explicitly handle."""
550+
return getattr(self._original, name)
551+
552+
@property
553+
def transcriptions(self):
554+
return WrappedTranscriptions(self._client, self._original.transcriptions)
555+
556+
557+
class WrappedTranscriptions:
558+
"""Wrapper for OpenAI audio transcriptions that tracks usage in PostHog."""
559+
560+
def __init__(self, client: OpenAI, original_transcriptions):
561+
self._client = client
562+
self._original = original_transcriptions
563+
564+
def __getattr__(self, name):
565+
"""Fallback to original transcriptions object for any methods we don't explicitly handle."""
566+
return getattr(self._original, name)
567+
568+
def create(
569+
self,
570+
posthog_distinct_id: Optional[str] = None,
571+
posthog_trace_id: Optional[str] = None,
572+
posthog_properties: Optional[Dict[str, Any]] = None,
573+
posthog_privacy_mode: bool = False,
574+
posthog_groups: Optional[Dict[str, Any]] = None,
575+
**kwargs: Any,
576+
):
577+
"""
578+
Create a transcription using OpenAI's 'audio.transcriptions.create' method,
579+
and track usage in PostHog.
580+
581+
Args:
582+
posthog_distinct_id: Optional ID to associate with the usage event.
583+
posthog_trace_id: Optional trace UUID for linking events.
584+
posthog_properties: Optional dictionary of extra properties to include in the event.
585+
posthog_privacy_mode: Whether to anonymize the input and output.
586+
posthog_groups: Optional dictionary of groups to associate with the event.
587+
**kwargs: Any additional parameters for the OpenAI Transcriptions API.
588+
589+
Returns:
590+
The response from OpenAI's audio.transcriptions.create call.
591+
"""
592+
if posthog_trace_id is None:
593+
posthog_trace_id = str(uuid.uuid4())
594+
595+
start_time = time.time()
596+
response = self._original.create(**kwargs)
597+
end_time = time.time()
598+
599+
latency = end_time - start_time
600+
601+
# Extract file info
602+
file_obj = kwargs.get("file")
603+
file_name = getattr(file_obj, "name", None) if file_obj else None
604+
605+
# Extract transcription output
606+
output_text = getattr(response, "text", None)
607+
608+
# Extract duration if available (verbose_json response format)
609+
duration = getattr(response, "duration", None)
610+
611+
# Build event properties
612+
event_properties = {
613+
"$ai_provider": "openai",
614+
"$ai_model": kwargs.get("model"),
615+
"$ai_input": with_privacy_mode(
616+
self._client._ph_client,
617+
posthog_privacy_mode,
618+
file_name,
619+
),
620+
"$ai_output_text": with_privacy_mode(
621+
self._client._ph_client,
622+
posthog_privacy_mode,
623+
output_text,
624+
),
625+
"$ai_http_status": 200,
626+
"$ai_latency": latency,
627+
"$ai_trace_id": posthog_trace_id,
628+
"$ai_base_url": str(self._client.base_url),
629+
**(posthog_properties or {}),
630+
}
631+
632+
# Add optional properties
633+
if kwargs.get("language"):
634+
event_properties["$ai_language"] = kwargs.get("language")
635+
636+
if duration is not None:
637+
event_properties["$ai_audio_duration"] = duration
638+
639+
if posthog_distinct_id is None:
640+
event_properties["$process_person_profile"] = False
641+
642+
# Capture event
643+
if hasattr(self._client._ph_client, "capture"):
644+
self._client._ph_client.capture(
645+
distinct_id=posthog_distinct_id or posthog_trace_id,
646+
event="$ai_transcription",
647+
properties=event_properties,
648+
groups=posthog_groups,
649+
)
650+
651+
return response
652+
653+
537654
class WrappedBeta:
538655
"""Wrapper for OpenAI beta features that tracks usage in PostHog."""
539656

posthog/ai/openai/openai_async.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
5454
self._original_embeddings = getattr(self, "embeddings", None)
5555
self._original_beta = getattr(self, "beta", None)
5656
self._original_responses = getattr(self, "responses", None)
57+
self._original_audio = getattr(self, "audio", None)
5758

5859
# Replace with wrapped versions (only if originals exist)
5960
if self._original_chat is not None:
@@ -68,6 +69,9 @@ def __init__(self, posthog_client: Optional[PostHogClient] = None, **kwargs):
6869
if self._original_responses is not None:
6970
self.responses = WrappedResponses(self, self._original_responses)
7071

72+
if self._original_audio is not None:
73+
self.audio = WrappedAudio(self, self._original_audio)
74+
7175

7276
class WrappedResponses:
7377
"""Async wrapper for OpenAI responses that tracks usage in PostHog."""
@@ -589,6 +593,119 @@ async def create(
589593
return response
590594

591595

596+
class WrappedAudio:
597+
"""Async wrapper for OpenAI audio that tracks usage in PostHog."""
598+
599+
def __init__(self, client: AsyncOpenAI, original_audio):
600+
self._client = client
601+
self._original = original_audio
602+
603+
def __getattr__(self, name):
604+
"""Fallback to original audio object for any methods we don't explicitly handle."""
605+
return getattr(self._original, name)
606+
607+
@property
608+
def transcriptions(self):
609+
return WrappedTranscriptions(self._client, self._original.transcriptions)
610+
611+
612+
class WrappedTranscriptions:
613+
"""Async wrapper for OpenAI audio transcriptions that tracks usage in PostHog."""
614+
615+
def __init__(self, client: AsyncOpenAI, original_transcriptions):
616+
self._client = client
617+
self._original = original_transcriptions
618+
619+
def __getattr__(self, name):
620+
"""Fallback to original transcriptions object for any methods we don't explicitly handle."""
621+
return getattr(self._original, name)
622+
623+
async def create(
624+
self,
625+
posthog_distinct_id: Optional[str] = None,
626+
posthog_trace_id: Optional[str] = None,
627+
posthog_properties: Optional[Dict[str, Any]] = None,
628+
posthog_privacy_mode: bool = False,
629+
posthog_groups: Optional[Dict[str, Any]] = None,
630+
**kwargs: Any,
631+
):
632+
"""
633+
Create a transcription using OpenAI's 'audio.transcriptions.create' method,
634+
and track usage in PostHog.
635+
636+
Args:
637+
posthog_distinct_id: Optional ID to associate with the usage event.
638+
posthog_trace_id: Optional trace UUID for linking events.
639+
posthog_properties: Optional dictionary of extra properties to include in the event.
640+
posthog_privacy_mode: Whether to anonymize the input and output.
641+
posthog_groups: Optional dictionary of groups to associate with the event.
642+
**kwargs: Any additional parameters for the OpenAI Transcriptions API.
643+
644+
Returns:
645+
The response from OpenAI's audio.transcriptions.create call.
646+
"""
647+
if posthog_trace_id is None:
648+
posthog_trace_id = str(uuid.uuid4())
649+
650+
start_time = time.time()
651+
response = await self._original.create(**kwargs)
652+
end_time = time.time()
653+
654+
latency = end_time - start_time
655+
656+
# Extract file info
657+
file_obj = kwargs.get("file")
658+
file_name = getattr(file_obj, "name", None) if file_obj else None
659+
660+
# Extract transcription output
661+
output_text = getattr(response, "text", None)
662+
663+
# Extract duration if available (verbose_json response format)
664+
duration = getattr(response, "duration", None)
665+
666+
# Build event properties
667+
event_properties = {
668+
"$ai_provider": "openai",
669+
"$ai_model": kwargs.get("model"),
670+
"$ai_input": with_privacy_mode(
671+
self._client._ph_client,
672+
posthog_privacy_mode,
673+
file_name,
674+
),
675+
"$ai_output_text": with_privacy_mode(
676+
self._client._ph_client,
677+
posthog_privacy_mode,
678+
output_text,
679+
),
680+
"$ai_http_status": 200,
681+
"$ai_latency": latency,
682+
"$ai_trace_id": posthog_trace_id,
683+
"$ai_base_url": str(self._client.base_url),
684+
**(posthog_properties or {}),
685+
}
686+
687+
# Add optional properties
688+
if kwargs.get("language"):
689+
event_properties["$ai_language"] = kwargs.get("language")
690+
691+
if duration is not None:
692+
event_properties["$ai_audio_duration"] = duration
693+
694+
if posthog_distinct_id is None:
695+
event_properties["$process_person_profile"] = False
696+
697+
# Capture event
698+
if hasattr(self._client._ph_client, "capture"):
699+
self._client._ph_client.capture(
700+
distinct_id=posthog_distinct_id or posthog_trace_id,
701+
event="$ai_transcription",
702+
properties=event_properties,
703+
groups=posthog_groups,
704+
)
705+
706+
return response
707+
708+
592709
class WrappedBeta:
593710
"""Async wrapper for OpenAI beta features that tracks usage in PostHog."""
594711

0 commit comments

Comments
 (0)