Since many LLM operations tend to be I/O bound, it is often useful to use threads to perform multiple operations at once. Usually, you’ll use the ThreadPoolExecutor class from the concurrent.futures module in the Python standard library, like this:

indexes = [pinecone.Index(f"index{i}") for i in range(3)]
executor = ThreadPoolExecutor(max_workers=3)
for i in range(3):
    executor.submit(indexes[i].query, [1.0, 2.0, 3.0], top_k=10)

Unfortunately, this won’t work as you expect and may cause you to see “broken” traces or missing spans.

The reason relies in how OpenTelemetry (which is what we use under the hood for tracing) uses Python’s context to propagate the trace.

You’ll need to explictly propagate the context to the threads:

indexes = [pinecone.Index(f"index{i}") for i in range(3)]
executor = ThreadPoolExecutor(max_workers=3)
for i in range(3):
    # add the context to the threads as shown below
	ctx = contextvars.copy_context()
    executor.submit(
        ctx.run,
        functools.partial(index.query, [1.0, 2.0, 3.0], top_k=10),
    )

Learn more