|
23 | 23 | from pydantic import AnyUrl |
24 | 24 | from pytest_mock import MockerFixture |
25 | 25 |
|
26 | | -from models.config import ModelContextProtocolServer |
| 26 | +from models.config import ByokRag, ModelContextProtocolServer |
27 | 27 | from models.requests import QueryRequest |
28 | 28 | from utils.responses import ( |
29 | 29 | build_mcp_tool_call_from_arguments_done, |
|
41 | 41 | parse_referenced_documents, |
42 | 42 | prepare_responses_params, |
43 | 43 | prepare_tools, |
| 44 | + resolve_vector_store_ids, |
44 | 45 | _build_chunk_attributes, |
45 | 46 | _increment_llm_call_metric, |
46 | 47 | _resolve_source_for_result, |
@@ -1000,6 +1001,132 @@ async def test_prepare_tools_no_tools_true(self, mocker: MockerFixture) -> None: |
1000 | 1001 | mock_client.vector_stores.list.assert_not_called() |
1001 | 1002 |
|
1002 | 1003 |
|
| 1004 | +class TestResolveVectorStoreIds: |
| 1005 | + """Tests for resolve_vector_store_ids function.""" |
| 1006 | + |
| 1007 | + @staticmethod |
| 1008 | + def _make_byok_rag(rag_id: str, vector_db_id: str) -> ByokRag: |
| 1009 | + """Create a ByokRag instance for testing.""" |
| 1010 | + return ByokRag( |
| 1011 | + rag_id=rag_id, |
| 1012 | + vector_db_id=vector_db_id, |
| 1013 | + db_path="tests/configuration/rag.txt", |
| 1014 | + ) |
| 1015 | + |
| 1016 | + def test_translates_customer_facing_ids_to_internal(self) -> None: |
| 1017 | + """Test that customer-facing rag_ids are translated to vector_db_ids.""" |
| 1018 | + byok_rags = [ |
| 1019 | + self._make_byok_rag("ocp_docs", "vs-001"), |
| 1020 | + self._make_byok_rag("knowledge_base", "vs-002"), |
| 1021 | + ] |
| 1022 | + result = resolve_vector_store_ids(["ocp_docs", "knowledge_base"], byok_rags) |
| 1023 | + assert result == ["vs-001", "vs-002"] |
| 1024 | + |
| 1025 | + def test_passes_through_unknown_ids(self) -> None: |
| 1026 | + """Test that IDs not matching any rag_id are passed through unchanged.""" |
| 1027 | + byok_rags = [self._make_byok_rag("ocp_docs", "vs-001")] |
| 1028 | + result = resolve_vector_store_ids(["unknown-id"], byok_rags) |
| 1029 | + assert result == ["unknown-id"] |
| 1030 | + |
| 1031 | + def test_mixed_known_and_unknown_ids(self) -> None: |
| 1032 | + """Test mix of customer-facing IDs and raw llama-stack IDs.""" |
| 1033 | + byok_rags = [self._make_byok_rag("ocp_docs", "vs-001")] |
| 1034 | + result = resolve_vector_store_ids(["ocp_docs", "already-internal"], byok_rags) |
| 1035 | + assert result == ["vs-001", "already-internal"] |
| 1036 | + |
| 1037 | + def test_empty_vector_store_ids(self) -> None: |
| 1038 | + """Test that empty input returns empty output.""" |
| 1039 | + byok_rags = [self._make_byok_rag("ocp_docs", "vs-001")] |
| 1040 | + result = resolve_vector_store_ids([], byok_rags) |
| 1041 | + assert result == [] |
| 1042 | + |
| 1043 | + def test_empty_byok_rags(self) -> None: |
| 1044 | + """Test that all IDs pass through when no BYOK RAGs are configured.""" |
| 1045 | + result = resolve_vector_store_ids(["vs-001", "vs-002"], []) |
| 1046 | + assert result == ["vs-001", "vs-002"] |
| 1047 | + |
| 1048 | + def test_preserves_order(self) -> None: |
| 1049 | + """Test that output order matches input order.""" |
| 1050 | + byok_rags = [ |
| 1051 | + self._make_byok_rag("b_rag", "vs-b"), |
| 1052 | + self._make_byok_rag("a_rag", "vs-a"), |
| 1053 | + ] |
| 1054 | + result = resolve_vector_store_ids(["a_rag", "b_rag"], byok_rags) |
| 1055 | + assert result == ["vs-a", "vs-b"] |
| 1056 | + |
| 1057 | + |
| 1058 | +class TestPrepareToolsTranslatesVectorStoreIds: |
| 1059 | + """Tests that prepare_tools translates BYOK IDs before building RAG tools.""" |
| 1060 | + |
| 1061 | + @pytest.mark.asyncio |
| 1062 | + async def test_translates_byok_ids_in_prepare_tools( |
| 1063 | + self, mocker: MockerFixture |
| 1064 | + ) -> None: |
| 1065 | + """Test that prepare_tools translates customer-facing IDs to internal IDs.""" |
| 1066 | + mock_client = mocker.AsyncMock() |
| 1067 | + mocker.patch("utils.responses.get_mcp_tools", return_value=None) |
| 1068 | + |
| 1069 | + # Configure BYOK RAG mapping |
| 1070 | + mock_byok_rag = mocker.Mock() |
| 1071 | + mock_byok_rag.rag_id = "ocp_docs" |
| 1072 | + mock_byok_rag.vector_db_id = "vs-001" |
| 1073 | + mock_config = mocker.Mock() |
| 1074 | + mock_config.configuration.byok_rag = [mock_byok_rag] |
| 1075 | + mocker.patch("utils.responses.configuration", mock_config) |
| 1076 | + |
| 1077 | + result = await prepare_tools(mock_client, ["ocp_docs"], False, "token") |
| 1078 | + assert result is not None |
| 1079 | + assert len(result) == 1 |
| 1080 | + assert result[0].type == "file_search" |
| 1081 | + assert result[0].vector_store_ids == ["vs-001"] |
| 1082 | + |
| 1083 | + @pytest.mark.asyncio |
| 1084 | + async def test_passes_through_unknown_ids_in_prepare_tools( |
| 1085 | + self, mocker: MockerFixture |
| 1086 | + ) -> None: |
| 1087 | + """Test that prepare_tools passes through IDs not in BYOK config.""" |
| 1088 | + mock_client = mocker.AsyncMock() |
| 1089 | + mocker.patch("utils.responses.get_mcp_tools", return_value=None) |
| 1090 | + |
| 1091 | + # Configure empty BYOK RAG |
| 1092 | + mock_config = mocker.Mock() |
| 1093 | + mock_config.configuration.byok_rag = [] |
| 1094 | + mocker.patch("utils.responses.configuration", mock_config) |
| 1095 | + |
| 1096 | + result = await prepare_tools(mock_client, ["raw-internal-id"], False, "token") |
| 1097 | + assert result is not None |
| 1098 | + assert result[0].vector_store_ids == ["raw-internal-id"] |
| 1099 | + |
| 1100 | + @pytest.mark.asyncio |
| 1101 | + async def test_does_not_translate_when_ids_fetched_from_llama_stack( |
| 1102 | + self, mocker: MockerFixture |
| 1103 | + ) -> None: |
| 1104 | + """Test that IDs fetched from llama-stack (None path) are not translated.""" |
| 1105 | + mock_client = mocker.AsyncMock() |
| 1106 | + mock_vector_store = mocker.Mock() |
| 1107 | + mock_vector_store.id = "vs-internal" |
| 1108 | + mock_vector_stores = mocker.Mock() |
| 1109 | + mock_vector_stores.data = [mock_vector_store] |
| 1110 | + mock_client.vector_stores.list = mocker.AsyncMock( |
| 1111 | + return_value=mock_vector_stores |
| 1112 | + ) |
| 1113 | + mocker.patch("utils.responses.get_mcp_tools", return_value=None) |
| 1114 | + |
| 1115 | + # Configure BYOK RAG whose rag_id matches the fetched ID so that |
| 1116 | + # accidental translation would change the result and fail the assertion |
| 1117 | + mock_byok_rag = mocker.Mock() |
| 1118 | + mock_byok_rag.rag_id = "vs-internal" |
| 1119 | + mock_byok_rag.vector_db_id = "vs-translated" |
| 1120 | + mock_config = mocker.Mock() |
| 1121 | + mock_config.configuration.byok_rag = [mock_byok_rag] |
| 1122 | + mocker.patch("utils.responses.configuration", mock_config) |
| 1123 | + |
| 1124 | + result = await prepare_tools(mock_client, None, False, "token") |
| 1125 | + assert result is not None |
| 1126 | + # The IDs from llama-stack should be used as-is (no BYOK translation on None path) |
| 1127 | + assert result[0].vector_store_ids == ["vs-internal"] |
| 1128 | + |
| 1129 | + |
1003 | 1130 | class TestPrepareResponsesParams: |
1004 | 1131 | """Tests for prepare_responses_params function.""" |
1005 | 1132 |
|
|
0 commit comments