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 tracedWhere an integration exists for both languages, the page shows the Python and TypeScript versions as tabs in the code block.
Cloud & direct providers
OpenAI
The OpenAI SDK — chat, responses, embeddings.
Anthropic
Claude via the Anthropic SDK.
Google Gemini
The google-genai / @google/genai SDK.
Azure OpenAI
Azure-hosted OpenAI deployments.
AWS Bedrock
Bedrock Converse + InvokeModel.
Vertex AI
Gemini on Vertex AI.
OpenRouter
200+ models through one API.
Agent frameworks
LangChain
LangChain & LangGraph (Python + TS).
CrewAI
Multi-agent crews (Python).
Mastra
Mastra agents & workflows (TS).
Vercel AI SDK
generateText / streamText (TS).
Pydantic AI
Pydantic AI agents (Python).
DSPy
DSPy modules (Python).
Agno
Agno agents, teams, workflows (Python).
Strands
Strands agents (Python + TS).
Google ADK
Agent Development Kit runner (Python).
Pi Agent
Pi agents (TS).
OpenAI Agents SDK
OpenAI Agents (Python + TS).
Agent SDKs
Developer tools
Claude Code
Capture every Claude Code session as a trace.
opencode
Trace opencode CLI sessions.
Agent Skills
Teach your coding agent to instrument code.
MCP Server
Query traces & triage from your agent.
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. AWORKFLOWroot 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.