-
James Briggs authoredJames Briggs authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
test_layer.py 31.84 KiB
import importlib
import os
import tempfile
from unittest.mock import mock_open, patch
import pytest
import time
from semantic_router.encoders import BaseEncoder, CohereEncoder, OpenAIEncoder
from semantic_router.index.local import LocalIndex
from semantic_router.index.pinecone import PineconeIndex
from semantic_router.index.qdrant import QdrantIndex
from semantic_router.layer import LayerConfig, RouteLayer
from semantic_router.llms.base import BaseLLM
from semantic_router.route import Route
def mock_encoder_call(utterances):
# Define a mapping of utterances to return values
mock_responses = {
"Hello": [0.1, 0.2, 0.3],
"Hi": [0.4, 0.5, 0.6],
"Goodbye": [0.7, 0.8, 0.9],
"Bye": [1.0, 1.1, 1.2],
"Au revoir": [1.3, 1.4, 1.5],
}
return [mock_responses.get(u, [0.0, 0.0, 0.0]) for u in utterances]
def layer_json():
return """{
"encoder_type": "cohere",
"encoder_name": "embed-english-v3.0",
"routes": [
{
"name": "politics",
"utterances": [
"isn't politics the best thing ever",
"why don't you tell me about your political opinions"
],
"description": null,
"function_schema": null
},
{
"name": "chitchat",
"utterances": [
"how's the weather today?",
"how are things going?"
],
"description": null,
"function_schema": null
}
]
}"""
def layer_yaml():
return """encoder_name: embed-english-v3.0
encoder_type: cohere
routes:
- description: null
function_schema: null
name: politics
utterances:
- isn't politics the best thing ever
- why don't you tell me about your political opinions
- description: null
function_schema: null
name: chitchat
utterances:
- how's the weather today?
- how are things going?
"""
@pytest.fixture
def base_encoder():
return BaseEncoder(name="test-encoder", score_threshold=0.5)
@pytest.fixture
def cohere_encoder(mocker):
mocker.patch.object(CohereEncoder, "__call__", side_effect=mock_encoder_call)
return CohereEncoder(name="test-cohere-encoder", cohere_api_key="test_api_key")
@pytest.fixture
def openai_encoder(mocker):
mocker.patch.object(OpenAIEncoder, "__call__", side_effect=mock_encoder_call)
return OpenAIEncoder(name="test-openai-encoder", openai_api_key="test_api_key")
@pytest.fixture
def routes():
return [
Route(name="Route 1", utterances=["Hello", "Hi"]),
Route(name="Route 2", utterances=["Goodbye", "Bye", "Au revoir"]),
]
@pytest.fixture
def routes_2():
return [
Route(name="Route 1", utterances=["Hello"]),
Route(name="Route 2", utterances=["Hello"]),
]
@pytest.fixture
def routes_3():
return [
Route(name="Route 1", utterances=["Hello"]),
Route(name="Route 2", utterances=["Asparagus"]),
]
@pytest.fixture
def dynamic_routes():
return [
Route(
name="Route 1", utterances=["Hello", "Hi"], function_schema={"name": "test"}
),
Route(
name="Route 2",
utterances=["Goodbye", "Bye", "Au revoir"],
function_schema={"name": "test"},
),
]
@pytest.fixture
def test_data():
return [
("What's your opinion on the current government?", "politics"),
("what's the weather like today?", "chitchat"),
("what is the Pythagorean theorem?", "mathematics"),
("what is photosynthesis?", "biology"),
("tell me an interesting fact", None),
]
def get_test_indexes():
indexes = [LocalIndex]
if importlib.util.find_spec("qdrant_client") is not None:
indexes.append(QdrantIndex)
return indexes
@pytest.mark.parametrize("index_cls", get_test_indexes())
class TestRouteLayer:
def test_initialization(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, top_k=10, index=index_cls()
)
assert openai_encoder.score_threshold == 0.82
assert route_layer.score_threshold == 0.82
assert route_layer.top_k == 10
assert len(route_layer.index) if route_layer.index is not None else 0 == 5
assert (
len(set(route_layer._get_route_names()))
if route_layer._get_route_names() is not None
else 0 == 2
)
def test_initialization_different_encoders(
self, cohere_encoder, openai_encoder, index_cls
):
route_layer_cohere = RouteLayer(encoder=cohere_encoder, index=index_cls())
assert cohere_encoder.score_threshold == 0.3
assert route_layer_cohere.score_threshold == 0.3
route_layer_openai = RouteLayer(encoder=openai_encoder, index=index_cls())
assert route_layer_openai.score_threshold == 0.82
def test_initialization_no_encoder(self, openai_encoder, index_cls):
os.environ["OPENAI_API_KEY"] = "test_api_key"
route_layer_none = RouteLayer(encoder=None)
assert route_layer_none.score_threshold == openai_encoder.score_threshold
def test_initialization_dynamic_route(
self, cohere_encoder, openai_encoder, dynamic_routes, index_cls
):
route_layer_cohere = RouteLayer(
encoder=cohere_encoder, routes=dynamic_routes, index=index_cls()
)
assert route_layer_cohere.score_threshold == 0.3
route_layer_openai = RouteLayer(
encoder=openai_encoder, routes=dynamic_routes, index=index_cls()
)
assert openai_encoder.score_threshold == 0.82
assert route_layer_openai.score_threshold == 0.82
def test_add_route(self, openai_encoder, index_cls):
route_layer = RouteLayer(encoder=openai_encoder, index=index_cls())
route1 = Route(name="Route 1", utterances=["Yes", "No"])
route2 = Route(name="Route 2", utterances=["Maybe", "Sure"])
# Initially, the routes list should be empty
assert route_layer.routes == []
# Add route1 and check
route_layer.add(route=route1)
assert route_layer.routes == [route1]
assert route_layer.index is not None
# Use the describe method to get the number of vectors
assert route_layer.index.describe()["vectors"] == 2
# Add route2 and check
route_layer.add(route=route2)
assert route_layer.routes == [route1, route2]
assert route_layer.index.describe()["vectors"] == 4
def test_list_route_names(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_names = route_layer.list_route_names()
assert set(route_names) == {
route.name for route in routes
}, "The list of route names should match the names of the routes added."
def test_delete_route(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
# Delete a route by name
route_to_delete = routes[0].name
route_layer.delete(route_to_delete)
# Ensure the route is no longer in the route layer
assert (
route_to_delete not in route_layer.list_route_names()
), "The route should be deleted from the route layer."
# Ensure the route's utterances are no longer in the index
for utterance in routes[0].utterances:
assert (
utterance not in route_layer.index
), "The route's utterances should be deleted from the index."
def test_remove_route_not_found(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
# Attempt to remove a route that does not exist
non_existent_route = "non-existent-route"
with pytest.raises(ValueError) as excinfo:
route_layer.delete(non_existent_route)
assert (
str(excinfo.value) == f"Route `{non_existent_route}` not found"
), "Attempting to remove a non-existent route should raise a ValueError."
def test_add_multiple_routes(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(encoder=openai_encoder, index=index_cls())
route_layer._add_routes(routes=routes)
assert route_layer.index is not None
assert route_layer.index.describe()["vectors"] == 5
def test_query_and_classification(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
query_result = route_layer(text="Hello").name
assert query_result in ["Route 1", "Route 2"]
def test_query_filter(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
query_result = route_layer(text="Hello", route_filter=["Route 1"]).name
try:
route_layer(text="Hello", route_filter=["Route 8"]).name
except ValueError:
assert True
assert query_result in ["Route 1"]
def test_query_filter_pinecone(self, openai_encoder, routes, index_cls):
pinecone_api_key = os.environ["PINECONE_API_KEY"]
pineconeindex = PineconeIndex(api_key=pinecone_api_key)
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=pineconeindex
)
time.sleep(5) # allow for index to be populated
query_result = route_layer(text="Hello", route_filter=["Route 1"]).name
try:
route_layer(text="Hello", route_filter=["Route 8"]).name
except ValueError:
assert True
# delete index
pineconeindex.delete_index()
assert query_result in ["Route 1"]
def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls):
pinecone_api_key = os.environ["PINECONE_API_KEY"]
pineconeindex = PineconeIndex(api_key=pinecone_api_key, namespace="test")
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=pineconeindex
)
time.sleep(5) # allow for index to be populated
query_result = route_layer(text="Hello", route_filter=["Route 1"]).name
try:
route_layer(text="Hello", route_filter=["Route 8"]).name
except ValueError:
assert True
# delete index
pineconeindex.delete_index()
assert query_result in ["Route 1"]
def test_query_with_no_index(self, openai_encoder, index_cls):
route_layer = RouteLayer(encoder=openai_encoder, index=index_cls())
with pytest.raises(ValueError):
assert route_layer(text="Anything").name is None
def test_query_with_vector(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
vector = [0.1, 0.2, 0.3]
query_result = route_layer(vector=vector).name
assert query_result in ["Route 1", "Route 2"]
def test_query_with_no_text_or_vector(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
with pytest.raises(ValueError):
route_layer()
def test_semantic_classify(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
classification, score = route_layer._semantic_classify(
[
{"route": "Route 1", "score": 0.9},
{"route": "Route 2", "score": 0.1},
]
)
assert classification == "Route 1"
assert score == [0.9]
def test_semantic_classify_multiple_routes(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
classification, score = route_layer._semantic_classify(
[
{"route": "Route 1", "score": 0.9},
{"route": "Route 2", "score": 0.1},
{"route": "Route 1", "score": 0.8},
]
)
assert classification == "Route 1"
assert score == [0.9, 0.8]
def test_query_no_text_dynamic_route(
self, openai_encoder, dynamic_routes, index_cls
):
route_layer = RouteLayer(
encoder=openai_encoder, routes=dynamic_routes, index=index_cls()
)
vector = [0.1, 0.2, 0.3]
with pytest.raises(ValueError):
route_layer(vector=vector)
def test_pass_threshold(self, openai_encoder, index_cls):
route_layer = RouteLayer(encoder=openai_encoder, index=index_cls())
assert not route_layer._pass_threshold([], 0.5)
assert route_layer._pass_threshold([0.6, 0.7], 0.5)
def test_failover_score_threshold(self, base_encoder, index_cls):
route_layer = RouteLayer(encoder=base_encoder, index=index_cls())
assert route_layer.score_threshold == 0.5
def test_json(self, openai_encoder, routes, index_cls):
temp = tempfile.NamedTemporaryFile(suffix=".yaml", delete=False)
try:
temp_path = temp.name # Save the temporary file's path
temp.close() # Close the file to ensure it can be opened again on Windows
os.environ["OPENAI_API_KEY"] = "test_api_key"
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_layer.to_json(temp_path)
assert os.path.exists(temp_path)
route_layer_from_file = RouteLayer.from_json(temp_path)
assert (
route_layer_from_file.index is not None
and route_layer_from_file._get_route_names() is not None
)
finally:
os.remove(temp_path) # Ensure the file is deleted even if the test fails
def test_yaml(self, openai_encoder, routes, index_cls):
temp = tempfile.NamedTemporaryFile(suffix=".yaml", delete=False)
try:
temp_path = temp.name # Save the temporary file's path
temp.close() # Close the file to ensure it can be opened again on Windows
os.environ["OPENAI_API_KEY"] = "test_api_key"
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_layer.to_yaml(temp_path)
assert os.path.exists(temp_path)
route_layer_from_file = RouteLayer.from_yaml(temp_path)
assert (
route_layer_from_file.index is not None
and route_layer_from_file._get_route_names() is not None
)
finally:
os.remove(temp_path) # Ensure the file is deleted even if the test fails
def test_from_file_json(openai_encoder, tmp_path, index_cls):
# Create a temporary JSON file with layer configuration
config_path = tmp_path / "config.json"
config_path.write_text(
layer_json()
) # Assuming layer_json() returns a valid JSON string
# Load the LayerConfig from the temporary file
layer_config = LayerConfig.from_file(str(config_path))
# Assertions to verify the loaded configuration
assert layer_config.encoder_type == "cohere"
assert layer_config.encoder_name == "embed-english-v3.0"
assert len(layer_config.routes) == 2
assert layer_config.routes[0].name == "politics"
def test_from_file_yaml(openai_encoder, tmp_path, index_cls):
# Create a temporary YAML file with layer configuration
config_path = tmp_path / "config.yaml"
config_path.write_text(
layer_yaml()
) # Assuming layer_yaml() returns a valid YAML string
# Load the LayerConfig from the temporary file
layer_config = LayerConfig.from_file(str(config_path))
# Assertions to verify the loaded configuration
assert layer_config.encoder_type == "cohere"
assert layer_config.encoder_name == "embed-english-v3.0"
assert len(layer_config.routes) == 2
assert layer_config.routes[0].name == "politics"
def test_from_file_invalid_path(self, index_cls):
with pytest.raises(FileNotFoundError) as excinfo:
LayerConfig.from_file("nonexistent_path.json")
assert "[Errno 2] No such file or directory: 'nonexistent_path.json'" in str(
excinfo.value
)
def test_from_file_unsupported_type(self, tmp_path, index_cls):
# Create a temporary unsupported file
config_path = tmp_path / "config.unsupported"
config_path.write_text(layer_json())
with pytest.raises(ValueError) as excinfo:
LayerConfig.from_file(str(config_path))
assert "Unsupported file type" in str(excinfo.value)
def test_from_file_invalid_config(self, tmp_path, index_cls):
# Define an invalid configuration JSON
invalid_config_json = """
{
"encoder_type": "cohere",
"encoder_name": "embed-english-v3.0",
"routes": "This should be a list, not a string"
}"""
# Write the invalid configuration to a temporary JSON file
config_path = tmp_path / "invalid_config.json"
with open(config_path, "w") as file:
file.write(invalid_config_json)
# Patch the is_valid function to return False for this test
with patch("semantic_router.layer.is_valid", return_value=False):
# Attempt to load the LayerConfig from the temporary file
# and assert that it raises an exception due to invalid configuration
with pytest.raises(Exception) as excinfo:
LayerConfig.from_file(str(config_path))
assert "Invalid config JSON or YAML" in str(
excinfo.value
), "Loading an invalid configuration should raise an exception."
def test_from_file_with_llm(self, tmp_path, index_cls):
llm_config_json = """
{
"encoder_type": "cohere",
"encoder_name": "embed-english-v3.0",
"routes": [
{
"name": "llm_route",
"utterances": ["tell me a joke", "say something funny"],
"llm": {
"module": "semantic_router.llms.base",
"class": "BaseLLM",
"model": "fake-model-v1"
}
}
]
}"""
config_path = tmp_path / "config_with_llm.json"
with open(config_path, "w") as file:
file.write(llm_config_json)
# Load the LayerConfig from the temporary file
layer_config = LayerConfig.from_file(str(config_path))
# Using BaseLLM because trying to create a usable Mock LLM is a nightmare.
assert isinstance(
layer_config.routes[0].llm, BaseLLM
), "LLM should be instantiated and associated with the route based on the "
"config"
assert (
layer_config.routes[0].llm.name == "fake-model-v1"
), "LLM instance should have the 'name' attribute set correctly"
def test_config(self, openai_encoder, routes, index_cls):
os.environ["OPENAI_API_KEY"] = "test_api_key"
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
# confirm route creation functions as expected
layer_config = route_layer.to_config()
assert layer_config.routes == routes
# now load from config and confirm it's the same
route_layer_from_config = RouteLayer.from_config(layer_config, index_cls())
assert (
route_layer_from_config._get_route_names() == route_layer._get_route_names()
)
assert route_layer_from_config.score_threshold == route_layer.score_threshold
def test_get_thresholds(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
assert route_layer.get_thresholds() == {"Route 1": 0.82, "Route 2": 0.82}
def test_with_multiple_routes_passing_threshold(
self, openai_encoder, routes, index_cls
):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_layer.score_threshold = 0.5 # Set the score_threshold if needed
# Assuming route_layer is already set up with routes "Route 1" and "Route 2"
query_results = [
{"route": "Route 1", "score": 0.6},
{"route": "Route 2", "score": 0.7},
{"route": "Route 1", "score": 0.8},
]
# Override _pass_threshold to always return True for this test
route_layer._pass_threshold = lambda scores, threshold: True
expected = [("Route 1", 0.8), ("Route 2", 0.7)]
results = route_layer._semantic_classify_multiple_routes(query_results)
assert sorted(results) == sorted(
expected
), "Should classify and return routes above their thresholds"
def test_with_no_routes_passing_threshold(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_layer.score_threshold = 0.5
# Override _pass_threshold to always return False for this test
route_layer._pass_threshold = lambda scores, threshold: False
query_results = [
{"route": "Route 1", "score": 0.3},
{"route": "Route 2", "score": 0.2},
]
expected = []
results = route_layer._semantic_classify_multiple_routes(query_results)
assert (
results == expected
), "Should return an empty list when no routes pass their thresholds"
def test_with_no_query_results(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_layer.score_threshold = 0.5
query_results = []
expected = []
results = route_layer._semantic_classify_multiple_routes(query_results)
assert (
results == expected
), "Should return an empty list when there are no query results"
def test_with_unrecognized_route(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
route_layer.score_threshold = 0.5
# Test with a route name that does not exist in the route_layer's routes
query_results = [{"route": "UnrecognizedRoute", "score": 0.9}]
expected = []
results = route_layer._semantic_classify_multiple_routes(query_results)
assert results == expected, "Should ignore and not return unrecognized routes"
def test_retrieve_with_text(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
text = "Hello"
results = route_layer.retrieve_multiple_routes(text=text)
assert len(results) >= 1, "Expected at least one result"
assert any(
result.name in ["Route 1", "Route 2"] for result in results
), "Expected the result to be either 'Route 1' or 'Route 2'"
def test_retrieve_with_vector(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
vector = [0.1, 0.2, 0.3]
results = route_layer.retrieve_multiple_routes(vector=vector)
assert len(results) >= 1, "Expected at least one result"
assert any(
result.name in ["Route 1", "Route 2"] for result in results
), "Expected the result to be either 'Route 1' or 'Route 2'"
def test_retrieve_without_text_or_vector(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
with pytest.raises(ValueError, match="Either text or vector must be provided"):
route_layer.retrieve_multiple_routes()
def test_retrieve_no_matches(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
text = "Asparagus"
results = route_layer.retrieve_multiple_routes(text=text)
assert len(results) == 0, f"Expected no results, but got {len(results)}"
def test_retrieve_one_match(self, openai_encoder, routes_3, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes_3, index=index_cls()
)
text = "Hello"
results = route_layer.retrieve_multiple_routes(text=text)
assert len(results) == 1, f"Expected one result, and got {len(results)}"
matched_routes = [result.name for result in results]
assert "Route 1" in matched_routes, "Expected 'Route 1' to be a match"
def test_retrieve_with_text_for_multiple_matches(
self, openai_encoder, routes_2, index_cls
):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes_2, index=index_cls()
)
text = "Hello"
results = route_layer.retrieve_multiple_routes(text=text)
assert len(results) == 2, "Expected two results"
matched_routes = [result.name for result in results]
assert "Route 1" in matched_routes, "Expected 'Route 1' to be a match"
assert "Route 2" in matched_routes, "Expected 'Route 2' to be a match"
def test_set_aggregation_method_with_unsupported_value(
self, openai_encoder, routes, index_cls
):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
unsupported_aggregation = "unsupported_aggregation_method"
with pytest.raises(
ValueError,
match=f"Unsupported aggregation method chosen: {unsupported_aggregation}. Choose either 'SUM', 'MEAN', or 'MAX'.",
):
route_layer._set_aggregation_method(unsupported_aggregation)
def test_refresh_routes_not_implemented(self, openai_encoder, routes, index_cls):
route_layer = RouteLayer(
encoder=openai_encoder, routes=routes, index=index_cls()
)
with pytest.raises(
NotImplementedError, match="This method has not yet been implemented."
):
route_layer._refresh_routes()
class TestLayerFit:
def test_eval(self, openai_encoder, routes, test_data):
route_layer = RouteLayer(encoder=openai_encoder, routes=routes)
# unpack test data
X, y = zip(*test_data)
# evaluate
route_layer.evaluate(X=X, y=y, batch_size=int(len(test_data) / 5))
def test_fit(self, openai_encoder, routes, test_data):
route_layer = RouteLayer(encoder=openai_encoder, routes=routes)
# unpack test data
X, y = zip(*test_data)
route_layer.fit(X=X, y=y, batch_size=int(len(test_data) / 5))
# Add more tests for edge cases and error handling as needed.
class TestLayerConfig:
def test_init(self):
layer_config = LayerConfig()
assert layer_config.routes == []
def test_to_file_json(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
with patch("builtins.open", mock_open()) as mocked_open:
layer_config.to_file("data/test_output.json")
mocked_open.assert_called_once_with("data/test_output.json", "w")
def test_to_file_yaml(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
with patch("builtins.open", mock_open()) as mocked_open:
layer_config.to_file("data/test_output.yaml")
mocked_open.assert_called_once_with("data/test_output.yaml", "w")
def test_to_file_invalid(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
with pytest.raises(ValueError):
layer_config.to_file("test_output.txt")
def test_from_file_json(self):
mock_json_data = layer_json()
with patch("builtins.open", mock_open(read_data=mock_json_data)) as mocked_open:
layer_config = LayerConfig.from_file("data/test.json")
mocked_open.assert_called_once_with("data/test.json", "r")
assert isinstance(layer_config, LayerConfig)
def test_from_file_yaml(self):
mock_yaml_data = layer_yaml()
with patch("builtins.open", mock_open(read_data=mock_yaml_data)) as mocked_open:
layer_config = LayerConfig.from_file("data/test.yaml")
mocked_open.assert_called_once_with("data/test.yaml", "r")
assert isinstance(layer_config, LayerConfig)
def test_from_file_invalid(self):
with open("test.txt", "w") as f:
f.write("dummy content")
with pytest.raises(ValueError):
LayerConfig.from_file("test.txt")
os.remove("test.txt")
def test_to_dict(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
assert layer_config.to_dict()["routes"] == [route.to_dict()]
def test_add(self):
route = Route(name="test", utterances=["utterance"])
route2 = Route(name="test2", utterances=["utterance2"])
layer_config = LayerConfig()
layer_config.add(route)
# confirm route added
assert layer_config.routes == [route]
# add second route and check updates
layer_config.add(route2)
assert layer_config.routes == [route, route2]
def test_get(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
assert layer_config.get("test") == route
def test_get_not_found(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
assert layer_config.get("not_found") is None
def test_remove(self):
route = Route(name="test", utterances=["utterance"])
layer_config = LayerConfig(routes=[route])
layer_config.remove("test")
assert layer_config.routes == []
def test_setting_aggregation_methods(self, openai_encoder, routes):
for agg in ["sum", "mean", "max"]:
route_layer = RouteLayer(
encoder=openai_encoder,
routes=routes,
aggregation=agg,
)
assert route_layer.aggregation == agg
def test_semantic_classify_multiple_routes_with_different_aggregation(
self, openai_encoder, routes
):
route_scores = [
{"route": "Route 1", "score": 0.5},
{"route": "Route 1", "score": 0.5},
{"route": "Route 1", "score": 0.5},
{"route": "Route 1", "score": 0.5},
{"route": "Route 2", "score": 0.4},
{"route": "Route 2", "score": 0.6},
{"route": "Route 2", "score": 0.8},
{"route": "Route 3", "score": 0.1},
{"route": "Route 3", "score": 1.0},
]
for agg in ["sum", "mean", "max"]:
route_layer = RouteLayer(
encoder=openai_encoder,
routes=routes,
aggregation=agg,
)
classification, score = route_layer._semantic_classify(route_scores)
if agg == "sum":
assert classification == "Route 1"
assert score == [0.5, 0.5, 0.5, 0.5]
elif agg == "mean":
assert classification == "Route 2"
assert score == [0.4, 0.6, 0.8]
elif agg == "max":
assert classification == "Route 3"
assert score == [0.1, 1.0]