Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

1082 lines
34 KiB
Python

"""E2E tests for TestSuite API.
These tests verify the core test suite functionality:
1. Item-level assertions stored as dataset item fields
2. Suite-level assertions applied to all items
3. Execution policy handling (runs_per_item, pass_threshold)
4. Pass/fail determination based on assertion results
5. Persistence: create, get, update, delete operations
Key concepts:
- Assertions are checked by an LLM (internally using LLMJudge)
- Suite-level assertions and execution_policy are stored at dataset version level
- Item-level assertions and execution_policy are stored as dataset item fields
- Items without assertions pass by default (no assertions to fail)
- Pass/fail is determined by: runs_passed >= pass_threshold
"""
from typing import Dict, Any
import pytest
import opik
from .. import verifiers
from ...testlib import ThreadSafeCounter, environment, generate_project_name
PROJECT_NAME = generate_project_name("e2e", __name__)
# =============================================================================
# MAIN FLOW: Item-level assertions
# =============================================================================
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__multiple_assertions_per_item__all_scores_created(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Test that multiple assertions on a single item create multiple
feedback scores, each evaluated independently, with boolean values.
"""
assertion_1 = "The response is factually correct"
assertion_2 = "The response is concise and clear"
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test multiple assertions per item",
project_name=PROJECT_NAME,
)
suite.insert(
[
{
"data": {"input": {"question": "What is the capital of France?"}},
"assertions": [assertion_1, assertion_2],
}
]
)
def task(item: Dict[str, Any]) -> Dict[str, Any]:
return {"input": item["input"], "output": "Paris is the capital of France."}
# opik.run_tests must handle flushing
suite_result = opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=1,
items_passed=1,
experiment_items_count=1,
total_feedback_scores=2, # 2 assertions on 1 experiment item
expected_score_names={assertion_1, assertion_2},
project_name=PROJECT_NAME,
)
retrieved_experiment = opik_client.get_experiment_by_name(
experiment_name, project_name=PROJECT_NAME
)
for exp_item in retrieved_experiment.get_items():
for score in exp_item.feedback_scores:
assert score["value"] in [0.0, 1.0, True, False], (
f"Score value should be boolean, got {score['value']}"
)
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__combined_suite_and_item_level_assertions__all_scores_created(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Test that suite-level and item-level assertions are combined:
total feedback scores = suite-level assertions + item-level assertions.
"""
suite_assertion = "The response is helpful and informative"
item_assertion = "The response correctly identifies Paris as the capital"
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test combined suite and item level assertions",
global_assertions=[suite_assertion],
project_name=PROJECT_NAME,
)
suite.insert(
[
{
"data": {"input": {"question": "What is the capital of France?"}},
"assertions": [item_assertion],
}
]
)
def task(item: Dict[str, Any]) -> Dict[str, Any]:
return {"input": item["input"], "output": "The capital of France is Paris."}
# opik.run_tests must handle flushing
suite_result = opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=1,
experiment_items_count=1,
total_feedback_scores=2, # 1 suite + 1 item assertion
expected_score_names={suite_assertion, item_assertion},
project_name=PROJECT_NAME,
)
# =============================================================================
# PASS/FAIL DETERMINATION
# =============================================================================
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__assertion_fails__item_fails(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Test that items fail when assertions fail.
"""
failing_assertion = "The response correctly states that 2 + 2 equals 5"
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test assertion failure",
project_name=PROJECT_NAME,
)
suite.insert(
[
{
"data": {"input": {"question": "What is 2 + 2?"}},
"assertions": [failing_assertion],
}
]
)
def task(item: Dict[str, Any]) -> Dict[str, Any]:
return {"input": item["input"], "output": "2 + 2 equals 4."}
suite_result = opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=1,
items_passed=0,
experiment_items_count=1,
total_feedback_scores=1,
expected_score_names={failing_assertion},
project_name=PROJECT_NAME,
)
# Additionally verify the assertion result indicates failure
retrieved_experiment = opik_client.get_experiment_by_name(
experiment_name, project_name=PROJECT_NAME
)
items = retrieved_experiment.get_items()
assert len(items) > 0, "Expected at least 1 experiment item"
assert len(items[0].assertion_results) > 0, "Expected at least 1 assertion result"
assertion = items[0].assertion_results[0]
assert assertion["passed"] is False, (
f"Expected failing assertion (passed=False), got {assertion['passed']}"
)
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__pass_threshold_not_met__item_fails(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Test that items fail when pass_threshold is not met across multiple runs.
With runs_per_item=2, pass_threshold=2: only the first run returns a
correct answer, so only 1 run passes (< threshold of 2).
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test pass threshold failure",
global_assertions=["The response gives the numeric answer 4"],
global_execution_policy={"runs_per_item": 2, "pass_threshold": 2},
project_name=PROJECT_NAME,
)
suite.insert([{"data": {"input": {"question": "What is 2 + 2?"}}}])
call_count = ThreadSafeCounter()
def task(item: Dict[str, Any]) -> Dict[str, Any]:
n = call_count.increment()
# Only first run returns correct answer
if n == 1:
return {"input": item["input"], "output": "2 + 2 equals 4."}
return {"input": item["input"], "output": "I don't know."}
suite_result = opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=1,
items_passed=0,
project_name=PROJECT_NAME,
)
item_result = list(suite_result.item_results.values())[0]
assert item_result.passed is False
assert item_result.runs_total == 2
assert item_result.pass_threshold == 2
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__multiple_assertions_multiple_runs__pass_threshold_logic(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Comprehensive pass/fail logic test:
- 1 item, 3 assertions, runs_per_item=2, pass_threshold=1
- Consistent correct answers -> all runs pass -> item passes
Pass/fail logic:
1. A RUN passes if ALL assertions in that run pass
2. An ITEM passes if runs_passed >= pass_threshold
3. The SUITE passes if all items pass
"""
assertion_1 = "The response mentions Paris"
assertion_2 = "The response mentions France"
assertion_3 = "The response is factually correct"
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test multiple assertions with multiple runs",
global_assertions=[assertion_1, assertion_2, assertion_3],
global_execution_policy={"runs_per_item": 2, "pass_threshold": 1},
project_name=PROJECT_NAME,
)
suite.insert([{"data": {"input": {"question": "What is the capital of France?"}}}])
def task(item: Dict[str, Any]) -> Dict[str, Any]:
return {"input": item["input"], "output": "The capital of France is Paris."}
# opik.run_tests must handle flushing
suite_result = opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
assert suite_result.pass_rate == 1.0
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=1,
items_passed=1,
experiment_items_count=2, # 1 item * 2 runs
total_feedback_scores=6, # 3 assertions * 2 runs
expected_score_names={assertion_1, assertion_2, assertion_3},
project_name=PROJECT_NAME,
)
item_result = list(suite_result.item_results.values())[0]
assert item_result.runs_total == 2
assert item_result.pass_threshold == 1
assert item_result.runs_passed >= 1
assert item_result.passed is True
# Verify each experiment item has exactly 3 assertion results (one per assertion)
retrieved_experiment = opik_client.get_experiment_by_name(
experiment_name, project_name=PROJECT_NAME
)
assert retrieved_experiment.project_name == PROJECT_NAME
for exp_item in retrieved_experiment.get_items():
assert exp_item.assertion_results is not None
assert len(exp_item.assertion_results) == 3, (
f"Expected 3 assertion results per run, got {len(exp_item.assertion_results)}"
)
assertion_names = {ar["value"] for ar in exp_item.assertion_results}
assert assertion_names == {assertion_1, assertion_2, assertion_3}, (
f"Expected all 3 assertion names on each run, got {assertion_names}"
)
# =============================================================================
# PERSISTENCE: Create, get, update, delete
# =============================================================================
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__create_get_and_run__end_to_end(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
End-to-end test: create a suite, retrieve it via get_test_suite(),
then run it. Verifies that suite-level config survives the round-trip.
"""
suite_assertion = "The response correctly identifies Paris as the capital of France"
item_assertion = "Response is correct"
# 1. Create suite with assertions + execution_policy
suite = opik_client.create_test_suite(
name=dataset_name,
description="Persistence test suite",
global_assertions=[suite_assertion],
global_execution_policy={"runs_per_item": 2, "pass_threshold": 1},
project_name=PROJECT_NAME,
)
suite.insert(
[
{
"data": {"input": {"question": "What is the capital of France?"}},
"assertions": [item_assertion],
"description": "Geography: France capital",
},
{
"data": {"input": {"question": "What is the capital of Germany?"}},
"description": "Geography: Germany capital",
},
]
)
# 2. Retrieve from backend (simulates a fresh client loading existing suite)
retrieved_suite = opik_client.get_test_suite(
name=dataset_name, project_name=PROJECT_NAME
)
# Verify item descriptions survived the round-trip
retrieved_items = retrieved_suite.get_items()
retrieved_descriptions = {i["description"] for i in retrieved_items}
assert "Geography: France capital" in retrieved_descriptions
assert "Geography: Germany capital" in retrieved_descriptions
# Verify item-level assertions survived the round-trip
items_with_assertions = [i for i in retrieved_items if len(i["assertions"]) > 0]
assert len(items_with_assertions) == 1
assert items_with_assertions[0]["assertions"] == [item_assertion]
# 3. Run the retrieved suite — assertions/execution_policy come from BE
def task(item: Dict[str, Any]) -> Dict[str, Any]:
question = item["input"]["question"]
if "France" in question:
return {"input": item["input"], "output": "The capital of France is Paris."}
return {"input": item["input"], "output": "The capital of Germany is Berlin."}
# opik.run_tests must handle flushing
suite_result = opik.run_tests(
test_suite=retrieved_suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
# Verify suite ran with persisted execution policy (runs_per_item=2)
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=2,
experiment_items_count=4, # 2 items * 2 runs
total_feedback_scores=6, # France: 2 runs * 2 assertions + Germany: 2 runs * 1 assertion
expected_score_names={suite_assertion, item_assertion},
project_name=PROJECT_NAME,
)
for item_result in suite_result.item_results.values():
assert item_result.runs_total == 2
assert item_result.pass_threshold == 1
def test_test_suite__delete__items_removed(opik_client: opik.Opik, dataset_name: str):
"""
Test that delete() removes items from the suite.
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test delete items",
)
suite.insert(
[
{
"data": {"input": {"question": "Question 1"}},
"description": "First question",
},
{
"data": {"input": {"question": "Question 2"}},
"description": "Second question",
},
{"data": {"input": {"question": "Question 3"}}},
]
)
items = suite.get_items()
assert len(items) == 3
# Verify descriptions are present before deletion
descriptions = {i.get("description") for i in items}
assert "First question" in descriptions
assert "Second question" in descriptions
assert None in descriptions
item_id_to_delete = items[0]["id"]
suite.delete([item_id_to_delete])
remaining_items = suite.get_items()
assert len(remaining_items) == 2
def test_test_suite__get_global_assertions__returns_assertion_strings(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_assertions() returns assertion strings from suite-level config.
"""
opik_client.create_test_suite(
name=dataset_name,
description="Test get_assertions",
global_assertions=[
"Response is helpful",
"Response is accurate",
"Response is concise",
],
)
# Retrieve from BE to verify persistence
retrieved_suite = opik_client.get_test_suite(name=dataset_name)
assertions = retrieved_suite.get_global_assertions()
assert set(assertions) == {
"Response is helpful",
"Response is accurate",
"Response is concise",
}
def test_test_suite__get_global_execution_policy__returns_persisted_policy(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_execution_policy() returns the persisted execution policy.
"""
opik_client.create_test_suite(
name=dataset_name,
description="Test get_execution_policy",
global_execution_policy={"runs_per_item": 5, "pass_threshold": 3},
project_name=PROJECT_NAME,
)
# Retrieve from BE to verify persistence
retrieved_suite = opik_client.get_test_suite(
name=dataset_name, project_name=PROJECT_NAME
)
assert retrieved_suite.project_name == PROJECT_NAME
policy = retrieved_suite.get_global_execution_policy()
assert policy["runs_per_item"] == 5
assert policy["pass_threshold"] == 3
def test_test_suite__get_global_execution_policy__default_when_not_set(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_execution_policy() returns default policy when none was set.
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test default execution policy",
)
policy = suite.get_global_execution_policy()
assert policy["runs_per_item"] == 1
assert policy["pass_threshold"] == 1
def test_test_suite__update_test_settings__changes_assertions_and_policy(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that update() changes suite-level assertions and execution policy.
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test update",
global_assertions=["Response is helpful"],
global_execution_policy={"runs_per_item": 1, "pass_threshold": 1},
)
# Verify initial state
assertions = suite.get_global_assertions()
assert set(assertions) == {"Response is helpful"}
policy = suite.get_global_execution_policy()
assert policy["runs_per_item"] == 1
# Update with new assertions and policy
suite.update_test_settings(
global_assertions=["Response is accurate", "Response is concise"],
global_execution_policy={"runs_per_item": 3, "pass_threshold": 2},
)
# Retrieve from BE to verify persistence
retrieved_suite = opik_client.get_test_suite(name=dataset_name)
updated_assertions = retrieved_suite.get_global_assertions()
assert set(updated_assertions) == {
"Response is accurate",
"Response is concise",
}
updated_policy = retrieved_suite.get_global_execution_policy()
assert updated_policy["runs_per_item"] == 3
assert updated_policy["pass_threshold"] == 2
def test_get_or_create_test_suite__existing__returns_existing(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_or_create_test_suite returns an existing suite
without creating a new one.
"""
opik_client.create_test_suite(
name=dataset_name,
description="Original suite",
)
suite = opik_client.get_or_create_test_suite(
name=dataset_name,
description="Should be ignored",
)
assert suite.name == dataset_name
def test_get_or_create_test_suite__new__creates_suite(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_or_create_test_suite creates a new suite when
none exists with the given name.
"""
suite = opik_client.get_or_create_test_suite(
name=dataset_name,
description="New suite via get_or_create",
)
assert suite.name == dataset_name
retrieved = opik_client.get_test_suite(name=dataset_name)
assert retrieved.name == dataset_name
def test_get_or_create_test_suite__existing_with_different_assertions__does_not_modify(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_or_create_test_suite does not modify an existing suite's
assertions even when different values are passed.
"""
opik_client.create_test_suite(
name=dataset_name,
description="Original suite",
global_assertions=["Response is helpful"],
)
opik_client.get_or_create_test_suite(
name=dataset_name,
global_assertions=["Response is accurate", "Response is concise"],
)
retrieved = opik_client.get_test_suite(name=dataset_name)
assertions = retrieved.get_global_assertions()
assert set(assertions) == {"Response is helpful"}
def test_get_or_create_test_suite__existing_with_different_policy__does_not_modify(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_or_create_test_suite does not modify an existing suite's
execution policy even when different values are passed.
"""
opik_client.create_test_suite(
name=dataset_name,
description="Original suite",
global_assertions=["Response is helpful"],
global_execution_policy={"runs_per_item": 1, "pass_threshold": 1},
)
opik_client.get_or_create_test_suite(
name=dataset_name,
global_execution_policy={"runs_per_item": 5, "pass_threshold": 3},
)
retrieved = opik_client.get_test_suite(name=dataset_name)
policy = retrieved.get_global_execution_policy()
assert policy["runs_per_item"] == 1
assert policy["pass_threshold"] == 1
assertions = retrieved.get_global_assertions()
assert set(assertions) == {"Response is helpful"}
def test_test_suite__update_test_settings_assertions_only__keeps_existing_policy(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that update() with only assertions keeps the existing execution policy.
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test partial update",
global_assertions=["Response is helpful"],
global_execution_policy={"runs_per_item": 3, "pass_threshold": 2},
)
suite.update_test_settings(global_assertions=["Response is accurate"])
retrieved = opik_client.get_test_suite(name=dataset_name)
assertions = retrieved.get_global_assertions()
assert set(assertions) == {"Response is accurate"}
policy = retrieved.get_global_execution_policy()
assert policy["runs_per_item"] == 3
assert policy["pass_threshold"] == 2
def test_test_suite__update_test_settings_policy_only__keeps_existing_assertions(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that update() with only execution_policy keeps existing assertions.
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test partial update",
global_assertions=["Response is helpful", "Response is accurate"],
global_execution_policy={"runs_per_item": 1, "pass_threshold": 1},
)
suite.update_test_settings(
global_execution_policy={"runs_per_item": 5, "pass_threshold": 3}
)
retrieved = opik_client.get_test_suite(name=dataset_name)
policy = retrieved.get_global_execution_policy()
assert policy["runs_per_item"] == 5
assert policy["pass_threshold"] == 3
assertions = retrieved.get_global_assertions()
assert set(assertions) == {
"Response is helpful",
"Response is accurate",
}
def test_test_suite__update_test_settings_with_empty_assertions__clears_assertions(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that update(assertions=[]) clears all suite-level assertions.
"""
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test clearing assertions",
global_assertions=["Response is helpful", "Response is accurate"],
global_execution_policy={"runs_per_item": 1, "pass_threshold": 1},
)
assert len(suite.get_global_assertions()) == 2
suite.update_test_settings(global_assertions=[])
retrieved = opik_client.get_test_suite(name=dataset_name)
assert retrieved.get_global_assertions() == []
policy = retrieved.get_global_execution_policy()
assert policy["runs_per_item"] == 1
assert policy["pass_threshold"] == 1
# =============================================================================
# TAGS
# =============================================================================
def test_test_suite__create_with_tags__tags_persisted(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that tags passed to create_test_suite are persisted
and can be retrieved via get_test_suite().
"""
opik_client.create_test_suite(
name=dataset_name,
description="Suite with tags",
tags=["regression", "v2"],
)
suite = opik_client.get_test_suite(dataset_name)
assert sorted(suite.get_tags()) == ["regression", "v2"]
def test_get_or_create_test_suite__with_tags__tags_persisted(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_or_create passes tags on creation but does not
modify tags on an existing suite.
"""
opik_client.get_or_create_test_suite(
name=dataset_name,
tags=["v1"],
)
suite = opik_client.get_test_suite(dataset_name)
assert suite.get_tags() == ["v1"]
opik_client.get_or_create_test_suite(
name=dataset_name,
tags=["v2", "production"],
)
suite = opik_client.get_test_suite(dataset_name)
assert suite.get_tags() == ["v1"]
@pytest.mark.skipif(
not environment.has_openai_api_key(), reason="OPENAI_API_KEY is not set"
)
def test_test_suite__insert_batch__all_items_persisted(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Test that insert() adds multiple items in a single batch.
"""
assertion = "The response is factually correct"
suite = opik_client.create_test_suite(
name=dataset_name,
description="Test batch insert",
)
suite.insert(
[
{
"data": {"input": {"question": "What is the capital of France?"}},
"assertions": [assertion],
},
{
"data": {"input": {"question": "What is the capital of Germany?"}},
"assertions": [assertion],
},
{
"data": {"input": {"question": "What is the capital of Spain?"}},
"assertions": [assertion],
},
]
)
def task(item: Dict[str, Any]) -> Dict[str, Any]:
answers = {
"What is the capital of France?": "Paris",
"What is the capital of Germany?": "Berlin",
"What is the capital of Spain?": "Madrid",
}
question = item["input"]["question"]
return {"input": item["input"], "output": answers.get(question, "Unknown")}
# opik.run_tests must handle flushing
suite_result = opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
verifiers.verify_test_suite_result(
opik_client=opik_client,
suite_result=suite_result,
items_total=3,
items_passed=3,
experiment_items_count=3,
total_feedback_scores=3,
expected_score_names={assertion},
)
# =============================================================================
# CLIENT-LEVEL: delete, list, get_experiments (OPIK-5802)
# =============================================================================
def test_delete_test_suite__removes_suite(opik_client: opik.Opik, dataset_name: str):
"""
Test that delete_test_suite() removes the suite.
"""
opik_client.create_test_suite(name=dataset_name)
opik_client.delete_test_suite(name=dataset_name)
with pytest.raises(Exception):
opik_client.get_test_suite(name=dataset_name)
def test_get_test_suites__returns_only_test_suites(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_test_suites() only returns test suites, not regular datasets.
"""
suite_name = dataset_name + "-suite"
dataset_regular_name = dataset_name + "-dataset"
opik_client.create_test_suite(name=suite_name)
opik_client.create_dataset(name=dataset_regular_name)
suites = opik_client.get_test_suites()
suite_names = {s.name for s in suites}
assert suite_name in suite_names
assert dataset_regular_name not in suite_names
# get_datasets() returns all datasets including test suites for backward compat
datasets = opik_client.get_datasets()
dataset_names = {d.name for d in datasets}
assert dataset_regular_name in dataset_names
assert suite_name in dataset_names
# cleanup
opik_client.delete_test_suite(name=suite_name)
opik_client.delete_dataset(name=dataset_regular_name)
def test_get_test_suite_experiments__returns_experiments(
opik_client: opik.Opik, dataset_name: str, experiment_name: str
):
"""
Test that get_test_suite_experiments() returns experiments run on the suite.
"""
suite = opik_client.create_test_suite(name=dataset_name)
suite.insert([{"data": {"input": {"question": "Hello"}}}])
def task(item: Dict[str, Any]) -> Dict[str, Any]:
return {"input": item["input"], "output": "World"}
# opik.run_tests must handle flushing
opik.run_tests(
test_suite=suite,
task=task,
experiment_name=experiment_name,
verbose=0,
)
experiments = opik_client.get_test_suite_experiments(name=dataset_name)
experiment_names = {e.name for e in experiments}
assert experiment_name in experiment_names
# =============================================================================
# ITEM MANAGEMENT: update_items, update_item_assertions/policy (OPIK-5803)
# =============================================================================
def test_update__updates_existing_items(opik_client: opik.Opik, dataset_name: str):
"""
Test that update_items() updates data on existing items.
"""
suite = opik_client.create_test_suite(name=dataset_name)
suite.insert(
[
{"data": {"question": "Original question"}},
]
)
items = suite.get_items()
assert len(items) == 1
item_id = items[0]["id"]
suite.update(
[
{"id": item_id, "data": {"question": "Updated question"}},
]
)
updated_items = suite.get_items()
assert len(updated_items) == 1
assert updated_items[0]["data"]["question"] == "Updated question"
def test_update__missing_id__raises_error(opik_client: opik.Opik, dataset_name: str):
"""
Test that update_items() raises error when item has no id.
"""
suite = opik_client.create_test_suite(name=dataset_name)
with pytest.raises(Exception, match="Missing id"):
suite.update([{"data": {"question": "No ID"}}])
# =============================================================================
# ITEM MANAGEMENT: clear, get_items params (OPIK-5804)
# =============================================================================
def test_clear__removes_all_items(opik_client: opik.Opik, dataset_name: str):
"""
Test that clear() removes all items from the suite.
"""
suite = opik_client.create_test_suite(name=dataset_name)
suite.insert(
[
{"data": {"question": "Q1"}},
{"data": {"question": "Q2"}},
{"data": {"question": "Q3"}},
]
)
assert len(suite.get_items()) == 3
suite.clear()
assert len(suite.get_items()) == 0
def test_get_items__nb_samples__limits_results(
opik_client: opik.Opik, dataset_name: str
):
"""
Test that get_items(nb_samples=N) returns at most N items.
"""
suite = opik_client.create_test_suite(name=dataset_name)
suite.insert(
[
{"data": {"question": "Q1"}},
{"data": {"question": "Q2"}},
{"data": {"question": "Q3"}},
]
)
items = suite.get_items(nb_samples=2)
assert len(items) == 2
# =============================================================================
# VERSIONING: suite without initial version (OPIK-5815)
# =============================================================================
def test_test_suite__create_without_metadata_then_insert__items_persisted(
opik_client: opik.Opik, dataset_name: str
):
"""
OPIK-5815: create a suite without evaluators or execution_policy,
then add items. Verifies insert works without an initial version.
"""
suite = opik_client.create_test_suite(name=dataset_name)
assert suite.get_version_info() is None
suite.insert(
[
{"data": {"input": {"question": "What is 2+2?"}}},
{"data": {"input": {"question": "What is 3+3?"}}},
]
)
reloaded = opik_client.get_test_suite(name=dataset_name)
assert len(reloaded.get_items()) == 2
def test_test_suite__create_without_metadata_then_update__metadata_persisted(
opik_client: opik.Opik, dataset_name: str
):
"""
OPIK-5815: create a suite without evaluators or execution_policy,
then update with assertions and policy. Verifies update works
without an initial version.
"""
assertion = "The response is factually correct"
suite = opik_client.create_test_suite(name=dataset_name)
assert suite.get_version_info() is None
suite.update_test_settings(
global_assertions=[assertion],
global_execution_policy={"runs_per_item": 2, "pass_threshold": 1},
)
reloaded = opik_client.get_test_suite(name=dataset_name)
assert reloaded.get_global_assertions() == [assertion]
assert reloaded.get_global_execution_policy() == {
"runs_per_item": 2,
"pass_threshold": 1,
}
# =============================================================================
# ITEMS COUNT: items_count property (OPIK-5983)
# =============================================================================
def test_test_suite__items_count__returns_correct_count_after_insert(
opik_client: opik.Opik, dataset_name: str
):
"""
OPIK-5983: items_count should return the actual count after insert,
not None.
"""
from opik import synchronization
suite = opik_client.create_test_suite(name=dataset_name)
suite.insert(
[
{"data": {"input": {"question": "What is 2+2?"}}},
{"data": {"input": {"question": "What is 3+3?"}}},
]
)
success = synchronization.until(
lambda: suite.items_count == 2,
max_try_seconds=30,
)
assert success, f"Expected items_count=2, got {suite.items_count}"