Skip to content

[OPIK-5699] [P SDK] feat: inherit project_name from context for all Opik operations#6106

Merged
alexkuzmik merged 7 commits intomainfrom
aliaksandrk/OPIK-5699-inherit-project-from-trace-context
Apr 8, 2026
Merged

[OPIK-5699] [P SDK] feat: inherit project_name from context for all Opik operations#6106
alexkuzmik merged 7 commits intomainfrom
aliaksandrk/OPIK-5699-inherit-project-from-trace-context

Conversation

@alexkuzmik
Copy link
Copy Markdown
Collaborator

@alexkuzmik alexkuzmik commented Apr 7, 2026

Details

Introduces a dedicated project_name ContextVar with ownership semantics so that the outermost @track(project_name=...) or opik.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-based try_acquire_context_project_name / release_context_project_name_if_owner API. 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: Resolves project_name from the ContextVar at span creation time. Warns when a nested @track requests a conflicting project.
  • opik_client.py: _resolve_project_name() checks: explicit arg → ContextVar → client default. All 10 inline project_name or self._project_name patterns 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 both opik.project_context and opik.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:

  • OPIK-5699: get_agent_config() and create_agent_config_version() (and all other Opik client methods) now inherit project from the active context.
  • OPIK-5700: opik.project_context("X") context manager for multi-agent setups.

Change checklist

  • User facing
  • Documentation update

Issues

  • OPIK-5699
  • OPIK-5700

Testing

14 new unit tests in tests/unit/decorator/test_project_name_context.py:

  • @track(project_name="X") propagates to trace and span
  • ContextVar available inside tracked function, reset after exit
  • Nested @track without project_name inherits parent's project
  • Nested @track with conflicting project_name: outer sticks + warning
  • Nested @track with same project_name: no warning
  • _resolve_project_name precedence: explicit arg > ContextVar > client default
  • opik.project_context(): sets project for tracked functions, resets after exit
  • Nested opik.project_context(): outer sticks
  • opik.project_context() sticks over nested @track(project_name=...)
  • Async @track with project_name

Full regression: 2150 unit tests passed, 0 failures.

Documentation

  • All project_name parameter docstrings in opik_client.py updated to document the resolution order: explicit argument → active project context (from @track or opik.project_context) → client's default.

alexkuzmik and others added 4 commits April 7, 2026 14:58
…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>
@github-actions github-actions bot added python Pull requests that update Python code tests Including test files, or tests related like configuration. Python SDK labels Apr 7, 2026
- 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>
Copy link
Copy Markdown
Collaborator Author

@alexkuzmik alexkuzmik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressing remaining baz-reviewer comments:

1. context_storage.py:320temporary_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:322resolve_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:251resolve_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

@alexkuzmik alexkuzmik marked this pull request as ready for review April 7, 2026 15:13
@alexkuzmik alexkuzmik requested a review from a team as a code owner April 7, 2026 15:13
@alexkuzmik alexkuzmik merged commit 9c0c10f into main Apr 8, 2026
139 of 140 checks passed
@alexkuzmik alexkuzmik deleted the aliaksandrk/OPIK-5699-inherit-project-from-trace-context branch April 8, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Python SDK python Pull requests that update Python code tests Including test files, or tests related like configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants