Whetstone.
Module 0: the objects you monitorThreads, metadata, and the Python/TypeScript split
Module 0, Lesson 215 min

Threads, metadata, and the Python/TypeScript split

A trace is one invocation. A conversation is many. Nothing in the trace model connects them, so out of the box a ten turn chat is ten unrelated traces sitting in a table next to ten unrelated traces from somebody else.

Threads are the fix, and they are the smallest fix in this whole course: attach the same identifier to every run in a conversation and LangSmith groups them.

The mechanic, and it really is this small

Put the identifier in the run’s metadata. There is no thread object to create, no registration call, nothing to open or close. A thread is an emergent grouping over runs that happen to share a key, which is why you can start threading an existing app by adding one field and shipping.

LangSmith accepts two keys for this, thread_id and session_id, and the docs are not perfectly consistent about their relationship. The Configure threads page lists them as alternatives: the key name “should be one of” session_id or thread_id, with no order implied. The Query threads using the SDK page is sharper, and states a rule: the backend looks for thread_id in metadata, falling back to session_id.

What the threads view buys you

Some defects are invisible at trace level by construction. Every turn answered the question it was given with the context it was given, returned successfully, and looked fine. The user is still furious, because turn four forgot what they said in turn two.

The threads view is the only surface where “between the turns” exists as a visible object. The tell is simple: individually fine, collectively broken. If your traces look healthy and your users do not, stop opening traces.

One detail to bank now, because it comes back in every later module: feedback attaches to a run or to a thread. Run level suits anything about a single response. Thread level suits anything only true of the whole conversation, like whether the user got what they came for.

Instrumentation is where the language split lives

In Python traceable is a decorator. In TypeScript traceable() is a wrapper you pass a function to.

TypeScript: wrap, do not decorate
import { traceable } from "langsmith/traceable";

const answerQuestion = traceable(
  async (question: string) => {
    return await model.invoke(question);
  },
  { name: "answerQuestion", run_type: "chain" },
);

Same concept, same resulting run. with ls.trace(...) is the one construct that is Python only, because TypeScript has no with block to hang it on. Client call parameters follow each language’s house style too: trace_id and session_id in Python, runId and sessionId in JavaScript and TypeScript.

Practice

Try it yourself

Quiz

Both keys are present, which one wins

A run arrives carrying thread_id set to abc in its metadata and session_id set to xyz in the same metadata.

  1. ANeither, because the backend rejects a run whose two thread keys disagree
  2. Bxyz, because session_id is the older key and legacy keys keep priority
  3. CBoth, so the run appears under the abc thread and again under xyz
  4. Dabc, because the backend reads thread_id first and only falls back to session_id
Show answer

Correct answer: D — abc, because the backend reads thread_id first and only falls back to session_id

The documented lookup order on the query-threads page is thread_id first, session_id as the fallback, so abc wins. The xyz answer is the tempting one because backward compatibility usually does mean the legacy key keeps priority, and that instinct is normally sound engineering. It is simply not what this lookup does. A run belongs to exactly one thread, so neither the rejection answer nor the two threads answer describes behaviour the system has.

Quiz

Which construct does not exist in TypeScript

You write TypeScript. The exam is written by people who mostly write Python. One of these has no TypeScript equivalent at all.

  1. AThe traceable mechanism, a decorator in Python and a wrapper function in TypeScript
  2. Bwith ls.trace(...) as a context manager block
  3. CAttaching metadata and tags to a run
  4. DGrouping runs into a thread with a thread identifier
Show answer

Correct answer: B — with ls.trace(...) as a context manager block

The context manager form is Python only. TypeScript has no with statement, so there is nothing to port it onto. The first option is the good distractor, because traceable genuinely does differ between the two languages, decorator against wrapper function, but it exists in both. That is a difference in form rather than an absence. Metadata, tags and threads are plain data and behave identically either way.

Recall

Where the thread identifier goes

A precise little fact that is easy to get half right, and the half you get wrong fails silently.

Where exactly do you put the thread identifier, and what happens if you put it somewhere else?

Reveal answer

In the run's metadata. The backend reads thread_id from metadata and falls back to session_id in metadata. Attach it anywhere else, as a tag or buried in the input payload, and nothing errors and nothing groups. You get a project full of individually correct, completely unrelated traces, which is the worst kind of failure because it looks exactly like success. The value itself is just a stable string you own, normally whatever your app already calls a conversation.

Quiz

Two minutes, docs open, find the filter syntax

Exam scenario. You need to filter a project's runs on a custom metadata field and you cannot remember the query syntax. docs.langchain.com is open. Where do you go first?

  1. AThe LangGraph how-to guides, under persistence, checkpointers and thread state
  2. BThe LangSmith observability section, the pages on filtering runs and querying traces
  3. CThe LangSmith evaluation section, under datasets, experiments and evaluator filters
  4. DThe LangChain integrations index, under the page for your model provider
Show answer

Correct answer: B — The LangSmith observability section, the pages on filtering runs and querying traces

Filtering runs is observability, so it lives in the LangSmith observability pages beside tracing and querying. The evaluation section is the plausible near miss, because evaluators also take filters, but those pages document evaluator configuration rather than the query language itself. Persistence and checkpointers are LangGraph state, a different product surface entirely. Knowing which section owns a topic is the scoreable skill in a semi open book exam; memorising the syntax is not.

Check

Can you filter on it later

Look at one instrumented entry point in your own code. This is a five minute audit with a very long payoff.

You should see

For every question you might ask that project during an incident, which user, which version, which environment, which variant, you can point at the metadata field that would let the runs table answer it. Any question with no corresponding field is a question you will not be able to ask.

Sign in to track your progress →