发布

  • [OPIK-6964] [SDK] feat: add --to-workspace option to opik import; fix data loss gaps (#7278)

    frostbyte_neo 发布于 2026-07-01 16:57:08 +00:00

    • [NA] [SDK] feat: add --to-workspace option to opik import for cross-workspace imports

    Previously the WORKSPACE positional arg served double duty: locating
    exported files on disk and setting the destination API workspace.
    Importing into a different workspace required pointing --path at the
    source workspace directory as a workaround.

    --to-workspace is now a subcommand-level option (alongside --to-project)
    on all import subcommands (all, traces, dataset, experiment, prompt).
    WORKSPACE still resolves the on-disk path; --to-workspace overrides the
    workspace used when calling opik.Opik(). This lets users write:

    opik import src-workspace my-project all
    --to-workspace dest-workspace --to-project new-project

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

    • [NA] [SDK] fix: include dest workspace in import manifest key, fix test naming
    • destination_manifest_dir now hashes <dest_workspace>:<dest_project>
      instead of just <dest_project>, so importing the same exported project
      into different --to-workspace targets keeps independent resume/completion
      state and cannot short-circuit via manifest.is_completed
    • Updated setup_import_manifest signature to accept destination_workspace
      and threaded it through both _import_by_type and import_all
    • Renamed test to follow test_WHAT__CASE__EXPECTED convention and switched
      from direct import_all() call to CliRunner.invoke(import_group, ...)
      to exercise the public CLI surface
    • Fixed _manifest_dir test helper to pass workspace so its hash matches
      the new key format

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

    • [NA] [SDK] fix: retry attachment 429s during export instead of dropping the trace

    _fetch_attachments and _download_attachment_file convert ApiError 429s
    into OpikCloudRequestsRateLimited before raising. This exception is not
    an ApiError or httpx.* type, so _allowed_to_retry returned False and the
    retry decorator did nothing — the 429 propagated straight to the thread
    pool handler, which printed "Unexpected error processing trace" and
    skipped the trace without retrying.

    Fix:

    • Add _export_allowed_to_retry that extends the standard predicate to
      also match OpikCloudRequestsRateLimited
    • Update _export_wait_duration to read retry_after directly from the
      exception (attachment ops already extract it from response headers)
    • Apply @_export_rest_retry to _fetch_attachments and
      _download_attachment_file so attachment 429s wait and retry up to 8
      times before giving up, consistent with trace/span fetching

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

    • [NA] [SDK] fix: prevent silent data loss in export CLI (8 error-handling gaps)

    Audit of the export CLI found 8 places where API errors were silently
    swallowed, causing traces, experiments, datasets, or prompts to be
    dropped with no indication in the manifest or exit code.

    HIGH

    • experiment._fetch_trace_data: add @_export_rest_retry so 429/5xx
      trigger wait-and-retry instead of being caught and returned as None
      (which was counted as a skip and allowed manifest.complete() to fire).
      Only genuine 404s now return None.
    • experiment.export_experiment_by_id: re-raise instead of swallowing
      all exceptions; callers in all.py already set had_errors=True when
      the future raises.
    • all._fetch_experiments_page_raw: add @_export_rest_retry and convert
      429 to OpikCloudRequestsRateLimited; remove silent return ([], 0)
      so an entire experiments page is no longer silently dropped on 429.

    MEDIUM

    • project._process_trace: only call manifest.mark_trace_downloaded()
      when attachment download also succeeded, so a re-run can retry
      missing attachments without --force.
    • dataset.export_experiment_datasets: track and return an error count
      so callers can distinguish "0 exported" from "all failed".
    • prompt.export_prompts_by_ids: same - add error count to return tuple.

    LOW

    • prompt.export_single_prompt / export_related_prompts_by_name: upgrade
      history-fetch failure from debug-only to always-visible warning
      so users know the export is incomplete.
    • all._export_all_prompts: add error counter; propagate to had_errors
      in export_all so the process exits with code 1 on prompt failures.

    Adds 8 new unit tests in tests/unit/cli/test_export_error_handling.py
    covering the three HIGH fixes.

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

    • [NA] [SDK] fix: resolve mypy type error in _export_wait_duration

    Use a separate local variable parsed for the Optional[float] header
    lookups instead of reusing seconds (which is typed float earlier in
    the same function scope), eliminating the incompatible-assignment error
    mypy reported on line 110.

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

    • [NA] [SDK] fix: address PR review comments on export error handling
    • all.py: return (0, 0, 1) when prompt listing fails so export_all exits 1
    • dataset.py: fix docstring to document 3-tuple return value
    • experiment.py: add LOGGER, log non-404 API errors before re-raise
    • experiment.py: propagate dataset/prompt error counts to sys.exit(1)
    • prompt.py: extract _safe_prompt_history helper to remove duplication
    • tests: switch TestFetchTraceDataRetry to test through public export_traces_by_ids

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

    • [NA] [SDK] fix: use non-retryable status code in unexpected_api_error test

    503 is in RETRYABLE_STATUS_CODES so @_export_rest_retry retried it 8 times
    with exponential backoff, causing the unit test to timeout in CI after ~2 min.
    Switch to 400 which is not retried and raises immediately.

    Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com


    Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com

    下载附件