MME-4 Replace recipe embedding job with recipe summary job; rename recipe_embedding to recipe_summary and add column.
This commit is contained in:
parent
164a3aa5f0
commit
fa7afbaa76
@ -23,8 +23,7 @@ import java.util.UUID;
|
|||||||
import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes;
|
import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
// TODO: test mainImage included
|
// TODO: test mainImage included
|
||||||
// TODO: test prep/cooking/total times included
|
// TODO: test prep/cooking/total times included
|
||||||
@ -61,7 +60,7 @@ public class RecipeServiceTests {
|
|||||||
.rawText("Hello!")
|
.rawText("Hello!")
|
||||||
.isPublic(isPublic)
|
.isPublic(isPublic)
|
||||||
.build();
|
.build();
|
||||||
return this.recipeService.create(owner, spec);
|
return this.recipeService.create(owner, spec, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -259,7 +258,7 @@ public class RecipeServiceTests {
|
|||||||
.title("My Recipe")
|
.title("My Recipe")
|
||||||
.rawText("# A Heading")
|
.rawText("# A Heading")
|
||||||
.build();
|
.build();
|
||||||
Recipe recipe = this.recipeService.create(owner, createSpec);
|
Recipe recipe = this.recipeService.create(owner, createSpec, false);
|
||||||
final String newRawText = "# A Heading\n## A Subheading";
|
final String newRawText = "# A Heading\n## A Subheading";
|
||||||
final RecipeUpdateSpec updateSpec = RecipeUpdateSpec.fromRecipeToBuilder(recipe)
|
final RecipeUpdateSpec updateSpec = RecipeUpdateSpec.fromRecipeToBuilder(recipe)
|
||||||
.rawText(newRawText)
|
.rawText(newRawText)
|
||||||
|
|||||||
@ -83,7 +83,7 @@ public class RecipesControllerTests {
|
|||||||
.rawText("# Hello, World!")
|
.rawText("# Hello, World!")
|
||||||
.isPublic(isPublic)
|
.isPublic(isPublic)
|
||||||
.build();
|
.build();
|
||||||
return this.recipeService.create(owner, spec);
|
return this.recipeService.create(owner, spec, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAccessToken(User user) throws LoginException {
|
private String getAccessToken(User user) throws LoginException {
|
||||||
@ -261,7 +261,7 @@ public class RecipesControllerTests {
|
|||||||
.rawText("# Hello, World!")
|
.rawText("# Hello, World!")
|
||||||
.mainImage(hal9000)
|
.mainImage(hal9000)
|
||||||
.build();
|
.build();
|
||||||
Recipe recipe = this.recipeService.create(owner, createSpec);
|
Recipe recipe = this.recipeService.create(owner, createSpec, false);
|
||||||
|
|
||||||
final RecipeUpdateSpec updateSpec = RecipeUpdateSpec.builder()
|
final RecipeUpdateSpec updateSpec = RecipeUpdateSpec.builder()
|
||||||
.title("Updated Test Recipe")
|
.title("Updated Test Recipe")
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import java.util.UUID;
|
|||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(PostgresTestsExtension.class)
|
@ExtendWith(PostgresTestsExtension.class)
|
||||||
@ -49,7 +49,7 @@ public class RecipeStarServiceTests {
|
|||||||
.rawText("My great recipe has five ingredients.")
|
.rawText("My great recipe has five ingredients.")
|
||||||
.isPublic(true)
|
.isPublic(true)
|
||||||
.build();
|
.build();
|
||||||
return this.recipeService.create(owner, spec);
|
return this.recipeService.create(owner, spec, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -3,8 +3,7 @@ package app.mealsmadeeasy.api;
|
|||||||
import app.mealsmadeeasy.api.job.JobService;
|
import app.mealsmadeeasy.api.job.JobService;
|
||||||
import app.mealsmadeeasy.api.recipe.Recipe;
|
import app.mealsmadeeasy.api.recipe.Recipe;
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
||||||
import app.mealsmadeeasy.api.recipe.job.RecipeEmbeddingJobHandler;
|
import app.mealsmadeeasy.api.recipe.job.RecipeSummaryJobHandler;
|
||||||
import app.mealsmadeeasy.api.recipe.job.RecipeEmbeddingJobPayload;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.boot.ApplicationArguments;
|
import org.springframework.boot.ApplicationArguments;
|
||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
@ -23,9 +22,12 @@ public class BackfillRecipeEmbeddings implements ApplicationRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(ApplicationArguments args) {
|
public void run(ApplicationArguments args) {
|
||||||
final List<Recipe> recipeEntities = this.recipeRepository.findAllByEmbeddingIsNull();
|
final List<Recipe> recipeEntities = this.recipeRepository.findAllBySummaryIsNull();
|
||||||
for (final Recipe recipe : recipeEntities) {
|
for (final Recipe recipe : recipeEntities) {
|
||||||
this.jobService.create(RecipeEmbeddingJobHandler.JOB_KEY, new RecipeEmbeddingJobPayload(recipe.getId()));
|
this.jobService.create(
|
||||||
|
RecipeSummaryJobHandler.JOB_KEY,
|
||||||
|
new RecipeSummaryJobHandler.RecipeSummaryJobPayload(recipe.getId())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.recipeRepository.flush();
|
this.recipeRepository.flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,7 @@ public class DevConfiguration {
|
|||||||
.isPublic(frontMatter.isPublic)
|
.isPublic(frontMatter.isPublic)
|
||||||
.mainImage(mainImage)
|
.mainImage(mainImage)
|
||||||
.build();
|
.build();
|
||||||
final Recipe recipe = this.recipeService.create(testUser, recipeCreateSpec);
|
final Recipe recipe = this.recipeService.create(testUser, recipeCreateSpec, false);
|
||||||
logger.info("Created recipe {}", recipe);
|
logger.info("Created recipe {}", recipe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,14 @@ import org.springframework.ai.converter.BeanOutputConverter;
|
|||||||
import org.springframework.ai.ollama.api.OllamaChatOptions;
|
import org.springframework.ai.ollama.api.OllamaChatOptions;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class InferenceService {
|
public class InferenceService {
|
||||||
|
|
||||||
|
private static final String OLLAMA_MODEL = "gemma3:latest";
|
||||||
|
|
||||||
private final ChatModel chatModel;
|
private final ChatModel chatModel;
|
||||||
|
|
||||||
public <T> @Nullable T extract(
|
public <T> @Nullable T extract(
|
||||||
@ -23,7 +27,7 @@ public class InferenceService {
|
|||||||
BeanOutputConverter<T> converter
|
BeanOutputConverter<T> converter
|
||||||
) {
|
) {
|
||||||
final ChatOptions extractChatOptions = OllamaChatOptions.builder()
|
final ChatOptions extractChatOptions = OllamaChatOptions.builder()
|
||||||
.model("gemma3:latest")
|
.model(OLLAMA_MODEL)
|
||||||
.format(converter.getJsonSchemaMap())
|
.format(converter.getJsonSchemaMap())
|
||||||
.build();
|
.build();
|
||||||
final Prompt extractPrompt = Prompt.builder()
|
final Prompt extractPrompt = Prompt.builder()
|
||||||
@ -38,4 +42,16 @@ public class InferenceService {
|
|||||||
return converter.convert(extractContent);
|
return converter.convert(extractContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable String infer(List<Message> messages) {
|
||||||
|
final ChatOptions chatOptions = OllamaChatOptions.builder()
|
||||||
|
.model(OLLAMA_MODEL)
|
||||||
|
.build();
|
||||||
|
final Prompt prompt = Prompt.builder()
|
||||||
|
.messages(messages)
|
||||||
|
.chatOptions(chatOptions)
|
||||||
|
.build();
|
||||||
|
final ChatResponse chatResponse = this.chatModel.call(prompt);
|
||||||
|
return chatResponse.getResult().getOutput().getText();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public class Recipe {
|
|||||||
private Image mainImage;
|
private Image mainImage;
|
||||||
|
|
||||||
@OneToOne(mappedBy = "recipe", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToOne(mappedBy = "recipe", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private RecipeEmbedding embedding;
|
private RecipeSummary summary;
|
||||||
|
|
||||||
@PrePersist
|
@PrePersist
|
||||||
private void prePersist() {
|
private void prePersist() {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ public interface RecipeRepository extends JpaRepository<Recipe, Integer> {
|
|||||||
@Query("SELECT r FROM Recipe r WHERE r.isPublic OR r.owner = ?1 OR ?1 MEMBER OF r.viewers")
|
@Query("SELECT r FROM Recipe r WHERE r.isPublic OR r.owner = ?1 OR ?1 MEMBER OF r.viewers")
|
||||||
Slice<Recipe> findAllViewableBy(User viewer, Pageable pageable);
|
Slice<Recipe> findAllViewableBy(User viewer, Pageable pageable);
|
||||||
|
|
||||||
List<Recipe> findAllByEmbeddingIsNull();
|
List<Recipe> findAllBySummaryIsNull();
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
nativeQuery = true,
|
nativeQuery = true,
|
||||||
|
|||||||
@ -6,10 +6,9 @@ import app.mealsmadeeasy.api.image.ImageService;
|
|||||||
import app.mealsmadeeasy.api.job.JobService;
|
import app.mealsmadeeasy.api.job.JobService;
|
||||||
import app.mealsmadeeasy.api.markdown.MarkdownService;
|
import app.mealsmadeeasy.api.markdown.MarkdownService;
|
||||||
import app.mealsmadeeasy.api.recipe.body.RecipeAiSearchBody;
|
import app.mealsmadeeasy.api.recipe.body.RecipeAiSearchBody;
|
||||||
import app.mealsmadeeasy.api.recipe.job.RecipeEmbeddingJobHandler;
|
|
||||||
import app.mealsmadeeasy.api.recipe.job.RecipeEmbeddingJobPayload;
|
|
||||||
import app.mealsmadeeasy.api.recipe.job.RecipeInferJobHandler;
|
import app.mealsmadeeasy.api.recipe.job.RecipeInferJobHandler;
|
||||||
import app.mealsmadeeasy.api.recipe.job.RecipeInferJobPayload;
|
import app.mealsmadeeasy.api.recipe.job.RecipeInferJobPayload;
|
||||||
|
import app.mealsmadeeasy.api.recipe.job.RecipeSummaryJobHandler;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeDraftUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeDraftUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
@ -46,7 +45,7 @@ public class RecipeService {
|
|||||||
private final RecipeDraftRepository recipeDraftRepository;
|
private final RecipeDraftRepository recipeDraftRepository;
|
||||||
private final JobService jobService;
|
private final JobService jobService;
|
||||||
|
|
||||||
public Recipe create(User owner, RecipeCreateSpec spec) {
|
public Recipe create(User owner, RecipeCreateSpec spec, boolean queueSummaryJob) {
|
||||||
final Recipe draft = new Recipe();
|
final Recipe draft = new Recipe();
|
||||||
draft.setOwner(owner);
|
draft.setOwner(owner);
|
||||||
draft.setSlug(spec.getSlug());
|
draft.setSlug(spec.getSlug());
|
||||||
@ -55,10 +54,19 @@ public class RecipeService {
|
|||||||
draft.setMainImage(spec.getMainImage());
|
draft.setMainImage(spec.getMainImage());
|
||||||
draft.setIsPublic(spec.isPublic());
|
draft.setIsPublic(spec.isPublic());
|
||||||
final Recipe saved = this.recipeRepository.save(draft);
|
final Recipe saved = this.recipeRepository.save(draft);
|
||||||
this.jobService.create(RecipeEmbeddingJobHandler.JOB_KEY, new RecipeEmbeddingJobPayload(saved.getId()));
|
if (queueSummaryJob) {
|
||||||
|
this.jobService.create(
|
||||||
|
RecipeSummaryJobHandler.JOB_KEY,
|
||||||
|
new RecipeSummaryJobHandler.RecipeSummaryJobPayload(saved.getId())
|
||||||
|
);
|
||||||
|
}
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Recipe create(User owner, RecipeCreateSpec spec) {
|
||||||
|
return this.create(owner, spec, false);
|
||||||
|
}
|
||||||
|
|
||||||
private Recipe getById(Integer id) {
|
private Recipe getById(Integer id) {
|
||||||
return this.recipeRepository.findById(id).orElseThrow(() -> new NoSuchEntityWithIdException(Recipe.class, id));
|
return this.recipeRepository.findById(id).orElseThrow(() -> new NoSuchEntityWithIdException(Recipe.class, id));
|
||||||
}
|
}
|
||||||
@ -324,7 +332,7 @@ public class RecipeService {
|
|||||||
.isPublic(false)
|
.isPublic(false)
|
||||||
.mainImage(recipeDraft.getMainImage())
|
.mainImage(recipeDraft.getMainImage())
|
||||||
.build();
|
.build();
|
||||||
final Recipe recipe = this.create(recipeDraft.getOwner(), spec);
|
final Recipe recipe = this.create(recipeDraft.getOwner(), spec, true);
|
||||||
this.recipeDraftRepository.deleteById(draftId); // delete old draft
|
this.recipeDraftRepository.deleteById(draftId); // delete old draft
|
||||||
return recipe;
|
return recipe;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "recipe_embedding")
|
@Table(name = "recipe_summary")
|
||||||
@Data
|
@Data
|
||||||
public class RecipeEmbedding {
|
public class RecipeSummary {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
private Integer id;
|
private Integer id;
|
||||||
@ -22,10 +22,12 @@ public class RecipeEmbedding {
|
|||||||
@JoinColumn(name = "recipe_id")
|
@JoinColumn(name = "recipe_id")
|
||||||
private Recipe recipe;
|
private Recipe recipe;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private @Nullable String summary;
|
||||||
|
|
||||||
@JdbcTypeCode(SqlTypes.VECTOR)
|
@JdbcTypeCode(SqlTypes.VECTOR)
|
||||||
@Array(length = 1024)
|
@Array(length = 1024)
|
||||||
@Nullable
|
private @Nullable float[] embedding;
|
||||||
private float[] embedding;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private OffsetDateTime timestamp;
|
private OffsetDateTime timestamp;
|
||||||
@ -1,58 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.job;
|
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.job.Job;
|
|
||||||
import app.mealsmadeeasy.api.job.JobHandler;
|
|
||||||
import app.mealsmadeeasy.api.recipe.Recipe;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeEmbedding;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeService;
|
|
||||||
import app.mealsmadeeasy.api.util.NoSuchEntityWithIdException;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.ai.embedding.EmbeddingModel;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class RecipeEmbeddingJobHandler implements JobHandler<RecipeEmbeddingJobPayload> {
|
|
||||||
|
|
||||||
public static final String JOB_KEY = "RECIPE_EMBEDDING";
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RecipeEmbeddingJobHandler.class);
|
|
||||||
|
|
||||||
private final RecipeRepository recipeRepository;
|
|
||||||
private final RecipeService recipeService;
|
|
||||||
private final EmbeddingModel embeddingModel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<RecipeEmbeddingJobPayload> getPayloadType() {
|
|
||||||
return RecipeEmbeddingJobPayload.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJobKey() {
|
|
||||||
return JOB_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(Job job, RecipeEmbeddingJobPayload payload) {
|
|
||||||
logger.info("Calculating embedding for recipeId {}", payload.recipeId());
|
|
||||||
final Recipe recipe = this.recipeRepository.findById(payload.recipeId())
|
|
||||||
.orElseThrow(() -> new NoSuchEntityWithIdException(Recipe.class, payload.recipeId()));
|
|
||||||
final String renderedMarkdown = this.recipeService.getRenderedMarkdown(recipe);
|
|
||||||
final String toEmbed = "<h1>" + recipe.getTitle() + "</h1>\n" + renderedMarkdown;
|
|
||||||
final float[] embedding = this.embeddingModel.embed(toEmbed);
|
|
||||||
|
|
||||||
final RecipeEmbedding recipeEmbedding = new RecipeEmbedding();
|
|
||||||
recipeEmbedding.setRecipe(recipe);
|
|
||||||
recipeEmbedding.setEmbedding(embedding);
|
|
||||||
recipeEmbedding.setTimestamp(OffsetDateTime.now());
|
|
||||||
recipe.setEmbedding(recipeEmbedding);
|
|
||||||
this.recipeRepository.save(recipe);
|
|
||||||
logger.info("Finished calculating embedding for recipeId {}", payload.recipeId());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.job;
|
|
||||||
|
|
||||||
public record RecipeEmbeddingJobPayload(Integer recipeId) {}
|
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
package app.mealsmadeeasy.api.recipe.job;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.ai.InferenceService;
|
||||||
|
import app.mealsmadeeasy.api.job.Job;
|
||||||
|
import app.mealsmadeeasy.api.job.JobHandler;
|
||||||
|
import app.mealsmadeeasy.api.recipe.Recipe;
|
||||||
|
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
||||||
|
import app.mealsmadeeasy.api.recipe.RecipeSummary;
|
||||||
|
import app.mealsmadeeasy.api.util.NoSuchEntityWithIdException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.ai.chat.messages.SystemMessage;
|
||||||
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
|
import org.springframework.ai.embedding.EmbeddingModel;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RecipeSummaryJobHandler implements JobHandler<RecipeSummaryJobHandler.RecipeSummaryJobPayload> {
|
||||||
|
|
||||||
|
public static final String JOB_KEY = "RECIPE_SUMMARY";
|
||||||
|
|
||||||
|
public record RecipeSummaryJobPayload(Integer recipeId) {}
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RecipeSummaryJobHandler.class);
|
||||||
|
|
||||||
|
private final RecipeRepository recipeRepository;
|
||||||
|
private final InferenceService inferenceService;
|
||||||
|
private final EmbeddingModel embeddingModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<RecipeSummaryJobPayload> getPayloadType() {
|
||||||
|
return RecipeSummaryJobPayload.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getJobKey() {
|
||||||
|
return JOB_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Job job, RecipeSummaryJobPayload payload) {
|
||||||
|
logger.info("Summarizing recipe with id {}", payload.recipeId());
|
||||||
|
final Recipe recipe = this.recipeRepository.findById(payload.recipeId())
|
||||||
|
.orElseThrow(() -> new NoSuchEntityWithIdException(Recipe.class, payload.recipeId()));
|
||||||
|
final @Nullable String summary = this.inferenceService.infer(List.of(
|
||||||
|
SystemMessage.builder()
|
||||||
|
.text("Summarize the given recipe in three sentences or less. Output your summary as plain text.")
|
||||||
|
.build(),
|
||||||
|
UserMessage.builder().text(
|
||||||
|
"<title>" + recipe.getTitle() + "</title>\n" + recipe.getRawText()
|
||||||
|
).build()
|
||||||
|
));
|
||||||
|
if (summary == null) {
|
||||||
|
throw new RuntimeException("Summary of recipe with id " + recipe.getId() + " came back null");
|
||||||
|
}
|
||||||
|
logger.debug("Summary of recipe with id {}: {}", recipe.getId(), summary);
|
||||||
|
|
||||||
|
// todo: save summary to db
|
||||||
|
|
||||||
|
final float[] summaryEmbedding = this.embeddingModel.embed(summary);
|
||||||
|
|
||||||
|
final RecipeSummary recipeSummary = new RecipeSummary();
|
||||||
|
recipeSummary.setEmbedding(summaryEmbedding);
|
||||||
|
recipeSummary.setRecipe(recipe);
|
||||||
|
recipeSummary.setTimestamp(OffsetDateTime.now());
|
||||||
|
recipe.setSummary(recipeSummary);
|
||||||
|
this.recipeRepository.saveAndFlush(recipe);
|
||||||
|
|
||||||
|
logger.info("Finished summarizing and creating embedding for recipe with id {}", recipe.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
2
src/main/resources/db/migration/V9__recipe_summary.sql
Normal file
2
src/main/resources/db/migration/V9__recipe_summary.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE recipe_embedding RENAME TO recipe_summary;
|
||||||
|
ALTER TABLE recipe_summary ADD COLUMN summary TEXT;
|
||||||
Loading…
Reference in New Issue
Block a user