Add inference service.

This commit is contained in:
Jesse Brault 2025-12-31 14:05:07 -06:00
parent 0b62e06646
commit a38ac6b6f5
6 changed files with 161 additions and 0 deletions

View File

@ -0,0 +1,37 @@
package app.mealsmadeeasy.api.inference;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("/inferences")
public class InferenceController {
private final InferenceService inferenceService;
public InferenceController(InferenceService inferenceService) {
this.inferenceService = inferenceService;
}
@PutMapping("/recipe-extract")
public ResponseEntity<String> recipeExtract(@RequestParam MultipartFile recipeImageFile) throws IOException {
return ResponseEntity.ok(this.inferenceService.extractRecipe(recipeImageFile.getInputStream()));
}
@PutMapping(value = "/recipe-extract-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Map<String, String>>> recipeExtractStream(@RequestParam MultipartFile recipeImageFile) throws IOException {
return this.inferenceService.extractRecipeStream(recipeImageFile.getInputStream())
.map(data -> ServerSentEvent.builder(Map.of("delta", data)).build());
}
}

View File

@ -0,0 +1,52 @@
package app.mealsmadeeasy.api.inference;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.content.Media;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeType;
import reactor.core.publisher.Flux;
import java.io.InputStream;
@Service
public class InferenceService {
private final ChatClient chatClient;
public InferenceService(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
public String extractRecipe(InputStream recipeImageInputStream) {
final Media media = Media.builder()
.data(new InputStreamResource(recipeImageInputStream))
.mimeType(MimeType.valueOf("image/jpeg"))
.build();
final String markdownResponse = this.chatClient.prompt()
.user(u ->
u.text(new ClassPathResource("app/mealsmadeeasy/api/inference/recipe-extract-user-prompt.md"))
.media(media)
)
.call()
.content();
return markdownResponse;
}
public Flux<String> extractRecipeStream(InputStream recipeImageInputStream) {
final Media media = Media.builder()
.data(new InputStreamResource(recipeImageInputStream))
.mimeType(MimeType.valueOf("image/jpeg"))
.build();
return this.chatClient.prompt()
.user(u ->
u.text(new ClassPathResource("app/mealsmadeeasy/api/inference/recipe-extract-user-prompt.md"))
.media(media)
)
.stream().content();
}
}

View File

@ -0,0 +1,36 @@
package app.mealsmadeeasy.api.inference;
import jakarta.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "recipe_extraction")
public class RecipeExtractionEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Lob
@Column(columnDefinition = "TEXT")
@Basic(fetch = FetchType.LAZY)
private String markdown;
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public String getMarkdown() {
return this.markdown;
}
public void setMarkdown(String markdown) {
this.markdown = markdown;
}
}

View File

@ -0,0 +1,33 @@
package app.mealsmadeeasy.api.inference;
import java.util.UUID;
public class RecipeExtractionView {
public static RecipeExtractionView from(RecipeExtractionEntity entity) {
final var view = new RecipeExtractionView();
view.setId(entity.getId());
view.setMarkdown(entity.getMarkdown());
return view;
}
private UUID id;
private String markdown;
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public String getMarkdown() {
return this.markdown;
}
public void setMarkdown(String markdown) {
this.markdown = markdown;
}
}

View File

@ -0,0 +1 @@
Convert the recipe in the image to Markdown.

View File

@ -19,3 +19,5 @@ app.mealsmadeeasy.api.images.bucketName=images
# AI # AI
spring.ai.vectorstore.pgvector.dimensions=1024 spring.ai.vectorstore.pgvector.dimensions=1024
spring.ai.ollama.chat.options.model=deepseek-ocr:latest
spring.ai.ollama.init.pull-model-strategy=never