# Claude Agent SDK LLM analytics installation - Docs

1.  1

    ## Install the PostHog SDK

    Required

    Setting up analytics starts with installing the PostHog Python SDK.

    ```bash
    pip install posthog
    ```

2.  2

    ## Install the Claude Agent SDK

    Required

    Install the Claude Agent SDK. PostHog instruments your agent queries by wrapping the `query()` function. The PostHog SDK **does not** proxy your calls.

    ```bash
    pip install claude-agent-sdk
    ```

    **Proxy note**

    These SDKs **do not** proxy your calls. They only fire off an async call to PostHog in the background to send the data. You can also use LLM analytics with other SDKs or our API, but you will need to capture the data in the right format. See the schema in the [manual capture section](/docs/llm-analytics/installation/manual-capture.md) for more details.

3.  3

    ## Initialize PostHog and run a query

    Required

    Initialize PostHog with your project token and host from [your project settings](https://app.posthog.com/settings/project), then use the PostHog `query()` wrapper as a drop-in replacement for `claude_agent_sdk.query()`. This automatically captures `$ai_generation`, `$ai_span`, and `$ai_trace` events.

    ```python
    import asyncio
    from posthog import Posthog
    from posthog.ai.claude_agent_sdk import query
    from claude_agent_sdk import ClaudeAgentOptions
    posthog = Posthog(
        "<ph_project_token>",
        host="https://us.i.posthog.com"
    )
    async def main():
        options = ClaudeAgentOptions(
            max_turns=5,
            permission_mode="plan",
        )
        async for message in query(
            prompt="Tell me a fun fact about hedgehogs",
            options=options,
            posthog_client=posthog,
            posthog_distinct_id="user_123", # optional
            posthog_trace_id="trace_123", # optional
            posthog_properties={"conversation_id": "abc123"}, # optional
            posthog_groups={"company": "company_id_in_your_db"}, # optional
            posthog_privacy_mode=False, # optional
        ):
            print(message)
    asyncio.run(main())
    posthog.shutdown()
    ```

    > **Notes:**
    >
    > -   All original messages are yielded unchanged — the wrapper is fully transparent.
    > -   If you want to capture LLM events anonymously, **don't** pass a distinct ID. See our docs on [anonymous vs identified events](/docs/data/anonymous-vs-identified-events.md) to learn more.

    You can expect captured `$ai_generation` events to have the following properties:

    | Property | Description |
    | --- | --- |
    | $ai_model | The specific model, like gpt-5-mini or claude-4-sonnet |
    | $ai_latency | The latency of the LLM call in seconds |
    | $ai_time_to_first_token | Time to first token in seconds (streaming only) |
    | $ai_tools | Tools and functions available to the LLM |
    | $ai_input | List of messages sent to the LLM |
    | $ai_input_tokens | The number of tokens in the input (often found in response.usage) |
    | $ai_output_choices | List of response choices from the LLM |
    | $ai_output_tokens | The number of tokens in the output (often found in response.usage) |
    | $ai_total_cost_usd | The total cost in USD (input + output) |
    | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties |

4.  4

    ## Reusable configuration with instrument()

    Optional

    If you make multiple `query()` calls with the same PostHog configuration, use `instrument()` to configure once and reuse across queries.

    ```python
    import asyncio
    from posthog import Posthog
    from posthog.ai.claude_agent_sdk import instrument
    from claude_agent_sdk import ClaudeAgentOptions
    posthog = Posthog(
        "<ph_project_token>",
        host="https://us.i.posthog.com"
    )
    ph = instrument(
        client=posthog,
        distinct_id="user_123",
        properties={"app": "my-agent"},
    )
    options = ClaudeAgentOptions(max_turns=10)
    async def main():
        # All queries share the same PostHog config
        async for msg in ph.query(prompt="Question 1", options=options):
            ...
        async for msg in ph.query(prompt="Question 2", options=options):
            ...
    asyncio.run(main())
    ```

    You can override any PostHog parameter per-query:

    ```js
    async for msg in ph.query(
        prompt="...",
        options=options,
        posthog_distinct_id="different_user",
        posthog_properties={"extra": "data"},
    ):
        ...
    ```

5.  5

    ## Tool usage and multi-turn conversations

    Optional

    PostHog captures the full trace hierarchy for multi-turn agent conversations with tool calls. Each tool use is captured as an `$ai_span` event linked to its parent generation.

    ```python
    import asyncio
    from posthog import Posthog
    from posthog.ai.claude_agent_sdk import query
    from claude_agent_sdk import ClaudeAgentOptions, AssistantMessage, TextBlock, ToolUseBlock
    posthog = Posthog(
        "<ph_project_token>",
        host="https://us.i.posthog.com"
    )
    options = ClaudeAgentOptions(
        max_turns=10,
        allowed_tools=["Read", "Glob", "Grep", "Bash"],
        permission_mode="bypassPermissions",
        cwd="/path/to/your/project",
    )
    async def main():
        async for message in query(
            prompt="Read the README and summarize this project",
            options=options,
            posthog_client=posthog,
            posthog_distinct_id="user_123",
        ):
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(block.text)
                    elif isinstance(block, ToolUseBlock):
                        print(f"Tool: {block.name}")
    asyncio.run(main())
    posthog.shutdown()
    ```

    This captures:

    -   `$ai_generation` events for each LLM turn (with token counts, cost, and cache metrics)
    -   `$ai_span` events for each tool use (Read, Glob, Grep, Bash, etc.)
    -   An `$ai_trace` event grouping the entire conversation with total cost and latency

6.  6

    ## Multi-turn conversations with history

    Optional

    For stateful, multi-turn conversations where each follow-up has full context from previous turns, use `PostHogClaudeSDKClient`. This wraps the Claude Agent SDK's `ClaudeSDKClient` and instruments each turn automatically. All turns share a single trace.

    ```python
    from posthog import Posthog
    from posthog.ai.claude_agent_sdk import PostHogClaudeSDKClient
    from claude_agent_sdk import ClaudeAgentOptions, AssistantMessage
    from claude_agent_sdk.types import TextBlock
    posthog = Posthog(
        "<ph_project_token>",
        host="https://us.i.posthog.com"
    )
    options = ClaudeAgentOptions(max_turns=5)
    async with PostHogClaudeSDKClient(
        options,
        posthog_client=posthog,
        posthog_distinct_id="user_123",
        posthog_properties={"app": "my-agent"},
    ) as client:
        # Turn 1
        await client.query("What is the capital of France?")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(block.text)
        # Turn 2 — has full conversation history
        await client.query("What language do they speak there?")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(block.text)
    ```

    Each `receive_response()` cycle emits `$ai_generation` events for that turn. When the client disconnects (exiting the `async with` block), a single `$ai_trace` event is emitted covering the entire session with aggregate latency.

7.  ## Verify traces and generations

    Recommended

    *Confirm LLM events are being sent to PostHog*

    Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs.

    ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png)

    [Check for LLM events in PostHog](https://app.posthog.com/llm-analytics/generations)

8.  7

    ## Next steps

    Recommended

    Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform.

    | Resource | Description |
    | --- | --- |
    | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. |
    | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. |
    | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. |
    | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. |
    | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. |

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better