Skip to content

Commit 9b3b243

Browse files
authored
Merge branch 'master' into feat/support-vertexai
2 parents 43f5740 + 07cf32b commit 9b3b243

File tree

14 files changed

+1235
-304
lines changed

14 files changed

+1235
-304
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 6.3.4 - 2025-08-04
2+
3+
- fix: Set `$ai_tools` for all providers and `$ai_output_choices` for all non-streaming provider flows properly
4+
15
# 6.3.3 - 2025-08-01
26

37
- fix: `get_feature_flag_result` now correctly returns FeatureFlagResult when payload is empty string instead of None

example.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,19 @@
5959
# get payload
6060
print(posthog.get_feature_flag_payload("beta-feature", "distinct_id"))
6161
print(posthog.get_all_flags_and_payloads("distinct_id"))
62-
exit()
63-
# # Alias a previous distinct id with a new one
62+
63+
# get feature flag result with all details (enabled, variant, payload, key, reason)
64+
result = posthog.get_feature_flag_result("beta-feature", "distinct_id")
65+
if result:
66+
print(f"Flag key: {result.key}")
67+
print(f"Flag enabled: {result.enabled}")
68+
print(f"Variant: {result.variant}")
69+
print(f"Payload: {result.payload}")
70+
print(f"Reason: {result.reason}")
71+
# get_value() returns the variant if it exists, otherwise the enabled value
72+
print(f"Value (variant or enabled): {result.get_value()}")
73+
74+
# Alias a previous distinct id with a new one
6475

6576
posthog.alias("distinct_id", "new_distinct_id")
6677

posthog/__init__.py

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
set_context_session as inner_set_context_session,
1212
identify_context as inner_identify_context,
1313
)
14-
from posthog.types import FeatureFlag, FlagsAndPayloads
14+
from posthog.types import FeatureFlag, FlagsAndPayloads, FeatureFlagResult
1515
from posthog.version import VERSION
1616

