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
328 lines
10 KiB
Python
328 lines
10 KiB
Python
"""Tests for ColBERT utility module."""
|
|
|
|
import copy
|
|
from collections.abc import Iterator
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from opik_optimizer.utils.tools.colbert import (
|
|
ColBERTv2,
|
|
dotdict,
|
|
colbertv2_get_request,
|
|
colbertv2_post_request,
|
|
_create_session_with_retries,
|
|
)
|
|
|
|
|
|
def _json_response(payload: dict) -> MagicMock:
|
|
response = MagicMock()
|
|
response.json.return_value = payload
|
|
return response
|
|
|
|
|
|
@pytest.fixture
|
|
def mocked_requests_session() -> Iterator[MagicMock]:
|
|
"""
|
|
Patch `requests.Session` construction inside the ColBERT module.
|
|
|
|
Tests can then configure `.get` / `.post` return values or side effects without
|
|
repeating the same patch boilerplate.
|
|
"""
|
|
with patch("opik_optimizer.utils.tools.colbert.requests.Session") as session_cls:
|
|
session = MagicMock()
|
|
session_cls.return_value = session
|
|
yield session
|
|
|
|
|
|
class TestDotDict:
|
|
"""Tests for dotdict helper class."""
|
|
|
|
def test_attribute_access(self) -> None:
|
|
d = dotdict({"name": "test", "value": 42})
|
|
assert d.name == "test"
|
|
assert d.value == 42
|
|
|
|
def test_attribute_assignment(self) -> None:
|
|
d = dotdict()
|
|
d.name = "test"
|
|
d.value = 42
|
|
assert d["name"] == "test"
|
|
assert d["value"] == 42
|
|
|
|
def test_attribute_deletion(self) -> None:
|
|
d = dotdict({"name": "test", "value": 42})
|
|
del d.name
|
|
assert "name" not in d
|
|
assert d.value == 42
|
|
|
|
def test_raises_attribute_error_for_missing_key(self) -> None:
|
|
d = dotdict({"name": "test"})
|
|
with pytest.raises(AttributeError, match="has no attribute"):
|
|
_ = d.missing_key
|
|
|
|
def test_dunder_attribute_passthrough(self) -> None:
|
|
d = dotdict()
|
|
# Should not raise - dunder attributes use parent behavior
|
|
repr(d)
|
|
str(d)
|
|
|
|
def test_deepcopy(self) -> None:
|
|
d = dotdict({"nested": {"value": 42}})
|
|
copied = copy.deepcopy(d)
|
|
assert copied.nested["value"] == 42
|
|
# Modify original - copy should be independent
|
|
d.nested["value"] = 100
|
|
assert copied.nested["value"] == 42
|
|
|
|
def test_dict_operations(self) -> None:
|
|
d = dotdict({"a": 1, "b": 2})
|
|
assert len(d) == 2
|
|
assert list(d.keys()) == ["a", "b"]
|
|
assert list(d.values()) == [1, 2]
|
|
|
|
|
|
class TestCreateSessionWithRetries:
|
|
"""Tests for _create_session_with_retries function."""
|
|
|
|
def test_creates_session(self) -> None:
|
|
session = _create_session_with_retries()
|
|
assert session is not None
|
|
# Check adapters are mounted
|
|
assert "http://" in session.adapters
|
|
assert "https://" in session.adapters
|
|
|
|
def test_custom_max_retries(self) -> None:
|
|
session = _create_session_with_retries(max_retries=10)
|
|
assert session is not None
|
|
|
|
|
|
class TestColbertv2GetRequest:
|
|
"""Tests for colbertv2_get_request function."""
|
|
|
|
def test_successful_request(self, mocked_requests_session: MagicMock) -> None:
|
|
mock_response = {
|
|
"topk": [
|
|
{"text": "Result 1", "score": 0.9},
|
|
{"text": "Result 2", "score": 0.8},
|
|
]
|
|
}
|
|
mocked_requests_session.get.return_value = _json_response(mock_response)
|
|
|
|
results = colbertv2_get_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=2,
|
|
)
|
|
|
|
assert len(results) == 2
|
|
assert results[0]["text"] == "Result 1"
|
|
assert results[0]["long_text"] == "Result 1"
|
|
|
|
def test_raises_on_k_greater_than_100(self) -> None:
|
|
with pytest.raises(AssertionError, match="k <= 100"):
|
|
colbertv2_get_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=101,
|
|
)
|
|
|
|
def test_handles_server_error_response(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response = {
|
|
"error": True,
|
|
"message": "Internal server error",
|
|
}
|
|
mocked_requests_session.get.return_value = _json_response(mock_response)
|
|
|
|
with pytest.raises(Exception, match="ColBERTv2 server error"):
|
|
colbertv2_get_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=5,
|
|
)
|
|
|
|
def test_retries_on_connection_error(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response_error = {
|
|
"error": True,
|
|
"message": "Cannot connect to host",
|
|
}
|
|
mock_response_success = {"topk": [{"text": "Result", "score": 0.9}]}
|
|
mocked_requests_session.get.side_effect = [
|
|
_json_response(mock_response_error),
|
|
_json_response(mock_response_success),
|
|
]
|
|
|
|
with patch("opik_optimizer.utils.tools.colbert.time.sleep"):
|
|
results = colbertv2_get_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=1,
|
|
)
|
|
|
|
assert len(results) == 1
|
|
|
|
def test_handles_unexpected_response_format(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response = {"unexpected_key": "value"}
|
|
mocked_requests_session.get.return_value = _json_response(mock_response)
|
|
|
|
with pytest.raises(Exception, match="Unexpected response format"):
|
|
colbertv2_get_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=5,
|
|
)
|
|
|
|
def test_handles_request_exception(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
import requests
|
|
|
|
mocked_requests_session.get.side_effect = requests.RequestException(
|
|
"Network error"
|
|
)
|
|
|
|
with patch("opik_optimizer.utils.tools.colbert.time.sleep"):
|
|
with pytest.raises(Exception, match="ColBERTv2 request failed"):
|
|
colbertv2_get_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=5,
|
|
max_retries=2,
|
|
)
|
|
|
|
|
|
class TestColbertv2PostRequest:
|
|
"""Tests for colbertv2_post_request function."""
|
|
|
|
def test_successful_post_request(self, mocked_requests_session: MagicMock) -> None:
|
|
mock_response = {
|
|
"topk": [
|
|
{"text": "Result 1", "score": 0.9},
|
|
{"text": "Result 2", "score": 0.8},
|
|
]
|
|
}
|
|
mocked_requests_session.post.return_value = _json_response(mock_response)
|
|
|
|
results = colbertv2_post_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=2,
|
|
)
|
|
|
|
assert len(results) == 2
|
|
assert results[0]["text"] == "Result 1"
|
|
|
|
def test_handles_server_error_response(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response = {
|
|
"error": True,
|
|
"message": "Bad request",
|
|
}
|
|
mocked_requests_session.post.return_value = _json_response(mock_response)
|
|
|
|
with pytest.raises(Exception, match="ColBERTv2 server error"):
|
|
colbertv2_post_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=5,
|
|
)
|
|
|
|
def test_retries_on_connection_refused(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response_error = {
|
|
"error": True,
|
|
"message": "Connection refused",
|
|
}
|
|
mocked_requests_session.post.return_value = _json_response(mock_response_error)
|
|
|
|
with patch("opik_optimizer.utils.tools.colbert.time.sleep"):
|
|
with pytest.raises(Exception, match="ColBERTv2 server error"):
|
|
colbertv2_post_request(
|
|
url="http://localhost:8000/search",
|
|
query="test query",
|
|
k=5,
|
|
max_retries=2,
|
|
)
|
|
|
|
|
|
class TestColBERTv2Class:
|
|
"""Tests for ColBERTv2 wrapper class."""
|
|
|
|
def test_initialization_with_defaults(self) -> None:
|
|
client = ColBERTv2()
|
|
assert client.url == "http://0.0.0.0"
|
|
assert client.post_requests is False
|
|
|
|
def test_initialization_with_port(self) -> None:
|
|
client = ColBERTv2(url="http://localhost", port=8080)
|
|
assert client.url == "http://localhost:8080"
|
|
|
|
def test_initialization_with_post_requests(self) -> None:
|
|
client = ColBERTv2(post_requests=True)
|
|
assert client.post_requests is True
|
|
|
|
def test_call_with_get_request(self, mocked_requests_session: MagicMock) -> None:
|
|
mock_response = {
|
|
"topk": [
|
|
{"text": "Result 1", "score": 0.9},
|
|
{"text": "Result 2", "score": 0.8},
|
|
]
|
|
}
|
|
mocked_requests_session.get.return_value = _json_response(mock_response)
|
|
|
|
client = ColBERTv2(post_requests=False)
|
|
results = client(query="test", k=2)
|
|
|
|
assert len(results) == 2
|
|
assert isinstance(results[0], dotdict)
|
|
|
|
def test_call_with_post_request(self, mocked_requests_session: MagicMock) -> None:
|
|
mock_response = {
|
|
"topk": [
|
|
{"text": "Result 1", "score": 0.9},
|
|
]
|
|
}
|
|
mocked_requests_session.post.return_value = _json_response(mock_response)
|
|
|
|
client = ColBERTv2(post_requests=True)
|
|
results = client(query="test", k=1)
|
|
|
|
assert len(results) == 1
|
|
mocked_requests_session.post.assert_called_once()
|
|
|
|
def test_call_with_simplify_returns_strings(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response = {
|
|
"topk": [
|
|
{"text": "Result 1", "score": 0.9, "long_text": "Result 1 full"},
|
|
{"text": "Result 2", "score": 0.8, "long_text": "Result 2 full"},
|
|
]
|
|
}
|
|
mocked_requests_session.get.return_value = _json_response(mock_response)
|
|
|
|
client = ColBERTv2()
|
|
results = client(query="test", k=2, simplify=True)
|
|
|
|
assert len(results) == 2
|
|
assert isinstance(results[0], str)
|
|
|
|
def test_call_with_custom_max_retries(
|
|
self, mocked_requests_session: MagicMock
|
|
) -> None:
|
|
mock_response = {"topk": [{"text": "Result", "score": 0.9}]}
|
|
mocked_requests_session.get.return_value = _json_response(mock_response)
|
|
|
|
client = ColBERTv2()
|
|
results = client(query="test", k=1, max_retries=10)
|
|
|
|
assert len(results) == 1
|