feat(ai): warn when AI wrapper is pointed at the PostHog AI Gateway#658
Open
richardsolomou wants to merge 2 commits into
Open
feat(ai): warn when AI wrapper is pointed at the PostHog AI Gateway#658richardsolomou wants to merge 2 commits into
richardsolomou wants to merge 2 commits into
Conversation
An AI wrapper pointed at gateway.us.posthog.com captures each generation twice (once by the wrapper, once by the gateway), doubling events and cost. Warn on every routed call without dropping the event, since the wrapper event carries groups, custom properties, and trace hierarchy the gateway never sees. Ports posthog-js #3793. Generated-By: PostHog Code Task-Id: d4ca2f3d-2579-4155-b6d4-1f072c7e9ace
Contributor
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
posthog/test/ai/test_gateway.py:31-54
**Non-parameterized tests violate the "always prefer parameterised tests" rule**
The three `warn_if_posthog_ai_gateway` test functions each have a fixed set of inputs and an expected outcome, making them natural candidates for a single `@parameterized.expand` case. As written, adding a new gateway host to `POSTHOG_AI_GATEWAY_HOSTS` requires editing three functions instead of one table. Consider consolidating them into a parameterized test that covers the cases `(gateway_url, expect_warning)` alongside the silent cases.
### Issue 2 of 3
posthog/ai/gateway.py:9-11
**EU gateway host may be absent from the detection list**
`POSTHOG_AI_GATEWAY_HOSTS` is intentionally plural (and the comment says "Hosts that resolve to…"), yet only `gateway.us.posthog.com` is listed. If a `gateway.eu.posthog.com` (or equivalent) exists or is planned, users pointing their wrapper at it would receive no warning and could silently double-capture. Is this an intentional decision to address in a follow-up, or does the EU region not yet have a gateway host?
### Issue 3 of 3
posthog/ai/gateway.py:25-43
**URL parsed twice when the warning fires**
`is_posthog_ai_gateway_url` already extracts `hostname` from `urlparse`, but `warn_if_posthog_ai_gateway` calls `urlparse` a second time inside `log.warning` just to retrieve the same hostname. Extracting the host once keeps the logic OnceAndOnlyOnce and removes the redundant parse.
```suggestion
def warn_if_posthog_ai_gateway(base_url: Any) -> None:
"""
Warn when an AI wrapper is pointed at the PostHog AI Gateway.
The wrapper and the gateway each capture the LLM generation, which would
double-count (and double-bill) the event. We only warn and never drop the
event, because the wrapper event carries data the gateway never sees
(groups, custom properties, trace hierarchy). We warn on every call rather
than once, since a single startup line is easy to miss.
"""
try:
host = urlparse(str(base_url)).hostname if base_url else None
except Exception:
return
if host in POSTHOG_AI_GATEWAY_HOSTS:
log.warning(
"Your PostHog AI wrapper is pointed at the PostHog AI Gateway (%s). "
"This will capture and bill each LLM generation twice — once by this "
"wrapper and once by the gateway. Point the wrapper at the model "
"provider's API directly, or remove the wrapper and rely on the "
"gateway. See https://posthog.com/docs/ai-observability",
host,
)
```
Reviews (1): Last reviewed commit: "feat(ai): warn when AI wrapper is pointe..." | Re-trigger Greptile |
Contributor
posthog-python Compliance ReportDate: 2026-06-10 20:14:14 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
Address review on #658 by matching gatewayWarning.ts as written: - List all five deployed gateway hosts, not just gateway.us.posthog.com, so EU and ai-gateway hosts are detected too. - Tolerate base URLs without a scheme (e.g. gateway.us.posthog.com/v1), which urlparse otherwise reads as a path with no hostname. - Use a static warning message, dropping the redundant second urlparse. - Detect the gateway on the OTel span path (server.address / url.full) in the processor and exporter, since those spans bypass the funnels. - Consolidate tests with pytest.mark.parametrize. Generated-By: PostHog Code Task-Id: d4ca2f3d-2579-4155-b6d4-1f072c7e9ace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
A wrapper pointed at the PostHog AI Gateway makes both the wrapper and the gateway capture
$ai_generation, so every call is double-counted and double-billed. Detection is host-based (the five deployed gateway hosts, scheme/port/path tolerant) and warns on every routed call across the wrapper funnels (OpenAI, Anthropic, LangChain) and the OTel span path. It only warns, never drops the event, since the wrapper event carries groups, custom properties, and trace hierarchy the gateway never sees. Ports posthog-js #3793.💚 How did you test it?
Added
posthog/test/ai/test_gateway.py: host detection, scheme/port/case variants, look-alike domains, warn-on-every-call, and the OTelserver.address/url.fullpaths.ruff checkandruff formatclean.📝 Checklist
If releasing new changes
sampo addto generate a changeset file