posthoganalytics 6.7.1__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.
- posthoganalytics/ai/anthropic/__init__.py +10 -0
- posthoganalytics/ai/anthropic/anthropic.py +93 -63
- posthoganalytics/ai/anthropic/anthropic_async.py +86 -20
- posthoganalytics/ai/anthropic/anthropic_converter.py +393 -0
- posthoganalytics/ai/gemini/__init__.py +12 -1
- posthoganalytics/ai/gemini/gemini.py +60 -67
- posthoganalytics/ai/gemini/gemini_converter.py +438 -0
- posthoganalytics/ai/openai/__init__.py +16 -1
- posthoganalytics/ai/openai/openai.py +110 -151
- posthoganalytics/ai/openai/openai_async.py +62 -70
- posthoganalytics/ai/openai/openai_converter.py +585 -0
- posthoganalytics/ai/types.py +142 -0
- posthoganalytics/ai/utils.py +205 -253
- posthoganalytics/client.py +7 -7
- posthoganalytics/test/test_feature_flags.py +2 -2
- posthoganalytics/version.py +1 -1
- {posthoganalytics-6.7.1.dist-info → posthoganalytics-6.7.2.dist-info}/METADATA +1 -1
- {posthoganalytics-6.7.1.dist-info → posthoganalytics-6.7.2.dist-info}/RECORD +21 -17
- {posthoganalytics-6.7.1.dist-info → posthoganalytics-6.7.2.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.7.1.dist-info → posthoganalytics-6.7.2.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.7.1.dist-info → posthoganalytics-6.7.2.dist-info}/top_level.txt +0 -0
|
@@ -12,9 +12,15 @@ except ImportError:
|
|
|
12
12
|
from posthoganalytics.ai.utils import (
|
|
13
13
|
call_llm_and_track_usage,
|
|
14
14
|
extract_available_tool_calls,
|
|
15
|
-
|
|
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
|
+
)
|
|
18
24
|
from posthoganalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
|
|
19
25
|
from posthoganalytics.client import Client as PostHogClient
|
|
20
26
|
from posthoganalytics import setup
|
|
@@ -34,6 +40,7 @@ class OpenAI(openai.OpenAI):
|
|
|
34
40
|
posthog_client: If provided, events will be captured via this client instead of the global `posthog`.
|
|
35
41
|
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
|
|
36
42
|
"""
|
|
43
|
+
|
|
37
44
|
super().__init__(**kwargs)
|
|
38
45
|
self._ph_client = posthog_client or setup()
|
|
39
46
|
|
|
@@ -123,35 +130,17 @@ class WrappedResponses:
|
|
|
123
130
|
|
|
124
131
|
try:
|
|
125
132
|
for chunk in response:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
)
|
|
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)
|
|
155
144
|
|
|
156
145
|
yield chunk
|
|
157
146
|
|
|
@@ -169,7 +158,7 @@ class WrappedResponses:
|
|
|
169
158
|
usage_stats,
|
|
170
159
|
latency,
|
|
171
160
|
output,
|
|
172
|
-
|
|
161
|
+
None, # Responses API doesn't have tools
|
|
173
162
|
)
|
|
174
163
|
|
|
175
164
|
return generator()
|
|
@@ -187,49 +176,36 @@ class WrappedResponses:
|
|
|
187
176
|
output: Any,
|
|
188
177
|
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
189
178
|
):
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
**(posthog_properties or {}),
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if available_tool_calls:
|
|
221
|
-
event_properties["$ai_tools"] = available_tool_calls
|
|
222
|
-
|
|
223
|
-
if posthog_distinct_id is None:
|
|
224
|
-
event_properties["$process_person_profile"] = False
|
|
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
|
+
)
|
|
225
206
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
229
|
-
event="$ai_generation",
|
|
230
|
-
properties=event_properties,
|
|
231
|
-
groups=posthog_groups,
|
|
232
|
-
)
|
|
207
|
+
# Use the common capture function
|
|
208
|
+
capture_streaming_event(self._client._ph_client, event_data)
|
|
233
209
|
|
|
234
210
|
def parse(
|
|
235
211
|
self,
|
|
@@ -342,6 +318,7 @@ class WrappedCompletions:
|
|
|
342
318
|
start_time = time.time()
|
|
343
319
|
usage_stats: Dict[str, int] = {}
|
|
344
320
|
accumulated_content = []
|
|
321
|
+
accumulated_tool_calls: Dict[int, Dict[str, Any]] = {}
|
|
345
322
|
if "stream_options" not in kwargs:
|
|
346
323
|
kwargs["stream_options"] = {}
|
|
347
324
|
kwargs["stream_options"]["include_usage"] = True
|
|
@@ -350,50 +327,42 @@ class WrappedCompletions:
|
|
|
350
327
|
def generator():
|
|
351
328
|
nonlocal usage_stats
|
|
352
329
|
nonlocal accumulated_content # noqa: F824
|
|
330
|
+
nonlocal accumulated_tool_calls
|
|
353
331
|
|
|
354
332
|
try:
|
|
355
333
|
for chunk in response:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
375
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
376
|
-
):
|
|
377
|
-
usage_stats["reasoning_tokens"] = (
|
|
378
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
if (
|
|
382
|
-
hasattr(chunk, "choices")
|
|
383
|
-
and chunk.choices
|
|
384
|
-
and len(chunk.choices) > 0
|
|
385
|
-
):
|
|
386
|
-
if chunk.choices[0].delta and chunk.choices[0].delta.content:
|
|
387
|
-
content = chunk.choices[0].delta.content
|
|
388
|
-
if content:
|
|
389
|
-
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
|
+
)
|
|
390
352
|
|
|
391
353
|
yield chunk
|
|
392
354
|
|
|
393
355
|
finally:
|
|
394
356
|
end_time = time.time()
|
|
395
357
|
latency = end_time - start_time
|
|
396
|
-
|
|
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
|
+
|
|
397
366
|
self._capture_streaming_event(
|
|
398
367
|
posthog_distinct_id,
|
|
399
368
|
posthog_trace_id,
|
|
@@ -403,7 +372,8 @@ class WrappedCompletions:
|
|
|
403
372
|
kwargs,
|
|
404
373
|
usage_stats,
|
|
405
374
|
latency,
|
|
406
|
-
|
|
375
|
+
accumulated_content,
|
|
376
|
+
tool_calls_list,
|
|
407
377
|
extract_available_tool_calls("openai", kwargs),
|
|
408
378
|
)
|
|
409
379
|
|
|
@@ -420,51 +390,39 @@ class WrappedCompletions:
|
|
|
420
390
|
usage_stats: Dict[str, int],
|
|
421
391
|
latency: float,
|
|
422
392
|
output: Any,
|
|
393
|
+
tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
423
394
|
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
424
395
|
):
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
**(posthog_properties or {}),
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if available_tool_calls:
|
|
456
|
-
event_properties["$ai_tools"] = available_tool_calls
|
|
457
|
-
|
|
458
|
-
if posthog_distinct_id is None:
|
|
459
|
-
event_properties["$process_person_profile"] = False
|
|
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
|
+
)
|
|
460
423
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
464
|
-
event="$ai_generation",
|
|
465
|
-
properties=event_properties,
|
|
466
|
-
groups=posthog_groups,
|
|
467
|
-
)
|
|
424
|
+
# Use the common capture function
|
|
425
|
+
capture_streaming_event(self._client._ph_client, event_data)
|
|
468
426
|
|
|
469
427
|
|
|
470
428
|
class WrappedEmbeddings:
|
|
@@ -501,6 +459,7 @@ class WrappedEmbeddings:
|
|
|
501
459
|
Returns:
|
|
502
460
|
The response from OpenAI's embeddings.create call.
|
|
503
461
|
"""
|
|
462
|
+
|
|
504
463
|
if posthog_trace_id is None:
|
|
505
464
|
posthog_trace_id = str(uuid.uuid4())
|
|
506
465
|
|
|
@@ -14,8 +14,16 @@ 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
|
+
)
|
|
19
27
|
from posthoganalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
|
|
20
28
|
from posthoganalytics.client import Client as PostHogClient
|
|
21
29
|
|
|
@@ -35,6 +43,7 @@ class AsyncOpenAI(openai.AsyncOpenAI):
|
|
|
35
43
|
of the global posthog.
|
|
36
44
|
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
|
|
37
45
|
"""
|
|
46
|
+
|
|
38
47
|
super().__init__(**kwargs)
|
|
39
48
|
self._ph_client = posthog_client or setup()
|
|
40
49
|
|
|
@@ -67,6 +76,7 @@ class WrappedResponses:
|
|
|
67
76
|
|
|
68
77
|
def __getattr__(self, name):
|
|
69
78
|
"""Fallback to original responses object for any methods we don't explicitly handle."""
|
|
79
|
+
|
|
70
80
|
return getattr(self._original, name)
|
|
71
81
|
|
|
72
82
|
async def create(
|
|
@@ -116,7 +126,7 @@ class WrappedResponses:
|
|
|
116
126
|
start_time = time.time()
|
|
117
127
|
usage_stats: Dict[str, int] = {}
|
|
118
128
|
final_content = []
|
|
119
|
-
response =
|
|
129
|
+
response = self._original.create(**kwargs)
|
|
120
130
|
|
|
121
131
|
async def async_generator():
|
|
122
132
|
nonlocal usage_stats
|
|
@@ -124,35 +134,17 @@ class WrappedResponses:
|
|
|
124
134
|
|
|
125
135
|
try:
|
|
126
136
|
async for chunk in response:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"total_tokens",
|
|
139
|
-
]
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
# Add support for cached tokens
|
|
143
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
144
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
145
|
-
):
|
|
146
|
-
usage_stats["reasoning_tokens"] = (
|
|
147
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
if hasattr(chunk.usage, "input_tokens_details") and hasattr(
|
|
151
|
-
chunk.usage.input_tokens_details, "cached_tokens"
|
|
152
|
-
):
|
|
153
|
-
usage_stats["cache_read_input_tokens"] = (
|
|
154
|
-
chunk.usage.input_tokens_details.cached_tokens
|
|
155
|
-
)
|
|
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)
|
|
156
148
|
|
|
157
149
|
yield chunk
|
|
158
150
|
|
|
@@ -160,6 +152,7 @@ class WrappedResponses:
|
|
|
160
152
|
end_time = time.time()
|
|
161
153
|
latency = end_time - start_time
|
|
162
154
|
output = final_content
|
|
155
|
+
|
|
163
156
|
await self._capture_streaming_event(
|
|
164
157
|
posthog_distinct_id,
|
|
165
158
|
posthog_trace_id,
|
|
@@ -203,7 +196,7 @@ class WrappedResponses:
|
|
|
203
196
|
"$ai_output_choices": with_privacy_mode(
|
|
204
197
|
self._client._ph_client,
|
|
205
198
|
posthog_privacy_mode,
|
|
206
|
-
output,
|
|
199
|
+
format_openai_streaming_output(output, "responses"),
|
|
207
200
|
),
|
|
208
201
|
"$ai_http_status": 200,
|
|
209
202
|
"$ai_input_tokens": usage_stats.get("input_tokens", 0),
|
|
@@ -345,59 +338,50 @@ class WrappedCompletions:
|
|
|
345
338
|
start_time = time.time()
|
|
346
339
|
usage_stats: Dict[str, int] = {}
|
|
347
340
|
accumulated_content = []
|
|
341
|
+
accumulated_tool_calls: Dict[int, Dict[str, Any]] = {}
|
|
348
342
|
|
|
349
343
|
if "stream_options" not in kwargs:
|
|
350
344
|
kwargs["stream_options"] = {}
|
|
351
345
|
kwargs["stream_options"]["include_usage"] = True
|
|
352
|
-
response =
|
|
346
|
+
response = self._original.create(**kwargs)
|
|
353
347
|
|
|
354
348
|
async def async_generator():
|
|
355
349
|
nonlocal usage_stats
|
|
356
350
|
nonlocal accumulated_content # noqa: F824
|
|
351
|
+
nonlocal accumulated_tool_calls
|
|
357
352
|
|
|
358
353
|
try:
|
|
359
354
|
async for chunk in response:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
379
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
380
|
-
):
|
|
381
|
-
usage_stats["reasoning_tokens"] = (
|
|
382
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
hasattr(chunk, "choices")
|
|
387
|
-
and chunk.choices
|
|
388
|
-
and len(chunk.choices) > 0
|
|
389
|
-
):
|
|
390
|
-
if chunk.choices[0].delta and chunk.choices[0].delta.content:
|
|
391
|
-
content = chunk.choices[0].delta.content
|
|
392
|
-
if content:
|
|
393
|
-
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
|
+
)
|
|
394
371
|
|
|
395
372
|
yield chunk
|
|
396
373
|
|
|
397
374
|
finally:
|
|
398
375
|
end_time = time.time()
|
|
399
376
|
latency = end_time - start_time
|
|
400
|
-
|
|
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
|
+
|
|
401
385
|
await self._capture_streaming_event(
|
|
402
386
|
posthog_distinct_id,
|
|
403
387
|
posthog_trace_id,
|
|
@@ -407,7 +391,8 @@ class WrappedCompletions:
|
|
|
407
391
|
kwargs,
|
|
408
392
|
usage_stats,
|
|
409
393
|
latency,
|
|
410
|
-
|
|
394
|
+
accumulated_content,
|
|
395
|
+
tool_calls_list,
|
|
411
396
|
extract_available_tool_calls("openai", kwargs),
|
|
412
397
|
)
|
|
413
398
|
|
|
@@ -424,6 +409,7 @@ class WrappedCompletions:
|
|
|
424
409
|
usage_stats: Dict[str, int],
|
|
425
410
|
latency: float,
|
|
426
411
|
output: Any,
|
|
412
|
+
tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
427
413
|
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
428
414
|
):
|
|
429
415
|
if posthog_trace_id is None:
|
|
@@ -441,7 +427,7 @@ class WrappedCompletions:
|
|
|
441
427
|
"$ai_output_choices": with_privacy_mode(
|
|
442
428
|
self._client._ph_client,
|
|
443
429
|
posthog_privacy_mode,
|
|
444
|
-
|
|
430
|
+
format_openai_streaming_output(output, "chat", tool_calls),
|
|
445
431
|
),
|
|
446
432
|
"$ai_http_status": 200,
|
|
447
433
|
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
|
|
@@ -480,6 +466,7 @@ class WrappedEmbeddings:
|
|
|
480
466
|
|
|
481
467
|
def __getattr__(self, name):
|
|
482
468
|
"""Fallback to original embeddings object for any methods we don't explicitly handle."""
|
|
469
|
+
|
|
483
470
|
return getattr(self._original, name)
|
|
484
471
|
|
|
485
472
|
async def create(
|
|
@@ -505,15 +492,17 @@ class WrappedEmbeddings:
|
|
|
505
492
|
Returns:
|
|
506
493
|
The response from OpenAI's embeddings.create call.
|
|
507
494
|
"""
|
|
495
|
+
|
|
508
496
|
if posthog_trace_id is None:
|
|
509
497
|
posthog_trace_id = str(uuid.uuid4())
|
|
510
498
|
|
|
511
499
|
start_time = time.time()
|
|
512
|
-
response =
|
|
500
|
+
response = self._original.create(**kwargs)
|
|
513
501
|
end_time = time.time()
|
|
514
502
|
|
|
515
503
|
# Extract usage statistics if available
|
|
516
504
|
usage_stats = {}
|
|
505
|
+
|
|
517
506
|
if hasattr(response, "usage") and response.usage:
|
|
518
507
|
usage_stats = {
|
|
519
508
|
"prompt_tokens": getattr(response.usage, "prompt_tokens", 0),
|
|
@@ -563,6 +552,7 @@ class WrappedBeta:
|
|
|
563
552
|
|
|
564
553
|
def __getattr__(self, name):
|
|
565
554
|
"""Fallback to original beta object for any methods we don't explicitly handle."""
|
|
555
|
+
|
|
566
556
|
return getattr(self._original, name)
|
|
567
557
|
|
|
568
558
|
@property
|
|
@@ -579,6 +569,7 @@ class WrappedBetaChat:
|
|
|
579
569
|
|
|
580
570
|
def __getattr__(self, name):
|
|
581
571
|
"""Fallback to original beta chat object for any methods we don't explicitly handle."""
|
|
572
|
+
|
|
582
573
|
return getattr(self._original, name)
|
|
583
574
|
|
|
584
575
|
@property
|
|
@@ -595,6 +586,7 @@ class WrappedBetaCompletions:
|
|
|
595
586
|
|
|
596
587
|
def __getattr__(self, name):
|
|
597
588
|
"""Fallback to original beta completions object for any methods we don't explicitly handle."""
|
|
589
|
+
|
|
598
590
|
return getattr(self._original, name)
|
|
599
591
|
|
|
600
592
|
async def parse(
|