|
2 | 2 | # |
3 | 3 | # SPDX-License-Identifier: Apache-2.0 |
4 | 4 |
|
| 5 | +import yaml |
| 6 | +from pydantic import BaseModel |
| 7 | + |
5 | 8 | from deepset_mcp.api.exceptions import BadRequestError, ResourceNotFoundError, UnexpectedAPIError |
6 | 9 | from deepset_mcp.api.indexes.models import Index |
7 | 10 | from deepset_mcp.api.pipeline.models import PipelineValidationResult |
8 | 11 | from deepset_mcp.api.protocols import AsyncClientProtocol |
9 | 12 | from deepset_mcp.api.shared_models import PaginatedResponse |
10 | 13 |
|
11 | 14 |
|
| 15 | +class IndexValidationResultWithYaml(BaseModel): |
| 16 | + """Model for index validation result that includes the original YAML.""" |
| 17 | + |
| 18 | + validation_result: PipelineValidationResult |
| 19 | + "Result of validating the index configuration" |
| 20 | + yaml_config: str |
| 21 | + "Original YAML configuration that was validated" |
| 22 | + |
| 23 | + |
| 24 | +class IndexOperationWithErrors(BaseModel): |
| 25 | + """Model for index operations that complete with validation errors.""" |
| 26 | + |
| 27 | + message: str |
| 28 | + "Descriptive message about the index operation" |
| 29 | + validation_result: PipelineValidationResult |
| 30 | + "Validation errors encountered during the operation" |
| 31 | + index: Index |
| 32 | + "Index object after the operation completed" |
| 33 | + |
| 34 | + |
12 | 35 | async def list_indexes( |
13 | 36 | *, client: AsyncClientProtocol, workspace: str, after: str | None = None |
14 | 37 | ) -> PaginatedResponse[Index] | str: |
@@ -43,6 +66,33 @@ async def get_index(*, client: AsyncClientProtocol, workspace: str, index_name: |
43 | 66 | return response |
44 | 67 |
|
45 | 68 |
|
| 69 | +async def validate_index( |
| 70 | + *, client: AsyncClientProtocol, workspace: str, yaml_configuration: str |
| 71 | +) -> IndexValidationResultWithYaml | str: |
| 72 | + """Validates the provided index YAML configuration against the deepset API. |
| 73 | +
|
| 74 | + :param client: The async client for API communication. |
| 75 | + :param workspace: The workspace name. |
| 76 | + :param yaml_configuration: The YAML configuration to validate. |
| 77 | + :returns: Validation result with original YAML or error message. |
| 78 | + """ |
| 79 | + if not yaml_configuration or not yaml_configuration.strip(): |
| 80 | + return "You need to provide a YAML configuration to validate." |
| 81 | + |
| 82 | + try: |
| 83 | + yaml.safe_load(yaml_configuration) |
| 84 | + except yaml.YAMLError as e: |
| 85 | + return f"Invalid YAML provided: {e}" |
| 86 | + |
| 87 | + try: |
| 88 | + response = await client.indexes(workspace=workspace).validate(yaml_configuration) |
| 89 | + return IndexValidationResultWithYaml(validation_result=response, yaml_config=yaml_configuration) |
| 90 | + except ResourceNotFoundError: |
| 91 | + return f"There is no workspace named '{workspace}'. Did you mean to configure it?" |
| 92 | + except (BadRequestError, UnexpectedAPIError) as e: |
| 93 | + return f"Failed to validate index: {e}" |
| 94 | + |
| 95 | + |
46 | 96 | async def create_index( |
47 | 97 | *, |
48 | 98 | client: AsyncClientProtocol, |
@@ -78,35 +128,78 @@ async def update_index( |
78 | 128 | client: AsyncClientProtocol, |
79 | 129 | workspace: str, |
80 | 130 | index_name: str, |
81 | | - updated_index_name: str | None = None, |
82 | | - yaml_configuration: str | None = None, |
83 | | -) -> dict[str, str | Index] | str: |
84 | | - """Updates an existing index in your deepset platform workspace. |
| 131 | + original_config_snippet: str, |
| 132 | + replacement_config_snippet: str, |
| 133 | + skip_validation_errors: bool = True, |
| 134 | +) -> Index | IndexOperationWithErrors | str: |
| 135 | + """ |
| 136 | + Updates an index configuration in the specified workspace with a replacement configuration snippet. |
85 | 137 |
|
86 | | - This function can update either the name or the configuration of an existing index, or both. |
87 | | - At least one of updated_index_name or yaml_configuration must be provided. |
| 138 | + This function validates the replacement configuration snippet before applying it to the index. |
| 139 | + If the validation fails and skip_validation_errors is False, it returns error messages. |
| 140 | + Otherwise, the replacement snippet is used to update the index's configuration. |
88 | 141 |
|
89 | | - :param client: Deepset API client to use. |
90 | | - :param workspace: Workspace in which to update the index. |
91 | | - :param index_name: Unique name of the index to update. |
92 | | - :param updated_index_name: Updated name of the index. |
93 | | - :param yaml_configuration: YAML configuration to update the index with. |
| 142 | + :param client: The async client for API communication. |
| 143 | + :param workspace: The workspace name. |
| 144 | + :param index_name: Name of the index to update. |
| 145 | + :param original_config_snippet: The configuration snippet to replace. |
| 146 | + :param replacement_config_snippet: The new configuration snippet. |
| 147 | + :param skip_validation_errors: If True (default), updates the index even if validation fails. |
| 148 | + If False, stops update when validation fails. |
| 149 | + :returns: Updated index or error message. |
94 | 150 | """ |
95 | | - if not updated_index_name and not yaml_configuration: |
96 | | - return "You must provide either a new name or a new configuration to update the index." |
97 | | - |
98 | 151 | try: |
99 | | - result = await client.indexes(workspace=workspace).update( |
100 | | - index_name=index_name, updated_index_name=updated_index_name, yaml_config=yaml_configuration |
| 152 | + original_index = await client.indexes(workspace=workspace).get(index_name=index_name) |
| 153 | + except ResourceNotFoundError: |
| 154 | + return f"There is no index named '{index_name}'. Did you mean to create it?" |
| 155 | + except (BadRequestError, UnexpectedAPIError) as e: |
| 156 | + return f"Failed to fetch index '{index_name}': {e}" |
| 157 | + |
| 158 | + if original_index.yaml_config is None: |
| 159 | + return f"The index '{index_name}' does not have a YAML configuration." |
| 160 | + |
| 161 | + occurrences = original_index.yaml_config.count(original_config_snippet) |
| 162 | + |
| 163 | + if occurrences == 0: |
| 164 | + return f"No occurrences of the provided configuration snippet were found in the index '{index_name}'." |
| 165 | + |
| 166 | + if occurrences > 1: |
| 167 | + return ( |
| 168 | + f"Multiple occurrences ({occurrences}) of the provided configuration snippet were found in the index " |
| 169 | + f"'{index_name}'. Specify a more precise snippet to proceed with the update." |
101 | 170 | ) |
| 171 | + |
| 172 | + updated_yaml_configuration = original_index.yaml_config.replace( |
| 173 | + original_config_snippet, replacement_config_snippet, 1 |
| 174 | + ) |
| 175 | + |
| 176 | + try: |
| 177 | + validation_response = await client.indexes(workspace=workspace).validate(updated_yaml_configuration) |
| 178 | + |
| 179 | + if not validation_response.valid and not skip_validation_errors: |
| 180 | + error_messages = [f"{error.code}: {error.message}" for error in validation_response.errors] |
| 181 | + return "Index validation failed:\n" + "\n".join(error_messages) |
| 182 | + |
| 183 | + await client.indexes(workspace=workspace).update(index_name=index_name, yaml_config=updated_yaml_configuration) |
| 184 | + |
| 185 | + # Get the full index after update |
| 186 | + index = await client.indexes(workspace=workspace).get(index_name) |
| 187 | + |
| 188 | + # If validation failed but we proceeded anyway, return the special model |
| 189 | + if not validation_response.valid: |
| 190 | + return IndexOperationWithErrors( |
| 191 | + message="The operation completed with errors", validation_result=validation_response, index=index |
| 192 | + ) |
| 193 | + |
| 194 | + # Otherwise return just the index |
| 195 | + return index |
| 196 | + |
102 | 197 | except ResourceNotFoundError: |
103 | 198 | return f"There is no index named '{index_name}'. Did you mean to create it?" |
104 | 199 | except BadRequestError as e: |
105 | | - return f"Failed to update index '{index_name}': {e}" |
| 200 | + return f"Failed to update the index '{index_name}': {e}" |
106 | 201 | except UnexpectedAPIError as e: |
107 | | - return f"Failed to update index '{index_name}': {e}" |
108 | | - |
109 | | - return {"message": f"Index '{index_name}' updated successfully.", "index": result} |
| 202 | + return f"Failed to update the index '{index_name}': {e}" |
110 | 203 |
|
111 | 204 |
|
112 | 205 | async def deploy_index( |
|
0 commit comments