Anthropic

Trace Claude via the Anthropic SDK in Python and TypeScript.

Wrap the Anthropic client and every messages.create / messages.stream call is captured as an LLM span — including thinking blocks, tool use, and cache tokens.

Prerequisites

pip install -U "neatlogs[anthropic]" anthropic

Usage

import os
import neatlogs

neatlogs.init(api_key=os.environ["NEATLOGS_API_KEY"], workflow_name="anthropic-demo")

from anthropic import Anthropic

client = neatlogs.wrap(Anthropic())

resp = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    messages=[{"role": "user", "content": "In one sentence, what is Claude?"}],
)
print(resp.content[0].text)

neatlogs.flush()
neatlogs.shutdown()

wrap() opens a WORKFLOW root for you, so this renders with no extra wrapper. Async clients (AsyncAnthropic) and streaming are handled the same way. AnthropicBedrock is also supported.

Making several calls in one run? Group them into one trace with a WORKFLOW root.

Sessions & end-user

Say you're building a chatbot on Claude and every reply is its own trace, but they're really one back-and-forth with one person — Neatlogs can stitch those turns into a single session and tie them to that user. Just wrap each Claude call in identify(); since wrap() already opens the trace root, the call picks up the session and end-user on its own, nothing to thread through:

# Same session_id every turn → one session; end_user_id attributes it to your user.
with neatlogs.identify(session_id=f"conv_{conversation_id}", end_user_id=user_id):
    resp = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=256,
        messages=[{"role": "user", "content": message}],
    )

The one thing to remember: reuse the same session_id for every turn of the conversation, and they'll all land under one session.

Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:

Warning

A standalone workflow must not carry a session_id. Omit it entirely — Neatlogs sets session_id = trace_id, so the run is its own single-turn session, fully attributed to the end-user. Reusing one session_id across unrelated runs wrongly folds them into a single multi-turn conversation.

def summarize(document, user):
    # Standalone workflow — attribute to the end-user; do NOT pass session_id.
    with neatlogs.identify(
        end_user_id=str(user.id),
        end_user_metadata={"plan": user.plan},
    ):
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=256,
            messages=[{"role": "user", "content": f"Summarize:\n{document}"}],
        )
        return resp.content[0].text

Want the whole picture? Head over to Sessions and End-User Identity.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

Ask anything about instrumenting, tracing, or the Neatlogs dashboard.