Chatbot Sessions

Group multi-turn chatbot traces into a single session by passing a session id per turn.

This guide shows how to instrument a multi-turn chatbot so every conversation turn appears as its own trace and all turns for a conversation are grouped together in the neatlogs platform.

The key idea: each turn is its own trace, and you pass the same session_id (your conversation id) on every turn so Neatlogs groups them. See Sessions for the full model.

Setup

import os
import neatlogs

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

from openai import OpenAI
client = OpenAI()

Instrument the turn

Decorate the per-turn agent function with @neatlogs.span(kind="WORKFLOW") and pass session_id (your conversation id) on it. Each call produces one trace; all turns that pass the same session_id group into one session. Set the end_user_id here too if you track users.

from neatlogs import SystemPromptTemplate, UserPromptTemplate

system_tpl = SystemPromptTemplate([
    {"role": "system", "content": "You are a helpful support assistant. Use the conversation history to give consistent answers."},
])
user_tpl = UserPromptTemplate([
    {"role": "user", "content": "{{message}}"},
])


def chatbot_turn(message: str, history: list, conversation_id: str, user_id: str) -> str:
    @neatlogs.span(
        kind="WORKFLOW",
        name="chatbot_turn",
        session_id=conversation_id,     # same value every turn → one session
        end_user_id=user_id,            # one end-user across the conversation
    )
    def _turn() -> str:
        with neatlogs.trace("respond", kind="LLM",
                            system_prompt_template=system_tpl,
                            user_prompt_template=user_tpl):
            system_msgs = system_tpl.compile()
            user_msgs = user_tpl.compile(message=message)
            messages = system_msgs + history + user_msgs

            response = client.chat.completions.create(
                model="gpt-4o",
                messages=messages,
            )
        return response.choices[0].message.content

    return _turn()

Run a multi-turn conversation

Generate one conversation id at the start of the chat and pass it on every turn:

import uuid

conversation_id = f"conv_{uuid.uuid4().hex[:12]}"   # one per conversation
user_id = "u_demo"
history = []

while True:
    user_input = input("You: ")
    if user_input.lower() in ("exit", "quit"):
        break

    reply = chatbot_turn(user_input, history, conversation_id, user_id)
    print(f"Bot: {reply}")

    history.append({"role": "user", "content": user_input})
    history.append({"role": "assistant", "content": reply})

neatlogs.flush()
neatlogs.shutdown()

On a server (many users)

On a web server, the conversation id comes from your request (a thread/conversation row in your DB), not a local variable — read it per request and pass it on the turn's root, exactly as above. If your turn handler only uses a wrapped client and opens no root of its own, bind identity with identify() instead:

client = neatlogs.wrap(OpenAI())

@app.post("/chat")
def chat(req):
    with neatlogs.identify(session_id=req.conversation_id, end_user_id=str(req.user.id)):
        return client.chat.completions.create(...)   # auto-root inherits both

What you'll see in the dashboard

A session timeline showing all conversation turns in order. Each turn is an individual trace with its own WORKFLOW root and LLM sub-span. Because they share a session_id, the dashboard groups them under one session view, attributed to one end-user.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

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