5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
1621 lines
49 KiB
Python
1621 lines
49 KiB
Python
import os
|
|
import time
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
import opik
|
|
from opik import opik_context, id_helpers, Attachment, exceptions
|
|
from opik.api_objects import helpers
|
|
from opik.types import FeedbackScoreDict, ErrorInfoDict
|
|
from . import verifiers
|
|
from .conftest import ATTACHMENT_FILE_SIZE
|
|
from ..testlib import ANY_STRING, generate_project_name
|
|
|
|
PROJECT_NAME = generate_project_name("e2e", __name__)
|
|
# Alternative projects passed as explicit `project_name=` overrides to verify
|
|
# that `@opik.track(project_name=...)` and `opik_client.trace(project_name=...)`
|
|
# route writes correctly when the env-default is bypassed.
|
|
_PROJECT_BATCHING = generate_project_name("e2e-tests-batching-messages")
|
|
_PROJECT_DECORATOR_OUTER = generate_project_name(
|
|
"e2e-tests-manual-project-name--decorator"
|
|
)
|
|
_PROJECT_DECORATOR_INNER_IGNORED = generate_project_name(
|
|
"e2e-tests-manual-project--this-will-be-ignored"
|
|
)
|
|
_PROJECT_UPDATE_TRACE = generate_project_name("update_trace_happy_flow")
|
|
|
|
|
|
@pytest.mark.parametrize("override_project_name", [True, False])
|
|
def test_tracked_function__happyflow(opik_client, override_project_name):
|
|
project_name = (
|
|
generate_project_name("e2e-tests-manual-project-name")
|
|
if override_project_name
|
|
else None
|
|
)
|
|
|
|
# Setup
|
|
ID_STORAGE = {}
|
|
|
|
@opik.track(
|
|
tags=["outer-tag1", "outer-tag2"],
|
|
metadata={"outer-metadata-key": "outer-metadata-value"},
|
|
project_name=project_name,
|
|
)
|
|
def f_outer(x):
|
|
ID_STORAGE["f_outer-trace-id"] = opik_context.get_current_trace_data().id
|
|
ID_STORAGE["f_outer-span-id"] = opik_context.get_current_span_data().id
|
|
|
|
f_inner("inner-input")
|
|
return "outer-output"
|
|
|
|
@opik.track(
|
|
tags=["inner-tag1", "inner-tag2"],
|
|
metadata={"inner-metadata-key": "inner-metadata-value"},
|
|
project_name=project_name,
|
|
)
|
|
def f_inner(y):
|
|
ID_STORAGE["f_inner-span-id"] = opik_context.get_current_span_data().id
|
|
return "inner-output"
|
|
|
|
# Call
|
|
f_outer("outer-input")
|
|
opik.flush_tracker()
|
|
|
|
# Verify trace
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output={"output": "outer-output"},
|
|
metadata={"outer-metadata-key": "outer-metadata-value"},
|
|
tags=["outer-tag1", "outer-tag2"],
|
|
project_name=project_name or PROJECT_NAME,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify top level span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_outer-span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output={"output": "outer-output"},
|
|
metadata={"outer-metadata-key": "outer-metadata-value"},
|
|
tags=["outer-tag1", "outer-tag2"],
|
|
project_name=project_name or PROJECT_NAME,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify nested span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_inner-span-id"],
|
|
parent_span_id=ID_STORAGE["f_outer-span-id"],
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_inner",
|
|
input={"y": "inner-input"},
|
|
output={"output": "inner-output"},
|
|
metadata={"inner-metadata-key": "inner-metadata-value"},
|
|
tags=["inner-tag1", "inner-tag2"],
|
|
project_name=project_name or PROJECT_NAME,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_tracked_function__error_inside_inner_function__caught_in_top_level_span__inner_span_has_error_info(
|
|
opik_client,
|
|
):
|
|
# Setup
|
|
ID_STORAGE = {}
|
|
|
|
@opik.track
|
|
def f_inner(y):
|
|
ID_STORAGE["f_inner-span-id"] = opik_context.get_current_span_data().id
|
|
raise ValueError("inner span error message")
|
|
|
|
@opik.track
|
|
def f_outer(x):
|
|
ID_STORAGE["f_outer-trace-id"] = opik_context.get_current_trace_data().id
|
|
ID_STORAGE["f_outer-span-id"] = opik_context.get_current_span_data().id
|
|
|
|
try:
|
|
f_inner("inner-input")
|
|
except Exception:
|
|
pass
|
|
|
|
return "outer-output"
|
|
|
|
# Call
|
|
f_outer("outer-input")
|
|
opik.flush_tracker()
|
|
|
|
# Verify trace
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output={"output": "outer-output"},
|
|
error_info=None,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify top level span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_outer-span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output={"output": "outer-output"},
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify nested span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_inner-span-id"],
|
|
parent_span_id=ID_STORAGE["f_outer-span-id"],
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_inner",
|
|
input={"y": "inner-input"},
|
|
output=None,
|
|
error_info=ErrorInfoDict(
|
|
exception_type="ValueError",
|
|
message="inner span error message",
|
|
traceback=ANY_STRING,
|
|
),
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_tracked_function__error_inside_inner_function__error_not_caught__trace_and_its_spans_have_error_info(
|
|
opik_client,
|
|
):
|
|
# Setup
|
|
ID_STORAGE = {}
|
|
|
|
@opik.track
|
|
def f_inner(y):
|
|
ID_STORAGE["f_inner-span-id"] = opik_context.get_current_span_data().id
|
|
raise ValueError("inner span error message")
|
|
|
|
@opik.track
|
|
def f_outer(x):
|
|
ID_STORAGE["f_outer-trace-id"] = opik_context.get_current_trace_data().id
|
|
ID_STORAGE["f_outer-span-id"] = opik_context.get_current_span_data().id
|
|
|
|
f_inner("inner-input")
|
|
|
|
# Call
|
|
with pytest.raises(ValueError):
|
|
f_outer("outer-input")
|
|
|
|
opik.flush_tracker()
|
|
|
|
# Verify trace
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output=None,
|
|
error_info=ErrorInfoDict(
|
|
exception_type="ValueError",
|
|
message="inner span error message",
|
|
traceback=ANY_STRING,
|
|
),
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify top level span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_outer-span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output=None,
|
|
error_info=ErrorInfoDict(
|
|
exception_type="ValueError",
|
|
message="inner span error message",
|
|
traceback=ANY_STRING,
|
|
),
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify nested span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_inner-span-id"],
|
|
parent_span_id=ID_STORAGE["f_outer-span-id"],
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_inner",
|
|
input={"y": "inner-input"},
|
|
output=None,
|
|
error_info=ErrorInfoDict(
|
|
exception_type="ValueError",
|
|
message="inner span error message",
|
|
traceback=ANY_STRING,
|
|
),
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_tracked_function__two_traces_and_two_spans__happyflow(opik_client):
|
|
# Setup
|
|
project_name = _PROJECT_BATCHING
|
|
ID_STORAGE = {}
|
|
|
|
@opik.track(project_name=project_name)
|
|
def f1(x):
|
|
ID_STORAGE["f1-trace-id"] = opik_context.get_current_trace_data().id
|
|
ID_STORAGE["f1-span-id"] = opik_context.get_current_span_data().id
|
|
return "f1-output"
|
|
|
|
@opik.track(project_name=project_name)
|
|
def f2(y):
|
|
ID_STORAGE["f2-trace-id"] = opik_context.get_current_trace_data().id
|
|
ID_STORAGE["f2-span-id"] = opik_context.get_current_span_data().id
|
|
return "f2-output"
|
|
|
|
# Call
|
|
f1("f1-input")
|
|
f2("f2-input")
|
|
opik.flush_tracker()
|
|
|
|
# Verify traces
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f1-trace-id"],
|
|
name="f1",
|
|
input={"x": "f1-input"},
|
|
output={"output": "f1-output"},
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f2-trace-id"],
|
|
name="f2",
|
|
input={"y": "f2-input"},
|
|
output={"output": "f2-output"},
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify spans
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f1-span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f1-trace-id"],
|
|
name="f1",
|
|
input={"x": "f1-input"},
|
|
output={"output": "f1-output"},
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f2-span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f2-trace-id"],
|
|
name="f2",
|
|
input={"y": "f2-input"},
|
|
output={"output": "f2-output"},
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_tracked_function__try_different_project_names(opik_client):
|
|
"""
|
|
In this test we will try to use different project names for outer and inner spans.
|
|
For both spans and for trace only outer span project name will be used.
|
|
"""
|
|
# Setup
|
|
project_name = _PROJECT_DECORATOR_OUTER
|
|
project_name2 = _PROJECT_DECORATOR_INNER_IGNORED
|
|
|
|
ID_STORAGE = {}
|
|
|
|
@opik.track(
|
|
tags=["outer-tag1", "outer-tag2"],
|
|
metadata={"outer-metadata-key": "outer-metadata-value"},
|
|
project_name=project_name,
|
|
)
|
|
def f_outer(x):
|
|
ID_STORAGE["f_outer-trace-id"] = opik_context.get_current_trace_data().id
|
|
ID_STORAGE["f_outer-span-id"] = opik_context.get_current_span_data().id
|
|
|
|
f_inner("inner-input")
|
|
return "outer-output"
|
|
|
|
@opik.track(
|
|
tags=["inner-tag1", "inner-tag2"],
|
|
metadata={"inner-metadata-key": "inner-metadata-value"},
|
|
project_name=project_name2,
|
|
)
|
|
def f_inner(y):
|
|
ID_STORAGE["f_inner-span-id"] = opik_context.get_current_span_data().id
|
|
return "inner-output"
|
|
|
|
# Call
|
|
f_outer("outer-input")
|
|
opik.flush_tracker()
|
|
|
|
# Verify trace
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output={"output": "outer-output"},
|
|
metadata={"outer-metadata-key": "outer-metadata-value"},
|
|
tags=["outer-tag1", "outer-tag2"],
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify top level span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_outer-span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_outer",
|
|
input={"x": "outer-input"},
|
|
output={"output": "outer-output"},
|
|
metadata={"outer-metadata-key": "outer-metadata-value"},
|
|
tags=["outer-tag1", "outer-tag2"],
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify nested span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_inner-span-id"],
|
|
parent_span_id=ID_STORAGE["f_outer-span-id"],
|
|
trace_id=ID_STORAGE["f_outer-trace-id"],
|
|
name="f_inner",
|
|
input={"y": "inner-input"},
|
|
output={"output": "inner-output"},
|
|
metadata={"inner-metadata-key": "inner-metadata-value"},
|
|
tags=["inner-tag1", "inner-tag2"],
|
|
project_name=project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("override_project_name", [True, False])
|
|
def test_manually_created_trace_and_span__happyflow(
|
|
opik_client: opik.Opik, override_project_name
|
|
):
|
|
project_name = (
|
|
generate_project_name("e2e-tests-manual-project-name")
|
|
if override_project_name
|
|
else None
|
|
)
|
|
|
|
model_name = "some-llm"
|
|
provider_name = "some-llm-provider"
|
|
|
|
# Call
|
|
trace = opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": "trace-input"},
|
|
output={"output": "trace-output"},
|
|
tags=["trace-tag"],
|
|
metadata={"trace-metadata-key": "trace-metadata-value"},
|
|
project_name=project_name,
|
|
)
|
|
span = trace.span(
|
|
name="span-name",
|
|
input={"input": "span-input"},
|
|
output={"output": "span-output"},
|
|
tags=["span-tag"],
|
|
metadata={"span-metadata-key": "span-metadata-value"},
|
|
model=model_name,
|
|
provider=provider_name,
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Verify trace
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name="trace-name",
|
|
input={"input": "trace-input"},
|
|
output={"output": "trace-output"},
|
|
tags=["trace-tag"],
|
|
metadata={
|
|
"providers": [
|
|
provider_name
|
|
], # BE injects "providers" array as first field in metadata of trace
|
|
"trace-metadata-key": "trace-metadata-value",
|
|
},
|
|
project_name=project_name or PROJECT_NAME,
|
|
source="sdk",
|
|
)
|
|
|
|
# Verify span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=span.id,
|
|
parent_span_id=None,
|
|
trace_id=span.trace_id,
|
|
name="span-name",
|
|
input={"input": "span-input"},
|
|
output={"output": "span-output"},
|
|
tags=["span-tag"],
|
|
metadata={
|
|
"provider": provider_name, # BE injects "provider" string as first field in metadata of span
|
|
"span-metadata-key": "span-metadata-value",
|
|
},
|
|
project_name=project_name or PROJECT_NAME,
|
|
model=model_name,
|
|
provider=provider_name,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_search_traces__happyflow(opik_client):
|
|
# To define a unique search query, we will create a unique identifier that will be part of the trace input
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
|
|
filter_string = f'input contains "{unique_identifier}"'
|
|
|
|
# Send a trace that has this input
|
|
trace = opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
|
|
# Send traces that don't match
|
|
for input_value in range(2):
|
|
opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": "some-random-input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Search for the traces - Note that we use a large max_results to ensure that we get all traces, if the project has more than 100000 matching traces it is possible
|
|
traces = opik_client.search_traces(
|
|
project_name=PROJECT_NAME, filter_string=filter_string
|
|
)
|
|
|
|
# Verify that the matching trace is returned
|
|
assert len(traces) == 1, "Expected to find 1 matching trace"
|
|
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name="trace-name",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_search_traces__wait_for_at_least__happyflow(opik_client):
|
|
# check that synchronized searching for traces is working
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
|
|
# Send traces that have this input
|
|
trace_ids = []
|
|
matching_count = 1000
|
|
for i in range(matching_count):
|
|
trace = opik_client.trace(
|
|
name=f"trace-name-{i}",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
trace_ids.append(trace.id)
|
|
|
|
# send not matching traces
|
|
opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": "some-random-input-1"},
|
|
)
|
|
opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": "some-random-input-2"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Search for the traces with synchronization
|
|
filter_string = f'input contains "{unique_identifier}"'
|
|
traces = opik_client.search_traces(
|
|
project_name=PROJECT_NAME,
|
|
filter_string=filter_string,
|
|
wait_for_at_least=matching_count,
|
|
wait_for_timeout=10,
|
|
)
|
|
|
|
# Verify that the matching trace is returned
|
|
assert len(traces) == matching_count, (
|
|
f"Expected to find {matching_count} matching traces"
|
|
)
|
|
for trace in traces:
|
|
assert trace.id in trace_ids, (
|
|
f"Expected to find the matching trace id {trace.id}"
|
|
)
|
|
|
|
|
|
def test_search_traces__wait_for_at_least__timeout__exception_raised(opik_client):
|
|
# check that synchronized searching for traces is working
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
|
|
# Send traces that have this input
|
|
opik_client.trace(
|
|
name="trace-name-3",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
|
|
# send not matching traces
|
|
opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": "some-random-input-1"},
|
|
)
|
|
opik_client.trace(
|
|
name="trace-name",
|
|
input={"input": "some-random-input-2"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Search for the traces with synchronization
|
|
unmatchable_count = 1000
|
|
filter_string = f'input contains "{unique_identifier}"'
|
|
with pytest.raises(exceptions.SearchTimeoutError):
|
|
opik_client.search_traces(
|
|
project_name=PROJECT_NAME,
|
|
filter_string=filter_string,
|
|
wait_for_at_least=unmatchable_count,
|
|
wait_for_timeout=1,
|
|
)
|
|
|
|
|
|
def test_search_spans__happyflow(opik_client: opik.Opik):
|
|
# To define a unique search query, we will create a unique identifier that will be part of the trace input
|
|
trace_id = helpers.generate_id()
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
|
|
filter_string = f'input contains "{unique_identifier}"'
|
|
|
|
# Send a trace that matches the input filter
|
|
trace = opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
matching_span = trace.span(
|
|
name="span-name",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "span-output"},
|
|
)
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 1"},
|
|
output={"output": "span-output"},
|
|
)
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Search for the spans
|
|
spans = opik_client.search_spans(
|
|
project_name=PROJECT_NAME,
|
|
trace_id=trace_id,
|
|
filter_string=filter_string,
|
|
)
|
|
|
|
# Verify that the matching trace is returned
|
|
assert len(spans) == 1, "Expected to find 1 matching span"
|
|
assert spans[0].id == matching_span.id, "Expected to find the matching span"
|
|
|
|
|
|
def test_search_spans__wait_for_at_least__happy_flow(opik_client: opik.Opik):
|
|
# check that synchronized searching for spans is working
|
|
trace_id = helpers.generate_id()
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
|
|
# Send a trace that matches the input filter
|
|
trace = opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
matching_count = 1000
|
|
matching_span_ids = []
|
|
for i in range(matching_count):
|
|
matching_span = trace.span(
|
|
name=f"span-name-{i}",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "span-output"},
|
|
)
|
|
matching_span_ids.append(matching_span.id)
|
|
|
|
# adding two not matching spans
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 1"},
|
|
output={"output": "span-output"},
|
|
)
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
filter_string = f'input contains "{unique_identifier}"'
|
|
|
|
# Search for the spans with synchronization
|
|
spans = opik_client.search_spans(
|
|
project_name=PROJECT_NAME,
|
|
trace_id=trace_id,
|
|
filter_string=filter_string,
|
|
wait_for_at_least=matching_count,
|
|
wait_for_timeout=10,
|
|
)
|
|
|
|
# Verify that the matching trace is returned
|
|
assert len(spans) == matching_count, (
|
|
f"Expected to find {matching_count} matching spans"
|
|
)
|
|
for span in spans:
|
|
assert span.id in matching_span_ids, (
|
|
f"Expected to find the matching span id {span.id}"
|
|
)
|
|
|
|
|
|
def test_search_spans__wait_for_at_least__timeout__exception_raised(
|
|
opik_client: opik.Opik,
|
|
):
|
|
trace_id = helpers.generate_id()
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
|
|
# Send a trace that matches the input filter
|
|
trace = opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": f"Some random input - {unique_identifier}"},
|
|
output={"output": "span-output"},
|
|
)
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 1"},
|
|
output={"output": "span-output"},
|
|
)
|
|
trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Search for the spans
|
|
unmatchable_count = 1000
|
|
filter_string = f'input contains "{unique_identifier}"'
|
|
with pytest.raises(exceptions.SearchTimeoutError):
|
|
opik_client.search_spans(
|
|
project_name=PROJECT_NAME,
|
|
trace_id=trace_id,
|
|
filter_string=filter_string,
|
|
wait_for_at_least=unmatchable_count,
|
|
wait_for_timeout=1,
|
|
)
|
|
|
|
|
|
def test_copy_traces__happyflow(opik_client):
|
|
# Log traces
|
|
unique_identifier = str(uuid.uuid4())[-6:]
|
|
ID_STORAGE = {}
|
|
|
|
project_name = f"e2e-tests-copy-traces-project - {unique_identifier}"
|
|
for i in range(2):
|
|
trace = opik_client.trace(
|
|
name="trace",
|
|
project_name=project_name,
|
|
input={"input": f"test input - {i}"},
|
|
output={"output": f"test output - {i}"},
|
|
feedback_scores=[
|
|
{
|
|
"name": "score_trace",
|
|
"value": i,
|
|
"category_name": "category_",
|
|
"reason": "reason_",
|
|
}
|
|
],
|
|
metadata={"value": i},
|
|
tags=["a", "b"],
|
|
)
|
|
ID_STORAGE[f"trace-{i}-id"] = trace.id
|
|
|
|
trace.span(
|
|
name="span - 0",
|
|
input={"input": f"test input - {i} - 0"},
|
|
)
|
|
|
|
# Sleep so span timestamps are ordered due to uuid7
|
|
time.sleep(0.001)
|
|
|
|
trace.span(
|
|
name="span - 1",
|
|
input={"input": f"test input - {i} - 1"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
new_project_name = project_name + "_v2"
|
|
opik_client.copy_traces(
|
|
project_name=project_name,
|
|
destination_project_name=new_project_name,
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
traces = opik_client.search_traces(project_name=new_project_name)
|
|
for i, trace in enumerate(reversed(traces)):
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name="trace",
|
|
input={"input": f"test input - {i}"},
|
|
output={"output": f"test output - {i}"},
|
|
feedback_scores=[
|
|
FeedbackScoreDict(
|
|
id=trace.id,
|
|
name="score_trace",
|
|
value=i,
|
|
category_name="category_",
|
|
reason="reason_",
|
|
)
|
|
],
|
|
metadata={"value": i},
|
|
tags=["a", "b"],
|
|
project_name=new_project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
trace_spans = opik_client.search_spans(
|
|
project_name=new_project_name, trace_id=trace.id
|
|
)
|
|
for j, span in enumerate(reversed(trace_spans)):
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=span.id,
|
|
trace_id=trace.id,
|
|
name=f"span - {j}",
|
|
input={"input": f"test input - {i} - {j}"},
|
|
parent_span_id=span.parent_span_id,
|
|
project_name=new_project_name,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_tracked_function__update_current_span_and_trace_called__happyflow(opik_client):
|
|
# Setup
|
|
ID_STORAGE = {}
|
|
THREAD_ID = id_helpers.generate_id()
|
|
|
|
@opik.track
|
|
def f():
|
|
opik_context.update_current_span(
|
|
name="span-name",
|
|
input={"span-input": "span-input-value"},
|
|
output={"span-output": "span-output-value"},
|
|
metadata={"span-metadata-key": "span-metadata-value"},
|
|
total_cost=0.42,
|
|
)
|
|
opik_context.update_current_trace(
|
|
name="trace-name",
|
|
input={"trace-input": "trace-input-value"},
|
|
output={"trace-output": "trace-output-value"},
|
|
metadata={"trace-metadata-key": "trace-metadata-value"},
|
|
thread_id=THREAD_ID,
|
|
)
|
|
ID_STORAGE["f_span-id"] = opik_context.get_current_span_data().id
|
|
ID_STORAGE["f_trace-id"] = opik_context.get_current_trace_data().id
|
|
|
|
# Call
|
|
f()
|
|
opik.flush_tracker()
|
|
|
|
# Verify top level span
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=ID_STORAGE["f_span-id"],
|
|
parent_span_id=None,
|
|
trace_id=ID_STORAGE["f_trace-id"],
|
|
project_name=PROJECT_NAME,
|
|
name="span-name",
|
|
input={"span-input": "span-input-value"},
|
|
output={"span-output": "span-output-value"},
|
|
metadata={"span-metadata-key": "span-metadata-value"},
|
|
total_cost=0.42,
|
|
source="sdk",
|
|
)
|
|
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=ID_STORAGE["f_trace-id"],
|
|
name="trace-name",
|
|
input={"trace-input": "trace-input-value"},
|
|
output={"trace-output": "trace-output-value"},
|
|
metadata={"trace-metadata-key": "trace-metadata-value"},
|
|
thread_id=THREAD_ID,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_opik_trace__attachments(opik_client, attachment_data_file):
|
|
trace_id = helpers.generate_id()
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
names = [file_name + "_first", file_name + "_second"]
|
|
attachments = {
|
|
names[0]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[0],
|
|
content_type="application/octet-stream",
|
|
),
|
|
names[1]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[1],
|
|
content_type="application/octet-stream",
|
|
),
|
|
}
|
|
data_sizes = {
|
|
names[0]: ATTACHMENT_FILE_SIZE,
|
|
names[1]: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
# Send a trace that matches the input filter
|
|
opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
attachments=attachments.values(),
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="trace",
|
|
entity_id=trace_id,
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_tracked_function__update_current_trace__with_attachments(
|
|
opik_client, attachment_data_file
|
|
):
|
|
# Setup
|
|
ID_STORAGE = {}
|
|
THREAD_ID = id_helpers.generate_id()
|
|
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
attachments = {
|
|
file_name: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=file_name,
|
|
content_type="application/octet-stream",
|
|
)
|
|
}
|
|
data_sizes = {
|
|
file_name: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
@opik.track
|
|
def f():
|
|
opik_context.update_current_trace(
|
|
name="trace-name",
|
|
input={"trace-input": "trace-input-value"},
|
|
output={"trace-output": "trace-output-value"},
|
|
metadata={"trace-metadata-key": "trace-metadata-value"},
|
|
thread_id=THREAD_ID,
|
|
attachments=attachments.values(),
|
|
)
|
|
ID_STORAGE["f_trace-id"] = opik_context.get_current_trace_data().id
|
|
|
|
# Call
|
|
f()
|
|
opik.flush_tracker()
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="trace",
|
|
entity_id=ID_STORAGE["f_trace-id"],
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_opik_client_span__attachments(opik_client, attachment_data_file):
|
|
trace_id = helpers.generate_id()
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
names = [file_name + "_first", file_name + "_second"]
|
|
attachments = {
|
|
names[0]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[0],
|
|
content_type="application/octet-stream",
|
|
),
|
|
names[1]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[1],
|
|
content_type="application/octet-stream",
|
|
),
|
|
}
|
|
data_sizes = {
|
|
names[0]: ATTACHMENT_FILE_SIZE,
|
|
names[1]: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
# Send a trace that matches the input filter
|
|
opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
span = opik_client.span(
|
|
trace_id=trace_id,
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
attachments=attachments.values(),
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="span",
|
|
entity_id=span.id,
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_opik_client_span__attachment_with_file_like_data(
|
|
opik_client, attachment_data_file
|
|
):
|
|
"""
|
|
Test that a span can be created with an attachment that has file-like data.
|
|
"""
|
|
trace_id = helpers.generate_id()
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
names = [file_name + "_first", file_name + "_without_mime_type"]
|
|
# read file bytes into memory
|
|
attachment_data_file.seek(0)
|
|
attachment_data = attachment_data_file.read()
|
|
|
|
attachments = {
|
|
names[0]: Attachment(
|
|
data=attachment_data,
|
|
file_name=names[0],
|
|
content_type="application/octet-stream",
|
|
),
|
|
names[1]: Attachment(
|
|
data=attachment_data,
|
|
file_name=names[1],
|
|
),
|
|
}
|
|
data_sizes = {
|
|
names[0]: ATTACHMENT_FILE_SIZE,
|
|
names[1]: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
# Send a trace that matches the input filter
|
|
opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
span = opik_client.span(
|
|
trace_id=trace_id,
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
attachments=attachments.values(),
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
expected_attachments = {
|
|
names[0]: attachments[names[0]],
|
|
names[1]: Attachment(
|
|
data=attachment_data,
|
|
file_name=names[1],
|
|
content_type="application/octet-stream", # should be inferred from data
|
|
),
|
|
}
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="span",
|
|
entity_id=span.id,
|
|
attachments=expected_attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_span_span__attachments(opik_client, attachment_data_file):
|
|
trace_id = helpers.generate_id()
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
names = [file_name + "_first", file_name + "_second"]
|
|
attachments = {
|
|
names[0]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[0],
|
|
content_type="application/octet-stream",
|
|
),
|
|
names[1]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[1],
|
|
content_type="application/octet-stream",
|
|
),
|
|
}
|
|
data_sizes = {
|
|
names[0]: ATTACHMENT_FILE_SIZE,
|
|
names[1]: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
# Send a trace that matches the input filter
|
|
opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
span = opik_client.span(
|
|
trace_id=trace_id,
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
)
|
|
last_span = span.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
attachments=attachments.values(),
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="span",
|
|
entity_id=last_span.id,
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_trace_span__attachments(opik_client, attachment_data_file):
|
|
trace_id = helpers.generate_id()
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
names = [file_name + "_first", file_name + "_second"]
|
|
attachments = {
|
|
names[0]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[0],
|
|
content_type="application/octet-stream",
|
|
),
|
|
names[1]: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=names[1],
|
|
content_type="application/octet-stream",
|
|
),
|
|
}
|
|
data_sizes = {
|
|
names[0]: ATTACHMENT_FILE_SIZE,
|
|
names[1]: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
# Send a trace that matches the input filter
|
|
trace = opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
span = trace.span(
|
|
name="span-name",
|
|
input={"input": "Some random input 2"},
|
|
output={"output": "span-output"},
|
|
attachments=attachments.values(),
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="span",
|
|
entity_id=span.id,
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_tracked_function__update_current_span__with_attachments(
|
|
opik_client, attachment_data_file
|
|
):
|
|
# Setup
|
|
ID_STORAGE = {}
|
|
THREAD_ID = id_helpers.generate_id()
|
|
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
attachments = {
|
|
file_name: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=file_name,
|
|
content_type="application/octet-stream",
|
|
)
|
|
}
|
|
data_sizes = {
|
|
file_name: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
@opik.track
|
|
def f():
|
|
opik_context.update_current_span(
|
|
name="span-name",
|
|
input={"span-input": "span-input-value"},
|
|
output={"span-output": "span-output-value"},
|
|
metadata={"span-metadata-key": "span-metadata-value"},
|
|
total_cost=0.42,
|
|
attachments=attachments.values(),
|
|
)
|
|
opik_context.update_current_trace(
|
|
name="trace-name",
|
|
input={"trace-input": "trace-input-value"},
|
|
output={"trace-output": "trace-output-value"},
|
|
metadata={"trace-metadata-key": "trace-metadata-value"},
|
|
thread_id=THREAD_ID,
|
|
)
|
|
ID_STORAGE["f_span-id"] = opik_context.get_current_span_data().id
|
|
|
|
# Call
|
|
f()
|
|
opik.flush_tracker()
|
|
|
|
# check that the attachment was uploaded
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="span",
|
|
entity_id=ID_STORAGE["f_span-id"],
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
)
|
|
|
|
|
|
def test_opik_client__update_span_with_attachments__original_fields_preserved_but_some_are_patched(
|
|
opik_client: opik.Opik, attachment_data_file
|
|
):
|
|
root_span_client = opik_client.span(
|
|
name="root-span-name",
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
child_span_client = root_span_client.span(
|
|
name="child-span-name",
|
|
input={"input": "original-span-input"},
|
|
output={"output": "original-span-output"},
|
|
)
|
|
opik_client.flush()
|
|
|
|
file_name = os.path.basename(attachment_data_file.name)
|
|
attachments = {
|
|
file_name: Attachment(
|
|
data=attachment_data_file.name,
|
|
file_name=file_name,
|
|
content_type="application/octet-stream",
|
|
)
|
|
}
|
|
data_sizes = {
|
|
file_name: ATTACHMENT_FILE_SIZE,
|
|
}
|
|
|
|
opik_client.update_span(
|
|
id=child_span_client.id,
|
|
trace_id=child_span_client.trace_id,
|
|
parent_span_id=child_span_client.parent_span_id,
|
|
project_name=PROJECT_NAME,
|
|
input={"input": "new-span-input"},
|
|
attachments=attachments.values(),
|
|
)
|
|
opik_client.flush()
|
|
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=child_span_client.id,
|
|
parent_span_id=root_span_client.id,
|
|
trace_id=child_span_client.trace_id,
|
|
input={"input": "new-span-input"},
|
|
output={"output": "original-span-output"},
|
|
name="child-span-name",
|
|
source="sdk",
|
|
)
|
|
verifiers.verify_attachments(
|
|
opik_client=opik_client,
|
|
entity_type="span",
|
|
entity_id=child_span_client.id,
|
|
attachments=attachments,
|
|
data_sizes=data_sizes,
|
|
timeout=30,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"new_input,new_output,new_tags,new_metadata,new_thread_id",
|
|
[
|
|
({"input": "new-trace-input-value"}, None, None, None, None),
|
|
(None, {"output": "new-trace-output-value"}, None, None, None),
|
|
(None, None, ["new-trace-tag"], None, None),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
{"new-trace-metadata-key": "new-trace-metadata-value"},
|
|
None,
|
|
),
|
|
# Stable string instead of id_helpers.generate_id() so xdist workers
|
|
# collect identical parametrize ids; the trace itself is created with
|
|
# a fresh id at test time, so this only needs to be a valid UUID-shaped
|
|
# value for the update path.
|
|
(None, None, None, None, "0190a1b2-c3d4-7fe5-8a6b-c7d8e9f0a1b2"),
|
|
],
|
|
)
|
|
def test_opik_client__update_trace__happy_flow(
|
|
new_input, new_output, new_tags, new_metadata, new_thread_id, opik_client: opik.Opik
|
|
):
|
|
# test that the trace update works by updating only one field at a time
|
|
project_name = _PROJECT_UPDATE_TRACE
|
|
trace_name = "trace_name"
|
|
input = {"input": "trace-input-value"}
|
|
output = {"output": "trace-output-value"}
|
|
tags = ["trace-tag"]
|
|
metadata = {"trace-metadata-key": "trace-metadata-value"}
|
|
thread_id = id_helpers.generate_id()
|
|
trace = opik_client.trace(
|
|
name=trace_name,
|
|
input=input,
|
|
output=output,
|
|
tags=tags,
|
|
metadata=metadata,
|
|
project_name=project_name,
|
|
thread_id=thread_id,
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# verify that the trace was saved
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name=trace_name,
|
|
project_name=project_name,
|
|
input=input,
|
|
output=output,
|
|
metadata=metadata,
|
|
tags=tags,
|
|
thread_id=thread_id,
|
|
source="sdk",
|
|
)
|
|
|
|
#
|
|
# Do partial update
|
|
#
|
|
opik_client.update_trace(
|
|
trace_id=trace.id,
|
|
project_name=project_name,
|
|
input=new_input,
|
|
output=new_output,
|
|
tags=new_tags,
|
|
metadata=new_metadata,
|
|
thread_id=new_thread_id,
|
|
)
|
|
|
|
# flush to make sure the update was logged to server
|
|
opik_client.flush()
|
|
|
|
input = new_input or input
|
|
output = new_output or output
|
|
tags = new_tags or tags
|
|
metadata = new_metadata or metadata
|
|
thread_id = new_thread_id or thread_id
|
|
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name=trace_name,
|
|
project_name=project_name,
|
|
input=input,
|
|
output=output,
|
|
tags=tags,
|
|
metadata=metadata,
|
|
thread_id=thread_id,
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_search_traces__filter_by_feedback_score__is_empty_and_equals(
|
|
opik_client: opik.Opik,
|
|
):
|
|
# Create a unique metric name to avoid conflicts with other tests
|
|
unique_metric = f"test_metric_{str(uuid.uuid4()).replace('-', '_')[-8:]}"
|
|
|
|
# Create trace with the feedback score
|
|
trace_with_score = opik_client.trace(
|
|
name="trace-with-score",
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
trace_with_score.log_feedback_score(
|
|
unique_metric, value=0.75, category_name="test-category", reason="test-reason"
|
|
)
|
|
|
|
# Create trace without the feedback score
|
|
trace_without_score = opik_client.trace(
|
|
name="trace-without-score",
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Test filtering with is_empty - should find trace without the score
|
|
traces_empty = opik_client.search_traces(
|
|
project_name=PROJECT_NAME,
|
|
filter_string=f"feedback_scores.{unique_metric} is_empty",
|
|
)
|
|
trace_ids_empty = {trace.id for trace in traces_empty}
|
|
assert trace_without_score.id in trace_ids_empty, (
|
|
"Trace without score should be found with is_empty filter"
|
|
)
|
|
assert trace_with_score.id not in trace_ids_empty, (
|
|
"Trace with score should not be found with is_empty filter"
|
|
)
|
|
|
|
# Test filtering with is_not_empty - should find trace with the score
|
|
traces_not_empty = opik_client.search_traces(
|
|
project_name=PROJECT_NAME,
|
|
filter_string=f"feedback_scores.{unique_metric} is_not_empty",
|
|
)
|
|
trace_ids_not_empty = {trace.id for trace in traces_not_empty}
|
|
assert trace_with_score.id in trace_ids_not_empty, (
|
|
"Trace with score should be found with is_not_empty filter"
|
|
)
|
|
assert trace_without_score.id not in trace_ids_not_empty, (
|
|
"Trace without score should not be found with is_not_empty filter"
|
|
)
|
|
|
|
# Test filtering with = operator - should find trace with the specific score value
|
|
traces_with_value = opik_client.search_traces(
|
|
project_name=PROJECT_NAME,
|
|
filter_string=f"feedback_scores.{unique_metric} = 0.75",
|
|
)
|
|
trace_ids_with_value = {trace.id for trace in traces_with_value}
|
|
assert trace_with_score.id in trace_ids_with_value, (
|
|
"Trace with score value 0.75 should be found"
|
|
)
|
|
assert trace_without_score.id not in trace_ids_with_value, (
|
|
"Trace without score should not be found"
|
|
)
|
|
|
|
# Verify is_not_empty and = return the same trace
|
|
assert trace_ids_not_empty == trace_ids_with_value, (
|
|
"is_not_empty and = filters should return the same traces for this test case"
|
|
)
|
|
|
|
|
|
def test_search_spans__filter_by_feedback_score__is_empty_and_equals(
|
|
opik_client: opik.Opik,
|
|
):
|
|
# Create a unique metric name to avoid conflicts with other tests
|
|
unique_metric = f"test_metric_{str(uuid.uuid4()).replace('-', '_')[-8:]}"
|
|
trace_id = helpers.generate_id()
|
|
|
|
# Create a trace with two spans
|
|
trace = opik_client.trace(
|
|
id=trace_id,
|
|
name="trace-name",
|
|
input={"input": "Some random input"},
|
|
output={"output": "trace-output"},
|
|
project_name=PROJECT_NAME,
|
|
)
|
|
|
|
# Create span with the feedback score
|
|
span_with_score = trace.span(
|
|
name="span-with-score",
|
|
input={"input": "span-input-1"},
|
|
output={"output": "span-output-1"},
|
|
)
|
|
span_with_score.log_feedback_score(
|
|
unique_metric, value=0.85, category_name="test-category", reason="test-reason"
|
|
)
|
|
|
|
# Create span without the feedback score
|
|
span_without_score = trace.span(
|
|
name="span-without-score",
|
|
input={"input": "span-input-2"},
|
|
output={"output": "span-output-2"},
|
|
)
|
|
|
|
opik_client.flush()
|
|
|
|
# Test filtering with is_empty - should find span without the score
|
|
spans_empty = opik_client.search_spans(
|
|
project_name=PROJECT_NAME,
|
|
trace_id=trace_id,
|
|
filter_string=f"feedback_scores.{unique_metric} is_empty",
|
|
)
|
|
span_ids_empty = {span.id for span in spans_empty}
|
|
assert span_without_score.id in span_ids_empty, (
|
|
"Span without score should be found with is_empty filter"
|
|
)
|
|
assert span_with_score.id not in span_ids_empty, (
|
|
"Span with score should not be found with is_empty filter"
|
|
)
|
|
|
|
# Test filtering with is_not_empty - should find span with the score
|
|
spans_not_empty = opik_client.search_spans(
|
|
project_name=PROJECT_NAME,
|
|
trace_id=trace_id,
|
|
filter_string=f"feedback_scores.{unique_metric} is_not_empty",
|
|
)
|
|
span_ids_not_empty = {span.id for span in spans_not_empty}
|
|
assert span_with_score.id in span_ids_not_empty, (
|
|
"Span with score should be found with is_not_empty filter"
|
|
)
|
|
assert span_without_score.id not in span_ids_not_empty, (
|
|
"Span without score should not be found with is_not_empty filter"
|
|
)
|
|
|
|
# Test filtering with = operator - should find span with the specific score value
|
|
spans_with_value = opik_client.search_spans(
|
|
project_name=PROJECT_NAME,
|
|
trace_id=trace_id,
|
|
filter_string=f"feedback_scores.{unique_metric} = 0.85",
|
|
)
|
|
span_ids_with_value = {span.id for span in spans_with_value}
|
|
assert span_with_score.id in span_ids_with_value, (
|
|
"Span with score value 0.85 should be found"
|
|
)
|
|
assert span_without_score.id not in span_ids_with_value, (
|
|
"Span without score should not be found"
|
|
)
|
|
|
|
# Verify is_not_empty and = return the same span
|
|
assert span_ids_not_empty == span_ids_with_value, (
|
|
"is_not_empty and = filters should return the same spans for this test case"
|
|
)
|
|
|
|
|
|
def test_internal_api_trace__source_is_set(opik_client):
|
|
"""__internal_api__trace__ persists the given source to the backend."""
|
|
trace = opik_client.__internal_api__trace__(
|
|
name="source-test-trace",
|
|
input={"x": 1},
|
|
source="optimization",
|
|
)
|
|
opik.flush_tracker()
|
|
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name="source-test-trace",
|
|
source="optimization",
|
|
)
|
|
|
|
|
|
def test_internal_api_trace__default_source_is_sdk(opik_client):
|
|
"""__internal_api__trace__ uses 'sdk' as the default source when none is given."""
|
|
trace = opik_client.__internal_api__trace__(
|
|
name="default-source-trace",
|
|
input={"x": 1},
|
|
)
|
|
opik.flush_tracker()
|
|
|
|
verifiers.verify_trace(
|
|
opik_client=opik_client,
|
|
trace_id=trace.id,
|
|
name="default-source-trace",
|
|
source="sdk",
|
|
)
|
|
|
|
|
|
def test_internal_api_span__source_is_set(opik_client):
|
|
"""__internal_api__span__ persists the given source to the backend."""
|
|
trace = opik_client.__internal_api__trace__(
|
|
name="span-source-test-trace",
|
|
)
|
|
span = opik_client.__internal_api__span__(
|
|
trace_id=trace.id,
|
|
name="source-test-span",
|
|
input={"y": 2},
|
|
source="optimization",
|
|
)
|
|
opik.flush_tracker()
|
|
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=span.id,
|
|
trace_id=trace.id,
|
|
parent_span_id=None,
|
|
name="source-test-span",
|
|
source="optimization",
|
|
)
|
|
|
|
|
|
def test_internal_api_span__default_source_is_sdk(opik_client):
|
|
"""__internal_api__span__ uses 'sdk' as the default source when none is given."""
|
|
trace = opik_client.__internal_api__trace__(
|
|
name="span-default-source-trace",
|
|
)
|
|
span = opik_client.__internal_api__span__(
|
|
trace_id=trace.id,
|
|
name="default-source-span",
|
|
input={"y": 2},
|
|
)
|
|
opik.flush_tracker()
|
|
|
|
verifiers.verify_span(
|
|
opik_client=opik_client,
|
|
span_id=span.id,
|
|
trace_id=trace.id,
|
|
parent_span_id=None,
|
|
name="default-source-span",
|
|
source="sdk",
|
|
)
|