Files
zeekay 3ca982343e 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
2026-03-13 20:29:13 -07:00

249 lines
8.6 KiB
Python

try:
import anthropic
from anthropic.resources import Messages
except ImportError:
raise ModuleNotFoundError(
"Please install the Anthropic SDK to use this feature: 'pip install anthropic'"
)
import time
import uuid
from typing import Any, Dict, List, Optional
from hanzo_insights.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
from hanzo_insights.ai.utils import (
call_llm_and_track_usage,
merge_usage_stats,
)
from hanzo_insights.ai.anthropic.anthropic_converter import (
extract_anthropic_usage_from_event,
handle_anthropic_content_block_start,
handle_anthropic_text_delta,
handle_anthropic_tool_delta,
finalize_anthropic_tool_input,
)
from hanzo_insights.ai.sanitization import sanitize_anthropic
from hanzo_insights.client import Client as InsightsClient
from hanzo_insights import setup
class Anthropic(anthropic.Anthropic):
"""
A wrapper around the Anthropic SDK that automatically sends LLM usage events to Insights.
"""
_ph_client: InsightsClient
def __init__(self, insights_client: Optional[InsightsClient] = None, **kwargs):
"""
Args:
insights_client: Insights client for tracking usage
**kwargs: Additional arguments passed to the Anthropic client
"""
super().__init__(**kwargs)
self._ph_client = insights_client or setup()
self.messages = WrappedMessages(self)
class WrappedMessages(Messages):
_client: Anthropic
def create(
self,
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:
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 insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
if kwargs.get("stream", False):
return self._create_streaming(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
return call_llm_and_track_usage(
insights_distinct_id,
self._client._ph_client,
"anthropic",
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
self._client.base_url,
super().create,
**kwargs,
)
def stream(
self,
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 insights_trace_id is None:
insights_trace_id = str(uuid.uuid4())
return self._create_streaming(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
**kwargs,
)
def _create_streaming(
self,
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()
usage_stats: TokenUsage = TokenUsage(input_tokens=0, output_tokens=0)
accumulated_content = ""
content_blocks: List[StreamingContentBlock] = []
tools_in_progress: Dict[str, ToolInProgress] = {}
current_text_block: Optional[StreamingContentBlock] = None
response = super().create(**kwargs)
def generator():
nonlocal usage_stats
nonlocal accumulated_content
nonlocal content_blocks
nonlocal tools_in_progress
nonlocal current_text_block
try:
for event in response:
# Extract usage stats from event
event_usage = extract_anthropic_usage_from_event(event)
merge_usage_stats(usage_stats, event_usage)
# Handle content block start events
if hasattr(event, "type") and event.type == "content_block_start":
block, tool = handle_anthropic_content_block_start(event)
if block:
content_blocks.append(block)
if block.get("type") == "text":
current_text_block = block
else:
current_text_block = None
if tool:
tool_id = tool["block"].get("id")
if tool_id:
tools_in_progress[tool_id] = tool
# Handle text delta events
delta_text = handle_anthropic_text_delta(event, current_text_block)
if delta_text:
accumulated_content += delta_text
# Handle tool input delta events
handle_anthropic_tool_delta(
event, content_blocks, tools_in_progress
)
# Handle content block stop events
if hasattr(event, "type") and event.type == "content_block_stop":
current_text_block = None
finalize_anthropic_tool_input(
event, content_blocks, tools_in_progress
)
yield event
finally:
end_time = time.time()
latency = end_time - start_time
self._capture_streaming_event(
insights_distinct_id,
insights_trace_id,
insights_properties,
insights_privacy_mode,
insights_groups,
kwargs,
usage_stats,
latency,
content_blocks,
accumulated_content,
)
return generator()
def _capture_streaming_event(
self,
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,
content_blocks: List[StreamingContentBlock],
accumulated_content: str,
):
from hanzo_insights.ai.types import StreamingEventData
from hanzo_insights.ai.anthropic.anthropic_converter import (
format_anthropic_streaming_input,
format_anthropic_streaming_output_complete,
)
from hanzo_insights.ai.utils import capture_streaming_event
# Prepare standardized event data
formatted_input = format_anthropic_streaming_input(kwargs)
sanitized_input = sanitize_anthropic(formatted_input)
event_data = StreamingEventData(
provider="anthropic",
model=kwargs.get("model", "unknown"),
base_url=str(self._client.base_url),
kwargs=kwargs,
formatted_input=sanitized_input,
formatted_output=format_anthropic_streaming_output_complete(
content_blocks, accumulated_content
),
usage_stats=usage_stats,
latency=latency,
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
capture_streaming_event(self._client._ph_client, event_data)