Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OPIK-72 Make experiment name optional #267

Merged
merged 3 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.comet.opik.infrastructure.bundle.LiquibaseBundle;
import com.comet.opik.infrastructure.db.DatabaseAnalyticsModule;
import com.comet.opik.infrastructure.db.IdGeneratorModule;
import com.comet.opik.infrastructure.db.NameGeneratorModule;
import com.comet.opik.infrastructure.redis.RedisModule;
import com.comet.opik.utils.JsonBigDecimalDeserializer;
import com.fasterxml.jackson.annotation.JsonInclude;
Expand Down Expand Up @@ -58,7 +59,8 @@ public void initialize(Bootstrap<OpikConfiguration> bootstrap) {
bootstrap.addBundle(GuiceBundle.builder()
.bundles(JdbiBundle.<OpikConfiguration>forDatabase((conf, env) -> conf.getDatabase())
.withPlugins(new SqlObjectPlugin(), new Jackson2Plugin()))
.modules(new DatabaseAnalyticsModule(), new IdGeneratorModule(), new AuthModule(), new RedisModule())
.modules(new DatabaseAnalyticsModule(), new IdGeneratorModule(), new AuthModule(), new RedisModule(),
new NameGeneratorModule())
.enableAutoConfig()
.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public record Experiment(
Experiment.View.Public.class, Experiment.View.Write.class}) UUID id,
@JsonView({Experiment.View.Public.class, Experiment.View.Write.class}) @NotBlank String datasetName,
@JsonView({Experiment.View.Public.class}) @Schema(accessMode = Schema.AccessMode.READ_ONLY) UUID datasetId,
@JsonView({Experiment.View.Public.class, Experiment.View.Write.class}) @NotBlank String name,
@JsonView({Experiment.View.Public.class, Experiment.View.Write.class}) String name,
@JsonView({Experiment.View.Public.class, Experiment.View.Write.class}) JsonNode metadata,
@JsonView({
Experiment.View.Public.class}) @Schema(accessMode = Schema.AccessMode.READ_ONLY) List<FeedbackScoreAverage> feedbackScores,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

Expand All @@ -31,6 +32,7 @@ public class ExperimentService {
private final @NonNull ExperimentDAO experimentDAO;
private final @NonNull DatasetService datasetService;
private final @NonNull IdGenerator idGenerator;
private final @NonNull NameGenerator nameGenerator;

public Mono<Experiment.ExperimentPage> find(
int page, int size, @NonNull ExperimentSearchCriteria experimentSearchCriteria) {
Expand Down Expand Up @@ -73,34 +75,35 @@ public Mono<Experiment> getById(@NonNull UUID id) {
public Mono<Experiment> create(@NonNull Experiment experiment) {
var id = experiment.id() == null ? idGenerator.generateId() : experiment.id();
IdGenerator.validateVersion(id, "Experiment");
var name = StringUtils.getIfBlank(experiment.name(), nameGenerator::generateName);

return getOrCreateDataset(experiment)
.onErrorResume(e -> handleDatasetCreationError(e, experiment).map(Dataset::id))
.flatMap(datasetId -> create(experiment, id, datasetId))
return getOrCreateDataset(experiment.datasetName())
.onErrorResume(e -> handleDatasetCreationError(e, experiment.datasetName()).map(Dataset::id))
.flatMap(datasetId -> create(experiment, id, name, datasetId))
.onErrorResume(exception -> handleCreateError(exception, id));
}

private Mono<UUID> getOrCreateDataset(Experiment experiment) {
private Mono<UUID> getOrCreateDataset(String datasetName) {
return Mono.deferContextual(ctx -> {
String userName = ctx.get(RequestContext.USER_NAME);
String workspaceId = ctx.get(RequestContext.WORKSPACE_ID);

return Mono.fromCallable(() -> datasetService.getOrCreate(workspaceId, experiment.datasetName(), userName))
return Mono.fromCallable(() -> datasetService.getOrCreate(workspaceId, datasetName, userName))
.subscribeOn(Schedulers.boundedElastic());
});
}

private Mono<Experiment> create(Experiment experiment, UUID id, UUID datasetId) {
var newExperiment = experiment.toBuilder().id(id).datasetId(datasetId).build();
return experimentDAO.insert(newExperiment).thenReturn(newExperiment);
private Mono<Experiment> create(Experiment experiment, UUID id, String name, UUID datasetId) {
experiment = experiment.toBuilder().id(id).name(name).datasetId(datasetId).build();
return experimentDAO.insert(experiment).thenReturn(experiment);
}

private Mono<Dataset> handleDatasetCreationError(Throwable throwable, Experiment experiment) {
private Mono<Dataset> handleDatasetCreationError(Throwable throwable, String datasetName) {
if (throwable instanceof EntityAlreadyExistsException) {
return Mono.deferContextual(ctx -> {
String workspaceId = ctx.get(RequestContext.WORKSPACE_ID);

return Mono.fromCallable(() -> datasetService.findByName(workspaceId, experiment.datasetName()))
return Mono.fromCallable(() -> datasetService.findByName(workspaceId, datasetName))
.subscribeOn(Schedulers.boundedElastic());
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.comet.opik.domain;

import lombok.Builder;
import lombok.NonNull;

import java.security.SecureRandom;
import java.util.List;

@Builder
public class NameGenerator {

private final @NonNull SecureRandom secureRandom;

private final @NonNull List<String> adjectives;
private final @NonNull List<String> nouns;

public String generateName() {
var adjective = getRandom(adjectives);
var noun = getRandom(nouns);
var number = secureRandom.nextInt(0, 10000);
return "%s_%s_%s".formatted(adjective, noun, number);
}

private String getRandom(List<String> strings) {
int index = secureRandom.nextInt(0, strings.size());
return strings.get(index);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.comet.opik.infrastructure.db;

import com.comet.opik.domain.NameGenerator;
import com.comet.opik.infrastructure.OpikConfiguration;
import com.comet.opik.utils.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.inject.Provides;
import jakarta.inject.Singleton;
import ru.vyarus.dropwizard.guice.module.support.DropwizardAwareModule;

import java.io.FileNotFoundException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;

public class NameGeneratorModule extends DropwizardAwareModule<OpikConfiguration> {

private static final TypeReference<List<String>> STRING_LIST_TYPE_REFERENCE = new TypeReference<>() {
};

@Provides
@Singleton
public NameGenerator getNameGenerator() throws FileNotFoundException, NoSuchAlgorithmException {
return NameGenerator.builder()
.secureRandom(SecureRandom.getInstanceStrong())
.adjectives(getResource("/name-generator/adjectives.json"))
.nouns(getResource("/name-generator/nouns.json"))
.build();
}

private List<String> getResource(String path) throws FileNotFoundException {
var inputStream = NameGeneratorModule.class.getResourceAsStream(path);
if (inputStream == null) {
throw new FileNotFoundException("Resource not found in path '%s'".formatted(path));
}
return JsonUtils.readValue(inputStream, NameGeneratorModule.STRING_LIST_TYPE_REFERENCE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.math.BigDecimal;

Expand Down Expand Up @@ -46,6 +47,14 @@ public <T> T readValue(@NonNull String content, @NonNull TypeReference<T> valueT
}
}

public <T> T readValue(@NonNull InputStream inputStream, @NonNull TypeReference<T> valueTypeRef) {
try {
return MAPPER.readValue(inputStream, valueTypeRef);
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
}

public String writeValueAsString(@NonNull Object value) {
try {
return MAPPER.writeValueAsString(value);
Expand Down
Loading