1717
__version__ = VERSION
@@ -388,9 +388,9 @@ def capture_exception(
388388
def feature_enabled(
389389
key, # type: str
390390
distinct_id, # type: str
391-
groups={}, # type: dict
392-
person_properties={}, # type: dict
393-
group_properties={}, # type: dict
391+
groups=None, # type: Optional[dict]
392+
person_properties=None, # type: Optional[dict]
393+
group_properties=None, # type: Optional[dict]
394394
only_evaluate_locally=False, # type: bool
395395
send_feature_flag_events=True, # type: bool
396396
disable_geoip=None, # type: Optional[bool]
@@ -427,9 +427,9 @@ def feature_enabled(
427427
"feature_enabled",
428428
key=key,
429429
distinct_id=distinct_id,
430-
groups=groups,
431-
person_properties=person_properties,
432-
group_properties=group_properties,
430+
groups=groups or {},
431+
person_properties=person_properties or {},
432+
group_properties=group_properties or {},
433433
only_evaluate_locally=only_evaluate_locally,
434434
send_feature_flag_events=send_feature_flag_events,
435435
disable_geoip=disable_geoip,
@@ -439,9 +439,9 @@ def feature_enabled(
439439
def get_feature_flag(
440440
key, # type: str
441441
distinct_id, # type: str
442-
groups={}, # type: dict
443-
person_properties={}, # type: dict
444-
group_properties={}, # type: dict
442+
groups=None, # type: Optional[dict]
443+
person_properties=None, # type: Optional[dict]
444+
group_properties=None, # type: Optional[dict]
445445
only_evaluate_locally=False, # type: bool
446446
send_feature_flag_events=True, # type: bool
447447
disable_geoip=None, # type: Optional[bool]
@@ -477,9 +477,9 @@ def get_feature_flag(
477477
"get_feature_flag",
478478
key=key,
479479
distinct_id=distinct_id,
480-
groups=groups,
481-
person_properties=person_properties,
482-
group_properties=group_properties,
480+
groups=groups or {},
481+
person_properties=person_properties or {},
482+
group_properties=group_properties or {},
483483
only_evaluate_locally=only_evaluate_locally,
484484
send_feature_flag_events=send_feature_flag_events,
485485
disable_geoip=disable_geoip,
@@ -488,9 +488,9 @@ def get_feature_flag(
488488

489489
def get_all_flags(
490490
distinct_id, # type: str
491-
groups={}, # type: dict
492-
person_properties={}, # type: dict
493-
group_properties={}, # type: dict
491+
groups=None, # type: Optional[dict]
492+
person_properties=None, # type: Optional[dict]
493+
group_properties=None, # type: Optional[dict]
494494
only_evaluate_locally=False, # type: bool
495495
disable_geoip=None, # type: Optional[bool]
496496
) -> Optional[dict[str, FeatureFlag]]:
@@ -520,21 +520,64 @@ def get_all_flags(
520520
return _proxy(
521521
"get_all_flags",
522522
distinct_id=distinct_id,
523-
groups=groups,
524-
person_properties=person_properties,
525-
group_properties=group_properties,
523+
groups=groups or {},
524+
person_properties=person_properties or {},
525+
group_properties=group_properties or {},
526526
only_evaluate_locally=only_evaluate_locally,
527527
disable_geoip=disable_geoip,
528528
)
529529

530530

531+
def get_feature_flag_result(
532+
key,
533+
distinct_id,
534+
groups=None, # type: Optional[dict]
535+
person_properties=None, # type: Optional[dict]
536+
group_properties=None, # type: Optional[dict]
537+
only_evaluate_locally=False,
538+
send_feature_flag_events=True,
539+
disable_geoip=None, # type: Optional[bool]
540+
):
541+
# type: (...) -> Optional[FeatureFlagResult]
542+
"""
543+
Get a FeatureFlagResult object which contains the flag result and payload.
544+
545+
This method evaluates a feature flag and returns a FeatureFlagResult object containing:
546+
- enabled: Whether the flag is enabled
547+
- variant: The variant value if the flag has variants
548+
- payload: The payload associated with the flag (automatically deserialized from JSON)
549+
- key: The flag key
550+
- reason: Why the flag was enabled/disabled
551+
552+
Example:
553+
```python
554+
result = posthog.get_feature_flag_result('beta-feature', 'distinct_id')
555+
if result and result.enabled:
556+
# Use the variant and payload
557+
print(f"Variant: {result.variant}")
558+
print(f"Payload: {result.payload}")
559+
```
560+
"""
561+
return _proxy(
562+
"get_feature_flag_result",
563+
key=key,
564+
distinct_id=distinct_id,
565+
groups=groups or {},
566+
person_properties=person_properties or {},
567+
group_properties=group_properties or {},
568+
only_evaluate_locally=only_evaluate_locally,
569+
send_feature_flag_events=send_feature_flag_events,
570+
disable_geoip=disable_geoip,
571+
)
572+
573+
531574
def get_feature_flag_payload(
532575
key,
533576
distinct_id,
534577
match_value=None,
535-
groups={},
536-
person_properties={},
537-
group_properties={},
578+
groups=None, # type: Optional[dict]
579+
person_properties=None, # type: Optional[dict]
580+
group_properties=None, # type: Optional[dict]
538581
only_evaluate_locally=False,
539582
send_feature_flag_events=True,
540583
disable_geoip=None, # type: Optional[bool]
@@ -544,9 +587,9 @@ def get_feature_flag_payload(
544587
key=key,
545588
distinct_id=distinct_id,
546589
match_value=match_value,
547-
groups=groups,
548-
person_properties=person_properties,
549-
group_properties=group_properties,
590+
groups=groups or {},
591+
person_properties=person_properties or {},
592+
group_properties=group_properties or {},
550593
only_evaluate_locally=only_evaluate_locally,
551594
send_feature_flag_events=send_feature_flag_events,
552595
disable_geoip=disable_geoip,
@@ -575,18 +618,18 @@ def get_remote_config_payload(
575618

576619
def get_all_flags_and_payloads(
577620
distinct_id,
578-
groups={},
579-
person_properties={},
580-
group_properties={},
621+
groups=None, # type: Optional[dict]
622+
person_properties=None, # type: Optional[dict]
623+
group_properties=None, # type: Optional[dict]
581624
only_evaluate_locally=False,
582625
disable_geoip=None, # type: Optional[bool]
583626
) -> FlagsAndPayloads:
584627
return _proxy(
585628
"get_all_flags_and_payloads",
586629
distinct_id=distinct_id,
587-
groups=groups,
588-
person_properties=person_properties,
589-
group_properties=group_properties,
630+
groups=groups or {},
631+
person_properties=person_properties or {},
632+
group_properties=group_properties or {},
590633
only_evaluate_locally=only_evaluate_locally,
591634
disable_geoip=disable_geoip,
592635
)

posthog/ai/langchain/callbacks.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -556,12 +556,9 @@ def _capture_generation(
556556
"$ai_latency": run.latency,
557557
"$ai_base_url": run.base_url,
558558
}
559+
559560
if run.tools:
560-
event_properties["$ai_tools"] = with_privacy_mode(
561-
self._ph_client,
562-
self._privacy_mode,
563-
run.tools,
564-
)
561+
event_properties["$ai_tools"] = run.tools
565562

566563
if isinstance(output, BaseException):
567564
event_properties["$ai_http_status"] = _get_http_status(output)
@@ -587,7 +584,8 @@ def _capture_generation(
587584
]
588585
else:
589586
completions = [
590-
_extract_raw_esponse(generation) for generation in generation_result
587+
_extract_raw_response(generation)
588+
for generation in generation_result
591589
]
592590
event_properties["$ai_output_choices"] = with_privacy_mode(
593591
self._ph_client, self._privacy_mode, completions
@@ -618,7 +616,7 @@ def _log_debug_event(
618616
)
619617

620618

621-
def _extract_raw_esponse(last_response):
619+
def _extract_raw_response(last_response):
622620
"""Extract the response from the last response of the LLM call."""
623621
# We return the text of the response if not empty
624622
if last_response.text is not None and last_response.text.strip() != "":

posthog/ai/openai/openai.py

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from posthog.ai.utils import (
1313
call_llm_and_track_usage,
14+
extract_available_tool_calls,
1415
get_model_params,
1516
with_privacy_mode,
1617
)
@@ -167,6 +168,7 @@ def generator():
167168
usage_stats,
168169
latency,
169170
output,
171+
extract_available_tool_calls("openai", kwargs),
170172
)
171173

172174
return generator()
@@ -182,7 +184,7 @@ def _capture_streaming_event(
182184
usage_stats: Dict[str, int],
183185
latency: float,
184186
output: Any,
185-
tool_calls: Optional[List[Dict[str, Any]]] = None,
187+
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
186188
):
187189
if posthog_trace_id is None:
188190
posthog_trace_id = str(uuid.uuid4())
@@ -212,12 +214,8 @@ def _capture_streaming_event(
212214
**(posthog_properties or {}),
213215
}
214216

215-
if tool_calls:
216-
event_properties["$ai_tools"] = with_privacy_mode(
217-
self._client._ph_client,
218-
posthog_privacy_mode,
219-
tool_calls,
220-
)
217+
if available_tool_calls:
218+
event_properties["$ai_tools"] = available_tool_calls
221219

222220
if posthog_distinct_id is None:
223221
event_properties["$process_person_profile"] = False
@@ -341,7 +339,6 @@ def _create_streaming(
341339
start_time = time.time()
342340
usage_stats: Dict[str, int] = {}
343341
accumulated_content = []
344-
accumulated_tools = {}
345342
if "stream_options" not in kwargs:
346343
kwargs["stream_options"] = {}
347344
kwargs["stream_options"]["include_usage"] = True
@@ -350,7 +347,6 @@ def _create_streaming(
350347
def generator():
351348
nonlocal usage_stats
352349
nonlocal accumulated_content # noqa: F824
353-
nonlocal accumulated_tools # noqa: F824
354350

355351
try:
356352
for chunk in response:
@@ -389,31 +385,12 @@ def generator():
389385
if content:
390386
accumulated_content.append(content)
391387

392-
# Process tool calls
393-
tool_calls = getattr(chunk.choices[0].delta, "tool_calls", None)
394-
if tool_calls:
395-
for tool_call in tool_calls:
396-
index = tool_call.index
397-
if index not in accumulated_tools:
398-
accumulated_tools[index] = tool_call
399-
else:
400-
# Append arguments for existing tool calls
401-
if hasattr(tool_call, "function") and hasattr(
402-
tool_call.function, "arguments"
403-
):
404-
accumulated_tools[
405-
index
406-
].function.arguments += (
407-
tool_call.function.arguments
408-
)
409-
410388
yield chunk
411389

412390
finally:
413391
end_time = time.time()
414392
latency = end_time - start_time
415393
output = "".join(accumulated_content)
416-
tools = list(accumulated_tools.values()) if accumulated_tools else None
417394
self._capture_streaming_event(
418395
posthog_distinct_id,
419396
posthog_trace_id,
@@ -424,7 +401,7 @@ def generator():
424401
usage_stats,
425402
latency,
426403
output,
427-
tools,
404+
extract_available_tool_calls("openai", kwargs),
428405
)
429406

430407
return generator()
@@ -440,7 +417,7 @@ def _capture_streaming_event(
440417
usage_stats: Dict[str, int],
441418
latency: float,
442419
output: Any,
443-
tool_calls: Optional[List[Dict[str, Any]]] = None,
420+
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
444421
):
445422
if posthog_trace_id is None:
446423
posthog_trace_id = str(uuid.uuid4())
@@ -470,12 +447,8 @@ def _capture_streaming_event(
470447
**(posthog_properties or {}),
471448
}
472449

473-
if tool_calls:
474-
event_properties["$ai_tools"] = with_privacy_mode(
475-
self._client._ph_client,
476-
posthog_privacy_mode,
477-
tool_calls,
478-
)
450+
if available_tool_calls:
451+
event_properties["$ai_tools"] = available_tool_calls
479452

480453
if posthog_distinct_id is None:
481454
event_properties["$process_person_profile"] = False

0 commit comments

Comments
 (0)