rebrand: remove all compat aliases, zero posthog refs

- Remove Posthog = Insights alias from __init__.py
- Remove PosthogContextMiddleware alias from django.py
- Remove PostHogTracingProcessor alias from processor.py
- Change $lib from "posthog-python" to "insights-python"
- Change ingestion URLs from posthog.com to insights.hanzo.ai
- Rename all posthog_* kwargs to insights_* across AI wrappers
- Rename __posthog_exception_captured to __insights_exception_captured
- Rename posthog_context_stack contextvar to insights_context_stack
- Rename posthog🎏 Redis prefix to insights🎏
- Rename $$_posthog_redacted_* sentinels to $$_insights_redacted_*
- Remove POSTHOG_MW_* Django settings fallback, X-POSTHOG-* headers
- Rename Prompts(posthog=) param to Prompts(client=)
- Update APP_ENDPOINT to us.insights.hanzo.ai
- Update all tests, examples, docs, mypy config
This commit is contained in:
Hanzo Dev
2026-03-13 20:29:13 -07:00
parent 9fb72596af
commit 34b739cd6b
50 changed files with 1171 additions and 1189 deletions
+13 -6
View File
@@ -17,7 +17,7 @@ uv run pytest
```
posthog-python/
hanzo_insights/ # Main package
__init__.py # Module-level API, Insights class (alias: Posthog)
__init__.py # Module-level API, Insights class
client.py # Client class
ai/ # AI provider integrations (OpenAI, Anthropic, Gemini, LangChain)
integrations/ # Framework integrations (Django middleware)
@@ -34,8 +34,15 @@ posthog-python/
- `hanzo_insights/client.py` -- Client implementation
## Rebrand Notes
- Main class: `Insights` (backward compat alias: `Posthog = Insights`)
- Django middleware: `InsightsContextMiddleware` (alias: `PosthogContextMiddleware`)
- OpenAI Agents: `InsightsTracingProcessor` (alias: `PostHogTracingProcessor`)
- Internal protocol values (`$lib`, `posthog.com` ingestion URLs) kept for server compat
- `posthog_*` parameter names in AI wrappers kept for API compat
- Main class: `Insights` (no backward compat aliases)
- Django middleware: `InsightsContextMiddleware` (no backward compat aliases)
- OpenAI Agents: `InsightsTracingProcessor` (no backward compat aliases)
- `$lib` protocol value: `insights-python`
- Ingestion URLs: `us.i.insights.hanzo.ai` / `eu.i.insights.hanzo.ai`
- AI wrapper kwargs: `insights_*` (e.g. `insights_distinct_id`, `insights_trace_id`)
- Exception attrs: `__insights_exception_captured`, `__insights_exception_uuid`
- Context var: `insights_context_stack`
- Redis prefix: `insights:flags:`
- Redaction sentinels: `$$_insights_redacted_*`, `$$_insights_value_too_long_*`
- Django settings: `INSIGHTS_MW_*` only (no `POSTHOG_MW_*` fallback)
- Django headers: `X-INSIGHTS-SESSION-ID`, `X-INSIGHTS-DISTINCT-ID` only
-9
View File
@@ -65,15 +65,6 @@ make test
pytest -k test_no_api_key
```
## Backward Compatibility
For users migrating from `posthog` or `posthoganalytics`, the `Posthog` class name is
available as an alias for `Insights`:
```python
from hanzo_insights import Posthog # works, same as Insights
```
## License
MIT
+1 -1
View File
@@ -4,5 +4,5 @@
source bin/helpers/_utils.sh
set_source_and_root_dir
flake8 posthog --ignore E501,W503
flake8 hanzo_insights --ignore E501,W503
mypy --no-site-packages --config-file mypy.ini . | mypy-baseline filter
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#/ Usage: bin/docs
#/ Description: Generate documentation for the PostHog Python SDK
#/ Description: Generate documentation for the Insights Python SDK
source bin/helpers/_utils.sh
set_source_and_root_dir
ensure_virtual_env
+5 -5
View File
@@ -1,5 +1,5 @@
"""
Constants for PostHog Python SDK documentation generation.
Constants for Insights Python SDK documentation generation.
"""
from typing import Dict, Union
@@ -8,8 +8,8 @@ from hanzo_insights.version import VERSION
# Documentation generation metadata
DOCUMENTATION_METADATA = {
"hogRef": "0.3",
"slugPrefix": "posthog-python",
"specUrl": "https://github.com/PostHog/posthog-python",
"slugPrefix": "insights-python",
"specUrl": "https://github.com/Insights/insights-python",
}
# Docstring parsing patterns for new format
@@ -29,8 +29,8 @@ DOCSTRING_PATTERNS = {
# Output file configuration
OUTPUT_CONFIG: Dict[str, Union[str, int]] = {
"output_dir": "./references",
"filename": f"posthog-python-references-{VERSION}.json",
"filename_latest": "posthog-python-references-latest.json",
"filename": f"insights-python-references-{VERSION}.json",
"filename_latest": "insights-python-references-latest.json",
"indent": 2,
}
+18 -18
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
Generate comprehensive SDK documentation JSON from PostHog Python SDK.
Generate comprehensive SDK documentation JSON from Insights Python SDK.
This script inspects the code and docstrings to create documentation in the specified format.
"""
@@ -337,8 +337,8 @@ def analyze_type(cls) -> dict:
def generate_sdk_documentation():
"""Generate complete SDK documentation in the requested format."""
# Import PostHog components
import posthog
# Import Insights components
import hanzo_insights
from hanzo_insights.client import Client
import hanzo_insights.types as types_module
import hanzo_insights.args as args_module
@@ -347,9 +347,9 @@ def generate_sdk_documentation():
# Main SDK info
sdk_info = {
"version": VERSION,
"id": "posthog-python",
"title": "PostHog Python SDK",
"description": "Integrate PostHog into any python application.",
"id": "insights-python",
"title": "Insights Python SDK",
"description": "Integrate Insights into any python application.",
"slugPrefix": DOCUMENTATION_METADATA["slugPrefix"],
"specUrl": DOCUMENTATION_METADATA["specUrl"],
}
@@ -388,24 +388,24 @@ def generate_sdk_documentation():
# Collect classes
classes_list = []
# Main PostHog class (renamed from Client)
# Main Insights class (renamed from Client)
client_class = analyze_class(Client)
client_class["id"] = "PostHog"
client_class["title"] = "PostHog"
client_class["id"] = "Insights"
client_class["title"] = "Insights"
classes_list.append(client_class)
# Global module functions (functions callable as hanzo_insights.function_name)
global_functions = []
for func_name in dir(posthog):
for func_name in dir(hanzo_insights):
# Skip private functions and non-callables
if func_name.startswith("_") or not callable(getattr(posthog, func_name)):
if func_name.startswith("_") or not callable(getattr(hanzo_insights, func_name)):
continue
func = getattr(posthog, func_name)
# Only include functions actually defined in the posthog module (not imported)
func = getattr(hanzo_insights, func_name)
# Only include functions actually defined in the hanzo_insights module (not imported)
# and exclude class references
if (
func_name not in ["Client", "Posthog"]
func_name not in ["Client", "Insights"]
and hasattr(func, "__module__")
and func.__module__ == "hanzo_insights"
):
@@ -421,8 +421,8 @@ def generate_sdk_documentation():
classes_list.append(
{
"id": "PostHogModule",
"title": "PostHog Module Functions",
"description": "Global functions available in the PostHog module",
"title": "Insights Module Functions",
"description": "Global functions available in the Insights module",
"functions": global_functions,
}
)
@@ -443,7 +443,7 @@ def generate_sdk_documentation():
# Create the final structure
result = {
"id": "posthog-python",
"id": "insights-python",
"hogRef": DOCUMENTATION_METADATA["hogRef"],
"info": sdk_info,
"types": types_list,
@@ -455,7 +455,7 @@ def generate_sdk_documentation():
if __name__ == "__main__":
print("Generating PostHog Python SDK documentation...")
print("Generating Insights Python SDK documentation...")
try:
documentation = generate_sdk_documentation()
+5 -5
View File
@@ -6,7 +6,7 @@ using Redis for multi-instance deployments (leader election pattern).
Usage:
import redis
from hanzo_insights import Posthog
from hanzo_insights import Insights
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
cache = RedisFlagCache(redis_client, service_key="my-service")
@@ -83,8 +83,8 @@ class RedisFlagCache(FlagDefinitionCacheProvider):
Examples: "my-api-prod", "checkout-service", "staging".
Redis Keys Created:
- posthog:flags:{service_key} - Cached flag definitions (JSON)
- posthog:flags:{service_key}:lock - Leader election lock
- insights:flags:{service_key} - Cached flag definitions (JSON)
- insights:flags:{service_key}:lock - Leader election lock
Example:
redis_client = redis.Redis(
@@ -95,8 +95,8 @@ class RedisFlagCache(FlagDefinitionCacheProvider):
cache = RedisFlagCache(redis_client, service_key="my-api-prod")
"""
self._redis = redis
self._cache_key = f"posthog:flags:{service_key}"
self._lock_key = f"posthog:flags:{service_key}:lock"
self._cache_key = f"insights:flags:{service_key}"
self._lock_key = f"insights:flags:{service_key}:lock"
self._instance_id = str(uuid.uuid4())
self._try_lead = self._redis.register_script(self._LUA_TRY_LEAD)
self._stop_lead = self._redis.register_script(self._LUA_STOP_LEAD)
+1 -1
View File
@@ -8,7 +8,7 @@ import hanzo_insights
# Initialize Insights client
hanzo_insights.api_key = "phc_..."
hanzo_insights.personal_api_key = "phs_..." # or "phx_..."
hanzo_insights.host = "http://localhost:8000" # or "https://us.posthog.com"
hanzo_insights.host = "http://localhost:8000" # or "https://us.insights.hanzo.ai"
hanzo_insights.debug = True
-4
View File
@@ -892,7 +892,3 @@ class Insights(Client):
"""Hanzo Insights client for product analytics."""
pass
# Backward compatibility alias
Posthog = Insights
+57 -57
View File
@@ -34,14 +34,14 @@ class Anthropic(anthropic.Anthropic):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
posthog_client: Insights client for tracking usage
insights_client: Insights client for tracking usage
**kwargs: Additional arguments passed to the Anthropic client
"""
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
self.messages = WrappedMessages(self)
@@ -50,46 +50,46 @@ class WrappedMessages(Messages):
def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
Create a message using Anthropic's API while tracking usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event
posthog_trace_id: Optional trace UUID for linking events
posthog_properties: Optional dictionary of extra properties to include in the event
posthog_privacy_mode: Whether to redact sensitive information in tracking
posthog_groups: Optional group analytics properties
insights_distinct_id: Optional ID to associate with the usage event
insights_trace_id: Optional trace UUID for linking events
insights_properties: Optional dictionary of extra properties to include in the event
insights_privacy_mode: Whether to redact sensitive information in tracking
insights_groups: Optional group analytics properties
**kwargs: Arguments passed to Anthropic's messages.create
"""
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
if kwargs.get("stream", False):
return self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
return call_llm_and_track_usage(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"anthropic",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
super().create,
**kwargs,
@@ -97,32 +97,32 @@ class WrappedMessages(Messages):
def stream(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
return self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
def _create_streaming(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
**kwargs: Any,
):
start_time = time.time()
@@ -188,11 +188,11 @@ class WrappedMessages(Messages):
latency = end_time - start_time
self._capture_streaming_event(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
@@ -204,11 +204,11 @@ class WrappedMessages(Messages):
def _capture_streaming_event(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
kwargs: Dict[str, Any],
usage_stats: TokenUsage,
latency: float,
@@ -237,11 +237,11 @@ class WrappedMessages(Messages):
),
usage_stats=usage_stats,
latency=latency,
distinct_id=posthog_distinct_id,
trace_id=posthog_trace_id,
properties=posthog_properties,
privacy_mode=posthog_privacy_mode,
groups=posthog_groups,
distinct_id=insights_distinct_id,
trace_id=insights_trace_id,
properties=insights_properties,
privacy_mode=insights_privacy_mode,
groups=insights_groups,
)
# Use the common capture function
+57 -57
View File
@@ -34,14 +34,14 @@ class AsyncAnthropic(anthropic.AsyncAnthropic):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
posthog_client: Insights client for tracking usage
insights_client: Insights client for tracking usage
**kwargs: Additional arguments passed to the Anthropic client
"""
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
self.messages = AsyncWrappedMessages(self)
@@ -50,46 +50,46 @@ class AsyncWrappedMessages(AsyncMessages):
async def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
Create a message using Anthropic's API while tracking usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event
posthog_trace_id: Optional trace UUID for linking events
posthog_properties: Optional dictionary of extra properties to include in the event
posthog_privacy_mode: Whether to redact sensitive information in tracking
posthog_groups: Optional group analytics properties
insights_distinct_id: Optional ID to associate with the usage event
insights_trace_id: Optional trace UUID for linking events
insights_properties: Optional dictionary of extra properties to include in the event
insights_privacy_mode: Whether to redact sensitive information in tracking
insights_groups: Optional group analytics properties
**kwargs: Arguments passed to Anthropic's messages.create
"""
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
if kwargs.get("stream", False):
return await self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
return await call_llm_and_track_usage_async(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"anthropic",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
super().create,
**kwargs,
@@ -97,32 +97,32 @@ class AsyncWrappedMessages(AsyncMessages):
async def stream(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
return await self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
async def _create_streaming(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
**kwargs: Any,
):
start_time = time.time()
@@ -188,11 +188,11 @@ class AsyncWrappedMessages(AsyncMessages):
latency = end_time - start_time
await self._capture_streaming_event(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
@@ -204,11 +204,11 @@ class AsyncWrappedMessages(AsyncMessages):
async def _capture_streaming_event(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
kwargs: Dict[str, Any],
usage_stats: TokenUsage,
latency: float,
@@ -237,11 +237,11 @@ class AsyncWrappedMessages(AsyncMessages):
),
usage_stats=usage_stats,
latency=latency,
distinct_id=posthog_distinct_id,
trace_id=posthog_trace_id,
properties=posthog_properties,
privacy_mode=posthog_privacy_mode,
groups=posthog_groups,
distinct_id=insights_distinct_id,
trace_id=insights_trace_id,
properties=insights_properties,
privacy_mode=insights_privacy_mode,
groups=insights_groups,
)
# Use the common capture function
@@ -20,9 +20,9 @@ class AnthropicBedrock(anthropic.AnthropicBedrock):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
self.messages = WrappedMessages(self)
@@ -33,9 +33,9 @@ class AsyncAnthropicBedrock(anthropic.AsyncAnthropicBedrock):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
self.messages = AsyncWrappedMessages(self)
@@ -46,9 +46,9 @@ class AnthropicVertex(anthropic.AnthropicVertex):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
self.messages = WrappedMessages(self)
@@ -59,7 +59,7 @@ class AsyncAnthropicVertex(anthropic.AsyncAnthropicVertex):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
self.messages = AsyncWrappedMessages(self)
+65 -65
View File
@@ -35,14 +35,14 @@ class Client:
Usage:
client = Client(
api_key="your_api_key",
posthog_client=posthog_client,
posthog_distinct_id="default_user", # Optional defaults
posthog_properties={"team": "ai"} # Optional defaults
insights_client=insights_client,
insights_distinct_id="default_user", # Optional defaults
insights_properties={"team": "ai"} # Optional defaults
)
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello world"],
posthog_distinct_id="specific_user" # Override default
insights_distinct_id="specific_user" # Override default
)
"""
@@ -57,11 +57,11 @@ class Client:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_client: Optional[InsightsClient] = None,
insights_distinct_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""
@@ -73,18 +73,18 @@ class Client:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls (can be overridden per call)
posthog_properties: Default properties for all calls (can be overridden per call)
posthog_privacy_mode: Default privacy mode for all calls (can be overridden per call)
posthog_groups: Default groups for all calls (can be overridden per call)
insights_client: Insights client for tracking usage
insights_distinct_id: Default distinct ID for all calls (can be overridden per call)
insights_properties: Default properties for all calls (can be overridden per call)
insights_privacy_mode: Default privacy mode for all calls (can be overridden per call)
insights_groups: Default groups for all calls (can be overridden per call)
**kwargs: Additional arguments (for future compatibility)
"""
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for Insights tracking")
raise ValueError("insights_client is required for Insights tracking")
self.models = Models(
api_key=api_key,
@@ -94,11 +94,11 @@ class Client:
location=location,
debug_config=debug_config,
http_options=http_options,
posthog_client=self._ph_client,
posthog_distinct_id=posthog_distinct_id,
posthog_properties=posthog_properties,
posthog_privacy_mode=posthog_privacy_mode,
posthog_groups=posthog_groups,
insights_client=self._ph_client,
insights_distinct_id=insights_distinct_id,
insights_properties=insights_properties,
insights_privacy_mode=insights_privacy_mode,
insights_groups=insights_groups,
**kwargs,
)
@@ -119,11 +119,11 @@ class Models:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_client: Optional[InsightsClient] = None,
insights_distinct_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""
@@ -135,24 +135,24 @@ class Models:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls
posthog_properties: Default properties for all calls
posthog_privacy_mode: Default privacy mode for all calls
posthog_groups: Default groups for all calls
insights_client: Insights client for tracking usage
insights_distinct_id: Default distinct ID for all calls
insights_properties: Default properties for all calls
insights_privacy_mode: Default privacy mode for all calls
insights_groups: Default groups for all calls
**kwargs: Additional arguments (for future compatibility)
"""
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for Insights tracking")
raise ValueError("insights_client is required for Insights tracking")
# Store default Insights settings
self._default_distinct_id = posthog_distinct_id
self._default_properties = posthog_properties or {}
self._default_privacy_mode = posthog_privacy_mode
self._default_groups = posthog_groups
self._default_distinct_id = insights_distinct_id
self._default_properties = insights_properties or {}
self._default_privacy_mode = insights_privacy_mode
self._default_groups = insights_groups
# Build genai.Client arguments
client_args: Dict[str, Any] = {}
@@ -196,7 +196,7 @@ class Models:
self._client = genai.Client(**client_args)
self._base_url = "https://generativelanguage.googleapis.com"
def _merge_posthog_params(
def _merge_insights_params(
self,
call_distinct_id: Optional[str],
call_trace_id: Optional[str],
@@ -234,11 +234,11 @@ class Models:
self,
model: str,
contents,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: Optional[bool] = None,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: Optional[bool] = None,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
@@ -250,22 +250,22 @@ class Models:
Args:
model: The model to use (e.g., 'gemini-2.0-flash')
contents: The input content for generation
posthog_distinct_id: ID to associate with the usage event (overrides client default)
posthog_trace_id: Trace UUID for linking events (auto-generated if not provided)
posthog_properties: Extra properties to include in the event (merged with client defaults)
posthog_privacy_mode: Whether to redact sensitive information (overrides client default)
posthog_groups: Group analytics properties (overrides client default)
insights_distinct_id: ID to associate with the usage event (overrides client default)
insights_trace_id: Trace UUID for linking events (auto-generated if not provided)
insights_properties: Extra properties to include in the event (merged with client defaults)
insights_privacy_mode: Whether to redact sensitive information (overrides client default)
insights_groups: Group analytics properties (overrides client default)
**kwargs: Arguments passed to Gemini's generate_content
"""
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
self._merge_insights_params(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
)
)
@@ -390,21 +390,21 @@ class Models:
self,
model: str,
contents,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: Optional[bool] = None,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: Optional[bool] = None,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
self._merge_insights_params(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
)
)
+65 -65
View File
@@ -35,14 +35,14 @@ class AsyncClient:
Usage:
client = AsyncClient(
api_key="your_api_key",
posthog_client=posthog_client,
posthog_distinct_id="default_user", # Optional defaults
posthog_properties={"team": "ai"} # Optional defaults
insights_client=insights_client,
insights_distinct_id="default_user", # Optional defaults
insights_properties={"team": "ai"} # Optional defaults
)
response = await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello world"],
posthog_distinct_id="specific_user" # Override default
insights_distinct_id="specific_user" # Override default
)
"""
@@ -57,11 +57,11 @@ class AsyncClient:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_client: Optional[InsightsClient] = None,
insights_distinct_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""
@@ -73,18 +73,18 @@ class AsyncClient:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls (can be overridden per call)
posthog_properties: Default properties for all calls (can be overridden per call)
posthog_privacy_mode: Default privacy mode for all calls (can be overridden per call)
posthog_groups: Default groups for all calls (can be overridden per call)
insights_client: Insights client for tracking usage
insights_distinct_id: Default distinct ID for all calls (can be overridden per call)
insights_properties: Default properties for all calls (can be overridden per call)
insights_privacy_mode: Default privacy mode for all calls (can be overridden per call)
insights_groups: Default groups for all calls (can be overridden per call)
**kwargs: Additional arguments (for future compatibility)
"""
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for Insights tracking")
raise ValueError("insights_client is required for Insights tracking")
self.models = AsyncModels(
api_key=api_key,
@@ -94,11 +94,11 @@ class AsyncClient:
location=location,
debug_config=debug_config,
http_options=http_options,
posthog_client=self._ph_client,
posthog_distinct_id=posthog_distinct_id,
posthog_properties=posthog_properties,
posthog_privacy_mode=posthog_privacy_mode,
posthog_groups=posthog_groups,
insights_client=self._ph_client,
insights_distinct_id=insights_distinct_id,
insights_properties=insights_properties,
insights_privacy_mode=insights_privacy_mode,
insights_groups=insights_groups,
**kwargs,
)
@@ -119,11 +119,11 @@ class AsyncModels:
location: Optional[str] = None,
debug_config: Optional[Any] = None,
http_options: Optional[Any] = None,
posthog_client: Optional[InsightsClient] = None,
posthog_distinct_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_client: Optional[InsightsClient] = None,
insights_distinct_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""
@@ -135,24 +135,24 @@ class AsyncModels:
location: GCP location for Vertex AI
debug_config: Debug configuration for the client
http_options: HTTP options for the client
posthog_client: Insights client for tracking usage
posthog_distinct_id: Default distinct ID for all calls
posthog_properties: Default properties for all calls
posthog_privacy_mode: Default privacy mode for all calls
posthog_groups: Default groups for all calls
insights_client: Insights client for tracking usage
insights_distinct_id: Default distinct ID for all calls
insights_properties: Default properties for all calls
insights_privacy_mode: Default privacy mode for all calls
insights_groups: Default groups for all calls
**kwargs: Additional arguments (for future compatibility)
"""
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
if self._ph_client is None:
raise ValueError("posthog_client is required for Insights tracking")
raise ValueError("insights_client is required for Insights tracking")
# Store default Insights settings
self._default_distinct_id = posthog_distinct_id
self._default_properties = posthog_properties or {}
self._default_privacy_mode = posthog_privacy_mode
self._default_groups = posthog_groups
self._default_distinct_id = insights_distinct_id
self._default_properties = insights_properties or {}
self._default_privacy_mode = insights_privacy_mode
self._default_groups = insights_groups
# Build genai.Client arguments
client_args: Dict[str, Any] = {}
@@ -196,7 +196,7 @@ class AsyncModels:
self._client = genai.Client(**client_args)
self._base_url = "https://generativelanguage.googleapis.com"
def _merge_posthog_params(
def _merge_insights_params(
self,
call_distinct_id: Optional[str],
call_trace_id: Optional[str],
@@ -234,11 +234,11 @@ class AsyncModels:
self,
model: str,
contents,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: Optional[bool] = None,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: Optional[bool] = None,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
@@ -250,22 +250,22 @@ class AsyncModels:
Args:
model: The model to use (e.g., 'gemini-2.0-flash')
contents: The input content for generation
posthog_distinct_id: ID to associate with the usage event (overrides client default)
posthog_trace_id: Trace UUID for linking events (auto-generated if not provided)
posthog_properties: Extra properties to include in the event (merged with client defaults)
posthog_privacy_mode: Whether to redact sensitive information (overrides client default)
posthog_groups: Group analytics properties (overrides client default)
insights_distinct_id: ID to associate with the usage event (overrides client default)
insights_trace_id: Trace UUID for linking events (auto-generated if not provided)
insights_properties: Extra properties to include in the event (merged with client defaults)
insights_privacy_mode: Whether to redact sensitive information (overrides client default)
insights_groups: Group analytics properties (overrides client default)
**kwargs: Arguments passed to Gemini's generate_content
"""
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
self._merge_insights_params(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
)
)
@@ -393,21 +393,21 @@ class AsyncModels:
self,
model: str,
contents,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: Optional[bool] = None,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: Optional[bool] = None,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
# Merge Insights parameters
distinct_id, trace_id, properties, privacy_mode, groups = (
self._merge_posthog_params(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
self._merge_insights_params(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
)
)
+4 -4
View File
@@ -79,7 +79,7 @@ class GenerationMetadata(SpanMetadata):
"""Base URL of the provider's API used in the run."""
tools: Optional[List[Dict[str, Any]]] = None
"""Tools provided to the model."""
posthog_properties: Optional[Dict[str, Any]] = None
insights_properties: Optional[Dict[str, Any]] = None
"""Insights properties of the run."""
@@ -423,7 +423,7 @@ class CallbackHandler(BaseCallbackHandler):
if provider := metadata.get("ls_provider"):
generation.provider = provider
generation.posthog_properties = metadata.get("posthog_properties")
generation.insights_properties = metadata.get("insights_properties")
try:
base_url = serialized["kwargs"]["openai_api_base"]
if base_url is not None:
@@ -578,8 +578,8 @@ class CallbackHandler(BaseCallbackHandler):
"$ai_framework": "langchain",
}
if isinstance(run.posthog_properties, dict):
event_properties.update(run.posthog_properties)
if isinstance(run.insights_properties, dict):
event_properties.update(run.insights_properties)
if run.tools:
event_properties["$ai_tools"] = run.tools
+120 -120
View File
@@ -35,16 +35,16 @@ class OpenAI(openai.OpenAI):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: OpenAI API key.
posthog_client: If provided, events will be captured via this client instead of the global `posthog`.
insights_client: If provided, events will be captured via this client instead of the global client.
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
"""
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
# Store original objects after parent initialization (only if they exist)
self._original_chat = getattr(self, "chat", None)
@@ -79,34 +79,34 @@ class WrappedResponses:
def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
if kwargs.get("stream", False):
return self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
return call_llm_and_track_usage(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.create,
**kwargs,
@@ -114,11 +114,11 @@ class WrappedResponses:
def _create_streaming(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
**kwargs: Any,
):
start_time = time.time()
@@ -160,11 +160,11 @@ class WrappedResponses:
latency = end_time - start_time
output = final_content
self._capture_streaming_event(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
@@ -177,11 +177,11 @@ class WrappedResponses:
def _capture_streaming_event(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
kwargs: Dict[str, Any],
usage_stats: TokenUsage,
latency: float,
@@ -212,11 +212,11 @@ class WrappedResponses:
formatted_output=format_openai_streaming_output(output, "responses"),
usage_stats=usage_stats,
latency=latency,
distinct_id=posthog_distinct_id,
trace_id=posthog_trace_id,
properties=posthog_properties,
privacy_mode=posthog_privacy_mode,
groups=posthog_groups,
distinct_id=insights_distinct_id,
trace_id=insights_trace_id,
properties=insights_properties,
privacy_mode=insights_privacy_mode,
groups=insights_groups,
)
# Use the common capture function
@@ -224,35 +224,35 @@ class WrappedResponses:
def parse(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
Parse structured output using OpenAI's 'responses.parse' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
posthog_trace_id: Optional trace UUID for linking events.
posthog_properties: Optional dictionary of extra properties to include in the event.
posthog_privacy_mode: Whether to anonymize the input and output.
posthog_groups: Optional dictionary of groups to associate with the event.
insights_distinct_id: Optional ID to associate with the usage event.
insights_trace_id: Optional trace UUID for linking events.
insights_properties: Optional dictionary of extra properties to include in the event.
insights_privacy_mode: Whether to anonymize the input and output.
insights_groups: Optional dictionary of groups to associate with the event.
**kwargs: Any additional parameters for the OpenAI Responses Parse API.
Returns:
The response from OpenAI's responses.parse call.
"""
return call_llm_and_track_usage(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.parse,
**kwargs,
@@ -288,34 +288,34 @@ class WrappedCompletions:
def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
if kwargs.get("stream", False):
return self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
return call_llm_and_track_usage(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.create,
**kwargs,
@@ -323,11 +323,11 @@ class WrappedCompletions:
def _create_streaming(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
**kwargs: Any,
):
start_time = time.time()
@@ -385,11 +385,11 @@ class WrappedCompletions:
)
self._capture_streaming_event(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
@@ -403,11 +403,11 @@ class WrappedCompletions:
def _capture_streaming_event(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
kwargs: Dict[str, Any],
usage_stats: TokenUsage,
latency: float,
@@ -439,11 +439,11 @@ class WrappedCompletions:
formatted_output=format_openai_streaming_output(output, "chat", tool_calls),
usage_stats=usage_stats,
latency=latency,
distinct_id=posthog_distinct_id,
trace_id=posthog_trace_id,
properties=posthog_properties,
privacy_mode=posthog_privacy_mode,
groups=posthog_groups,
distinct_id=insights_distinct_id,
trace_id=insights_trace_id,
properties=insights_properties,
privacy_mode=insights_privacy_mode,
groups=insights_groups,
)
# Use the common capture function
@@ -463,30 +463,30 @@ class WrappedEmbeddings:
def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
posthog_trace_id: Optional trace UUID for linking events.
posthog_properties: Optional dictionary of extra properties to include in the event.
posthog_privacy_mode: Whether to anonymize the input and output.
posthog_groups: Optional dictionary of groups to associate with the event.
insights_distinct_id: Optional ID to associate with the usage event.
insights_trace_id: Optional trace UUID for linking events.
insights_properties: Optional dictionary of extra properties to include in the event.
insights_privacy_mode: Whether to anonymize the input and output.
insights_groups: Optional dictionary of groups to associate with the event.
**kwargs: Any additional parameters for the OpenAI Embeddings API.
Returns:
The response from OpenAI's embeddings.create call.
"""
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
start_time = time.time()
response = self._original.create(**kwargs)
@@ -508,27 +508,27 @@ class WrappedEmbeddings:
"$ai_model": kwargs.get("model"),
"$ai_input": with_privacy_mode(
self._client._ph_client,
posthog_privacy_mode,
insights_privacy_mode,
sanitize_openai_response(kwargs.get("input")),
),
"$ai_http_status": 200,
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
"$ai_latency": latency,
"$ai_trace_id": posthog_trace_id,
"$ai_trace_id": insights_trace_id,
"$ai_base_url": str(self._client.base_url),
**(posthog_properties or {}),
**(insights_properties or {}),
}
if posthog_distinct_id is None:
if insights_distinct_id is None:
event_properties["$process_person_profile"] = False
# Send capture event for embeddings
if hasattr(self._client._ph_client, "capture"):
self._client._ph_client.capture(
distinct_id=posthog_distinct_id or posthog_trace_id,
distinct_id=insights_distinct_id or insights_trace_id,
event="$ai_embedding",
properties=event_properties,
groups=posthog_groups,
groups=insights_groups,
)
return response
@@ -579,21 +579,21 @@ class WrappedBetaCompletions:
def parse(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
return call_llm_and_track_usage(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.parse,
**kwargs,
+128 -128
View File
@@ -37,17 +37,17 @@ class AsyncOpenAI(openai.AsyncOpenAI):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: OpenAI API key.
posthog_client: If provided, events will be captured via this client instead
insights_client: If provided, events will be captured via this client instead
of the global hanzo_insights.
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
"""
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
# Store original objects after parent initialization (only if they exist)
self._original_chat = getattr(self, "chat", None)
@@ -83,34 +83,34 @@ class WrappedResponses:
async def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
if kwargs.get("stream", False):
return await self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
return await call_llm_and_track_usage_async(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.create,
**kwargs,
@@ -118,11 +118,11 @@ class WrappedResponses:
async def _create_streaming(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
**kwargs: Any,
):
start_time = time.time()
@@ -165,11 +165,11 @@ class WrappedResponses:
output = final_content
await self._capture_streaming_event(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
@@ -182,11 +182,11 @@ class WrappedResponses:
async def _capture_streaming_event(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
kwargs: Dict[str, Any],
usage_stats: TokenUsage,
latency: float,
@@ -194,8 +194,8 @@ class WrappedResponses:
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
model_from_response: Optional[str] = None,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
# Use model from kwargs, fallback to model from response
model = kwargs.get("model") or model_from_response or "unknown"
@@ -206,12 +206,12 @@ class WrappedResponses:
"$ai_model_parameters": get_model_params(kwargs),
"$ai_input": with_privacy_mode(
self._client._ph_client,
posthog_privacy_mode,
insights_privacy_mode,
sanitize_openai_response(kwargs.get("input")),
),
"$ai_output_choices": with_privacy_mode(
self._client._ph_client,
posthog_privacy_mode,
insights_privacy_mode,
format_openai_streaming_output(output, "responses"),
),
"$ai_http_status": 200,
@@ -222,9 +222,9 @@ class WrappedResponses:
),
"$ai_reasoning_tokens": usage_stats.get("reasoning_tokens", 0),
"$ai_latency": latency,
"$ai_trace_id": posthog_trace_id,
"$ai_trace_id": insights_trace_id,
"$ai_base_url": str(self._client.base_url),
**(posthog_properties or {}),
**(insights_properties or {}),
}
# Add web search count if present
@@ -239,48 +239,48 @@ class WrappedResponses:
if available_tool_calls:
event_properties["$ai_tools"] = available_tool_calls
if posthog_distinct_id is None:
if insights_distinct_id is None:
event_properties["$process_person_profile"] = False
if hasattr(self._client._ph_client, "capture"):
self._client._ph_client.capture(
distinct_id=posthog_distinct_id or posthog_trace_id,
distinct_id=insights_distinct_id or insights_trace_id,
event="$ai_generation",
properties=event_properties,
groups=posthog_groups,
groups=insights_groups,
)
async def parse(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
Parse structured output using OpenAI's 'responses.parse' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
posthog_trace_id: Optional trace UUID for linking events.
posthog_properties: Optional dictionary of extra properties to include in the event.
posthog_privacy_mode: Whether to anonymize the input and output.
posthog_groups: Optional dictionary of groups to associate with the event.
insights_distinct_id: Optional ID to associate with the usage event.
insights_trace_id: Optional trace UUID for linking events.
insights_properties: Optional dictionary of extra properties to include in the event.
insights_privacy_mode: Whether to anonymize the input and output.
insights_groups: Optional dictionary of groups to associate with the event.
**kwargs: Any additional parameters for the OpenAI Responses Parse API.
Returns:
The response from OpenAI's responses.parse call.
"""
return await call_llm_and_track_usage_async(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.parse,
**kwargs,
@@ -316,35 +316,35 @@ class WrappedCompletions:
async def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
# If streaming, handle streaming specifically
if kwargs.get("stream", False):
return await self._create_streaming(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
response = await call_llm_and_track_usage_async(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.create,
**kwargs,
@@ -353,11 +353,11 @@ class WrappedCompletions:
async def _create_streaming(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
**kwargs: Any,
):
start_time = time.time()
@@ -414,11 +414,11 @@ class WrappedCompletions:
)
await self._capture_streaming_event(
posthog_distinct_id,
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
@@ -432,11 +432,11 @@ class WrappedCompletions:
async def _capture_streaming_event(
self,
posthog_distinct_id: Optional[str],
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_distinct_id: Optional[str],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
kwargs: Dict[str, Any],
usage_stats: TokenUsage,
latency: float,
@@ -445,8 +445,8 @@ class WrappedCompletions:
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
model_from_response: Optional[str] = None,
):
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
# Use model from kwargs, fallback to model from response
model = kwargs.get("model") or model_from_response or "unknown"
@@ -457,12 +457,12 @@ class WrappedCompletions:
"$ai_model_parameters": get_model_params(kwargs),
"$ai_input": with_privacy_mode(
self._client._ph_client,
posthog_privacy_mode,
insights_privacy_mode,
sanitize_openai(kwargs.get("messages")),
),
"$ai_output_choices": with_privacy_mode(
self._client._ph_client,
posthog_privacy_mode,
insights_privacy_mode,
format_openai_streaming_output(output, "chat", tool_calls),
),
"$ai_http_status": 200,
@@ -473,9 +473,9 @@ class WrappedCompletions:
),
"$ai_reasoning_tokens": usage_stats.get("reasoning_tokens", 0),
"$ai_latency": latency,
"$ai_trace_id": posthog_trace_id,
"$ai_trace_id": insights_trace_id,
"$ai_base_url": str(self._client.base_url),
**(posthog_properties or {}),
**(insights_properties or {}),
}
# Add web search count if present
@@ -491,15 +491,15 @@ class WrappedCompletions:
if available_tool_calls:
event_properties["$ai_tools"] = available_tool_calls
if posthog_distinct_id is None:
if insights_distinct_id is None:
event_properties["$process_person_profile"] = False
if hasattr(self._client._ph_client, "capture"):
self._client._ph_client.capture(
distinct_id=posthog_distinct_id or posthog_trace_id,
distinct_id=insights_distinct_id or insights_trace_id,
event="$ai_generation",
properties=event_properties,
groups=posthog_groups,
groups=insights_groups,
)
@@ -517,30 +517,30 @@ class WrappedEmbeddings:
async def create(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
"""
Create an embedding using OpenAI's 'embeddings.create' method, but also track usage in Insights.
Args:
posthog_distinct_id: Optional ID to associate with the usage event.
posthog_trace_id: Optional trace UUID for linking events.
posthog_properties: Optional dictionary of extra properties to include in the event.
posthog_privacy_mode: Whether to anonymize the input and output.
posthog_groups: Optional dictionary of groups to associate with the event.
insights_distinct_id: Optional ID to associate with the usage event.
insights_trace_id: Optional trace UUID for linking events.
insights_properties: Optional dictionary of extra properties to include in the event.
insights_privacy_mode: Whether to anonymize the input and output.
insights_groups: Optional dictionary of groups to associate with the event.
**kwargs: Any additional parameters for the OpenAI Embeddings API.
Returns:
The response from OpenAI's embeddings.create call.
"""
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
start_time = time.time()
response = await self._original.create(**kwargs)
@@ -563,27 +563,27 @@ class WrappedEmbeddings:
"$ai_model": kwargs.get("model"),
"$ai_input": with_privacy_mode(
self._client._ph_client,
posthog_privacy_mode,
insights_privacy_mode,
sanitize_openai_response(kwargs.get("input")),
),
"$ai_http_status": 200,
"$ai_input_tokens": usage_stats.get("input_tokens", 0),
"$ai_latency": latency,
"$ai_trace_id": posthog_trace_id,
"$ai_trace_id": insights_trace_id,
"$ai_base_url": str(self._client.base_url),
**(posthog_properties or {}),
**(insights_properties or {}),
}
if posthog_distinct_id is None:
if insights_distinct_id is None:
event_properties["$process_person_profile"] = False
# Send capture event for embeddings
if hasattr(self._client._ph_client, "capture"):
self._client._ph_client.capture(
distinct_id=posthog_distinct_id or posthog_trace_id,
distinct_id=insights_distinct_id or insights_trace_id,
event="$ai_embedding",
properties=event_properties,
groups=posthog_groups,
groups=insights_groups,
)
return response
@@ -637,21 +637,21 @@ class WrappedBetaCompletions:
async def parse(
self,
posthog_distinct_id: Optional[str] = None,
posthog_trace_id: Optional[str] = None,
posthog_properties: Optional[Dict[str, Any]] = None,
posthog_privacy_mode: bool = False,
posthog_groups: Optional[Dict[str, Any]] = None,
insights_distinct_id: Optional[str] = None,
insights_trace_id: Optional[str] = None,
insights_properties: Optional[Dict[str, Any]] = None,
insights_privacy_mode: bool = False,
insights_groups: Optional[Dict[str, Any]] = None,
**kwargs: Any,
):
return await call_llm_and_track_usage_async(
posthog_distinct_id,
insights_distinct_id,
self._client._ph_client,
"openai",
posthog_trace_id,
posthog_properties,
posthog_privacy_mode,
posthog_groups,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
self._original.parse,
**kwargs,
+6 -6
View File
@@ -28,16 +28,16 @@ class AzureOpenAI(openai.AzureOpenAI):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: Azure OpenAI API key.
posthog_client: If provided, events will be captured via this client instead
insights_client: If provided, events will be captured via this client instead
of the global hanzo_insights.
**openai_config: Any additional keyword args to set on Azure OpenAI (e.g. azure_endpoint="xxx").
"""
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
# Store original objects after parent initialization (only if they exist)
self._original_chat = getattr(self, "chat", None)
@@ -66,16 +66,16 @@ class AsyncAzureOpenAI(openai.AsyncAzureOpenAI):
_ph_client: InsightsClient
def __init__(self, posthog_client: Optional[InsightsClient] = None, **kwargs):
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
api_key: Azure OpenAI API key.
posthog_client: If provided, events will be captured via this client instead
insights_client: If provided, events will be captured via this client instead
of the global hanzo_insights.
**openai_config: Any additional keyword args to set on Azure OpenAI (e.g. azure_endpoint="xxx").
"""
super().__init__(**kwargs)
self._ph_client = posthog_client or setup()
self._ph_client = insights_client or setup()
# Store original objects after parent initialization (only if they exist)
self._original_chat = getattr(self, "chat", None)
+2 -2
View File
@@ -14,9 +14,9 @@ except ImportError:
"Please install the OpenAI Agents SDK to use this feature: 'pip install openai-agents'"
)
from hanzo_insights.ai.openai_agents.processor import InsightsTracingProcessor, PostHogTracingProcessor
from hanzo_insights.ai.openai_agents.processor import InsightsTracingProcessor
__all__ = ["InsightsTracingProcessor", "PostHogTracingProcessor", "instrument"]
__all__ = ["InsightsTracingProcessor", "instrument"]
def instrument(
@@ -861,7 +861,3 @@ class InsightsTracingProcessor(TracingProcessor):
self._client.flush()
except Exception as e:
log.debug(f"Error in force_flush: {e}")
# Backward compatibility alias
PostHogTracingProcessor = InsightsTracingProcessor
+11 -11
View File
@@ -15,7 +15,7 @@ from hanzo_insights.utils import remove_trailing_slash
log = logging.getLogger("hanzo_insights")
APP_ENDPOINT = "https://us.posthog.com"
APP_ENDPOINT = "https://us.insights.hanzo.ai"
DEFAULT_CACHE_TTL_SECONDS = 300 # 5 minutes
PromptVariables = Dict[str, Union[str, int, float, bool]]
@@ -64,14 +64,14 @@ class Prompts:
from hanzo_insights.ai.prompts import Prompts
# With Insights client
client = Insights('phc_xxx', host='https://us.posthog.com', personal_api_key='phx_xxx')
client = Insights('phc_xxx', host='https://us.insights.hanzo.ai', personal_api_key='phx_xxx')
prompts = Prompts(client)
# Or with direct options (no Insights client needed)
prompts = Prompts(
personal_api_key='phx_xxx',
project_api_key='phc_xxx',
host='https://us.posthog.com',
host='https://us.insights.hanzo.ai',
)
# Fetch with caching and fallback
@@ -90,7 +90,7 @@ class Prompts:
def __init__(
self,
posthog: Optional[Any] = None,
client: Optional[Any] = None,
*,
personal_api_key: Optional[str] = None,
project_api_key: Optional[str] = None,
@@ -101,9 +101,9 @@ class Prompts:
Initialize Prompts.
Args:
posthog: Insights client instance (optional if personal_api_key provided)
personal_api_key: Direct personal API key (optional if posthog provided)
project_api_key: Direct project API key (optional if posthog provided)
client: Insights client instance (optional if personal_api_key provided)
personal_api_key: Direct personal API key (optional if client provided)
project_api_key: Direct project API key (optional if client provided)
host: Insights host (defaults to app endpoint)
default_cache_ttl_seconds: Default cache TTL (defaults to 300)
"""
@@ -112,11 +112,11 @@ class Prompts:
)
self._cache: Dict[PromptCacheKey, CachedPrompt] = {}
if posthog is not None:
self._personal_api_key = getattr(posthog, "personal_api_key", None) or ""
self._project_api_key = getattr(posthog, "api_key", None) or ""
if client is not None:
self._personal_api_key = getattr(client, "personal_api_key", None) or ""
self._project_api_key = getattr(client, "api_key", None) or ""
self._host = remove_trailing_slash(
getattr(posthog, "raw_host", None) or APP_ENDPOINT
getattr(client, "raw_host", None) or APP_ENDPOINT
)
else:
self._personal_api_key = personal_api_key or ""
+41 -41
View File
@@ -26,10 +26,10 @@ _TOKEN_PROPERTY_KEYS = frozenset(
def _get_tokens_source(
sdk_tags: Dict[str, Any], posthog_properties: Optional[Dict[str, Any]]
sdk_tags: Dict[str, Any], insights_properties: Optional[Dict[str, Any]]
) -> str:
if posthog_properties and any(
key in posthog_properties for key in _TOKEN_PROPERTY_KEYS
if insights_properties and any(
key in insights_properties for key in _TOKEN_PROPERTY_KEYS
):
return "passthrough"
return "sdk"
@@ -319,13 +319,13 @@ def merge_system_prompt(
def call_llm_and_track_usage(
posthog_distinct_id: Optional[str],
insights_distinct_id: Optional[str],
ph_client: InsightsClient,
provider: str,
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
base_url: str,
call_method: Callable[..., Any],
**kwargs: Any,
@@ -342,8 +342,8 @@ def call_llm_and_track_usage(
error_params: Dict[str, Any] = {}
with new_context(client=ph_client, capture_exceptions=False):
if posthog_distinct_id:
identify_context(posthog_distinct_id)
if insights_distinct_id:
identify_context(insights_distinct_id)
try:
response = call_method(**kwargs)
@@ -363,18 +363,18 @@ def call_llm_and_track_usage(
end_time = time.time()
latency = end_time - start_time
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
# Check if we have a real user distinct_id (from param or outer context)
has_person_distinct_id = (
posthog_distinct_id is not None
insights_distinct_id is not None
or contexts.get_context_distinct_id() is not None
)
if not has_person_distinct_id:
# Fall back to trace_id as distinct_id when no real user id is available.
identify_context(posthog_trace_id)
identify_context(insights_trace_id)
if response and (
hasattr(response, "usage")
@@ -390,19 +390,19 @@ def call_llm_and_track_usage(
tag("$ai_model_parameters", get_model_params(kwargs))
tag(
"$ai_input",
with_privacy_mode(ph_client, posthog_privacy_mode, sanitized_messages),
with_privacy_mode(ph_client, insights_privacy_mode, sanitized_messages),
)
tag(
"$ai_output_choices",
with_privacy_mode(
ph_client, posthog_privacy_mode, format_response(response, provider)
ph_client, insights_privacy_mode, format_response(response, provider)
),
)
tag("$ai_http_status", http_status)
tag("$ai_input_tokens", usage.get("input_tokens", 0))
tag("$ai_output_tokens", usage.get("output_tokens", 0))
tag("$ai_latency", latency)
tag("$ai_trace_id", posthog_trace_id)
tag("$ai_trace_id", insights_trace_id)
tag("$ai_base_url", str(base_url))
available_tool_calls = extract_available_tool_calls(provider, kwargs)
@@ -439,26 +439,26 @@ def call_llm_and_track_usage(
tag(
"$ai_instructions",
with_privacy_mode(
ph_client, posthog_privacy_mode, kwargs.get("instructions")
ph_client, insights_privacy_mode, kwargs.get("instructions")
),
)
# send the event to posthog
# send the event to Insights
if hasattr(ph_client, "capture") and callable(ph_client.capture):
sdk_tags = get_tags()
merged_properties = {
**sdk_tags,
**(posthog_properties or {}),
**(insights_properties or {}),
**(error_params or {}),
}
merged_properties["$ai_tokens_source"] = _get_tokens_source(
sdk_tags, posthog_properties
sdk_tags, insights_properties
)
ph_client.capture(
distinct_id=contexts.get_context_distinct_id(),
event="$ai_generation",
properties=merged_properties,
groups=posthog_groups,
groups=insights_groups,
)
if error:
@@ -468,13 +468,13 @@ def call_llm_and_track_usage(
async def call_llm_and_track_usage_async(
posthog_distinct_id: Optional[str],
insights_distinct_id: Optional[str],
ph_client: InsightsClient,
provider: str,
posthog_trace_id: Optional[str],
posthog_properties: Optional[Dict[str, Any]],
posthog_privacy_mode: bool,
posthog_groups: Optional[Dict[str, Any]],
insights_trace_id: Optional[str],
insights_properties: Optional[Dict[str, Any]],
insights_privacy_mode: bool,
insights_groups: Optional[Dict[str, Any]],
base_url: str,
call_async_method: Callable[..., Any],
**kwargs: Any,
@@ -487,8 +487,8 @@ async def call_llm_and_track_usage_async(
error_params: Dict[str, Any] = {}
with new_context(client=ph_client, capture_exceptions=False):
if posthog_distinct_id:
identify_context(posthog_distinct_id)
if insights_distinct_id:
identify_context(insights_distinct_id)
try:
response = await call_async_method(**kwargs)
@@ -508,18 +508,18 @@ async def call_llm_and_track_usage_async(
end_time = time.time()
latency = end_time - start_time
if posthog_trace_id is None:
posthog_trace_id = str(uuid.uuid4())
if insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
# Check if we have a real user distinct_id (from param or outer context)
has_person_distinct_id = (
posthog_distinct_id is not None
insights_distinct_id is not None
or contexts.get_context_distinct_id() is not None
)
if not has_person_distinct_id:
# Fall back to trace_id as distinct_id when no real user id is available.
identify_context(posthog_trace_id)
identify_context(insights_trace_id)
if response and (
hasattr(response, "usage")
@@ -535,19 +535,19 @@ async def call_llm_and_track_usage_async(
tag("$ai_model_parameters", get_model_params(kwargs))
tag(
"$ai_input",
with_privacy_mode(ph_client, posthog_privacy_mode, sanitized_messages),
with_privacy_mode(ph_client, insights_privacy_mode, sanitized_messages),
)
tag(
"$ai_output_choices",
with_privacy_mode(
ph_client, posthog_privacy_mode, format_response(response, provider)
ph_client, insights_privacy_mode, format_response(response, provider)
),
)
tag("$ai_http_status", http_status)
tag("$ai_input_tokens", usage.get("input_tokens", 0))
tag("$ai_output_tokens", usage.get("output_tokens", 0))
tag("$ai_latency", latency)
tag("$ai_trace_id", posthog_trace_id)
tag("$ai_trace_id", insights_trace_id)
tag("$ai_base_url", str(base_url))
available_tool_calls = extract_available_tool_calls(provider, kwargs)
@@ -584,26 +584,26 @@ async def call_llm_and_track_usage_async(
tag(
"$ai_instructions",
with_privacy_mode(
ph_client, posthog_privacy_mode, kwargs.get("instructions")
ph_client, insights_privacy_mode, kwargs.get("instructions")
),
)
# send the event to posthog
# send the event to Insights
if hasattr(ph_client, "capture") and callable(ph_client.capture):
sdk_tags = get_tags()
merged_properties = {
**sdk_tags,
**(posthog_properties or {}),
**(insights_properties or {}),
**(error_params or {}),
}
merged_properties["$ai_tokens_source"] = _get_tokens_source(
sdk_tags, posthog_properties
sdk_tags, insights_properties
)
ph_client.capture(
distinct_id=contexts.get_context_distinct_id(),
event="$ai_generation",
properties=merged_properties,
groups=posthog_groups,
groups=insights_groups,
)
if error:
+1 -1
View File
@@ -1108,7 +1108,7 @@ class Client(object):
if not msg.get("properties"):
msg["properties"] = {}
msg["properties"]["$lib"] = "posthog-python"
msg["properties"]["$lib"] = "insights-python"
msg["properties"]["$lib_version"] = VERSION
if disable_geoip is None:
+1 -1
View File
@@ -104,7 +104,7 @@ class ContextScope:
_context_stack: contextvars.ContextVar[Optional[ContextScope]] = contextvars.ContextVar(
"posthog_context_stack", default=None
"insights_context_stack", default=None
)
+8 -8
View File
@@ -62,8 +62,8 @@ DEFAULT_CODE_VARIABLES_MASK_PATTERNS = [
DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS = [r"^__.*"]
CODE_VARIABLES_REDACTED_VALUE = "$$_posthog_redacted_based_on_masking_rules_$$"
CODE_VARIABLES_TOO_LONG_VALUE = "$$_posthog_value_too_long_$$"
CODE_VARIABLES_REDACTED_VALUE = "$$_insights_redacted_based_on_masking_rules_$$"
CODE_VARIABLES_TOO_LONG_VALUE = "$$_insights_value_too_long_$$"
_MAX_VALUE_LENGTH_FOR_PATTERN_MATCH = 5_000
_MAX_COLLECTION_ITEMS_TO_SCAN = 100
@@ -768,12 +768,12 @@ def set_in_app_in_frames(frames, in_app_exclude, in_app_include, project_root=No
def exception_is_already_captured(error):
# type: (ExceptionArg) -> bool
if isinstance(error, BaseException):
return hasattr(error, "__posthog_exception_captured")
return hasattr(error, "__insights_exception_captured")
# Autocaptured exceptions are passed as a tuple from our system hooks,
# the second item is the exception value (the first is the exception type)
elif isinstance(error, tuple) and len(error) > 1:
return error[1] is not None and hasattr(
error[1], "__posthog_exception_captured"
error[1], "__insights_exception_captured"
)
else:
return False # type: ignore[unreachable]
@@ -782,14 +782,14 @@ def exception_is_already_captured(error):
def mark_exception_as_captured(error, uuid):
# type: (ExceptionArg, str) -> None
if isinstance(error, BaseException):
setattr(error, "__posthog_exception_captured", True)
setattr(error, "__posthog_exception_uuid", uuid)
setattr(error, "__insights_exception_captured", True)
setattr(error, "__insights_exception_uuid", uuid)
# Autocaptured exceptions are passed as a tuple from our system hooks,
# the second item is the exception value (the first is the exception type)
elif isinstance(error, tuple) and len(error) > 1:
if error[1] is not None:
setattr(error[1], "__posthog_exception_captured", True)
setattr(error[1], "__posthog_exception_uuid", uuid)
setattr(error[1], "__insights_exception_captured", True)
setattr(error[1], "__insights_exception_uuid", uuid)
def exc_info_from_error(error):
+11 -19
View File
@@ -25,20 +25,20 @@ class InsightsContextMiddleware:
"""Middleware to automatically track Django requests.
This middleware wraps all calls with an Insights context. It attempts to extract the following from the request headers:
- Session ID, (extracted from `X-INSIGHTS-SESSION-ID` or `X-POSTHOG-SESSION-ID`)
- Distinct ID, (extracted from `X-INSIGHTS-DISTINCT-ID` or `X-POSTHOG-DISTINCT-ID`)
- Session ID, (extracted from `X-INSIGHTS-SESSION-ID`)
- Distinct ID, (extracted from `X-INSIGHTS-DISTINCT-ID`)
- Request URL as $current_url
- Request Method as $request_method
The context will also auto-capture exceptions and send them to Insights, unless you disable it by setting
`INSIGHTS_MW_CAPTURE_EXCEPTIONS` (or `POSTHOG_MW_CAPTURE_EXCEPTIONS`) to `False` in your Django settings.
`INSIGHTS_MW_CAPTURE_EXCEPTIONS` to `False` in your Django settings.
The exceptions are captured using the global client, unless the setting `INSIGHTS_MW_CLIENT`
(or `POSTHOG_MW_CLIENT`) is set to a custom client instance.
is set to a custom client instance.
The middleware behaviour is customisable through 3 additional functions:
- `INSIGHTS_MW_EXTRA_TAGS` (or `POSTHOG_MW_EXTRA_TAGS`), which is a Callable[[HttpRequest], Dict[str, Any]] expected to return a dictionary of additional tags to be added to the context.
- `INSIGHTS_MW_REQUEST_FILTER` (or `POSTHOG_MW_REQUEST_FILTER`), which is a Callable[[HttpRequest], bool] expected to return `False` if the request should not be tracked.
- `INSIGHTS_MW_TAG_MAP` (or `POSTHOG_MW_TAG_MAP`), which is a Callable[[Dict[str, Any]], Dict[str, Any]], which you can use to modify the tags before they're added to the context.
- `INSIGHTS_MW_EXTRA_TAGS`, which is a Callable[[HttpRequest], Dict[str, Any]] expected to return a dictionary of additional tags to be added to the context.
- `INSIGHTS_MW_REQUEST_FILTER`, which is a Callable[[HttpRequest], bool] expected to return `False` if the request should not be tracked.
- `INSIGHTS_MW_TAG_MAP`, which is a Callable[[Dict[str, Any]], Dict[str, Any]], which you can use to modify the tags before they're added to the context.
You can use the `INSIGHTS_MW_TAG_MAP` function to remove any default tags you don't want to capture, or override them with your own values.
@@ -67,14 +67,10 @@ class InsightsContextMiddleware:
from django.conf import settings
# Support both INSIGHTS_MW_* and legacy POSTHOG_MW_* setting names
def _get_setting(name):
insights_name = f"INSIGHTS_MW_{name}"
posthog_name = f"POSTHOG_MW_{name}"
if hasattr(settings, insights_name):
return getattr(settings, insights_name)
if hasattr(settings, posthog_name):
return getattr(settings, posthog_name)
return None
extra_tags = _get_setting("EXTRA_TAGS")
@@ -131,13 +127,13 @@ class InsightsContextMiddleware:
"""
tags = {}
# Extract session ID from X-INSIGHTS-SESSION-ID or X-POSTHOG-SESSION-ID header
session_id = request.headers.get("X-INSIGHTS-SESSION-ID") or request.headers.get("X-POSTHOG-SESSION-ID")
# Extract session ID from X-INSIGHTS-SESSION-ID header
session_id = request.headers.get("X-INSIGHTS-SESSION-ID")
if session_id:
contexts.set_context_session(session_id)
# Extract distinct ID from X-INSIGHTS-DISTINCT-ID or X-POSTHOG-DISTINCT-ID header or request user id
distinct_id = request.headers.get("X-INSIGHTS-DISTINCT-ID") or request.headers.get("X-POSTHOG-DISTINCT-ID") or user_id
# Extract distinct ID from X-INSIGHTS-DISTINCT-ID header or request user id
distinct_id = request.headers.get("X-INSIGHTS-DISTINCT-ID") or user_id
if distinct_id:
contexts.identify_context(distinct_id)
@@ -323,7 +319,3 @@ class InsightsContextMiddleware:
from hanzo_insights import capture_exception
capture_exception(exception)
# Backward compatibility alias
PosthogContextMiddleware = InsightsContextMiddleware
+4 -4
View File
@@ -159,8 +159,8 @@ def disable_connection_reuse() -> None:
_pooling_enabled = False
US_INGESTION_ENDPOINT = "https://us.i.posthog.com"
EU_INGESTION_ENDPOINT = "https://eu.i.posthog.com"
US_INGESTION_ENDPOINT = "https://us.i.insights.hanzo.ai"
EU_INGESTION_ENDPOINT = "https://eu.i.insights.hanzo.ai"
DEFAULT_HOST = US_INGESTION_ENDPOINT
USER_AGENT = "hanzo-insights-python/" + VERSION
@@ -169,9 +169,9 @@ def determine_server_host(host: Optional[str]) -> str:
"""Determines the server host to use."""
host_or_default = host or DEFAULT_HOST
trimmed_host = remove_trailing_slash(host_or_default)
if trimmed_host in ("https://app.posthog.com", "https://us.posthog.com", "https://insights.hanzo.ai"):
if trimmed_host in ("https://app.posthog.com", "https://us.posthog.com", "https://insights.hanzo.ai", "https://us.insights.hanzo.ai"):
return US_INGESTION_ENDPOINT
elif trimmed_host == "https://eu.posthog.com":
elif trimmed_host in ("https://eu.posthog.com", "https://eu.insights.hanzo.ai"):
return EU_INGESTION_ENDPOINT
else:
return host_or_default
@@ -279,12 +279,12 @@ def test_basic_completion(mock_client, mock_anthropic_response):
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_anthropic_response
@@ -325,12 +325,12 @@ def test_tokens_source_passthrough(mock_client, mock_anthropic_response):
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_properties={"$ai_input_tokens": 99999},
insights_distinct_id="test-id",
insights_properties={"$ai_input_tokens": 99999},
)
props = mock_client.capture.call_args[1]["properties"]
@@ -342,12 +342,12 @@ def test_groups(mock_client, mock_anthropic_response):
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_groups={"company": "test_company"},
insights_distinct_id="test-id",
insights_groups={"company": "test_company"},
)
assert response == mock_anthropic_response
@@ -361,12 +361,12 @@ def test_privacy_mode_local(mock_client, mock_anthropic_response):
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_privacy_mode=True,
insights_distinct_id="test-id",
insights_privacy_mode=True,
)
assert response == mock_anthropic_response
@@ -383,12 +383,12 @@ def test_privacy_mode_global(mock_client, mock_anthropic_response):
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
mock_client.privacy_mode = True
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_privacy_mode=False,
insights_distinct_id="test-id",
insights_privacy_mode=False,
)
assert response == mock_anthropic_response
@@ -407,14 +407,14 @@ def test_basic_integration(mock_client):
"anthropic.resources.Messages.create",
return_value=create_mock_response(),
):
client = Anthropic(posthog_client=mock_client)
client = Anthropic(insights_client=mock_client)
client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Foo"}],
max_tokens=1,
temperature=0,
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
system="You must always answer with 'Bar'.",
)
@@ -452,7 +452,7 @@ async def test_basic_async_integration(mock_client):
"anthropic.resources.messages.AsyncMessages.create",
side_effect=mock_async_create,
):
client = AsyncAnthropic(posthog_client=mock_client)
client = AsyncAnthropic(insights_client=mock_client)
await client.messages.create(
model="claude-3-opus-20240229",
messages=[
@@ -460,8 +460,8 @@ async def test_basic_async_integration(mock_client):
],
max_tokens=1,
temperature=0,
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert mock_client.capture.call_count == 1
@@ -513,7 +513,7 @@ async def test_async_streaming_system_prompt(mock_client):
"anthropic.resources.messages.AsyncMessages.create",
side_effect=async_create_wrapper,
):
client = AsyncAnthropic(posthog_client=mock_client)
client = AsyncAnthropic(insights_client=mock_client)
response = await client.messages.create(
model="claude-3-opus-20240229",
system="You must always answer with 'Bar'.",
@@ -541,7 +541,7 @@ def test_error(mock_client, mock_anthropic_response):
with patch(
"anthropic.resources.Messages.create", side_effect=Exception("Test error")
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
with pytest.raises(Exception):
client.messages.create(
model="claude-3-opus-20240229",
@@ -561,12 +561,12 @@ def test_cached_tokens(mock_client, mock_anthropic_response_with_cached_tokens):
"anthropic.resources.Messages.create",
return_value=mock_anthropic_response_with_cached_tokens,
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_anthropic_response_with_cached_tokens
@@ -600,7 +600,7 @@ def test_tool_definition(mock_client, mock_anthropic_response):
"anthropic.resources.Messages.create",
return_value=mock_anthropic_response,
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
tools = [
{
@@ -625,8 +625,8 @@ def test_tool_definition(mock_client, mock_anthropic_response):
temperature=0.7,
tools=tools,
messages=[{"role": "user", "content": "hey"}],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_anthropic_response
@@ -662,7 +662,7 @@ def test_tool_calls_in_output_choices(
"anthropic.resources.Messages.create",
return_value=mock_anthropic_response_with_tool_calls,
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=200,
@@ -680,7 +680,7 @@ def test_tool_calls_in_output_choices(
},
}
],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_anthropic_response_with_tool_calls
@@ -724,7 +724,7 @@ def test_tool_calls_only_no_content(
"anthropic.resources.Messages.create",
return_value=mock_anthropic_response_tool_calls_only,
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=200,
@@ -743,7 +743,7 @@ def test_tool_calls_only_no_content(
},
}
],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_anthropic_response_tool_calls_only
@@ -790,7 +790,7 @@ def test_async_tool_calls_in_output_choices(
"anthropic.resources.AsyncMessages.create",
side_effect=mock_async_create,
):
async_client = AsyncAnthropic(api_key="test-key", posthog_client=mock_client)
async_client = AsyncAnthropic(api_key="test-key", insights_client=mock_client)
async def run_test():
return await async_client.messages.create(
@@ -810,7 +810,7 @@ def test_async_tool_calls_in_output_choices(
},
}
],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
response = asyncio.run(run_test())
@@ -855,7 +855,7 @@ def test_streaming_with_tool_calls(mock_client, mock_anthropic_stream_with_tools
"anthropic.resources.Messages.create",
return_value=mock_anthropic_stream_with_tools,
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
system="You are a helpful weather assistant.",
@@ -877,7 +877,7 @@ def test_streaming_with_tool_calls(mock_client, mock_anthropic_stream_with_tools
}
],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the stream - this triggers the finally block synchronously
@@ -977,7 +977,7 @@ def test_async_streaming_with_tool_calls(mock_client, mock_anthropic_stream_with
"anthropic.resources.AsyncMessages.create",
side_effect=mock_async_create,
):
async_client = AsyncAnthropic(api_key="test-key", posthog_client=mock_client)
async_client = AsyncAnthropic(api_key="test-key", insights_client=mock_client)
async def run_test():
response = await async_client.messages.create(
@@ -1001,7 +1001,7 @@ def test_async_streaming_with_tool_calls(mock_client, mock_anthropic_stream_with
}
],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the async stream
@@ -1101,11 +1101,11 @@ def test_web_search_count(mock_client):
mock_response = MockResponseWithWebSearch()
with patch("anthropic.resources.Messages.create", return_value=mock_response):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Search for recent news"}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1179,12 +1179,12 @@ def test_streaming_with_web_search(mock_client, mock_anthropic_stream_with_web_s
"anthropic.resources.Messages.create",
return_value=mock_anthropic_stream_with_web_search,
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
response = client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Search for recent news"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the stream - this triggers the finally block synchronously
@@ -1234,13 +1234,13 @@ def test_async_with_web_search(mock_client):
"anthropic.resources.AsyncMessages.create",
side_effect=mock_async_create,
):
async_client = AsyncAnthropic(api_key="test-key", posthog_client=mock_client)
async_client = AsyncAnthropic(api_key="test-key", insights_client=mock_client)
async def run_test():
response = await async_client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Search for recent news"}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
return response
@@ -1278,14 +1278,14 @@ def test_async_streaming_with_web_search(
"anthropic.resources.AsyncMessages.create",
side_effect=mock_async_create,
):
async_client = AsyncAnthropic(api_key="test-key", posthog_client=mock_client)
async_client = AsyncAnthropic(api_key="test-key", insights_client=mock_client)
async def run_test():
response = await async_client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Search for recent news"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the async stream
@@ -1318,11 +1318,11 @@ def test_no_distinct_id_uses_trace_id_and_personless(
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_trace_id="trace-123",
insights_trace_id="trace-123",
)
call_args = mock_client.capture.call_args[1]
@@ -1335,16 +1335,16 @@ def test_no_distinct_id_uses_trace_id_and_personless(
def test_explicit_distinct_id_creates_person_profile(
mock_client, mock_anthropic_response
):
"""When posthog_distinct_id is explicitly passed, it is used and event is not personless."""
"""When insights_distinct_id is explicitly passed, it is used and event is not personless."""
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="user-123",
posthog_trace_id="trace-123",
insights_distinct_id="user-123",
insights_trace_id="trace-123",
)
call_args = mock_client.capture.call_args[1]
@@ -1362,13 +1362,13 @@ def test_outer_context_distinct_id_is_used(mock_client, mock_anthropic_response)
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
with new_context():
identify_context("outer-user-456")
client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_trace_id="trace-123",
insights_trace_id="trace-123",
)
call_args = mock_client.capture.call_args[1]
@@ -1384,18 +1384,18 @@ def test_outer_context_distinct_id_is_used(mock_client, mock_anthropic_response)
def test_explicit_distinct_id_overrides_outer_context(
mock_client, mock_anthropic_response
):
"""When both outer context and explicit posthog_distinct_id are set, explicit wins."""
"""When both outer context and explicit insights_distinct_id are set, explicit wins."""
with patch(
"anthropic.resources.Messages.create", return_value=mock_anthropic_response
):
client = Anthropic(api_key="test-key", posthog_client=mock_client)
client = Anthropic(api_key="test-key", insights_client=mock_client)
with new_context():
identify_context("outer-user-456")
client.messages.create(
model="claude-3-opus-20240229",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="explicit-user-789",
posthog_trace_id="trace-123",
insights_distinct_id="explicit-user-789",
insights_trace_id="trace-123",
)
call_args = mock_client.capture.call_args[1]
+62 -62
View File
@@ -172,13 +172,13 @@ def test_new_client_basic_generation(
"""Test the new Client/Models API structure"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=["Tell me a fun fact about hedgehogs"],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_gemini_response
@@ -239,13 +239,13 @@ def test_new_client_streaming_with_generate_content_stream(
mock_streaming_response()
)
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content_stream(
model="gemini-2.0-flash",
contents=["Write a short story"],
posthog_distinct_id="test-id",
posthog_properties={"feature": "streaming"},
insights_distinct_id="test-id",
insights_properties={"feature": "streaming"},
)
chunks = list(response)
@@ -298,7 +298,7 @@ def test_new_client_streaming_with_tools(mock_client, mock_google_genai_client):
mock_streaming_response()
)
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
# Create mock tools configuration
mock_tool = MagicMock()
@@ -326,8 +326,8 @@ def test_new_client_streaming_with_tools(mock_client, mock_google_genai_client):
model="gemini-2.0-flash",
contents=["What's the weather in SF?"],
config=mock_config,
posthog_distinct_id="test-id",
posthog_properties={"feature": "streaming_with_tools"},
insights_distinct_id="test-id",
insights_properties={"feature": "streaming_with_tools"},
)
chunks = list(response)
@@ -357,13 +357,13 @@ def test_new_client_groups(mock_client, mock_google_genai_client, mock_gemini_re
"""Test groups functionality with new Client API"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
posthog_groups={"company": "company_123"},
insights_distinct_id="test-id",
insights_groups={"company": "company_123"},
)
call_args = mock_client.capture.call_args[1]
@@ -376,13 +376,13 @@ def test_new_client_privacy_mode_local(
"""Test local privacy mode with new Client API"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
posthog_privacy_mode=True,
insights_distinct_id="test-id",
insights_privacy_mode=True,
)
call_args = mock_client.capture.call_args[1]
@@ -399,12 +399,12 @@ def test_new_client_privacy_mode_global(
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
@@ -419,11 +419,11 @@ def test_new_client_different_input_formats(
"""Test different input formats with new Client API"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
# Test string input
client.models.generate_content(
model="gemini-2.0-flash", contents="Hello", posthog_distinct_id="test-id"
model="gemini-2.0-flash", contents="Hello", insights_distinct_id="test-id"
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -434,7 +434,7 @@ def test_new_client_different_input_formats(
client.models.generate_content(
model="gemini-2.0-flash",
contents=[{"role": "user", "parts": [{"text": "hey"}]}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -447,7 +447,7 @@ def test_new_client_different_input_formats(
client.models.generate_content(
model="gemini-2.0-flash",
contents=[{"role": "user", "parts": [{"text": "Hello "}, {"text": "world"}]}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -464,7 +464,7 @@ def test_new_client_different_input_formats(
# Test list input with string
mock_client.capture.reset_mock()
client.models.generate_content(
model="gemini-2.0-flash", contents=["List item"], posthog_distinct_id="test-id"
model="gemini-2.0-flash", contents=["List item"], insights_distinct_id="test-id"
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -477,12 +477,12 @@ def test_new_client_model_parameters(
"""Test model parameters with new Client API"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
temperature=0.7,
max_tokens=100,
)
@@ -496,16 +496,16 @@ def test_new_client_model_parameters(
def test_new_client_default_settings(
mock_client, mock_google_genai_client, mock_gemini_response
):
"""Test client with default PostHog settings"""
"""Test client with default Insights settings"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(
api_key="test-key",
posthog_client=mock_client,
posthog_distinct_id="default_user",
posthog_properties={"team": "ai"},
posthog_privacy_mode=False,
posthog_groups={"company": "acme_corp"},
insights_client=mock_client,
insights_distinct_id="default_user",
insights_properties={"team": "ai"},
insights_privacy_mode=False,
insights_groups={"company": "acme_corp"},
)
# Call without overriding defaults
@@ -527,21 +527,21 @@ def test_new_client_override_defaults(
client = Client(
api_key="test-key",
posthog_client=mock_client,
posthog_distinct_id="default_user",
posthog_properties={"team": "ai"},
posthog_privacy_mode=False,
posthog_groups={"company": "acme_corp"},
insights_client=mock_client,
insights_distinct_id="default_user",
insights_properties={"team": "ai"},
insights_privacy_mode=False,
insights_groups={"company": "acme_corp"},
)
# Override defaults in call
client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="specific_user",
posthog_properties={"feature": "chat", "urgent": True},
posthog_privacy_mode=True,
posthog_groups={"organization": "special_org"},
insights_distinct_id="specific_user",
insights_properties={"feature": "chat", "urgent": True},
insights_privacy_mode=True,
insights_groups={"organization": "special_org"},
)
call_args = mock_client.capture.call_args[1]
@@ -577,7 +577,7 @@ def test_vertex_ai_parameters_passed_through(
location="us-central1",
debug_config=mock_debug_config,
http_options=mock_http_options,
posthog_client=mock_client,
insights_client=mock_client,
)
# Verify genai.Client was called with correct parameters
@@ -597,7 +597,7 @@ def test_api_key_mode(mock_client, mock_google_genai_client):
# Create client with just API key (traditional mode)
Client(
api_key="test-api-key",
posthog_client=mock_client,
insights_client=mock_client,
)
# Verify genai.Client was called with only api_key
@@ -618,7 +618,7 @@ def test_vertex_ai_mode_with_optional_api_key(
api_key="test-api-key",
credentials=mock_credentials,
project="test-project",
posthog_client=mock_client,
insights_client=mock_client,
)
# Verify genai.Client was called with both Vertex AI params and API key
@@ -634,7 +634,7 @@ def test_tool_use_response(mock_client, mock_google_genai_client, mock_gemini_re
"""Test that tools defined in config are captured in $ai_tools property"""
mock_google_genai_client.models.generate_content.return_value = mock_gemini_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
# Create mock tools configuration
mock_tool = MagicMock()
@@ -664,8 +664,8 @@ def test_tool_use_response(mock_client, mock_google_genai_client, mock_gemini_re
model="gemini-2.5-flash",
contents=["hey"],
config=mock_config,
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_gemini_response
@@ -702,12 +702,12 @@ def test_function_calls_in_output_choices(
mock_gemini_response_with_function_calls
)
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=["What's the weather in San Francisco?"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_gemini_response_with_function_calls
@@ -751,12 +751,12 @@ def test_function_calls_only_no_content(
mock_gemini_response_function_calls_only
)
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=["Get weather for New York"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_gemini_response_function_calls_only
@@ -810,12 +810,12 @@ def test_cache_and_reasoning_tokens(mock_client, mock_google_genai_client):
mock_google_genai_client.models.generate_content.return_value = mock_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="Test with cache",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -869,19 +869,19 @@ def test_streaming_cache_and_reasoning_tokens(mock_client, mock_google_genai_cli
mock_stream = iter([chunk1, chunk2])
mock_google_genai_client.models.generate_content_stream.return_value = mock_stream
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content_stream(
model="gemini-2.5-pro",
contents="Test streaming with cache",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the stream
result = list(response)
assert len(result) == 2
# Check PostHog capture was called
# Check Insights capture was called
assert mock_client.capture.call_count == 1
call_args = mock_client.capture.call_args[1]
@@ -946,11 +946,11 @@ def test_web_search_grounding(mock_client, mock_google_genai_client):
# Mock the generate_content method
mock_google_genai_client.models.generate_content.return_value = mock_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="What's the latest news?",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1015,12 +1015,12 @@ def test_streaming_with_web_search(mock_client, mock_google_genai_client):
mock_streaming_response()
)
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content_stream(
model="gemini-2.5-flash",
contents="What's the latest news?",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
chunks = list(response)
@@ -1080,12 +1080,12 @@ def test_empty_grounding_metadata_no_web_search(mock_client, mock_google_genai_c
# Mock the generate_content method
mock_google_genai_client.models.generate_content.return_value = mock_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="Hello",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1143,12 +1143,12 @@ def test_empty_array_grounding_metadata_no_web_search(
# Mock the generate_content method
mock_google_genai_client.models.generate_content.return_value = mock_response
client = Client(api_key="test-key", posthog_client=mock_client)
client = Client(api_key="test-key", insights_client=mock_client)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents="What can you do?",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -121,13 +121,13 @@ async def test_async_client_basic_generation(
return_value=mock_gemini_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Tell me a fun fact about hedgehogs"],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_gemini_response
@@ -178,13 +178,13 @@ async def test_async_client_streaming_with_generate_content_stream(
return_value=mock_streaming_response()
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content_stream(
model="gemini-2.0-flash",
contents=["Write a short story"],
posthog_distinct_id="test-id",
posthog_properties={"feature": "streaming"},
insights_distinct_id="test-id",
insights_properties={"feature": "streaming"},
)
chunks = []
@@ -239,7 +239,7 @@ async def test_async_client_streaming_with_tools(mock_client, mock_google_genai_
return_value=mock_streaming_response()
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
# Create mock tools configuration
mock_tool = MagicMock()
@@ -267,8 +267,8 @@ async def test_async_client_streaming_with_tools(mock_client, mock_google_genai_
model="gemini-2.0-flash",
contents=["What's the weather in SF?"],
config=mock_config,
posthog_distinct_id="test-id",
posthog_properties={"feature": "streaming_with_tools"},
insights_distinct_id="test-id",
insights_properties={"feature": "streaming_with_tools"},
)
chunks = []
@@ -305,13 +305,13 @@ async def test_async_client_groups(
return_value=mock_gemini_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
posthog_groups={"company": "company_123"},
insights_distinct_id="test-id",
insights_groups={"company": "company_123"},
)
call_args = mock_client.capture.call_args[1]
@@ -326,13 +326,13 @@ async def test_async_client_privacy_mode_local(
return_value=mock_gemini_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
posthog_privacy_mode=True,
insights_distinct_id="test-id",
insights_privacy_mode=True,
)
call_args = mock_client.capture.call_args[1]
@@ -351,12 +351,12 @@ async def test_async_client_privacy_mode_global(
return_value=mock_gemini_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
@@ -373,11 +373,11 @@ async def test_async_client_different_input_formats(
return_value=mock_gemini_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
# Test string input
await client.models.generate_content(
model="gemini-2.0-flash", contents="Hello", posthog_distinct_id="test-id"
model="gemini-2.0-flash", contents="Hello", insights_distinct_id="test-id"
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -388,7 +388,7 @@ async def test_async_client_different_input_formats(
await client.models.generate_content(
model="gemini-2.0-flash",
contents=[{"role": "user", "parts": [{"text": "hey"}]}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -401,7 +401,7 @@ async def test_async_client_different_input_formats(
await client.models.generate_content(
model="gemini-2.0-flash",
contents=[{"role": "user", "parts": [{"text": "Hello "}, {"text": "world"}]}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -418,7 +418,7 @@ async def test_async_client_different_input_formats(
# Test list input with string
mock_client.capture.reset_mock()
await client.models.generate_content(
model="gemini-2.0-flash", contents=["List item"], posthog_distinct_id="test-id"
model="gemini-2.0-flash", contents=["List item"], insights_distinct_id="test-id"
)
call_args = mock_client.capture.call_args[1]
props = call_args["properties"]
@@ -433,12 +433,12 @@ async def test_async_client_model_parameters(
return_value=mock_gemini_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
temperature=0.7,
max_tokens=100,
)
@@ -452,18 +452,18 @@ async def test_async_client_model_parameters(
async def test_async_client_default_settings(
mock_client, mock_google_genai_client, mock_gemini_response
):
"""Test async client with default PostHog settings"""
"""Test async client with default Insights settings"""
mock_google_genai_client.aio.models.generate_content = AsyncMock(
return_value=mock_gemini_response
)
client = AsyncClient(
api_key="test-key",
posthog_client=mock_client,
posthog_distinct_id="default_user",
posthog_properties={"team": "ai"},
posthog_privacy_mode=False,
posthog_groups={"company": "acme_corp"},
insights_client=mock_client,
insights_distinct_id="default_user",
insights_properties={"team": "ai"},
insights_privacy_mode=False,
insights_groups={"company": "acme_corp"},
)
# Call without overriding defaults
@@ -487,21 +487,21 @@ async def test_async_client_override_defaults(
client = AsyncClient(
api_key="test-key",
posthog_client=mock_client,
posthog_distinct_id="default_user",
posthog_properties={"team": "ai"},
posthog_privacy_mode=False,
posthog_groups={"company": "acme_corp"},
insights_client=mock_client,
insights_distinct_id="default_user",
insights_properties={"team": "ai"},
insights_privacy_mode=False,
insights_groups={"company": "acme_corp"},
)
# Override defaults in call
await client.models.generate_content(
model="gemini-2.0-flash",
contents=["Hello"],
posthog_distinct_id="specific_user",
posthog_properties={"feature": "chat", "urgent": True},
posthog_privacy_mode=True,
posthog_groups={"organization": "special_org"},
insights_distinct_id="specific_user",
insights_properties={"feature": "chat", "urgent": True},
insights_privacy_mode=True,
insights_groups={"organization": "special_org"},
)
call_args = mock_client.capture.call_args[1]
@@ -539,7 +539,7 @@ async def test_async_vertex_ai_parameters_passed_through(
location="us-central1",
debug_config=mock_debug_config,
http_options=mock_http_options,
posthog_client=mock_client,
insights_client=mock_client,
)
# Verify genai.Client was called with correct parameters
@@ -559,7 +559,7 @@ async def test_async_api_key_mode(mock_client, mock_google_genai_client):
# Create async client with just API key (traditional mode)
AsyncClient(
api_key="test-api-key",
posthog_client=mock_client,
insights_client=mock_client,
)
# Verify genai.Client was called with only api_key
@@ -574,12 +574,12 @@ async def test_async_function_calls_in_output_choices(
return_value=mock_gemini_response_with_function_calls
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content(
model="gemini-2.5-flash",
contents=["What's the weather in San Francisco?"],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_gemini_response_with_function_calls
@@ -637,12 +637,12 @@ async def test_async_cache_and_reasoning_tokens(mock_client, mock_google_genai_c
return_value=mock_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content(
model="gemini-2.5-pro",
contents="Test with cache",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -689,12 +689,12 @@ async def test_async_streaming_cache_and_reasoning_tokens(
return_value=mock_streaming_response()
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content_stream(
model="gemini-2.5-pro",
contents="Test streaming with cache",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the stream
@@ -704,7 +704,7 @@ async def test_async_streaming_cache_and_reasoning_tokens(
assert len(result) == 2
# Check PostHog capture was called
# Check Insights capture was called
assert mock_client.capture.call_count == 1
call_args = mock_client.capture.call_args[1]
@@ -761,11 +761,11 @@ async def test_async_web_search_grounding(mock_client, mock_google_genai_client)
return_value=mock_response
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content(
model="gemini-2.5-flash",
contents="What's the latest news?",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -829,12 +829,12 @@ async def test_async_streaming_with_web_search(mock_client, mock_google_genai_cl
return_value=mock_streaming_response()
)
client = AsyncClient(api_key="test-key", posthog_client=mock_client)
client = AsyncClient(api_key="test-key", insights_client=mock_client)
response = await client.models.generate_content_stream(
model="gemini-2.5-flash",
contents="What's the latest news?",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
chunks = []
@@ -113,7 +113,7 @@ def test_metadata_capture(mock_client):
base_url="https://us.posthog.com",
name="test",
end_time=None,
posthog_properties=None,
insights_properties=None,
)
assert callbacks._runs[run_id] == expected
with patch("time.time", return_value=1234567891):
@@ -1049,7 +1049,7 @@ def test_base_url_retrieval(mock_client):
prompt = ChatPromptTemplate.from_messages([("user", "Foo")])
chain = prompt | ChatOpenAI(
api_key="test",
model="posthog-mini",
model="insights-mini",
base_url="https://test.posthog.com",
)
callbacks = CallbackHandler(mock_client)
@@ -1270,7 +1270,7 @@ def test_metadata_tools(mock_client):
name="test",
tools=tools,
end_time=None,
posthog_properties=None,
insights_properties=None,
)
assert callbacks._runs[run_id] == expected
with patch("time.time", return_value=1234567891):
@@ -1867,7 +1867,7 @@ def test_openai_reasoning_tokens_o4_mini(mock_client):
def test_callback_handler_without_client():
"""Test that CallbackHandler works properly when no PostHog client is passed."""
"""Test that CallbackHandler works properly when no Insights client is passed."""
with patch("hanzo_insights.ai.langchain.callbacks.setup") as mock_setup:
mock_client = mock_setup.return_value
@@ -1984,7 +1984,7 @@ def test_tool_definition(mock_client):
assert run == expected
assert callbacks._runs == {}
# Now test that the tools are properly captured in the PostHog event
# Now test that the tools are properly captured in the Insights event
mock_response = MagicMock()
mock_response.generations = [[MagicMock()]]
@@ -2266,8 +2266,8 @@ def test_agent_action_and_finish_imports():
assert call_args["event"] == "$ai_span"
def test_posthog_properties_field_in_generation_metadata(mock_client):
"""Test that posthog_properties is properly stored in GenerationMetadata."""
def test_insights_properties_field_in_generation_metadata(mock_client):
"""Test that insights_properties is properly stored in GenerationMetadata."""
callbacks = CallbackHandler(mock_client)
run_id = uuid.uuid4()
@@ -2281,7 +2281,7 @@ def test_posthog_properties_field_in_generation_metadata(mock_client):
metadata={
"ls_model_name": "gpt-4o",
"ls_provider": "openai",
"posthog_properties": {"$ai_billable": True},
"insights_properties": {"$ai_billable": True},
},
name="test",
)
@@ -2294,11 +2294,11 @@ def test_posthog_properties_field_in_generation_metadata(mock_client):
provider="openai",
base_url="https://api.openai.com",
name="test",
posthog_properties={"$ai_billable": True},
insights_properties={"$ai_billable": True},
end_time=None,
)
assert callbacks._runs[run_id] == expected
assert callbacks._runs[run_id].posthog_properties == {"$ai_billable": True}
assert callbacks._runs[run_id].insights_properties == {"$ai_billable": True}
callbacks._pop_run_metadata(run_id)
@@ -2313,15 +2313,15 @@ def test_posthog_properties_field_in_generation_metadata(mock_client):
metadata={
"ls_model_name": "gpt-4o",
"ls_provider": "openai",
"posthog_properties": {"$ai_billable": False},
"insights_properties": {"$ai_billable": False},
},
name="test",
)
assert callbacks._runs[run_id2].posthog_properties == {"$ai_billable": False}
assert callbacks._runs[run_id2].insights_properties == {"$ai_billable": False}
callbacks._pop_run_metadata(run_id2)
# Test when posthog_properties not provided
# Test when insights_properties not provided
run_id3 = uuid.uuid4()
with patch("time.time", return_value=1234567890):
callbacks._set_llm_metadata(
@@ -2333,7 +2333,7 @@ def test_posthog_properties_field_in_generation_metadata(mock_client):
name="test",
)
assert callbacks._runs[run_id3].posthog_properties is None
assert callbacks._runs[run_id3].insights_properties is None
def test_billable_property_in_generation_event(mock_client):
@@ -2349,7 +2349,7 @@ def test_billable_property_in_generation_event(mock_client):
run_id,
messages=[{"role": "user", "content": "Test"}],
metadata={
"posthog_properties": {"$ai_billable": True},
"insights_properties": {"$ai_billable": True},
"ls_model_name": "test-model",
},
invocation_params={},
@@ -2412,12 +2412,12 @@ def test_billable_with_real_chain(mock_client):
metadata={
"ls_model_name": "fake-model",
"ls_provider": "fake",
"posthog_properties": {"$ai_billable": True},
"insights_properties": {"$ai_billable": True},
},
invocation_params={"temperature": 0.7},
)
assert callbacks._runs[run_id].posthog_properties == {"$ai_billable": True}
assert callbacks._runs[run_id].insights_properties == {"$ai_billable": True}
mock_response = MagicMock()
mock_response.generations = [[MagicMock()]]
+80 -80
View File
@@ -467,12 +467,12 @@ def test_basic_completion(mock_client, mock_openai_response):
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_openai_response
@@ -513,12 +513,12 @@ def test_embeddings(mock_client, mock_embedding_response):
"openai.resources.embeddings.Embeddings.create",
return_value=mock_embedding_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.embeddings.create(
model="text-embedding-3-small",
input="Hello world",
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_embedding_response
@@ -543,12 +543,12 @@ def test_groups(mock_client, mock_openai_response):
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_groups={"company": "test_company"},
insights_distinct_id="test-id",
insights_groups={"company": "test_company"},
)
assert response == mock_openai_response
@@ -564,12 +564,12 @@ def test_privacy_mode_local(mock_client, mock_openai_response):
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_privacy_mode=True,
insights_distinct_id="test-id",
insights_privacy_mode=True,
)
assert response == mock_openai_response
@@ -587,12 +587,12 @@ def test_privacy_mode_global(mock_client, mock_openai_response):
return_value=mock_openai_response,
):
mock_client.privacy_mode = True
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_privacy_mode=False,
insights_distinct_id="test-id",
insights_privacy_mode=False,
)
assert response == mock_openai_response
@@ -609,7 +609,7 @@ def test_error(mock_client, mock_openai_response):
"openai.resources.chat.completions.Completions.create",
side_effect=Exception("Test error"),
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
with pytest.raises(Exception):
client.chat.completions.create(
model="gpt-4", messages=[{"role": "user", "content": "Hello"}]
@@ -628,12 +628,12 @@ def test_cached_tokens(mock_client, mock_openai_response_with_cached_tokens):
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response_with_cached_tokens,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_openai_response_with_cached_tokens
@@ -666,7 +666,7 @@ def test_tool_calls(mock_client, mock_openai_response_with_tool_calls):
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response_with_tool_calls,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[
@@ -682,7 +682,7 @@ def test_tool_calls(mock_client, mock_openai_response_with_tool_calls):
},
}
],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_openai_response_with_tool_calls
@@ -739,7 +739,7 @@ def test_tool_calls_only_no_content(mock_client, mock_openai_response_tool_calls
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response_tool_calls_only,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Get weather for New York"}],
@@ -753,7 +753,7 @@ def test_tool_calls_only_no_content(mock_client, mock_openai_response_tool_calls
},
}
],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_openai_response_tool_calls_only
@@ -793,7 +793,7 @@ def test_responses_api_tool_calls(mock_client, mock_responses_api_with_tool_call
"openai.resources.responses.Responses.create",
return_value=mock_responses_api_with_tool_calls,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.responses.create(
model="gpt-4o-mini",
input=[{"role": "user", "content": "What's the weather in Chicago?"}],
@@ -808,7 +808,7 @@ def test_responses_api_tool_calls(mock_client, mock_responses_api_with_tool_call
},
}
],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_responses_api_with_tool_calls
@@ -851,7 +851,7 @@ def test_streaming_with_tool_calls(mock_client, streaming_tool_call_chunks):
# Set up the mock to return our chunks when iterated
mock_create.return_value = streaming_tool_call_chunks
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Call the streaming method
response_generator = client.chat.completions.create(
@@ -870,7 +870,7 @@ def test_streaming_with_tool_calls(mock_client, streaming_tool_call_chunks):
}
],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the generator to trigger the event capture
@@ -949,12 +949,12 @@ def test_responses_api(mock_client, mock_openai_response_with_responses_api):
"openai.resources.responses.Responses.create",
return_value=mock_openai_response_with_responses_api,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.responses.create(
model="gpt-4o-mini",
input="Hello",
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_openai_response_with_responses_api
assert mock_client.capture.call_count == 1
@@ -986,7 +986,7 @@ def test_responses_parse(mock_client, mock_parsed_response):
"openai.resources.responses.Responses.parse",
return_value=mock_parsed_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.responses.parse(
model="gpt-4o-2024-08-06",
input=[
@@ -1016,8 +1016,8 @@ def test_responses_parse(mock_client, mock_parsed_response):
},
}
},
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_parsed_response
@@ -1101,15 +1101,15 @@ def test_responses_api_streaming_with_tokens(mock_client):
"openai.resources.responses.Responses.create",
side_effect=mock_streaming_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Consume the streaming response
response = client.responses.create(
model="gpt-4o-mini",
input=[{"role": "user", "content": "Test message"}],
stream=True,
posthog_distinct_id="test-id",
posthog_properties={"test": "streaming"},
insights_distinct_id="test-id",
insights_properties={"test": "streaming"},
)
# Consume all chunks
@@ -1153,7 +1153,7 @@ async def test_async_chat_streaming_with_tool_calls(
with patch(
"openai.resources.chat.completions.AsyncCompletions.create", new=mock_create
):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
response_stream = await client.chat.completions.create(
model="gpt-4",
@@ -1171,7 +1171,7 @@ async def test_async_chat_streaming_with_tool_calls(
}
],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
chunks = []
@@ -1239,14 +1239,14 @@ async def test_async_responses_streaming_with_tokens(mock_client):
return chunk_iterable()
with patch("openai.resources.responses.AsyncResponses.create", new=mock_create):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
response_stream = await client.responses.create(
model="gpt-4o-mini",
input=[{"role": "user", "content": "Test message"}],
stream=True,
posthog_distinct_id="test-id",
posthog_properties={"test": "streaming"},
insights_distinct_id="test-id",
insights_properties={"test": "streaming"},
)
async for _ in response_stream:
@@ -1274,13 +1274,13 @@ async def test_async_embeddings_create(mock_client, mock_embedding_response):
mock_create = AsyncMock(return_value=mock_embedding_response)
with patch("openai.resources.embeddings.AsyncEmbeddings.create", new=mock_create):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
response = await client.embeddings.create(
model="text-embedding-3-small",
input="Hello world",
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_embedding_response
@@ -1304,7 +1304,7 @@ def test_tool_definition(mock_client, mock_openai_response):
"openai.resources.chat.completions.Completions.create",
return_value=mock_openai_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Define tools to be passed to the create function
tools = [
@@ -1331,8 +1331,8 @@ def test_tool_definition(mock_client, mock_openai_response):
model="gpt-4o-mini",
messages=[{"role": "user", "content": "hey"}],
tools=tools,
posthog_distinct_id="test-id",
posthog_properties={"foo": "bar"},
insights_distinct_id="test-id",
insights_properties={"foo": "bar"},
)
assert response == mock_openai_response
@@ -1392,11 +1392,11 @@ def test_web_search_perplexity_style(mock_client):
mock_response = MockResponseWithAnnotations()
with patch("openai.resources.chat.Completions.create", return_value=mock_response):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[{"role": "user", "content": "What's happening in tech?"}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1445,13 +1445,13 @@ def test_web_search_responses_api(mock_client):
return mock_response
result = call_llm_and_track_usage(
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
ph_client=mock_client,
provider="openai",
posthog_trace_id=None,
posthog_properties=None,
posthog_privacy_mode=False,
posthog_groups=None,
insights_trace_id=None,
insights_properties=None,
insights_privacy_mode=False,
insights_groups=None,
base_url="https://api.openai.com/v1",
call_method=mock_create_call,
model="gpt-4o",
@@ -1533,12 +1533,12 @@ def test_streaming_with_web_search(mock_client, streaming_web_search_chunks):
with patch("openai.resources.chat.completions.Completions.create") as mock_create:
mock_create.return_value = streaming_web_search_chunks
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response_generator = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Search for recent news"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the generator to trigger the event capture
@@ -1569,12 +1569,12 @@ def test_streaming_with_web_search_on_non_usage_chunk(
with patch("openai.resources.chat.completions.Completions.create") as mock_create:
mock_create.return_value = streaming_web_search_chunks
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response_generator = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Search for recent news"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the generator to trigger the event capture
@@ -1629,12 +1629,12 @@ async def test_async_chat_with_web_search(mock_client):
with patch(
"openai.resources.chat.completions.AsyncCompletions.create", new=mock_create
):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
response = await client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Search for recent news"}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1672,13 +1672,13 @@ async def test_async_chat_streaming_with_web_search(
with patch(
"openai.resources.chat.completions.AsyncCompletions.create", new=mock_create
):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
response_stream = await client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Search for recent news"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
chunks = []
@@ -1742,13 +1742,13 @@ def test_streaming_chat_extracts_model_from_chunk_when_not_in_kwargs(mock_client
with patch("openai.resources.chat.completions.Completions.create") as mock_create:
mock_create.return_value = chunks
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Note: NOT passing model in kwargs - simulates stored prompt usage
response_generator = client.chat.completions.create(
messages=[{"role": "user", "content": "Hello"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
# Consume the generator
@@ -1788,13 +1788,13 @@ def test_streaming_chat_prefers_kwargs_model_over_chunk_model(mock_client):
with patch("openai.resources.chat.completions.Completions.create") as mock_create:
mock_create.return_value = chunks
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response_generator = client.chat.completions.create(
model="gpt-4o-from-kwargs", # Explicitly passed model
messages=[{"role": "user", "content": "Hello"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
list(response_generator)
@@ -1839,13 +1839,13 @@ def test_streaming_responses_api_extracts_model_from_response_object(mock_client
with patch("openai.resources.responses.Responses.create") as mock_create:
mock_create.return_value = iter(chunks)
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Note: NOT passing model - simulates stored prompt
response_generator = client.responses.create(
input=[{"role": "user", "content": "Hello"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
list(response_generator)
@@ -1886,12 +1886,12 @@ def test_non_streaming_extracts_model_from_response(mock_client):
"openai.resources.chat.completions.Completions.create",
return_value=mock_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Note: NOT passing model in kwargs
response = client.chat.completions.create(
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1948,12 +1948,12 @@ def test_non_streaming_responses_api_extracts_model_from_response(mock_client):
"openai.resources.responses.Responses.create",
return_value=mock_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Note: NOT passing model in kwargs
response = client.responses.create(
input="Hello",
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
assert response == mock_response
@@ -1995,12 +1995,12 @@ def test_non_streaming_returns_none_when_no_model(mock_client):
"openai.resources.chat.completions.Completions.create",
return_value=mock_response,
):
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
# Note: NOT passing model in kwargs and response has no model
client.chat.completions.create(
messages=[{"role": "user", "content": "Hello"}],
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
call_args = mock_client.capture.call_args[1]
@@ -2032,12 +2032,12 @@ def test_streaming_falls_back_to_unknown_when_no_model(mock_client):
with patch("openai.resources.chat.completions.Completions.create") as mock_create:
mock_create.return_value = [chunk]
client = OpenAI(api_key="test-key", posthog_client=mock_client)
client = OpenAI(api_key="test-key", insights_client=mock_client)
response_generator = client.chat.completions.create(
messages=[{"role": "user", "content": "Hello"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
list(response_generator)
@@ -2083,13 +2083,13 @@ async def test_async_streaming_chat_extracts_model_from_chunk(mock_client):
with patch(
"openai.resources.chat.completions.AsyncCompletions.create", new=mock_create
):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
# Note: NOT passing model
response_stream = await client.chat.completions.create(
messages=[{"role": "user", "content": "Hello"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
async for _ in response_stream:
@@ -2137,12 +2137,12 @@ async def test_async_streaming_responses_extracts_model_from_response(mock_clien
return chunk_iterable()
with patch("openai.resources.responses.AsyncResponses.create", new=mock_create):
client = AsyncOpenAI(api_key="test-key", posthog_client=mock_client)
client = AsyncOpenAI(api_key="test-key", insights_client=mock_client)
response_stream = await client.responses.create(
input=[{"role": "user", "content": "Hello"}],
stream=True,
posthog_distinct_id="test-id",
insights_distinct_id="test-id",
)
async for _ in response_stream:
@@ -16,7 +16,7 @@ try:
TranscriptionSpanData,
)
from hanzo_insights.ai.openai_agents import PostHogTracingProcessor, instrument
from hanzo_insights.ai.openai_agents import InsightsTracingProcessor, instrument
OPENAI_AGENTS_AVAILABLE = True
except ImportError:
@@ -39,7 +39,7 @@ def mock_client():
@pytest.fixture(scope="function")
def processor(mock_client):
return PostHogTracingProcessor(
return InsightsTracingProcessor(
client=mock_client,
distinct_id="test-user",
privacy_mode=False,
@@ -68,12 +68,12 @@ def mock_span():
return span
class TestPostHogTracingProcessor:
"""Tests for the PostHogTracingProcessor class."""
class TestInsightsTracingProcessor:
"""Tests for the InsightsTracingProcessor class."""
def test_initialization(self, mock_client):
"""Test processor initializes correctly."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id="user@example.com",
privacy_mode=True,
@@ -93,7 +93,7 @@ class TestPostHogTracingProcessor:
def resolver(trace):
return trace.metadata.get("user_id", "default")
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id=resolver,
)
@@ -127,7 +127,7 @@ class TestPostHogTracingProcessor:
def test_personless_mode_when_no_distinct_id(self, mock_client, mock_trace):
"""Test that trace events use personless mode when no distinct_id is provided."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
)
@@ -143,7 +143,7 @@ class TestPostHogTracingProcessor:
self, mock_client, mock_trace, mock_span
):
"""Test that span events use personless mode when no distinct_id is provided."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
)
@@ -168,7 +168,7 @@ class TestPostHogTracingProcessor:
def resolver(trace):
return None # Simulate no user ID available
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id=resolver,
)
@@ -188,7 +188,7 @@ class TestPostHogTracingProcessor:
def test_person_profile_when_distinct_id_provided(self, mock_client, mock_trace):
"""Test that events create person profiles when distinct_id is provided."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id="real-user",
)
@@ -375,7 +375,7 @@ class TestPostHogTracingProcessor:
def test_privacy_mode_redacts_content(self, mock_client, mock_span):
"""Test that privacy_mode redacts input/output content."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id="test-user",
privacy_mode=True,
@@ -636,7 +636,7 @@ class TestPostHogTracingProcessor:
def test_groups_included_in_events(self, mock_client, mock_trace, mock_span):
"""Test that groups are included in captured events."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id="test-user",
groups={"company": "acme", "team": "engineering"},
@@ -650,7 +650,7 @@ class TestPostHogTracingProcessor:
def test_additional_properties_included(self, mock_client, mock_trace):
"""Test that additional properties are included in events."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id="test-user",
properties={"environment": "production", "version": "1.0"},
@@ -734,7 +734,7 @@ class TestPostHogTracingProcessor:
def resolver(trace):
return f"user-{trace.name}"
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id=resolver,
)
@@ -755,7 +755,7 @@ class TestPostHogTracingProcessor:
def test_eviction_of_stale_entries(self, mock_client):
"""Test that stale entries are evicted when max is exceeded."""
processor = PostHogTracingProcessor(
processor = InsightsTracingProcessor(
client=mock_client,
distinct_id="test-user",
)
@@ -785,7 +785,7 @@ class TestInstrumentHelper:
)
mock_add.assert_called_once_with(processor)
assert isinstance(processor, PostHogTracingProcessor)
assert isinstance(processor, InsightsTracingProcessor)
def test_instrument_with_privacy_mode(self, mock_client):
"""Test instrument() respects privacy_mode."""
+77 -77
View File
@@ -32,13 +32,13 @@ class TestPrompts(unittest.TestCase):
"deleted": False,
}
def create_mock_posthog(
def create_mock_client(
self,
personal_api_key="phx_test_key",
project_api_key="phc_test_key",
host="https://us.posthog.com",
host="https://us.insights.hanzo.ai",
):
"""Create a mock PostHog client."""
"""Create a mock Insights client."""
mock = MagicMock()
mock.personal_api_key = personal_api_key
mock.api_key = project_api_key
@@ -55,8 +55,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.get("test-prompt")
@@ -65,7 +65,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key",
"https://us.insights.hanzo.ai/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key",
)
self.assertIn("Authorization", call_args[1]["headers"])
self.assertEqual(
@@ -83,8 +83,8 @@ class TestPromptsGet(TestPrompts):
}
mock_get.return_value = MockResponse(json_data=versioned_prompt_response)
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.get("test-prompt", version=1)
@@ -93,7 +93,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key&version=1",
"https://us.insights.hanzo.ai/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key&version=1",
)
@patch("hanzo_insights.ai.prompts._get_session")
@@ -104,8 +104,8 @@ class TestPromptsGet(TestPrompts):
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
mock_time.return_value = 1000.0
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
# First call - fetches from API
result1 = prompts.get("test-prompt", cache_ttl_seconds=300)
@@ -140,8 +140,8 @@ class TestPromptsGet(TestPrompts):
MockResponse(json_data=versioned_prompt_response),
]
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
self.assertEqual(prompts.get("test-prompt"), latest_prompt_response["prompt"])
self.assertEqual(
@@ -171,8 +171,8 @@ class TestPromptsGet(TestPrompts):
]
mock_time.return_value = 1000.0
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
# First call - fetches from API
result1 = prompts.get("test-prompt", cache_ttl_seconds=60)
@@ -201,8 +201,8 @@ class TestPromptsGet(TestPrompts):
]
mock_time.return_value = 1000.0
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
# First call - populates cache
result1 = prompts.get("test-prompt", cache_ttl_seconds=60)
@@ -229,8 +229,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.side_effect = Exception("Network error")
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
fallback = "Default system prompt."
result = prompts.get("test-prompt", fallback=fallback)
@@ -248,8 +248,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.side_effect = Exception("Network error")
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("test-prompt")
@@ -262,8 +262,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(status_code=404, ok=False)
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("nonexistent-prompt")
@@ -276,8 +276,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(status_code=404, ok=False)
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("nonexistent-prompt", version=3)
@@ -293,8 +293,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(status_code=403, ok=False)
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("restricted-prompt")
@@ -305,8 +305,8 @@ class TestPromptsGet(TestPrompts):
def test_throw_when_no_personal_api_key_configured(self):
"""Should throw when no personal_api_key is configured."""
posthog = self.create_mock_posthog(personal_api_key=None)
prompts = Prompts(posthog)
client = self.create_mock_client(personal_api_key=None)
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("test-prompt")
@@ -317,8 +317,8 @@ class TestPromptsGet(TestPrompts):
def test_throw_when_no_project_api_key_configured(self):
"""Should throw when no project_api_key is configured."""
posthog = self.create_mock_posthog(project_api_key=None)
prompts = Prompts(posthog)
client = self.create_mock_client(project_api_key=None)
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("test-prompt")
@@ -333,8 +333,8 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(json_data={"invalid": "response"})
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
with self.assertRaises(Exception) as context:
prompts.get("test-prompt")
@@ -342,22 +342,22 @@ class TestPromptsGet(TestPrompts):
self.assertIn("Invalid response format", str(context.exception))
@patch("hanzo_insights.ai.prompts._get_session")
def test_use_custom_host_from_posthog_options(self, mock_get_session):
"""Should use custom host from PostHog options."""
def test_use_custom_host_from_insights_options(self, mock_get_session):
"""Should use custom host from Insights options."""
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
posthog = self.create_mock_posthog(host="https://eu.posthog.com")
prompts = Prompts(posthog)
client = self.create_mock_client(host="https://eu.insights.hanzo.ai")
prompts = Prompts(client)
prompts.get("test-prompt")
call_args = mock_get.call_args
self.assertTrue(
call_args[0][0].startswith(
"https://eu.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key"
"https://eu.insights.hanzo.ai/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key"
),
f"Expected URL to start with 'https://eu.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key', got {call_args[0][0]}",
f"Expected URL to start with 'https://eu.insights.hanzo.ai/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_test_key', got {call_args[0][0]}",
)
@patch("hanzo_insights.ai.prompts._get_session")
@@ -368,8 +368,8 @@ class TestPromptsGet(TestPrompts):
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
mock_time.return_value = 1000.0
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
# First call
prompts.get("test-prompt")
@@ -399,8 +399,8 @@ class TestPromptsGet(TestPrompts):
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
mock_time.return_value = 1000.0
posthog = self.create_mock_posthog()
prompts = Prompts(posthog, default_cache_ttl_seconds=60)
client = self.create_mock_client()
prompts = Prompts(client, default_cache_ttl_seconds=60)
# First call
prompts.get("test-prompt")
@@ -419,20 +419,20 @@ class TestPromptsGet(TestPrompts):
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
prompts.get("prompt with spaces/and/slashes")
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.posthog.com/api/environments/@current/llm_prompts/name/prompt%20with%20spaces%2Fand%2Fslashes/?token=phc_test_key",
"https://us.insights.hanzo.ai/api/environments/@current/llm_prompts/name/prompt%20with%20spaces%2Fand%2Fslashes/?token=phc_test_key",
)
@patch("hanzo_insights.ai.prompts._get_session")
def test_work_with_direct_options_no_posthog_client(self, mock_get_session):
"""Should work with direct options (no PostHog client)."""
def test_work_with_direct_options_no_insights_client(self, mock_get_session):
"""Should work with direct options (no Insights client)."""
mock_get = mock_get_session.return_value.get
mock_get.return_value = MockResponse(json_data=self.mock_prompt_response)
@@ -446,7 +446,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://us.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
"https://us.insights.hanzo.ai/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
)
self.assertEqual(
call_args[1]["headers"]["Authorization"], "Bearer phx_direct_key"
@@ -461,7 +461,7 @@ class TestPromptsGet(TestPrompts):
prompts = Prompts(
personal_api_key="phx_direct_key",
project_api_key="phc_direct_key",
host="https://eu.posthog.com",
host="https://eu.insights.hanzo.ai",
)
prompts.get("test-prompt")
@@ -469,7 +469,7 @@ class TestPromptsGet(TestPrompts):
call_args = mock_get.call_args
self.assertEqual(
call_args[0][0],
"https://eu.posthog.com/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
"https://eu.insights.hanzo.ai/api/environments/@current/llm_prompts/name/test-prompt/?token=phc_direct_key",
)
@patch("hanzo_insights.ai.prompts._get_session")
@@ -505,8 +505,8 @@ class TestPromptsCompile(TestPrompts):
def test_replace_a_single_variable(self):
"""Should replace a single variable."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile("Hello, {{name}}!", {"name": "World"})
@@ -514,8 +514,8 @@ class TestPromptsCompile(TestPrompts):
def test_replace_multiple_variables(self):
"""Should replace multiple variables."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile(
"Hello, {{name}}! Welcome to {{company}}. Your tier is {{tier}}.",
@@ -528,8 +528,8 @@ class TestPromptsCompile(TestPrompts):
def test_handle_numbers(self):
"""Should handle numbers."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile("You have {{count}} items.", {"count": 42})
@@ -537,8 +537,8 @@ class TestPromptsCompile(TestPrompts):
def test_handle_booleans(self):
"""Should handle booleans."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile("Feature enabled: {{enabled}}", {"enabled": True})
@@ -546,8 +546,8 @@ class TestPromptsCompile(TestPrompts):
def test_leave_unmatched_variables_unchanged(self):
"""Should leave unmatched variables unchanged."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile(
"Hello, {{name}}! Your {{unknown}} is ready.", {"name": "World"}
@@ -557,8 +557,8 @@ class TestPromptsCompile(TestPrompts):
def test_handle_prompts_with_no_variables(self):
"""Should handle prompts with no variables."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile("You are a helpful assistant.", {})
@@ -566,8 +566,8 @@ class TestPromptsCompile(TestPrompts):
def test_handle_empty_variables_dict(self):
"""Should handle empty variables dict."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile("Hello, {{name}}!", {})
@@ -575,8 +575,8 @@ class TestPromptsCompile(TestPrompts):
def test_handle_multiple_occurrences_of_same_variable(self):
"""Should handle multiple occurrences of the same variable."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
result = prompts.compile(
"Hello, {{name}}! Goodbye, {{name}}!", {"name": "World"}
@@ -620,8 +620,8 @@ class TestPromptsClearCache(TestPrompts):
def test_clear_cache_with_version_and_no_name_raises_value_error(self):
"""Should enforce that versioned cache clearing requires a prompt name."""
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
with self.assertRaises(ValueError) as context:
prompts.clear_cache(version=1)
@@ -640,8 +640,8 @@ class TestPromptsClearCache(TestPrompts):
MockResponse(json_data=self.mock_prompt_response),
]
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
# Populate cache with two prompts
prompts.get("test-prompt")
@@ -680,8 +680,8 @@ class TestPromptsClearCache(TestPrompts):
MockResponse(json_data=versioned_prompt_response),
]
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
prompts.get("test-prompt")
prompts.get("test-prompt", version=1)
@@ -717,8 +717,8 @@ class TestPromptsClearCache(TestPrompts):
MockResponse(json_data=versioned_prompt_response),
]
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
prompts.get("test-prompt")
prompts.get("test-prompt", version=1)
@@ -743,8 +743,8 @@ class TestPromptsClearCache(TestPrompts):
MockResponse(json_data=other_prompt_response),
]
posthog = self.create_mock_posthog()
prompts = Prompts(posthog)
client = self.create_mock_client()
prompts = Prompts(client)
# Populate cache with two prompts
prompts.get("test-prompt")
+15 -15
View File
@@ -26,7 +26,7 @@ class TestSystemPromptCapture(unittest.TestCase):
self.test_user_message = "Hello, how are you?"
self.test_response = "I'm doing well, thank you!"
# Create mock PostHog client
# Create mock Insights client
self.client = Client(FAKE_TEST_API_KEY)
self.client._enqueue = MagicMock()
self.client.privacy_mode = False
@@ -88,7 +88,7 @@ class TestSystemPromptCapture(unittest.TestCase):
"openai.resources.chat.completions.Completions.create",
return_value=mock_response,
):
client = OpenAI(posthog_client=self.client, api_key="test")
client = OpenAI(insights_client=self.client, api_key="test")
messages = [
{"role": "system", "content": self.test_system_prompt},
@@ -96,7 +96,7 @@ class TestSystemPromptCapture(unittest.TestCase):
]
client.chat.completions.create(
model="gpt-4", messages=messages, posthog_distinct_id="test-user"
model="gpt-4", messages=messages, insights_distinct_id="test-user"
)
self.assertEqual(len(self.client._enqueue.call_args_list), 1)
@@ -137,7 +137,7 @@ class TestSystemPromptCapture(unittest.TestCase):
"openai.resources.chat.completions.Completions.create",
return_value=mock_response,
):
client = OpenAI(posthog_client=self.client, api_key="test")
client = OpenAI(insights_client=self.client, api_key="test")
messages = [{"role": "user", "content": self.test_user_message}]
@@ -145,7 +145,7 @@ class TestSystemPromptCapture(unittest.TestCase):
model="gpt-4",
messages=messages,
system=self.test_system_prompt,
posthog_distinct_id="test-user",
insights_distinct_id="test-user",
)
self.assertEqual(len(self.client._enqueue.call_args_list), 1)
@@ -201,7 +201,7 @@ class TestSystemPromptCapture(unittest.TestCase):
"openai.resources.chat.completions.Completions.create",
return_value=[chunk1, chunk2],
):
client = OpenAI(posthog_client=self.client, api_key="test")
client = OpenAI(insights_client=self.client, api_key="test")
messages = [{"role": "user", "content": self.test_user_message}]
@@ -210,7 +210,7 @@ class TestSystemPromptCapture(unittest.TestCase):
messages=messages,
system=self.test_system_prompt,
stream=True,
posthog_distinct_id="test-user",
insights_distinct_id="test-user",
)
list(response_generator) # Consume generator
@@ -235,7 +235,7 @@ class TestSystemPromptCapture(unittest.TestCase):
mock_response.usage.cache_creation_input_tokens = None
mock_create.return_value = mock_response
client = Anthropic(posthog_client=self.client, api_key="test")
client = Anthropic(insights_client=self.client, api_key="test")
messages = [
{"role": "system", "content": self.test_system_prompt},
@@ -245,7 +245,7 @@ class TestSystemPromptCapture(unittest.TestCase):
client.messages.create(
model="claude-3-5-sonnet-20241022",
messages=messages,
posthog_distinct_id="test-user",
insights_distinct_id="test-user",
)
self.assertEqual(len(self.client._enqueue.call_args_list), 1)
@@ -267,7 +267,7 @@ class TestSystemPromptCapture(unittest.TestCase):
mock_response.usage.cache_creation_input_tokens = None
mock_create.return_value = mock_response
client = Anthropic(posthog_client=self.client, api_key="test")
client = Anthropic(insights_client=self.client, api_key="test")
messages = [{"role": "user", "content": self.test_user_message}]
@@ -275,7 +275,7 @@ class TestSystemPromptCapture(unittest.TestCase):
model="claude-3-5-sonnet-20241022",
messages=messages,
system=self.test_system_prompt,
posthog_distinct_id="test-user",
insights_distinct_id="test-user",
)
self.assertEqual(len(self.client._enqueue.call_args_list), 1)
@@ -306,7 +306,7 @@ class TestSystemPromptCapture(unittest.TestCase):
mock_client_instance.models = mock_models_instance
mock_genai_class.return_value = mock_client_instance
client = Client(posthog_client=self.client, api_key="test")
client = Client(insights_client=self.client, api_key="test")
contents = [
{"role": "system", "content": self.test_system_prompt},
@@ -316,7 +316,7 @@ class TestSystemPromptCapture(unittest.TestCase):
client.models.generate_content(
model="gemini-2.0-flash",
contents=contents,
posthog_distinct_id="test-user",
insights_distinct_id="test-user",
)
self.assertEqual(len(self.client._enqueue.call_args_list), 1)
@@ -346,7 +346,7 @@ class TestSystemPromptCapture(unittest.TestCase):
mock_client_instance.models = mock_models_instance
mock_genai_class.return_value = mock_client_instance
client = Client(posthog_client=self.client, api_key="test")
client = Client(insights_client=self.client, api_key="test")
contents = [{"role": "user", "content": self.test_user_message}]
config = {"system_instruction": self.test_system_prompt}
@@ -355,7 +355,7 @@ class TestSystemPromptCapture(unittest.TestCase):
model="gemini-2.0-flash",
contents=contents,
config=config,
posthog_distinct_id="test-user",
insights_distinct_id="test-user",
)
self.assertEqual(len(self.client._enqueue.call_args_list), 1)
+5 -5
View File
@@ -5,10 +5,10 @@ from hanzo_insights.ai.utils import _get_tokens_source
@parameterized.expand(
[
("no_posthog_properties", {"$ai_input_tokens": 100}, None, "sdk"),
("empty_posthog_properties", {"$ai_input_tokens": 100}, {}, "sdk"),
("no_insights_properties", {"$ai_input_tokens": 100}, None, "sdk"),
("empty_insights_properties", {"$ai_input_tokens": 100}, {}, "sdk"),
(
"unrelated_posthog_properties",
"unrelated_insights_properties",
{"$ai_input_tokens": 100},
{"foo": "bar"},
"sdk",
@@ -57,6 +57,6 @@ from hanzo_insights.ai.utils import _get_tokens_source
),
]
)
def test_get_tokens_source(name, sdk_tags, posthog_properties, expected):
result = _get_tokens_source(sdk_tags, posthog_properties)
def test_get_tokens_source(name, sdk_tags, insights_properties, expected):
result = _get_tokens_source(sdk_tags, insights_properties)
assert result == expected
@@ -20,7 +20,7 @@ if not settings.configured:
)
django.setup()
from hanzo_insights.integrations.django import PosthogContextMiddleware
from hanzo_insights.integrations.django import InsightsContextMiddleware
class MockRequest:
@@ -45,7 +45,7 @@ class MockRequest:
return f"{scheme}://{self._host}{self.path}"
class TestPosthogContextMiddleware(unittest.TestCase):
class TestInsightsContextMiddleware(unittest.TestCase):
def create_middleware(
self,
extra_tags=None,
@@ -60,24 +60,24 @@ class TestPosthogContextMiddleware(unittest.TestCase):
with patch("django.conf.settings") as mock_settings:
# Configure mock settings
mock_settings.POSTHOG_MW_EXTRA_TAGS = extra_tags
mock_settings.POSTHOG_MW_REQUEST_FILTER = request_filter
mock_settings.POSTHOG_MW_TAG_MAP = tag_map
mock_settings.POSTHOG_MW_CAPTURE_EXCEPTIONS = capture_exceptions
mock_settings.POSTHOG_MW_CLIENT = None
mock_settings.INSIGHTS_MW_EXTRA_TAGS = extra_tags
mock_settings.INSIGHTS_MW_REQUEST_FILTER = request_filter
mock_settings.INSIGHTS_MW_TAG_MAP = tag_map
mock_settings.INSIGHTS_MW_CAPTURE_EXCEPTIONS = capture_exceptions
mock_settings.INSIGHTS_MW_CLIENT = None
# Make hasattr work correctly
def mock_hasattr(obj, name):
return name in [
"POSTHOG_MW_EXTRA_TAGS",
"POSTHOG_MW_REQUEST_FILTER",
"POSTHOG_MW_TAG_MAP",
"POSTHOG_MW_CAPTURE_EXCEPTIONS",
"POSTHOG_MW_CLIENT",
"INSIGHTS_MW_EXTRA_TAGS",
"INSIGHTS_MW_REQUEST_FILTER",
"INSIGHTS_MW_TAG_MAP",
"INSIGHTS_MW_CAPTURE_EXCEPTIONS",
"INSIGHTS_MW_CLIENT",
]
with patch("builtins.hasattr", side_effect=mock_hasattr):
middleware = PosthogContextMiddleware(get_response)
middleware = InsightsContextMiddleware(get_response)
return middleware
@@ -87,8 +87,8 @@ class TestPosthogContextMiddleware(unittest.TestCase):
middleware = self.create_middleware()
request = MockRequest(
headers={
"X-POSTHOG-SESSION-ID": "session-123",
"X-POSTHOG-DISTINCT-ID": "user-456",
"X-INSIGHTS-SESSION-ID": "session-123",
"X-INSIGHTS-DISTINCT-ID": "user-456",
},
method="POST",
path="/api/test",
@@ -104,7 +104,7 @@ class TestPosthogContextMiddleware(unittest.TestCase):
self.assertEqual(tags["$request_method"], "POST")
def test_extract_tags_missing_headers(self):
"""Test tag extraction when PostHog headers are missing"""
"""Test tag extraction when Insights headers are missing"""
with new_context():
middleware = self.create_middleware()
@@ -118,12 +118,12 @@ class TestPosthogContextMiddleware(unittest.TestCase):
self.assertEqual(tags["$request_method"], "GET")
def test_extract_tags_partial_headers(self):
"""Test tag extraction with only some PostHog headers present"""
"""Test tag extraction with only some Insights headers present"""
with new_context():
middleware = self.create_middleware()
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "session-only"}, method="PUT"
headers={"X-INSIGHTS-SESSION-ID": "session-only"}, method="PUT"
)
tags = middleware.extract_tags(request)
@@ -141,7 +141,7 @@ class TestPosthogContextMiddleware(unittest.TestCase):
with new_context():
middleware = self.create_middleware(extra_tags=extra_tags_func)
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "session-123"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "session-123"}, method="GET"
)
tags = middleware.extract_tags(request)
@@ -167,7 +167,7 @@ class TestPosthogContextMiddleware(unittest.TestCase):
tag_map=tag_map_func, extra_tags=extra_tags_func
)
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "session-123"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "session-123"}, method="GET"
)
tags = middleware.extract_tags(request)
@@ -230,7 +230,7 @@ class TestPosthogContextMiddleware(unittest.TestCase):
middleware.client = mock_client
request = MockRequest(
headers={"X-POSTHOG-DISTINCT-ID": "test-user"},
headers={"X-INSIGHTS-DISTINCT-ID": "test-user"},
method="POST",
path="/api/endpoint",
)
@@ -282,7 +282,7 @@ class TestPosthogContextMiddleware(unittest.TestCase):
mock_client.capture_exception.assert_not_called()
class TestPosthogContextMiddlewareSync(unittest.TestCase):
class TestInsightsContextMiddlewareSync(unittest.TestCase):
"""Test synchronous middleware behavior"""
def test_sync_middleware_call(self):
@@ -291,13 +291,13 @@ class TestPosthogContextMiddlewareSync(unittest.TestCase):
get_response = Mock(return_value=mock_response)
# Create middleware with sync get_response
middleware = PosthogContextMiddleware(get_response)
middleware = InsightsContextMiddleware(get_response)
# Verify sync mode detected
self.assertFalse(middleware._is_coroutine)
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "test-session"},
headers={"X-INSIGHTS-SESSION-ID": "test-session"},
method="GET",
path="/test",
)
@@ -318,7 +318,7 @@ class TestPosthogContextMiddlewareSync(unittest.TestCase):
def request_filter(req):
return False
middleware = PosthogContextMiddleware.__new__(PosthogContextMiddleware)
middleware = InsightsContextMiddleware.__new__(InsightsContextMiddleware)
middleware.get_response = get_response
middleware._is_coroutine = False
middleware.request_filter = request_filter
@@ -351,7 +351,7 @@ class TestPosthogContextMiddlewareSync(unittest.TestCase):
mock_client = Mock()
get_response = Mock(return_value=Mock(status_code=500))
middleware = PosthogContextMiddleware(get_response)
middleware = InsightsContextMiddleware(get_response)
middleware.client = mock_client
def get_response_simulating_django(request):
@@ -380,7 +380,7 @@ class TestPosthogContextMiddlewareSync(unittest.TestCase):
)
class TestPosthogContextMiddlewareAsync(unittest.TestCase):
class TestInsightsContextMiddlewareAsync(unittest.TestCase):
"""Test asynchronous middleware behavior"""
def test_async_middleware_detection(self):
@@ -389,7 +389,7 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
async def async_get_response(request):
return Mock()
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
# Verify async mode detected
self.assertTrue(middleware._is_coroutine)
@@ -403,10 +403,10 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
async def async_get_response(request):
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "async-session"},
headers={"X-INSIGHTS-SESSION-ID": "async-session"},
method="POST",
path="/async-test",
)
@@ -434,7 +434,7 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
return mock_response
# Properly initialize middleware
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
# Override request filter after initialization
middleware.request_filter = lambda req: False
@@ -459,10 +459,10 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
self.assertEqual(session_id, "async-session-123")
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "async-session-123"},
headers={"X-INSIGHTS-SESSION-ID": "async-session-123"},
method="GET",
)
@@ -483,7 +483,7 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
raise ValueError("Async test exception")
# Properly initialize middleware
middleware = PosthogContextMiddleware(raise_exception)
middleware = InsightsContextMiddleware(raise_exception)
middleware.client = mock_client # Override with mock client
request = MockRequest()
@@ -525,11 +525,11 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
self.assertEqual(distinct_id, "123")
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
middleware.client = Mock()
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "test-session"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "test-session"}, method="GET"
)
# Mock auser() to return authenticated user
@@ -561,11 +561,11 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
self.assertIsNone(distinct_id)
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
middleware.client = Mock()
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "test-session"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "test-session"}, method="GET"
)
async def mock_auser():
@@ -591,12 +591,12 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
async def async_get_response(request):
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
middleware.client = Mock()
# Request without auser method (no auth middleware)
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "test-session"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "test-session"}, method="GET"
)
with new_context():
@@ -621,12 +621,12 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
async def async_get_response(request):
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
middleware.extra_tags = extra_tags_callback
middleware.client = Mock()
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "test-session"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "test-session"}, method="GET"
)
# Mock auser for no user
@@ -658,12 +658,12 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
async def async_get_response(request):
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
middleware.tag_map = tag_map_callback
middleware.client = Mock()
request = MockRequest(
headers={"X-POSTHOG-SESSION-ID": "test-session"}, method="GET"
headers={"X-INSIGHTS-SESSION-ID": "test-session"}, method="GET"
)
# Mock auser for no user
@@ -699,12 +699,12 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
self.assertEqual(session_id, "async-sess-123")
return mock_response
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
middleware.client = Mock()
request = MockRequest(
headers={
"X-POSTHOG-SESSION-ID": "async-sess-123",
"X-INSIGHTS-SESSION-ID": "async-sess-123",
"X-Forwarded-For": "192.168.1.1",
"User-Agent": "TestAgent/1.0",
},
@@ -725,13 +725,13 @@ class TestPosthogContextMiddlewareAsync(unittest.TestCase):
asyncio.run(run_test())
class TestPosthogContextMiddlewareHybrid(unittest.TestCase):
class TestInsightsContextMiddlewareHybrid(unittest.TestCase):
"""Test hybrid middleware behavior with mixed sync/async chains"""
def test_hybrid_flags_set(self):
"""Test that both capability flags are set"""
self.assertTrue(PosthogContextMiddleware.sync_capable)
self.assertTrue(PosthogContextMiddleware.async_capable)
self.assertTrue(InsightsContextMiddleware.sync_capable)
self.assertTrue(InsightsContextMiddleware.async_capable)
def test_sync_to_async_routing(self):
"""Test that __call__ routes to __acall__ when async"""
@@ -740,7 +740,7 @@ class TestPosthogContextMiddlewareHybrid(unittest.TestCase):
async def async_get_response(request):
return Mock()
middleware = PosthogContextMiddleware(async_get_response)
middleware = InsightsContextMiddleware(async_get_response)
# Verify routing happens
request = MockRequest()
@@ -759,7 +759,7 @@ class TestPosthogContextMiddlewareHybrid(unittest.TestCase):
def sync_get_response(request):
return mock_response
middleware = PosthogContextMiddleware(sync_get_response)
middleware = InsightsContextMiddleware(sync_get_response)
request = MockRequest()
result = middleware(request)
+26 -26
View File
@@ -61,7 +61,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertIsNotNone(msg.get("uuid"))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
# these will change between platforms so just asssert on presence here
assert msg["properties"]["$python_runtime"] == mock.ANY
@@ -88,7 +88,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertEqual(msg["uuid"], uuid)
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
def test_basic_capture_with_project_api_key(self):
@@ -111,7 +111,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["event"], "python test event")
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
def test_basic_super_properties(self):
@@ -299,7 +299,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertIsNotNone(msg.get("uuid"))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertEqual(
msg["properties"]["$feature/beta-feature"], "random-variant"
@@ -424,7 +424,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertIsNotNone(msg.get("uuid"))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertEqual(
msg["properties"]["$feature/beta-feature-local"], "third-variant"
@@ -595,7 +595,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertIsNotNone(msg.get("uuid"))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertEqual(
msg["properties"]["$feature/beta-feature-local"], "my-custom-variant"
@@ -641,7 +641,7 @@ class TestClient(unittest.TestCase):
self.assertIsNotNone(msg.get("uuid"))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertTrue(msg["properties"]["$geoip_disable"])
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertEqual(
msg["properties"]["$feature/beta-feature"], "random-variant"
@@ -655,7 +655,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=3,
distinct_id="distinct_id",
groups={},
@@ -706,7 +706,7 @@ class TestClient(unittest.TestCase):
self.assertIsNotNone(msg.get("uuid"))
self.assertTrue("$geoip_disable" not in msg["properties"])
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertEqual(
msg["properties"]["$feature/beta-feature"], "random-variant"
@@ -720,7 +720,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(patch_flags.call_count, 1)
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=12,
distinct_id="distinct_id",
groups={},
@@ -758,7 +758,7 @@ class TestClient(unittest.TestCase):
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertIsNotNone(msg.get("uuid"))
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertTrue("$feature/beta-feature" not in msg["properties"])
self.assertTrue("$active_feature_flags" not in msg["properties"])
@@ -1156,7 +1156,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
self.assertEqual(msg["properties"]["property"], "value")
self.assertEqual(msg["event"], "python test event")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertEqual(msg["uuid"], "new-uuid")
self.assertEqual(msg["distinct_id"], "distinct_id")
@@ -1221,7 +1221,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
self.assertEqual(msg["$set"]["trait"], "value")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertEqual(msg["uuid"], "new-uuid")
@@ -1265,7 +1265,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["timestamp"], "2014-09-03T00:00:00+00:00")
self.assertEqual(msg["$set_once"]["trait"], "value")
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
self.assertTrue(isinstance(msg["timestamp"], str))
self.assertEqual(msg["uuid"], "new-uuid")
@@ -1290,7 +1290,7 @@ class TestClient(unittest.TestCase):
"$group_type": "organization",
"$group_key": "id:5",
"$group_set": {},
"$lib": "posthog-python",
"$lib": "insights-python",
"$lib_version": VERSION,
"$geoip_disable": True,
},
@@ -1319,7 +1319,7 @@ class TestClient(unittest.TestCase):
"$group_type": "organization",
"$group_key": "id:5",
"$group_set": {},
"$lib": "posthog-python",
"$lib": "insights-python",
"$lib_version": VERSION,
"$geoip_disable": True,
},
@@ -1352,7 +1352,7 @@ class TestClient(unittest.TestCase):
"$group_type": "organization",
"$group_key": "id:5",
"$group_set": {"trait": "value"},
"$lib": "posthog-python",
"$lib": "insights-python",
"$lib_version": VERSION,
"$geoip_disable": True,
},
@@ -1387,7 +1387,7 @@ class TestClient(unittest.TestCase):
"$group_type": "organization",
"$group_key": "id:5",
"$group_set": {"trait": "value"},
"$lib": "posthog-python",
"$lib": "insights-python",
"$lib_version": VERSION,
"$geoip_disable": True,
},
@@ -1454,7 +1454,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["event"], "python test event")
self.assertEqual(msg["distinct_id"], "distinct_id")
self.assertEqual(msg["properties"]["$session_id"], session_id)
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
# Check additional expected properties
@@ -1607,7 +1607,7 @@ class TestClient(unittest.TestCase):
self.assertEqual(msg["properties"][key], value)
# Verify system properties are still added
self.assertEqual(msg["properties"]["$lib"], "posthog-python")
self.assertEqual(msg["properties"]["$lib"], "insights-python")
self.assertEqual(msg["properties"]["$lib_version"], VERSION)
@parameterized.expand(
@@ -1920,7 +1920,7 @@ class TestClient(unittest.TestCase):
client.get_feature_flag("random_key", "some_id", disable_geoip=True)
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=3,
distinct_id="some_id",
groups={},
@@ -1936,7 +1936,7 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=3,
distinct_id="feature_enabled_distinct_id",
groups={},
@@ -1950,7 +1950,7 @@ class TestClient(unittest.TestCase):
client.get_all_flags_and_payloads("all_flags_payloads_id")
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=3,
distinct_id="all_flags_payloads_id",
groups={},
@@ -2107,7 +2107,7 @@ class TestClient(unittest.TestCase):
expected_call["flag_keys_to_evaluate"] = expected_flag_keys
patch_flags.assert_called_with(
"random_key", "https://us.i.posthog.com", timeout=3, **expected_call
"random_key", "https://us.i.insights.hanzo.ai", timeout=3, **expected_call
)
@mock.patch("hanzo_insights.client.flags")
@@ -2131,7 +2131,7 @@ class TestClient(unittest.TestCase):
client.get_feature_flag("random_key", "some_id")
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=3,
distinct_id="some_id",
groups={},
@@ -2151,7 +2151,7 @@ class TestClient(unittest.TestCase):
)
patch_flags.assert_called_with(
"random_key",
"https://us.i.posthog.com",
"https://us.i.insights.hanzo.ai",
timeout=3,
distinct_id="some_id",
groups={},
+40 -40
View File
@@ -10,8 +10,8 @@ def test_excepthook(tmpdir):
app.write(
dedent(
"""
from hanzo_insights import Posthog
posthog = Posthog('phc_x', host='https://eu.i.posthog.com', enable_exception_autocapture=True, debug=True, on_error=lambda e, batch: print('error handling batch: ', e, batch))
from hanzo_insights import Insights
client = Insights('phc_x', host='https://eu.i.insights.hanzo.ai', enable_exception_autocapture=True, debug=True, on_error=lambda e, batch: print('error handling batch: ', e, batch))
# frame_value = "LOL"
@@ -40,14 +40,14 @@ def test_code_variables_capture(tmpdir):
dedent(
"""
import os
from hanzo_insights import Posthog
from hanzo_insights import Insights
class UnserializableObject:
pass
posthog = Posthog(
client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -118,29 +118,29 @@ def test_code_variables_capture(tmpdir):
assert b"'my_bool': 'True'" in output
assert b'"my_dict": "{\\"name\\": \\"test\\", \\"value\\": 123}"' in output
assert (
b'{\\"safe_key\\": \\"safe_value\\", \\"password\\": \\"$$_posthog_redacted_based_on_masking_rules_$$\\", \\"other_key\\": \\"$$_posthog_redacted_based_on_masking_rules_$$\\"}'
b'{\\"safe_key\\": \\"safe_value\\", \\"password\\": \\"$$_insights_redacted_based_on_masking_rules_$$\\", \\"other_key\\": \\"$$_insights_redacted_based_on_masking_rules_$$\\"}'
in output
)
assert (
b'{\\"level1\\": {\\"level2\\": {\\"api_key\\": \\"$$_posthog_redacted_based_on_masking_rules_$$\\", \\"data\\": \\"$$_posthog_redacted_based_on_masking_rules_$$\\", \\"safe\\": \\"visible\\"}}}'
b'{\\"level1\\": {\\"level2\\": {\\"api_key\\": \\"$$_insights_redacted_based_on_masking_rules_$$\\", \\"data\\": \\"$$_insights_redacted_based_on_masking_rules_$$\\", \\"safe\\": \\"visible\\"}}}'
in output
)
assert (
b'[\\"safe_item\\", \\"$$_posthog_redacted_based_on_masking_rules_$$\\", \\"another_safe\\"]'
b'[\\"safe_item\\", \\"$$_insights_redacted_based_on_masking_rules_$$\\", \\"another_safe\\"]'
in output
)
assert (
b'[\\"tuple_safe\\", \\"$$_posthog_redacted_based_on_masking_rules_$$\\", \\"tuple_also_safe\\"]'
b'[\\"tuple_safe\\", \\"$$_insights_redacted_based_on_masking_rules_$$\\", \\"tuple_also_safe\\"]'
in output
)
assert (
b'[{\\"id\\": 1, \\"password\\": \\"$$_posthog_redacted_based_on_masking_rules_$$\\"}, {\\"id\\": 2, \\"value\\": \\"safe_value\\"}]'
b'[{\\"id\\": 1, \\"password\\": \\"$$_insights_redacted_based_on_masking_rules_$$\\"}, {\\"id\\": 2, \\"value\\": \\"safe_value\\"}]'
in output
)
assert b"<__main__.UnserializableObject object at" in output
assert b"'my_password': '$$_posthog_redacted_based_on_masking_rules_$$'" in output
assert b"'my_password': '$$_insights_redacted_based_on_masking_rules_$$'" in output
assert (
b"'my_innocent_var': '$$_posthog_redacted_based_on_masking_rules_$$'" in output
b"'my_innocent_var': '$$_insights_redacted_based_on_masking_rules_$$'" in output
)
assert b"'__should_be_ignored':" not in output
@@ -161,11 +161,11 @@ def test_code_variables_context_override(tmpdir):
"""
import os
import hanzo_insights
from hanzo_insights import Posthog
from hanzo_insights import Insights
posthog_client = Posthog(
insights_client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=False,
@@ -178,7 +178,7 @@ def test_code_variables_context_override(tmpdir):
1/0
with hanzo_insights.new_context(client=posthog_client):
with hanzo_insights.new_context(client=insights_client):
hanzo_insights.set_capture_exception_code_variables_context(True)
hanzo_insights.set_code_variables_mask_patterns_context([r"(?i).*bank.*"])
hanzo_insights.set_code_variables_ignore_patterns_context([])
@@ -195,7 +195,7 @@ def test_code_variables_context_override(tmpdir):
assert b"ZeroDivisionError" in output
assert b"code_variables" in output
assert b"'bank': '$$_posthog_redacted_based_on_masking_rules_$$'" in output
assert b"'bank': '$$_insights_redacted_based_on_masking_rules_$$'" in output
assert b"'__dunder_var': 'should_be_visible'" in output
@@ -205,11 +205,11 @@ def test_code_variables_size_limiter(tmpdir):
dedent(
"""
import os
from hanzo_insights import Posthog
from hanzo_insights import Insights
posthog = Posthog(
client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -299,11 +299,11 @@ def test_code_variables_disabled_capture(tmpdir):
dedent(
"""
import os
from hanzo_insights import Posthog
from hanzo_insights import Insights
posthog = Posthog(
client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=False,
@@ -341,11 +341,11 @@ def test_code_variables_enabled_then_disabled_in_context(tmpdir):
"""
import os
import hanzo_insights
from hanzo_insights import Posthog
from hanzo_insights import Insights
posthog_client = Posthog(
insights_client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -358,7 +358,7 @@ def test_code_variables_enabled_then_disabled_in_context(tmpdir):
1/0
with hanzo_insights.new_context(client=posthog_client):
with hanzo_insights.new_context(client=insights_client):
hanzo_insights.set_capture_exception_code_variables_context(False)
process_data()
@@ -388,15 +388,15 @@ def test_code_variables_repr_fallback(tmpdir):
from datetime import datetime, timedelta
from decimal import Decimal
from fractions import Fraction
from hanzo_insights import Posthog
from hanzo_insights import Insights
class CustomReprClass:
def __repr__(self):
return '<CustomReprClass: custom representation>'
posthog = Posthog(
client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -458,11 +458,11 @@ def test_code_variables_too_long_string_value_replaced(tmpdir):
dedent(
"""
import os
from hanzo_insights import Posthog
from hanzo_insights import Insights
posthog = Posthog(
client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -491,9 +491,9 @@ def test_code_variables_too_long_string_value_replaced(tmpdir):
assert "'short_value': 'I am short'" in output
assert "$$_posthog_value_too_long_$$" in output
assert "$$_insights_value_too_long_$$" in output
assert "'long_blob': '$$_posthog_value_too_long_$$'" in output
assert "'long_blob': '$$_insights_value_too_long_$$'" in output
def test_code_variables_too_long_string_in_nested_dict(tmpdir):
@@ -502,11 +502,11 @@ def test_code_variables_too_long_string_in_nested_dict(tmpdir):
dedent(
"""
import os
from hanzo_insights import Posthog
from hanzo_insights import Insights
posthog = Posthog(
client = Insights(
'phc_x',
host='https://eu.i.posthog.com',
host='https://eu.i.insights.hanzo.ai',
debug=True,
enable_exception_autocapture=True,
capture_exception_code_variables=True,
@@ -541,7 +541,7 @@ def test_code_variables_too_long_string_in_nested_dict(tmpdir):
assert "short_val" in output
assert "ok" in output
assert "$$_posthog_value_too_long_$$" in output
assert "$$_insights_value_too_long_$$" in output
assert "y" * 1000 not in output
assert "z" * 1000 not in output
@@ -567,7 +567,7 @@ def test_mask_sensitive_data_too_long_dict_key():
assert result["short"] == "visible"
# This then gets shortened by the JSON truncation at 1024 chars anyways so no worries
assert result["k" * 20000] == CODE_VARIABLES_TOO_LONG_VALUE
assert result["password"] == "$$_posthog_redacted_based_on_masking_rules_$$"
assert result["password"] == "$$_insights_redacted_based_on_masking_rules_$$"
def test_mask_sensitive_data_circular_ref():
@@ -712,7 +712,7 @@ class TestGetFeatureFlagResult(unittest.TestCase):
class TestFeatureFlagErrorWithStaleCacheFallback(unittest.TestCase):
"""Tests for stale cache fallback behavior when flag evaluation fails.
When the PostHog API is unavailable (timeout, connection error, etc.), the SDK
When the Insights API is unavailable (timeout, connection error, etc.), the SDK
falls back to stale cached flag values if available. These tests verify that:
1. The stale cached value is returned when an error occurs
2. The $feature_flag_error property is still set (for debugging)
@@ -1,7 +1,7 @@
"""
Tests for FlagDefinitionCacheProvider functionality.
These tests follow the patterns from the TypeScript implementation in posthog-js/packages/node.
These tests follow the patterns from the TypeScript implementation in insights-js/packages/node.
"""
import threading
+7 -7
View File
@@ -339,13 +339,13 @@ class TestGet(unittest.TestCase):
("https://us.posthog.com.rg.proxy.com", "https://us.posthog.com.rg.proxy.com"),
("app.posthog.com", "app.posthog.com"),
("eu.posthog.com", "eu.posthog.com"),
("https://app.posthog.com", "https://us.i.posthog.com"),
("https://eu.posthog.com", "https://eu.i.posthog.com"),
("https://us.posthog.com", "https://us.i.posthog.com"),
("https://app.posthog.com/", "https://us.i.posthog.com"),
("https://eu.posthog.com/", "https://eu.i.posthog.com"),
("https://us.posthog.com/", "https://us.i.posthog.com"),
(None, "https://us.i.posthog.com"),
("https://app.posthog.com", "https://us.i.insights.hanzo.ai"),
("https://eu.posthog.com", "https://eu.i.insights.hanzo.ai"),
("https://us.posthog.com", "https://us.i.insights.hanzo.ai"),
("https://app.posthog.com/", "https://us.i.insights.hanzo.ai"),
("https://eu.posthog.com/", "https://eu.i.insights.hanzo.ai"),
("https://us.posthog.com/", "https://us.i.insights.hanzo.ai"),
(None, "https://us.i.insights.hanzo.ai"),
],
)
def test_routing_to_custom_host(host, expected):
+1 -1
View File
@@ -277,7 +277,7 @@ class FlagCache:
class RedisFlagCache:
def __init__(
self, redis_client, default_ttl=300, stale_ttl=3600, key_prefix="posthog:flags:"
self, redis_client, default_ttl=300, stale_ttl=3600, key_prefix="insights:flags:"
):
self.redis = redis_client
self.default_ttl = default_ttl
+1 -1
View File
@@ -1,3 +1,3 @@
# Convenience re-export so `from insights import Insights` works.
from hanzo_insights import * # noqa: F401, F403
from hanzo_insights import Insights, Posthog, Client # noqa: F401
from hanzo_insights import Insights, Client # noqa: F401
@@ -1,10 +1,10 @@
"""
Test that verifies exception capture functionality.
These tests verify that exceptions are actually captured to PostHog, not just that
These tests verify that exceptions are actually captured to Insights, not just that
500 responses are returned.
Without process_exception(), view exceptions are NOT captured to PostHog (v6.7.11 and earlier).
Without process_exception(), view exceptions are NOT captured to Insights (v6.7.11 and earlier).
With process_exception(), Django calls this method to capture exceptions before
converting them to 500 responses.
"""
@@ -30,7 +30,7 @@ def asgi_app():
@pytest.mark.asyncio
async def test_async_exception_is_captured(asgi_app):
"""
Test that async view exceptions are captured to PostHog.
Test that async view exceptions are captured to Insights.
The middleware's process_exception() method ensures exceptions are captured.
Without it (v6.7.11 and earlier), exceptions are NOT captured even though 500 is returned.
@@ -50,7 +50,7 @@ async def test_async_exception_is_captured(asgi_app):
}
)
# Patch at the posthog module level where middleware imports from
# Patch at the hanzo_insights module level where middleware imports from
with patch("hanzo_insights.capture_exception", side_effect=mock_capture):
async with AsyncClient(
transport=ASGITransport(app=asgi_app), base_url="http://testserver"
@@ -60,8 +60,8 @@ async def test_async_exception_is_captured(asgi_app):
# Django returns 500
assert response.status_code == 500
# CRITICAL: Verify PostHog captured the exception
assert len(captured) > 0, "Exception was NOT captured to PostHog!"
# CRITICAL: Verify Insights captured the exception
assert len(captured) > 0, "Exception was NOT captured to Insights!"
# Verify it's the right exception
exception_data = captured[0]
@@ -72,7 +72,7 @@ async def test_async_exception_is_captured(asgi_app):
@pytest.mark.asyncio
async def test_sync_exception_is_captured(asgi_app):
"""
Test that sync view exceptions are captured to PostHog.
Test that sync view exceptions are captured to Insights.
The middleware's process_exception() method ensures exceptions are captured.
Without it (v6.7.11 and earlier), exceptions are NOT captured even though 500 is returned.
@@ -92,7 +92,7 @@ async def test_sync_exception_is_captured(asgi_app):
}
)
# Patch at the posthog module level where middleware imports from
# Patch at the hanzo_insights module level where middleware imports from
with patch("hanzo_insights.capture_exception", side_effect=mock_capture):
async with AsyncClient(
transport=ASGITransport(app=asgi_app), base_url="http://testserver"
@@ -102,8 +102,8 @@ async def test_sync_exception_is_captured(asgi_app):
# Django returns 500
assert response.status_code == 500
# CRITICAL: Verify PostHog captured the exception
assert len(captured) > 0, "Exception was NOT captured to PostHog!"
# CRITICAL: Verify Insights captured the exception
assert len(captured) > 0, "Exception was NOT captured to Insights!"
# Verify it's the right exception
exception_data = captured[0]
+5 -5
View File
@@ -1,5 +1,5 @@
"""
Tests for PostHog Django middleware in async context.
Tests for Insights Django middleware in async context.
These tests verify that the middleware correctly handles:
1. Async user access (request.auser() in Django 5)
@@ -103,7 +103,7 @@ async def test_async_authenticated_user_access(asgi_app):
# Make request with session cookie - this should trigger the bug in v6.7.11
# Disable exception capture to see the SynchronousOnlyOperation clearly
with override_settings(POSTHOG_MW_CAPTURE_EXCEPTIONS=False):
with override_settings(INSIGHTS_MW_CAPTURE_EXCEPTIONS=False):
async with AsyncClient(
transport=ASGITransport(app=asgi_app),
base_url="http://testserver",
@@ -139,10 +139,10 @@ async def test_async_exception_capture(asgi_app):
"""
Test that middleware handles exceptions from async views.
The middleware's process_exception() method captures view exceptions to PostHog
The middleware's process_exception() method captures view exceptions to Insights
before Django converts them to 500 responses. This test verifies the exception
causes a 500 response. See test_exception_capture.py for tests that verify
actual exception capture to PostHog.
actual exception capture to Insights.
"""
async with AsyncClient(
transport=ASGITransport(app=asgi_app), base_url="http://testserver"
@@ -158,7 +158,7 @@ async def test_sync_exception_capture(asgi_app):
"""
Test that middleware handles exceptions from sync views.
The middleware's process_exception() method captures view exceptions to PostHog.
The middleware's process_exception() method captures view exceptions to Insights.
This test verifies the exception causes a 500 response.
"""
async with AsyncClient(
@@ -47,7 +47,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"hanzo_insights.integrations.django.PosthogContextMiddleware", # Test PostHog middleware
"hanzo_insights.integrations.django.InsightsContextMiddleware", # Test Insights middleware
]
ROOT_URLCONF = "testdjango.urls"
@@ -123,7 +123,7 @@ STATIC_URL = "static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# PostHog settings for testing
POSTHOG_API_KEY = "test-key"
# Insights settings for testing
INSIGHTS_API_KEY = "test-key"
POSTHOG_HOST = "https://app.posthog.com"
POSTHOG_MW_CAPTURE_EXCEPTIONS = True
INSIGHTS_MW_CAPTURE_EXCEPTIONS = True
@@ -1,5 +1,5 @@
"""
Test views for validating PostHog middleware with Django 5 ASGI.
Test views for validating Insights middleware with Django 5 ASGI.
"""
from django.http import JsonResponse
+2 -2
View File
@@ -17,10 +17,10 @@ ignore_missing_imports = True
[mypy-sentry_sdk.*]
ignore_missing_imports = True
[mypy-posthog.test.*]
[mypy-hanzo_insights.test.*]
ignore_errors = True
[mypy-posthog.*.test.*]
[mypy-hanzo_insights.*.test.*]
ignore_errors = True
[mypy-openai.*]
+5 -5
View File
@@ -1,8 +1,8 @@
"""
PostHog Python SDK Test Adapter
Insights Python SDK Test Adapter
This adapter implements the SDK Test Adapter Interface defined in the PostHog Capture API Contract.
It wraps the posthog-python SDK and exposes a REST API for the test harness to exercise.
This adapter implements the SDK Test Adapter Interface defined in the Capture API Contract.
It wraps the insights-python SDK and exposes a REST API for the test harness to exercise.
"""
import logging
@@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional
from flask import Flask, jsonify, request
from posthog import Client
from hanzo_insights import Client
from hanzo_insights.request import batch_post as original_batch_post
from hanzo_insights.version import VERSION
@@ -193,7 +193,7 @@ def health():
"""Health check endpoint"""
return jsonify(
{
"sdk_name": "posthog-python",
"sdk_name": "insights-python",
"sdk_version": VERSION,
"adapter_version": "1.0.0",
}