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]" openaiUsage
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
WORKFLOWroot.
TypeScript alternative: instead of
wrapOpenAI, you can passinstrumentations: ['openai']toinit()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:
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.contentFor the complete picture, dig into Sessions and End-User Identity.
