Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
128f739
Import Search Evaluation
epugh Jun 13, 2025
662b3f5
Use correct package path
epugh Jun 13, 2025
ea68771
working POC style code
epugh Jun 14, 2025
117723c
Remove extra code that is not used
epugh Jun 14, 2025
e158809
enhance the test to reflect modern data structure for metrics
epugh Jun 14, 2025
fe0f9c3
Handle async failures better
epugh Jun 14, 2025
bf414db
Update fields to the new esci schema
epugh Jun 14, 2025
53233e5
150 query set imported, easy way to test import feature plus have act…
epugh Jun 14, 2025
ec8a3f6
Fix null safety in RestPostExperimentAction and update test expectations
sstults Jun 26, 2025
d87239a
Add validation to all fields in the request
sstults Aug 2, 2025
621624c
Add limit to the number of results we process
sstults Aug 2, 2025
797d999
Add logic to ERROR results if one of the entries fails, if all succee…
sstults Aug 2, 2025
614a78e
Changed the constant we reference for queryText
sstults Aug 4, 2025
b258ad2
Update src/test/data-esci/README.md
epugh Aug 4, 2025
12cb96a
Merge remote-tracking branch 'upstream/main' into use_post_to_import_…
epugh Aug 5, 2025
ec0ba8f
Clean up changelog
epugh Aug 5, 2025
6530990
Merge remote-tracking branch 'upstream/main' into use_post_to_import_…
epugh Aug 21, 2025
517c769
Responding to feedback
epugh Aug 21, 2025
2483142
Merge remote-tracking branch 'upstream/main' into use_post_to_import_…
epugh Aug 22, 2025
4132524
Correct the filename and provide some instructions on using it
epugh Aug 22, 2025
8d9443e
Correct the filename and provide some instructions on using it
epugh Aug 22, 2025
f447486
Merge branch 'opensearch-project:main' into use_post_to_import_experi…
epugh Aug 25, 2025
0c5ee12
Now have a way of testing a real export from another tool (Quepid) in…
epugh Aug 25, 2025
f2c25fd
Ensure that Integer ratings do not cause problems by making them strings
epugh Aug 25, 2025
bf70771
Merge remote-tracking branch 'upstream/main' into use_post_to_import_…
epugh Sep 6, 2025
3c1f109
Demonstrate importing a pointwise experiment.
epugh Sep 6, 2025
dea5c09
Language tweak
epugh Sep 6, 2025
dc97ef5
When importing, you need the experimentId to tie all the evaluation r…
epugh Sep 6, 2025
a670d37
Prep to have a pairwise example too
epugh Sep 6, 2025
dd9d1d5
Refactoring to prep for adding importPairwise experiment
epugh Sep 6, 2025
e9f633d
Introduce capablity to import a PAIRWISE_COMPARISON as well.
epugh Sep 6, 2025
d5476fb
Script demonstrating pairwise import.
epugh Sep 6, 2025
f7b5f3e
Fix constnats (respond to feedback)
epugh Sep 6, 2025
7b21763
lint
epugh Sep 6, 2025
914a79b
Reduce level of detail, prompted by feedback
epugh Sep 7, 2025
e3f1d90
Allow these demos to be run in any opensearch environment that has SR…
epugh Sep 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ testfixtures_shared/
# These are generated from .ci/jobs.t
.ci/jobs/
src/test/scripts/esci_us_opensearch-2025-06-06.json
src/test/data-external-evaluation/movies_experiment_tmp.json
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### Features

### Removed

### Enhancements

### Bug Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ private PluginConstants() {}
public static final String QUERYSET_ID = "querySetId";
public static final String SEARCH_CONFIGURATION_LIST = "searchConfigurationList";
public static final String JUDGMENT_LIST = "judgmentList";
public static final String EVALUATION_RESULT_LIST = "evaluationResultList";
public static final String RESULTS = "results";

public static final String JUDGMENT_RATINGS = "judgmentRatings";
public static final String CONTEXT_FIELDS = "contextFields";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ public EvaluationResult(
this(id, timestamp, searchConfigurationId, searchText, judgmentIds, documentIds, metrics, null, null, null);
}

