Files
zeekay bc4ddf332b chore: rebase the tree onto upstream master fa1a9a60
Mechanically re-derived: bin/debrand runs over a fresh import of
posthog/posthog@fa1a9a60d4 (2026-08-06).
The pipeline (.hanzo/workflows), the Dockerfiles and bin/docker*
entrypoints, compose files, LLM.md and the squashed initial migration
are restored from the shipped tree, same as the previous import.

The prune's import-closure guard kept 21 products the new core now
imports; dropping them is a refactor now, recorded rather than forced.
2026-08-06 01:25:36 -07:00

296 lines
14 KiB
TypeScript

import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper'
import { StepDefinition } from '../steps'
export const getClaudeAgentSDKSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => {
const { CodeBlock, CalloutBox, Markdown, Blockquote, dedent, snippets } = ctx
const NotableGenerationProperties = snippets?.NotableGenerationProperties
return [
{
title: 'Install the Insights SDK',
badge: 'required',
content: (
<>
<Markdown>Setting up analytics starts with installing the Insights Python SDK.</Markdown>
<CodeBlock
language="bash"
code={dedent`
pip install insights
`}
/>
</>
),
},
{
title: 'Install the Claude Agent SDK',
badge: 'required',
content: (
<>
<Markdown>
Install the Claude Agent SDK. Insights instruments your agent queries by wrapping the `query()`
function. The Insights SDK **does not** proxy your calls.
</Markdown>
<CodeBlock
language="bash"
code={dedent`
pip install claude-agent-sdk
`}
/>
<CalloutBox type="fyi" icon="IconInfo" title="Proxy note">
<Markdown>
These SDKs **do not** proxy your calls. They only fire off an async call to Insights in the
background to send the data. You can also use AI observability 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](https://hanzo.ai/docs/ai-observability/installation/manual-capture) for
more details.
</Markdown>
</CalloutBox>
</>
),
},
{
title: 'Initialize Insights and run a query',
badge: 'required',
content: (
<>
<Markdown>
{dedent`
Initialize Insights with your project token and host from [your project
settings](https://app.hanzo.ai/settings/project). Then use the Insights \`query()\`
wrapper as a drop-in replacement for \`claude_agent_sdk.query()\`. This automatically
captures \`$ai_generation\`, \`$ai_span\`, and \`$ai_trace\` events, including a span
for each tool the agent uses, such as \`Bash\` below.
`}
</Markdown>
<CodeBlock
language="python"
code={dedent`
import asyncio
from insights import Insights
from insights.ai.claude_agent_sdk import query
from claude_agent_sdk import ClaudeAgentOptions
insights = Insights(
"<ph_project_token>",
host="<ph_client_api_host>"
)
async def main():
options = ClaudeAgentOptions(
max_turns=5,
allowed_tools=["Read", "Glob", "Grep"],
cwd="/path/to/your/project",
)
async for message in query(
prompt="Read the README and summarize this project",
options=options,
insights_client=insights,
insights_distinct_id="user_123", # optional
insights_trace_id="trace_123", # optional
insights_properties={"conversation_id": "abc123", "$ai_session_id": "conversation-abc"}, # optional
insights_groups={"company": "company_id_in_your_db"}, # optional
insights_privacy_mode=False, # optional
):
print(message)
asyncio.run(main())
insights.shutdown()
`}
/>
<Blockquote>
<Markdown>
{dedent`
**Notes:**
- The wrapper yields all original messages unchanged, fully transparent to your code.
- Pass \`$ai_session_id\` in \`insights_properties\` to group every generation, span, and trace from a call into one Insights session.
- If you want to capture LLM events anonymously, **do not** pass a distinct ID. See our docs on [anonymous vs identified events](https://hanzo.ai/docs/data/anonymous-vs-identified-events) to learn more.
`}
</Markdown>
</Blockquote>
<Markdown>
{dedent`
You can expect captured \`$ai_generation\` events to have the following properties:
`}
</Markdown>
{NotableGenerationProperties && <NotableGenerationProperties />}
</>
),
},
{
title: 'Reusable configuration with instrument()',
badge: 'optional',
content: (
<>
<Markdown>
If you make multiple `query()` calls with the same Insights configuration, use `instrument()` to
configure once and reuse across queries.
</Markdown>
<CodeBlock
language="python"
code={dedent`
import asyncio
from insights import Insights
from insights.ai.claude_agent_sdk import instrument
from claude_agent_sdk import ClaudeAgentOptions
insights = Insights(
"<ph_project_token>",
host="<ph_client_api_host>"
)
ph = instrument(
client=insights,
distinct_id="user_123",
properties={"app": "my-agent"},
)
options = ClaudeAgentOptions(max_turns=10)
async def main():
# All queries share the same Insights 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())
`}
/>
<Markdown>You can override any Insights parameter per-query:</Markdown>
<CodeBlock
language="python"
code={dedent`
async for msg in ph.query(
prompt="...",
options=options,
insights_distinct_id="different_user",
insights_properties={"extra": "data"},
):
...
`}
/>
</>
),
},
{
title: 'Tool usage and multi-turn conversations',
badge: 'optional',
content: (
<>
<Markdown>
{dedent`
The recommended example above already runs a tool-using query, and \`max_turns\` lets
Claude take more than one internal turn before it replies. Inspect the message stream
to see each turn and tool call as they happen.
`}
</Markdown>
<CodeBlock
language="python"
code={dedent`
from claude_agent_sdk import AssistantMessage, TextBlock, ToolUseBlock
async for message in query(
prompt="Read the README and summarize this project",
options=options,
insights_client=insights,
insights_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}")
`}
/>
<Markdown>
{dedent`
Across the whole query, Insights 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
`}
</Markdown>
</>
),
},
{
title: 'Multi-turn conversations with history',
badge: 'optional',
content: (
<>
<Markdown>
For stateful, multi-turn conversations where each follow-up has full context from previous
turns, use `InsightsClaudeSDKClient`. This wraps the Claude Agent SDK's `ClaudeSDKClient` and
instruments each turn automatically. All turns share a single trace.
</Markdown>
<CodeBlock
language="python"
code={dedent`
from insights import Insights
from insights.ai.claude_agent_sdk import InsightsClaudeSDKClient
from claude_agent_sdk import ClaudeAgentOptions, AssistantMessage
from claude_agent_sdk.types import TextBlock
insights = Insights(
"<ph_project_token>",
host="<ph_client_api_host>"
)
options = ClaudeAgentOptions(max_turns=5)
async with InsightsClaudeSDKClient(
options,
insights_client=insights,
insights_distinct_id="user_123",
insights_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)
`}
/>
<Markdown>
{dedent`
Each \`receive_response()\` cycle emits \`$ai_generation\` events for that turn. When the
client disconnects, exiting the \`async with\` block, it emits a single \`$ai_trace\`
event covering the entire session with aggregate latency.
`}
</Markdown>
</>
),
},
]
}
export const ClaudeAgentSDKInstallation = createInstallation(getClaudeAgentSDKSteps)