Skip to content

Commit 61b6b8c

Browse files
committed
feat: embeddings
1 parent 8a9efcf commit 61b6b8c

File tree

3 files changed

+141
-9
lines changed

3 files changed

+141
-9
lines changed

llm_observability_examples.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
def main_sync():
2525
trace_id = str(uuid.uuid4())
2626
print("Trace ID:", trace_id)
27-
distinct_id = "test_distinct_id"
27+
distinct_id = "test2_distinct_id"
2828
properties = {"test_property": "test_value"}
2929

3030
try:
3131
basic_openai_call(distinct_id, trace_id, properties)
3232
streaming_openai_call(distinct_id, trace_id, properties)
33+
embedding_openai_call(distinct_id, trace_id, properties)
3334
image_openai_call()
34-
embedding_openai_call()
3535
except Exception as e:
3636
print("Error during OpenAI call:", str(e))
3737

@@ -45,7 +45,7 @@ async def main_async():
4545
try:
4646
await basic_async_openai_call(distinct_id, trace_id, properties)
4747
await streaming_async_openai_call(distinct_id, trace_id, properties)
48-
await embedding_async_openai_call()
48+
await embedding_async_openai_call(distinct_id, trace_id, properties)
4949
await image_async_openai_call()
5050
except Exception as e:
5151
print("Error during OpenAI call:", str(e))
@@ -150,13 +150,13 @@ async def image_async_openai_call():
150150
return response
151151

152152

153-
def embedding_openai_call():
154-
response = openai_client.embeddings.create(input="The hedgehog is cute", model="text-embedding-3-small")
153+
def embedding_openai_call(posthog_distinct_id, posthog_trace_id, posthog_properties):
154+
response = openai_client.embeddings.create(input="The hedgehog is cute", model="text-embedding-3-small", posthog_distinct_id=posthog_distinct_id, posthog_trace_id=posthog_trace_id, posthog_properties=posthog_properties)
155155
print(response)
156156
return response
157157

158-
async def embedding_async_openai_call():
159-
response = await async_openai_client.embeddings.create(input="The hedgehog is cute", model="text-embedding-3-small")
158+
async def embedding_async_openai_call(posthog_distinct_id, posthog_trace_id, posthog_properties):
159+
response = await async_openai_client.embeddings.create(input="The hedgehog is cute", model="text-embedding-3-small", posthog_distinct_id=posthog_distinct_id, posthog_trace_id=posthog_trace_id, posthog_properties=posthog_properties)
160160
print(response)
161161
return response
162162

posthog/ai/openai/openai.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(self, posthog_client: PostHogClient, **kwargs):
3030
super().__init__(**kwargs)
3131
self._ph_client = posthog_client
3232
self.chat = WrappedChat(self)
33+
self.embeddings = WrappedEmbeddings(self)
3334

3435

3536
class WrappedChat(openai.resources.chat.Chat):
@@ -167,4 +168,70 @@ def _capture_streaming_event(
167168
distinct_id=posthog_distinct_id or posthog_trace_id,
168169
event="$ai_generation",
169170
properties=event_properties,
170-
)
171+
)
172+
173+
174+
class WrappedEmbeddings(openai.resources.embeddings.Embeddings):
175+
_client: OpenAI
176+
177+
def create(
178+
self,
179+
posthog_distinct_id: Optional[str] = None,
180+
posthog_trace_id: Optional[str] = None,
181+
posthog_properties: Optional[Dict[str, Any]] = None,
182+
**kwargs: Any,
183+
):
184+
"""
185+
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in PostHog.
186+
187+
Args:
188+
posthog_distinct_id: Optional ID to associate with the usage event.
189+
posthog_trace_id: Optional trace UUID for linking events.
190+
posthog_properties: Optional dictionary of extra properties to include in the event.
191+
**kwargs: Any additional parameters for the OpenAI Embeddings API.
192+
193+
Returns:
194+
The response from OpenAI's embeddings.create call.
195+
"""
196+
if posthog_trace_id is None:
197+
posthog_trace_id = uuid.uuid4()
198+
199+
start_time = time.time()
200+
response = super().create(**kwargs)
201+
end_time = time.time()
202+
203+
# Extract usage statistics if available
204+
usage_stats = {}
205+
if hasattr(response, "usage") and response.usage:
206+
usage_stats = {
207+
"prompt_tokens": getattr(response.usage, "prompt_tokens", 0),
208+
"total_tokens": getattr(response.usage, "total_tokens", 0),
209+
}
210+
211+
latency = end_time - start_time
212+
213+
# Build the event properties
214+
event_properties = {
215+
"$ai_provider": "openai",
216+
"$ai_model": kwargs.get("model"),
217+
"$ai_input": kwargs.get("input"),
218+
"$ai_http_status": 200,
219+
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
220+
"$ai_latency": latency,
221+
"$ai_trace_id": posthog_trace_id,
222+
"$ai_base_url": str(self._client.base_url),
223+
**posthog_properties,
224+
}
225+
226+
if posthog_distinct_id is None:
227+
event_properties["$process_person_profile"] = False
228+
229+
# Send capture event for embeddings
230+
if hasattr(self._client._ph_client, "capture"):
231+
self._client._ph_client.capture(
232+
distinct_id=posthog_distinct_id or posthog_trace_id,
233+
event="$ai_embedding",
234+
properties=event_properties,
235+
)
236+
237+
return response