public EvaluationResult(
String id,
String timestamp,
String experimentId,
String searchConfigurationId,
String searchText,
List<String> judgmentIds,
List<String> documentIds,
List<Map<String, Object>> metrics
) {
this(id, timestamp, searchConfigurationId, searchText, judgmentIds, documentIds, metrics, experimentId, null, null);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
XContentBuilder xContentBuilder = builder.startObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.opensearch.searchrelevance.rest.RestGetJudgmentAction;
import org.opensearch.searchrelevance.rest.RestGetQuerySetAction;
import org.opensearch.searchrelevance.rest.RestGetSearchConfigurationAction;
import org.opensearch.searchrelevance.rest.RestPostExperimentAction;
import org.opensearch.searchrelevance.rest.RestPutExperimentAction;
import org.opensearch.searchrelevance.rest.RestPutJudgmentAction;
import org.opensearch.searchrelevance.rest.RestPutQuerySetAction;
Expand All @@ -75,6 +76,8 @@
import org.opensearch.searchrelevance.transport.experiment.DeleteExperimentTransportAction;
import org.opensearch.searchrelevance.transport.experiment.GetExperimentAction;
import org.opensearch.searchrelevance.transport.experiment.GetExperimentTransportAction;
import org.opensearch.searchrelevance.transport.experiment.PostExperimentAction;
import org.opensearch.searchrelevance.transport.experiment.PostExperimentTransportAction;
import org.opensearch.searchrelevance.transport.experiment.PutExperimentAction;
import org.opensearch.searchrelevance.transport.experiment.PutExperimentTransportAction;
import org.opensearch.searchrelevance.transport.judgment.DeleteJudgmentAction;
Expand Down Expand Up @@ -210,6 +213,7 @@ public List<RestHandler> getRestHandlers(
new RestPutSearchConfigurationAction(settingsAccessor),
new RestDeleteSearchConfigurationAction(settingsAccessor),
new RestGetSearchConfigurationAction(settingsAccessor),
new RestPostExperimentAction(settingsAccessor),
new RestPutExperimentAction(settingsAccessor),
new RestGetExperimentAction(settingsAccessor),
new RestDeleteExperimentAction(settingsAccessor),
Expand All @@ -230,6 +234,7 @@ public List<RestHandler> getRestHandlers(
new ActionHandler<>(PutSearchConfigurationAction.INSTANCE, PutSearchConfigurationTransportAction.class),
new ActionHandler<>(DeleteSearchConfigurationAction.INSTANCE, DeleteSearchConfigurationTransportAction.class),
new ActionHandler<>(GetSearchConfigurationAction.INSTANCE, GetSearchConfigurationTransportAction.class),
new ActionHandler<>(PostExperimentAction.INSTANCE, PostExperimentTransportAction.class),
new ActionHandler<>(PutExperimentAction.INSTANCE, PutExperimentTransportAction.class),
new ActionHandler<>(DeleteExperimentAction.INSTANCE, DeleteExperimentTransportAction.class),
new ActionHandler<>(GetExperimentAction.INSTANCE, GetExperimentTransportAction.class),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
package org.opensearch.searchrelevance.rest;

import static java.util.Collections.singletonList;
import static org.opensearch.rest.RestRequest.Method.POST;
import static org.opensearch.searchrelevance.common.PluginConstants.EVALUATION_RESULT_LIST;
import static org.opensearch.searchrelevance.common.PluginConstants.EXPERIMENTS_URI;
import static org.opensearch.searchrelevance.common.PluginConstants.JUDGMENT_LIST;
import static org.opensearch.searchrelevance.common.PluginConstants.QUERYSET_ID;
import static org.opensearch.searchrelevance.common.PluginConstants.RESULTS;
import static org.opensearch.searchrelevance.common.PluginConstants.SEARCH_CONFIGURATION_LIST;
import static org.opensearch.searchrelevance.common.PluginConstants.SIZE;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.opensearch.action.index.IndexResponse;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.rest.BaseRestHandler;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestRequest;
import org.opensearch.searchrelevance.exception.SearchRelevanceException;
import org.opensearch.searchrelevance.model.ExperimentType;
import org.opensearch.searchrelevance.settings.SearchRelevanceSettingsAccessor;
import org.opensearch.searchrelevance.transport.experiment.PostExperimentAction;
import org.opensearch.searchrelevance.transport.experiment.PostExperimentRequest;
import org.opensearch.searchrelevance.utils.ParserUtils;
import org.opensearch.transport.client.node.NodeClient;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Log4j2
/**
* Rest Action to facilitate requests to import a experiment.
*/
@AllArgsConstructor
public class RestPostExperimentAction extends BaseRestHandler {
private static final String POST_EXPERIMENT_ACTION = "post_experiment_action";
private SearchRelevanceSettingsAccessor settingsAccessor;

@Override
public String getName() {
return POST_EXPERIMENT_ACTION;
}

@Override
public List<Route> routes() {
return singletonList(new Route(POST, EXPERIMENTS_URI));
}

@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
if (!settingsAccessor.isWorkbenchEnabled()) {
return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "Search Relevance Workbench is disabled"));
}

PostExperimentRequest createRequest = null;
XContentParser parser = request.contentParser();
Map<String, Object> source = parser.map();

String typeString = (String) source.get("type");
ExperimentType type;
try {
type = ExperimentType.valueOf(typeString);
} catch (IllegalArgumentException | NullPointerException e) {
throw new IllegalArgumentException("Invalid or missing experiment type", e);
}

if (type == ExperimentType.POINTWISE_EVALUATION) {
String querySetId = (String) source.get(QUERYSET_ID);
List<String> searchConfigurationList = ParserUtils.convertObjToList(source, SEARCH_CONFIGURATION_LIST);
if (searchConfigurationList.size() != 1) {
throw new IOException("Must have exactly one search configuration. Had " + searchConfigurationList.size() + " size.");
}
Integer sizeObj = (Integer) source.get(SIZE);
int size = sizeObj != null ? sizeObj.intValue() : 10; // Default size to 10 if not provided

List<String> judgmentList = ParserUtils.convertObjToList(source, JUDGMENT_LIST);
if (judgmentList.size() != 1) {
throw new IOException("Must have exactly one judgment list. Had " + judgmentList.size() + " size.");
}

List<Map<String, Object>> evaluationResultList = ParserUtils.convertObjToListOfMaps(source, EVALUATION_RESULT_LIST);

createRequest = new PostExperimentRequest(
type,
querySetId,
searchConfigurationList,
judgmentList,
size,
evaluationResultList,
null
);
} else if (type == ExperimentType.PAIRWISE_COMPARISON) {
String querySetId = (String) source.get(QUERYSET_ID);
List<String> searchConfigurationList = ParserUtils.convertObjToList(source, SEARCH_CONFIGURATION_LIST);
if (searchConfigurationList.size() != 2) {
throw new IOException("Must have exactly two search configurations. Had " + searchConfigurationList.size() + " size.");
}
Integer sizeObj = (Integer) source.get(SIZE);
int size = sizeObj != null ? sizeObj.intValue() : 10; // Default size to 10 if not provided

List<Map<String, Object>> results = ParserUtils.convertObjToListOfMaps(source, RESULTS);

createRequest = new PostExperimentRequest(type, querySetId, searchConfigurationList, List.of(), size, null, results);
} else {
throw new SearchRelevanceException("Importing experimentType" + type + " is not supported", RestStatus.BAD_REQUEST);
}

PostExperimentRequest finalCreateRequest = createRequest;
return channel -> client.execute(PostExperimentAction.INSTANCE, finalCreateRequest, new ActionListener<IndexResponse>() {
@Override
public void onResponse(IndexResponse response) {
try {
XContentBuilder builder = channel.newBuilder();
builder.startObject();
builder.field("experiment_id", response.getId());
builder.field("experiment_result", response.getResult());
builder.endObject();
channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder));
} catch (IOException e) {
onFailure(e);
}
}

@Override
public void onFailure(Exception e) {
try {
channel.sendResponse(new BytesRestResponse(channel, RestStatus.INTERNAL_SERVER_ERROR, e));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please update the error handling here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you share some more detail on what you are looking for?

} catch (IOException ex) {
log.error("Failed to send error response", ex);
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

@Log4j2
/**
* Rest Action to facilitate requests to create a experiment.
* Rest Action to facilitate requests to create an experiment.
*/
@AllArgsConstructor
public class RestPutExperimentAction extends BaseRestHandler {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
package org.opensearch.searchrelevance.transport.experiment;

import static org.opensearch.searchrelevance.common.PluginConstants.TRANSPORT_ACTION_NAME_PREFIX;

import org.opensearch.action.ActionType;
import org.opensearch.action.index.IndexResponse;

/**
* External Action for public facing RestPostExperimentAction
*/
public class PostExperimentAction extends ActionType<IndexResponse> {
/** The name of this action */
public static final String NAME = TRANSPORT_ACTION_NAME_PREFIX + "experiment/import";

/** An instance of this action */
public static final PostExperimentAction INSTANCE = new PostExperimentAction();

private PostExperimentAction() {
super(NAME, IndexResponse::new);
}
}
Loading
Loading