[OPIK-5699] [P SDK] feat: inherit project_name from context for all Opik operations#6106
Conversation
…pik operations Add a project_name ContextVar with ownership semantics so that the outermost @track(project_name=...) or opik.project() context manager sets the project for all nested operations (traces, spans, agent configs, datasets, experiments, etc.). - context_storage: ownership-based try_acquire / release_if_owner API - span_creation_handler: resolve project_name from ContextVar at span creation time, warn on conflicting nested project names - opik_client: _resolve_project_name() checks ContextVar; all inline patterns refactored to use it - opik.project() context manager exported for multi-agent setups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All public methods now document the precedence: explicit argument → active project context (@track / opik.project_context) → client default. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ThreadsClient: use _resolve_project_name instead of inline fallback - temporary_context: guard _raw_set against None project_name - project_context: document ownership semantics in docstring - pop_end_candidate_trace_data: pass ensure_id to pop_trace_data - Tests: rename to WHAT__CASE__EXPECTED_RESULT convention, rework _resolve_project_name tests to use public API only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion Add context_storage.resolve_project_name(default, caller) that resolves ContextVar → init-time default, with a warning when the context overrides an explicitly initialized project name. All callback-based integrations (LangChain, DSPy, Haystack, LlamaIndex, OpenAI Agents, Gemini) now use this function so that opik.project_context and @track(project_name=...) properly override init-time defaults. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alexkuzmik
left a comment
There was a problem hiding this comment.
Addressing remaining baz-reviewer comments:
1. context_storage.py:320 — temporary_context span push/pop vs conditional project_token
Skipping — add_span_data/pop_span_data was always unconditional (pre-existing behavior). The conditional is only on the new project_token which correctly guards against setting None. The span lifecycle and project context lifecycle are intentionally independent.
2. test_project_name_context.py:329 — refactor repeated trace_trees[0].project_name assertions into a helper
Skipping — matches the existing assertion style in test_tracker_outputs.py. Only ~3 usages, extracting a helper would over-abstract.
3. langchain/opik_tracer.py:322 — resolve_project_name(self._project_name, "OpikTracer") duplicated across call sites
Skipping — each call site resolves project_name at the point of use. A cached property would hide the context_storage dependency and make the dynamic resolution (which reads a ContextVar per-call) harder to trace.
4. context_storage.py:251 — resolve_project_name warning on every override even when resolve_child_span_project_name later does the same
Skipping — this is not a double-warning. resolve_project_name overrides the child's project to match the context, so resolve_child_span_project_name then sees matching names and does NOT warn. The warning fires exactly once.
🤖 Reply posted via /address-github-pr-comments
Details
Introduces a dedicated
project_nameContextVar with ownership semantics so that the outermost@track(project_name=...)oropik.project_context(...)sets the project for all nested operations — traces, spans, agent configs, datasets, experiments, prompts, and more.Key changes:
context_storage.py: New ownership-basedtry_acquire_context_project_name/release_context_project_name_if_ownerAPI. First caller (identified by span/trace ID) becomes owner; nested callers with a different project name get a warning and the outer project sticks.span_creation_handler.py: Resolvesproject_namefrom the ContextVar at span creation time. Warns when a nested@trackrequests a conflicting project.opik_client.py:_resolve_project_name()checks: explicit arg → ContextVar → client default. All 10 inlineproject_name or self._project_namepatterns refactored to use it. Docstrings updated across all public methods to document the resolution order.opik.project_context(): New context manager for multi-agent setups — sets project for everything inside the block. Exported as bothopik.project_contextandopik.project(alias).base_track_decorator.py: Acquires/releases project ownership via span lifecycle hooks (add_start_candidates/pop_end_candidates). Zero structural changes to decorator wrapper functions.Covers both OPIK-5699 and OPIK-5700:
get_agent_config()andcreate_agent_config_version()(and all other Opik client methods) now inherit project from the active context.opik.project_context("X")context manager for multi-agent setups.Change checklist
Issues
Testing
14 new unit tests in
tests/unit/decorator/test_project_name_context.py:@track(project_name="X")propagates to trace and span@trackwithout project_name inherits parent's project@trackwith conflicting project_name: outer sticks + warning@trackwith same project_name: no warning_resolve_project_nameprecedence: explicit arg > ContextVar > client defaultopik.project_context(): sets project for tracked functions, resets after exitopik.project_context(): outer sticksopik.project_context()sticks over nested@track(project_name=...)@trackwith project_nameFull regression: 2150 unit tests passed, 0 failures.
Documentation
project_nameparameter docstrings inopik_client.pyupdated to document the resolution order: explicit argument → active project context (from@trackoropik.project_context) → client's default.