Skip to content

Commit cf5cd6f

Browse files
feat: Add AI provider integration examples
Add self-contained examples for each AI provider supported by posthog.ai: - Anthropic (chat, streaming, extended thinking) - OpenAI (Chat Completions, Responses, streaming, embeddings, transcription, image generation) - Google Gemini (chat, streaming, image generation) - LangChain (callback handler, OTEL) - LiteLLM (chat, streaming) - Pydantic AI (agent with OTEL) - OpenAI Agents SDK (multi-agent, single agent, guardrails, custom spans) Each example directory is self-contained with its own requirements.txt, .env.example, and README. Files are designed to be copy-pasted by users as starting points for their own integrations.
1 parent 7c58d11 commit cf5cd6f

43 files changed

Lines changed: 1095 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
POSTHOG_API_KEY=phc_your_project_api_key
2+
POSTHOG_HOST=https://us.i.posthog.com
3+
ANTHROPIC_API_KEY=sk-ant-your_api_key
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Anthropic + PostHog AI Examples
2+
3+
Track Anthropic Claude API calls with PostHog.
4+
5+
## Setup
6+
7+
```bash
8+
pip install -r requirements.txt
9+
cp .env.example .env
10+
# Fill in your API keys in .env
11+
```
12+
13+
## Examples
14+
15+
- **chat.py** - Basic chat with tool calling
16+
- **streaming.py** - Streaming responses
17+
- **extended_thinking.py** - Claude's extended thinking feature
18+
19+
## Run
20+
21+
```bash
22+
source .env
23+
python chat.py
24+
python streaming.py
25+
python extended_thinking.py
26+
```
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Anthropic chat with tool calling, tracked by PostHog."""
2+
3+
import os
4+
import json
5+
import urllib.request
6+
from posthog import Posthog
7+
from posthog.ai.anthropic import Anthropic
8+
9+
posthog = Posthog(os.environ["POSTHOG_API_KEY"], host=os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com"))
10+
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"], posthog_client=posthog)
11+
12+
tools = [
13+
{
14+
"name": "get_weather",
15+
"description": "Get current weather for a location",
16+
"input_schema": {
17+
"type": "object",
18+
"properties": {
19+
"latitude": {"type": "number"},
20+
"longitude": {"type": "number"},
21+
"location_name": {"type": "string"},
22+
},
23+
"required": ["latitude", "longitude", "location_name"],
24+
},
25+
}
26+
]
27+
28+
29+
def get_weather(latitude: float, longitude: float, location_name: str) -> str:
30+
url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,relative_humidity_2m,wind_speed_10m"
31+
with urllib.request.urlopen(url) as resp:
32+
data = json.loads(resp.read())
33+
current = data["current"]
34+
return f"Weather in {location_name}: {current['temperature_2m']}°C, humidity {current['relative_humidity_2m']}%, wind {current['wind_speed_10m']} km/h"
35+
36+
37+
message = client.messages.create(
38+
model="claude-sonnet-4-5-20250929",
39+
max_tokens=1024,
40+
posthog_distinct_id="example-user",
41+
tools=tools,
42+
messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
43+
)
44+
45+
# Handle tool use if the model requests it
46+
for block in message.content:
47+
if block.type == "text":
48+
print(block.text)
49+
elif block.type == "tool_use":
50+
result = get_weather(**block.input)
51+
print(result)
52+
53+
posthog.shutdown()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Anthropic extended thinking, tracked by PostHog.
2+
3+
Extended thinking lets Claude show its reasoning process before responding.
4+
"""
5+
6+
import os
7+
from posthog import Posthog
8+
from posthog.ai.anthropic import Anthropic
9+
10+
posthog = Posthog(os.environ["POSTHOG_API_KEY"], host=os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com"))
11+
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"], posthog_client=posthog)
12+
13+
message = client.messages.create(
14+
model="claude-sonnet-4-5-20250929",
15+
max_tokens=16000,
16+
posthog_distinct_id="example-user",
17+
thinking={"type": "enabled", "budget_tokens": 10000},
18+
messages=[{"role": "user", "content": "What is the probability of rolling at least one six in four rolls of a fair die?"}],
19+
)
20+
21+
for block in message.content:
22+
if block.type == "thinking":
23+
print(f"Thinking: {block.thinking}\n")
24+
elif block.type == "text":
25+
print(f"Answer: {block.text}")
26+
27+
posthog.shutdown()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
posthog>=6.6.1
2+
anthropic
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Anthropic streaming chat, tracked by PostHog."""
2+
3+
import os
4+
from posthog import Posthog
5+
from posthog.ai.anthropic import Anthropic
6+
7+
posthog = Posthog(os.environ["POSTHOG_API_KEY"], host=os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com"))
8+
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"], posthog_client=posthog)
9+
10+
stream = client.messages.create(
11+
model="claude-sonnet-4-5-20250929",
12+
max_tokens=1024,
13+
posthog_distinct_id="example-user",
14+
messages=[{"role": "user", "content": "Write a haiku about observability."}],
15+
stream=True,
16+
)
17+
18+
for event in stream:
19+
if hasattr(event, "type"):
20+
if event.type == "content_block_delta" and hasattr(event.delta, "text"):
21+
print(event.delta.text, end="", flush=True)
22+
23+
print()
24+
posthog.shutdown()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
POSTHOG_API_KEY=phc_your_project_api_key
2+
POSTHOG_HOST=https://us.i.posthog.com
3+
GEMINI_API_KEY=your_gemini_api_key
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Google Gemini + PostHog AI Examples
2+
3+
Track Google Gemini API calls with PostHog.
4+
5+
## Setup
6+
7+
```bash
8+
pip install -r requirements.txt
9+
cp .env.example .env
10+
# Fill in your API keys in .env
11+
```
12+
13+
## Examples
14+
15+
- **chat.py** - Chat with tool calling
16+
- **streaming.py** - Streaming responses
17+
- **image_generation.py** - Image generation
18+
19+
## Run
20+
21+
```bash
22+
source .env
23+
python chat.py
24+
python streaming.py
25+
```

‎examples/example-ai-gemini/chat.py‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Google Gemini chat with tool calling, tracked by PostHog."""
2+
3+
import os
4+
import json
5+
import urllib.request
6+
from google.genai import types
7+
from posthog import Posthog
8+
from posthog.ai.gemini import Client
9+
10+
posthog = Posthog(os.environ["POSTHOG_API_KEY"], host=os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com"))
11+
client = Client(api_key=os.environ["GEMINI_API_KEY"], posthog_client=posthog)
12+
13+
tool_declarations = [
14+
{
15+
"name": "get_weather",
16+
"description": "Get current weather for a location",
17+
"parameters": {
18+
"type": "object",
19+
"properties": {
20+
"latitude": {"type": "number"},
21+
"longitude": {"type": "number"},
22+
"location_name": {"type": "string"},
23+
},
24+
"required": ["latitude", "longitude", "location_name"],
25+
},
26+
}
27+
]
28+
29+
30+
def get_weather(latitude: float, longitude: float, location_name: str) -> str:
31+
url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,relative_humidity_2m,wind_speed_10m"
32+
with urllib.request.urlopen(url) as resp:
33+
data = json.loads(resp.read())
34+
current = data["current"]
35+
return f"Weather in {location_name}: {current['temperature_2m']}°C, humidity {current['relative_humidity_2m']}%, wind {current['wind_speed_10m']} km/h"
36+
37+
38+
config = types.GenerateContentConfig(
39+
tools=[types.Tool(function_declarations=tool_declarations)]
40+
)
41+
42+
response = client.models.generate_content(
43+
model="gemini-2.5-flash",
44+
posthog_distinct_id="example-user",
45+
contents=[{"role": "user", "parts": [{"text": "What's the weather in London?"}]}],
46+
config=config,
47+
)
48+
49+
for candidate in response.candidates:
50+
for part in candidate.content.parts:
51+
if hasattr(part, "function_call") and part.function_call:
52+
result = get_weather(
53+
latitude=part.function_call.args["latitude"],
54+
longitude=part.function_call.args["longitude"],
55+
location_name=part.function_call.args["location_name"],
56+
)
57+
print(result)
58+
elif hasattr(part, "text"):
59+
print(part.text)
60+
61+
posthog.shutdown()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Google Gemini image generation, tracked by PostHog."""
2+
3+
import os
4+
from posthog import Posthog
5+
from posthog.ai.gemini import Client
6+
7+
posthog = Posthog(os.environ["POSTHOG_API_KEY"], host=os.environ.get("POSTHOG_HOST", "https://us.i.posthog.com"))
8+
client = Client(api_key=os.environ["GEMINI_API_KEY"], posthog_client=posthog)
9+
10+
response = client.models.generate_content(
11+
model="gemini-2.5-flash-image",
12+
posthog_distinct_id="example-user",
13+
contents=[{"role": "user", "parts": [{"text": "Generate a pixel art hedgehog"}]}],
14+
)
15+
16+
for candidate in response.candidates:
17+
for part in candidate.content.parts:
18+
if hasattr(part, "inline_data") and part.inline_data:
19+
print(f"Generated image: {part.inline_data.mime_type}, {len(part.inline_data.data)} bytes")
20+
elif hasattr(part, "text"):
21+
print(part.text)
22+
23+
posthog.shutdown()

0 commit comments

Comments
 (0)