OpenAI

Trace the OpenAI SDK in Python and TypeScript.

Wrap the OpenAI client and chat.completions.create and responses.create calls are captured as LLM spans with the prompt, response, token counts, and model. In Python, embeddings.create is also traced (as an EMBEDDING span).

Prerequisites

pip install -U "neatlogs[openai]" openai

Usage

import os
import neatlogs

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

from openai import OpenAI

client = neatlogs.wrap(OpenAI())

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "In one sentence, what is OpenAI?"}],
)
print(resp.choices[0].message.content)

neatlogs.flush()
neatlogs.shutdown()

That's the whole setup — wrap() opens a WORKFLOW root for you, so this renders in the dashboard with no extra wrapper. Streaming and async clients (AsyncOpenAI) are traced too, and token counts are recorded even for streamed responses.

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

TypeScript alternative: instead of wrapOpenAI, you can pass instrumentations: ['openai'] to init() and import the SDK afterward. See the TypeScript SDK.

Sessions & end-user

Say you're building a chatbot on OpenAI and want every back-and-forth turn to land under one session, tied to whoever is chatting — just wrap your calls in identify() and Neatlogs stitches them together. Since neatlogs.wrap() already opened the trace root for your OpenAI client, there's nothing extra to thread through: the wrapped call simply picks up the session and end-user from the identify() surrounding it:

# 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.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": message}],
    )

The one rule to remember: hand back the same session_id on every turn, and the whole conversation stays glued together.

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.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": f"Summarize:\n{document}"}],
        )
        return resp.choices[0].message.content

For the complete picture, dig into 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.