Skip to content

Commit 91ad399

Browse files
committed
fix(llma): fix $process_person_profile with outer context distinct_id, add tests
- Fix personless check to consider outer context distinct_id (not just the explicit param), so events from users who set distinct_id via outer context are not incorrectly marked as personless. - Fix typo: "district_id" -> "distinct_id" in comments. - Add test coverage for distinct_id resolution: no id (personless), explicit param, outer context, and explicit overriding outer context.
1 parent cd0c2f5 commit 91ad399

2 files changed

Lines changed: 98 additions & 6 deletions

File tree

‎posthog/ai/utils.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,11 @@ def call_llm_and_track_usage(
366366
if posthog_trace_id is None:
367367
posthog_trace_id = str(uuid.uuid4())
368368

369-
if not contexts.get_context_distinct_id():
370-
# Use trace_id as district_id if it's not defined.
369+
# Check if we have a real user distinct_id (from param or outer context)
370+
has_person_distinct_id = posthog_distinct_id is not None or contexts.get_context_distinct_id() is not None
371+
372+
if not has_person_distinct_id:
373+
# Fall back to trace_id as distinct_id when no real user id is available.
371374
identify_context(posthog_trace_id)
372375

373376
if response and (
@@ -425,7 +428,7 @@ def call_llm_and_track_usage(
425428
# Already serialized by converters
426429
tag("$ai_usage", raw_usage)
427430

428-
if posthog_distinct_id is None:
431+
if not has_person_distinct_id:
429432
tag("$process_person_profile", False)
430433

431434
# Process instructions for Responses API
@@ -505,8 +508,11 @@ async def call_llm_and_track_usage_async(
505508
if posthog_trace_id is None:
506509
posthog_trace_id = str(uuid.uuid4())
507510

508-
if not contexts.get_context_distinct_id():
509-
# Use trace_id as district_id if it's not defined.
511+
# Check if we have a real user distinct_id (from param or outer context)
512+
has_person_distinct_id = posthog_distinct_id is not None or contexts.get_context_distinct_id() is not None
513+
514+
if not has_person_distinct_id:
515+
# Fall back to trace_id as distinct_id when no real user id is available.
510516
identify_context(posthog_trace_id)
511517

512518
if response and (
@@ -564,7 +570,7 @@ async def call_llm_and_track_usage_async(
564570
# Already serialized by converters
565571
tag("$ai_usage", raw_usage)
566572

567-
if posthog_distinct_id is None:
573+
if not has_person_distinct_id:
568574
tag("$process_person_profile", False)
569575

570576
# Process instructions for Responses API

‎posthog/test/ai/anthropic/test_anthropic.py‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import pytest
55

6+
from posthog import identify_context, new_context
7+
68
try:
79
from anthropic.types import Message, Usage
810

@@ -1302,3 +1304,87 @@ async def run_test():
13021304
assert props["$ai_web_search_count"] == 2
13031305
assert props["$ai_input_tokens"] == 50
13041306
assert props["$ai_output_tokens"] == 25
1307+
1308+
1309+
# =======================
1310+
# Distinct ID Context Tests
1311+
# =======================
1312+
1313+
1314+
def test_no_distinct_id_uses_trace_id_and_personless(mock_client, mock_anthropic_response):
1315+
"""When no distinct_id is provided and no outer context, trace_id is used and event is personless."""
1316+
with patch(
1317+
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
1318+
):
1319+
client = Anthropic(api_key="test-key", posthog_client=mock_client)
1320+
client.messages.create(
1321+
model="claude-3-opus-20240229",
1322+
messages=[{"role": "user", "content": "Hello"}],
1323+
posthog_trace_id="trace-123",
1324+
)
1325+
1326+
call_args = mock_client.capture.call_args[1]
1327+
props = call_args["properties"]
1328+
1329+
assert call_args["distinct_id"] == "trace-123"
1330+
assert props["$process_person_profile"] is False
1331+
1332+
1333+
def test_explicit_distinct_id_creates_person_profile(mock_client, mock_anthropic_response):
1334+
"""When posthog_distinct_id is explicitly passed, it is used and event is not personless."""
1335+
with patch(
1336+
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
1337+
):
1338+
client = Anthropic(api_key="test-key", posthog_client=mock_client)
1339+
client.messages.create(
1340+
model="claude-3-opus-20240229",
1341+
messages=[{"role": "user", "content": "Hello"}],
1342+
posthog_distinct_id="user-123",
1343+
posthog_trace_id="trace-123",
1344+
)
1345+
1346+
call_args = mock_client.capture.call_args[1]
1347+
props = call_args["properties"]
1348+
1349+
assert call_args["distinct_id"] == "user-123"
1350+
assert "$process_person_profile" not in props or props["$process_person_profile"] is not False
1351+
1352+
1353+
def test_outer_context_distinct_id_is_used(mock_client, mock_anthropic_response):
1354+
"""When an outer context has a distinct_id, it should be used instead of trace_id."""
1355+
with patch(
1356+
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
1357+
):
1358+
client = Anthropic(api_key="test-key", posthog_client=mock_client)
1359+
with new_context():
1360+
identify_context("outer-user-456")
1361+
client.messages.create(
1362+
model="claude-3-opus-20240229",
1363+
messages=[{"role": "user", "content": "Hello"}],
1364+
posthog_trace_id="trace-123",
1365+
)
1366+
1367+
call_args = mock_client.capture.call_args[1]
1368+
props = call_args["properties"]
1369+
1370+
assert call_args["distinct_id"] == "outer-user-456"
1371+
assert "$process_person_profile" not in props or props["$process_person_profile"] is not False
1372+
1373+
1374+
def test_explicit_distinct_id_overrides_outer_context(mock_client, mock_anthropic_response):
1375+
"""When both outer context and explicit posthog_distinct_id are set, explicit wins."""
1376+
with patch(
1377+
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
1378+
):
1379+
client = Anthropic(api_key="test-key", posthog_client=mock_client)
1380+
with new_context():
1381+
identify_context("outer-user-456")
1382+
client.messages.create(
1383+
model="claude-3-opus-20240229",
1384+
messages=[{"role": "user", "content": "Hello"}],
1385+
posthog_distinct_id="explicit-user-789",
1386+
posthog_trace_id="trace-123",
1387+
)
1388+
1389+
call_args = mock_client.capture.call_args[1]
1390+
assert call_args["distinct_id"] == "explicit-user-789"

0 commit comments

Comments
 (0)