Integrations

Trace OpenAI, Anthropic, Gemini, Azure, Bedrock, Vertex AI, OpenRouter, and more — in Python and TypeScript.

Neatlogs traces the AI providers and SDKs you already use. In most cases you wrap the client you construct and every call it makes is captured — no other code changes.

import neatlogs
from openai import OpenAI

neatlogs.init(api_key="YOUR_PROJECT_KEY", workflow_name="my-app")

client = neatlogs.wrap(OpenAI())   # every call is now traced

Where an integration exists for both languages, the page shows the Python and TypeScript versions as tabs in the code block.

Cloud & direct providers

Agent frameworks

Agent SDKs

Developer tools


Grouping calls into one trace

A wrapped call just works on its own: neatlogs.wrap() automatically opens a WORKFLOW root (named after your workflow_name) so a single call renders in the dashboard with no extra code.

Because of that, each wrapped call with no surrounding context becomes its own trace. When a run makes several calls — or mixes provider calls with your own functions — group them under one root so they appear together in one trace:

# Decorate your entry point — it becomes the single root for everything inside…
@neatlogs.span(kind="WORKFLOW")
def handle_request(q):
    client.chat.completions.create(...)   # nests under handle_request
    client.chat.completions.create(...)   # …same trace

# …or wrap a block when there's no single entry function:
with neatlogs.trace("my-run", kind="WORKFLOW"):
    client.chat.completions.create(...)
    client.chat.completions.create(...)

When you provide your own root, the automatic one steps aside — your calls nest under it. Agent SDKs and frameworks that already emit their own root (like the Claude Agent SDK, Hermes, or the OpenAI Agents SDK) work the same way: no extra wrapper needed. See Span Kinds for the full list of root kinds.

When to add your own WORKFLOW root

A lone wrapped call doesn't need one. Add an explicit WORKFLOW (or AGENT / CHAIN) root when:

  • A run makes several provider calls that belong together — e.g. a multi-turn exchange, or a retry loop. One root keeps them in a single trace instead of one trace per call.
  • You mix provider calls with your own functions or tools. This is the important one: spans you create with @neatlogs.span(kind="TOOL") / traceTool(...) are not root kinds, so if there's no active parent they have nothing to attach to and won't render on their own. A WORKFLOW root gives them a parent. (A tool-calling turn — LLM → tool → LLM — is the classic case.)
  • You want a meaningfully named trace rather than the default workflow_name.

No double root. Auto-root fires only when there's no active recording parent. The moment you open a WORKFLOW, the wrapper detects it and skips auto-root — so a manual root plus a wrapped call always produces exactly one root, never two.

To instrument your own functions (custom agents, pipelines, tools) rather than a known provider or framework, use the @span decorator — see the Python SDK or TypeScript SDK.

On this page