evaluate(), Lambda, and a long-running web server because session state is handled differently in each pattern.
Which Pattern?
| Runtime | Where to initialize | Session strategy | Why |
|---|---|---|---|
| Scripts / notebooks | Module-level in the entry point | One shared session is often enough | Simple single execution flow |
| AWS Lambda / Cloud Functions | Outside the handler with lazy init | create_session() per invocation | Reuse warm containers without sharing invocation state |
| FastAPI / Flask / Django | Once at app startup | create_session() or acreate_session() per request | Reuse one tracer while isolating concurrent requests |
Initialize the tracer before any instrumentor. Call
HoneyHiveTracer.init(...) first, then pass tracer.provider into instrumentor.instrument(...).What changes by runtime is not whether you initialize the tracer. What changes is where you place that initialization and how you create session context.For request-scoped and invocation-scoped runtimes, create_session() and acreate_session() put the active session ID in OpenTelemetry baggage. HoneyHive resolves that baggage session before falling back to the tracer instance’s startup session.Scripts and Notebooks
Initialize once at module level. All traced operations share the same session.Evaluation / Experiments
When running experiments withevaluate(), don’t create your own tracer. The SDK creates a new tracer per datapoint automatically, giving each datapoint its own isolated session.
Serverless
In serverless environments like Lambda and Cloud Functions, initialize the tracer outside the handler and reuse it across warm starts. Then callcreate_session() inside the handler so each invocation gets its own active session. The invocation-scoped baggage session takes precedence over any default session on the shared tracer.
If your function returns immediately after handling the request, call
tracer.force_flush() before returning so buffered spans are exported before the runtime freezes.Linking Lambda Invocations
To link multiple invocations into the same session (e.g., multi-turn conversations), pass asession_id through your event payload and reuse get_tracer() from the lazy-init pattern above:
Web Servers
For long-running servers (FastAPI, Flask, Django), initialize one tracer at startup and create a new session per request usingcreate_session() or its async variant acreate_session().
How session isolation works:
create_session() and acreate_session() store the active session ID in OpenTelemetry baggage, which uses Python context propagation and ContextVar for async/task-local state. HoneyHive reads the baggage session first and only falls back to the tracer instance when no request-scoped session is present, so one shared tracer can safely serve concurrent requests.FastAPI
Flask
For synchronous frameworks, usecreate_session() instead of acreate_session():
Multi-Turn Conversations
For multi-turn conversations, the first request creates a session and returns the ID to the client. Subsequent requests link to that session usingskip_api_call=True, which sets the session context without making an API call.
| Scenario | Code | When |
|---|---|---|
| Auto-generate ID | create_session(session_name="request") | New session, let HoneyHive assign the ID |
| Custom ID | create_session(session_id="my-id") | Use your own ID scheme |
| Link to existing | create_session(session_id="existing-id", skip_api_call=True) | Session already exists in HoneyHive |
Scoped Sessions
For single-use scripts, dedicated worker runs, or batch tasks where the rest of the current execution context belongs to the same logical unit of work,with_session can be convenient. For web requests, prefer create_session() or acreate_session() in middleware:
Thread and Process Safety
The global tracer +create_session() pattern is safe for:
- Multi-threaded servers (FastAPI, Flask with threads) — baggage uses
ContextVar, which is inherently thread-local - Multi-process deployments (Gunicorn workers, uWSGI) — each process gets its own tracer instance; processes don’t share state
Best Practices
Pass an explicit tracer to @trace
Pass an explicit tracer to @trace
Passing
tracer=tracer makes the binding explicit and avoids relying on implicit tracer discovery.Create sessions per logical unit of work
Create sessions per logical unit of work
Even with a global tracer, create sessions to isolate traces by request, user, or job.
Match session creation to the runtime
Match session creation to the runtime
Use the tracer placement that matches your runtime:
- Scripts and notebooks: initialize once in the module that starts the run
- Lambda and other serverless runtimes: lazy-init outside the handler, then create a session per invocation
- Web servers: initialize once at startup, then create a session per request
evaluate(): let the SDK create and manage tracers for you
Use test_mode for local development
Use test_mode for local development
test_mode=True disables OTLP export and generates a local session ID instead of creating one in HoneyHive. Use it for local development and tests when you want tracer setup without exporting spans over OTLP.