posthog/ai/openai/openai_async.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __init__(self, posthog_client: PostHogClient, **kwargs):
2929
super().__init__(**kwargs)
3030
self._ph_client = posthog_client
3131
self.chat = WrappedChat(self)
32-
32+
self.embeddings = WrappedEmbeddings(self)
3333

3434
class WrappedChat(openai.resources.chat.AsyncChat):
3535
_client: AsyncOpenAI
@@ -167,3 +167,68 @@ def _capture_streaming_event(
167167
event="$ai_generation",
168168
properties=event_properties,
169169
)
170+
171+
class WrappedEmbeddings(openai.resources.embeddings.AsyncEmbeddings):
172+
_client: AsyncOpenAI
173+
174+
async def create(
175+
self,
176+
posthog_distinct_id: Optional[str] = None,
177+
posthog_trace_id: Optional[str] = None,
178+
posthog_properties: Optional[Dict[str, Any]] = None,
179+
**kwargs: Any,
180+
):
181+
"""
182+
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in PostHog.
183+
184+
Args:
185+
posthog_distinct_id: Optional ID to associate with the usage event.
186+
posthog_trace_id: Optional trace UUID for linking events.
187+
posthog_properties: Optional dictionary of extra properties to include in the event.
188+
**kwargs: Any additional parameters for the OpenAI Embeddings API.
189+
190+
Returns:
191+
The response from OpenAI's embeddings.create call.
192+
"""
193+
if posthog_trace_id is None:
194+
posthog_trace_id = uuid.uuid4()
195+
196+
start_time = time.time()
197+
response = await super().create(**kwargs)
198+
end_time = time.time()
199+
200+
# Extract usage statistics if available
201+
usage_stats = {}
202+
if hasattr(response, "usage") and response.usage:
203+
usage_stats = {
204+
"prompt_tokens": getattr(response.usage, "prompt_tokens", 0),
205+
"total_tokens": getattr(response.usage, "total_tokens", 0),
206+
}
207+
208+
latency = end_time - start_time
209+
210+
# Build the event properties
211+
event_properties = {
212+
"$ai_provider": "openai",
213+
"$ai_model": kwargs.get("model"),
214+
"$ai_input": kwargs.get("input"),
215+
"$ai_http_status": 200,
216+
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
217+
"$ai_latency": latency,
218+
"$ai_trace_id": posthog_trace_id,
219+
"$ai_base_url": str(self._client.base_url),
220+
**posthog_properties,
221+
}
222+
223+
if posthog_distinct_id is None:
224+
event_properties["$process_person_profile"] = False
225+
226+
# Send capture event for embeddings
227+
if hasattr(self._client._ph_client, "capture"):
228+
self._client._ph_client.capture(
229+
distinct_id=posthog_distinct_id or posthog_trace_id,
230+
event="$ai_embedding",
231+
properties=event_properties,
232+
)
233+
234+
return response

0 commit comments

Comments
 (0)