如何在Ollama和Spring AI上使用本地AI/LLM查询图像数据库

译文
人工智能
本文介绍如何利用Ollama LLava模型和Spring AI,实现基于自然语言的图像数据库查询,并支持高效向量搜索和图像描述展示。

译者 | 李睿

审校 | 重楼

AIDocumentLibraryChat项目的功能已经扩展到可以查询图像的图像数据库。它使用了可以分析图像的Ollama的LLava模型。图像搜索功能利用PostgreSQL的PGVector扩展来处理嵌入(Embeddings)

架构

AIDocumentLibraryChat项目的架构见下图:

Angular前端向用户展示上传和提问功能。Spring AI后端可以调整模型的图像大小,使用数据库存储数据/向量,并使用Ollama的LLava模型创建图像描述。

图像上传/分析/存储流程见下图:

图像通过前端上传。后端将其调整为LLava模型可以处理的格式。然后,LLava模型根据提供的提示生成图像的描述。调整之后的图像和元数据存储在PostgreSQL的关系表中。然后使用图像描述来创建嵌入。嵌入与元数据一起存储在PGVector数据库中的描述中,以在PostgreSQL表中找到相应的行。然后在前端显示图像描述和调整后的图像。

图像查询的流程见下图:

用户可以在前端输入问题。后端将问题转换为嵌入,并在PGVector数据库中搜索最近的条目。该条目包含图像表中包含图像和元数据的行ID然后,将图像表数据与描述一起查询并显示给用户。

后端

为了运行PGVector数据库和Ollama框架,runPostgresql.sh和runOllama.sh文件中包含Docker命令。

后端需要application-ollama.properties文件配置以下条目

Properties files 
 # image processing
 spring.ai.ollama.chat.model=llava:34b-v1.6-q6_K
 spring.ai.ollama.chat.options.num-thread=8
 spring.ai.ollama.chat.options.keep_alive=1s

该应用程序需要使用Ollama支持(属性:‘useOllama’)构建,并使用‘Ollama’配置文件启动,需要激活这些属性以启用LLava模型并设置有用的keep_alive。只有当Ollama没有自动选择正确的线程数量时,才需要设置num_thread。

控制器(Controller

ImageController包含以下端点:

Java 
 @RestController
 @RequestMapping("rest/image")
 public class ImageController {
 ...
 @PostMapping("/query")
 public List<ImageDto> postImageQuery(@RequestParam("query") String 
 query,@RequestParam("type") String type) { 
 var result = this.imageService.queryImage(query); 
 return result;
 }
 
 @PostMapping("/import")
 public ImageDto postImportImage(@RequestParam("query") String query, 
 @RequestParam("type") String type, 
 @RequestParam("file") MultipartFile imageQuery) { 
 var result = 
 this.imageService.importImage(this.imageMapper.map(imageQuery, query), 
 this.imageMapper.map(imageQuery)); 
 return result;
 } 
 }

查询端点包含‘postImageQuery(…)’方法,该方法接收包含查询和图像类型的表单,并调用ImageService来处理请求。

导入端点包含‘postImportImage(…)’方法,该方法接收带有查询(提示)、图像类型和文件的表单。ImageMapper将表单转换为ImageQueryDto和Image实体,并调用ImageService来处理请求。

服务

调用ImageService如下:

Java 
 @Service
 @Transactional
 public class ImageService {
 ...
 public ImageDto importImage(ImageQueryDto imageDto, Image image) {
 var resultData = this.createAIResult(imageDto);
 image.setImageContent(resultData.imageQueryDto().getImageContent());
 var myImage = this.imageRepository.save(image);
 var aiDocument = new Document(resultData.answer());
 aiDocument.getMetadata().put(MetaData.ID, myImage.getId().toString());
 aiDocument.getMetadata().put(MetaData.DATATYPE, 
 MetaData.DataType.IMAGE.toString());
 this.documentVsRepository.add(List.of(aiDocument));
 return new ImageDto(resultData.answer(), 
 Base64.getEncoder().encodeToString(resultData.imageQueryDto()
 .getImageContent()), resultData.imageQueryDto().getImageType());
 }

 public List<ImageDto> queryImage(String imageQuery) {
 var aiDocuments = this.documentVsRepository.retrieve(imageQuery, 
 MetaData.DataType.IMAGE, this.resultSize.intValue())
 .stream().filter(myDoc -> myDoc.getMetadata()
 .get(MetaData.DATATYPE).equals(DataType.IMAGE.toString()))
 .sorted((myDocA, myDocB) -> 
 ((Float) myDocA.getMetadata().get(MetaData.DISTANCE))
 .compareTo(((Float) myDocB.getMetadata().get(MetaData.DISTANCE))))
 .toList();
 var imageMap = this.imageRepository.findAllById(
 aiDocuments.stream().map(myDoc -> 
 (String) myDoc.getMetadata().get(MetaData.ID)).map(myUuid -> 
 UUID.fromString(myUuid)).toList())
 .stream().collect(Collectors.toMap(myDoc -> myDoc.getId(), 
 myDoc -> myDoc));
 return imageMap.entrySet().stream().map(myEntry -> 
 createImageContainer(aiDocuments, myEntry))
 .sorted((containerA, containerB) -> 
 containerA.distance().compareTo(containerB.distance()))
 .map(myContainer -> new ImageDto(myContainer.document().getContent(), 
 Base64.getEncoder().encodeToString(
 myContainer.image().getImageContent()),
 myContainer.image().getImageType())).limit(this.resultSize)
 .toList();
 }

 private ImageContainer createImageContainer(List<Document> aiDocuments, 
 Entry<UUID, Image> myEntry) {
 return new ImageContainer(
 createIdFilteredStream(aiDocuments, myEntry)
 .findFirst().orElseThrow(),
 myEntry.getValue(),
 createIdFilteredStream(aiDocuments, myEntry).map(myDoc -> 
 (Float) myDoc.getMetadata().get(MetaData.DISTANCE))
 .findFirst().orElseThrow());
 }

 private Stream<Document> createIdFilteredStream(List<Document> aiDocuments, 
 Entry<UUID, Image> myEntry) {
 return aiDocuments.stream().filter(myDoc -> myEntry.getKey().toString()
 .equals((String) myDoc.getMetadata().get(MetaData.ID)));
 }

 private ResultData createAIResult(ImageQueryDto imageDto) {
 if (ImageType.JPEG.equals(imageDto.getImageType()) || 
 ImageType.PNG.equals(imageDto.getImageType())) {
 imageDto = this.resizeImage(imageDto);
 } 
 var prompt = new Prompt(new UserMessage(imageDto.getQuery(), 
 List.of(new Media(MimeType.valueOf(imageDto.getImageType()
 .getMediaType()), imageDto.getImageContent()))));
 var response = this.chatClient.call(prompt);
 var resultData = new 
 ResultData(response.getResult().getOutput().getContent(), imageDto);
 return resultData;
 }

 private ImageQueryDto resizeImage(ImageQueryDto imageDto) {
 ...
 }
 }

importImage(…)方法中,将调用createAIResult(…)方法。它检查图像类型,并调用resizeImage(…)方法将图像缩放到LLava模型支持的大小。然后使用提示文本和包含图像、媒体类型和图像字节数组的媒体创建Spring AI Prompt。然后,chatClient调用提示,并在‘ResultData记录中返回响应,其中包含描述和调整大小的图像。然后将调整大小的图像添加到图像实体中,并保留该实体。然后,在元数据中创建了包含嵌入、描述和图像实体ID的AI文档。最后使用描述、调整大小的图像和图像类型创建ImageDto并返回。

queryImage(…)’方法中,检索并过滤元数据中图像类型的AI文档的最低距离的Spring AI文档。按照最小的距离对文档进行排序。然后加载带有Spring AI文档元数据ID的图像实体。这样就可以创建具有匹配文档和图像实体的ImageDtos。图像以Base64编码字符串的形式提供。这使得MediaType可以轻松地在IMG标签中显示图像。

要显示Base64 Png图像,可以使用:‘<img src=”data:image/png;base64,iVBORw0KG…” />’

结果

用户界面(UI)的结果见下图:

该应用程序使用嵌入在向量数据库中找到大型飞机。选择第二张图像是因为天空相似,而图像搜索只花了不到一秒的时间。

结论

Spring AI和Ollama的支持为用户提供了使用免费LLava模型的机会,这使得该图像数据库的实现变得容易。LLava模型可以生成图像的良好描述,这些描述可以转换为嵌入以进行快速搜索。Spring AI缺少对生成API端点的支持,因为参数‘spring.ai.ollama.chat.options.keep_alive=1s’需要Keep_alive =1s '来避免上场景窗口中有旧数据。LLava模型需要GPU加速才能有效使用。LLava仅用于导入,这意味着描述的创建可以异步完成。在中等性能的笔记本电脑上,LLava模型在一个CPU上运行,每张图像的处理时间为510分钟。与以前的实现相比,这样的图像搜索解决方案是一个巨大的进步。随着采用更多GPU或CPU对人工智能的支持,这样的图像搜索解决方案将变得更加流行。

原文标题Questioning an Image Database With Local AI/LLM on Ollama and Spring AI作者:Sven Loesekann

责任编辑:华轩 来源: 51CTO
相关推荐

2023-07-28 08:00:00

人工智能向量数据库

2020-09-10 18:14:51

人工智能 IBM

2020-09-11 10:59:05

数据库

2024-09-06 11:34:15

RustAI语言

2011-03-24 17:28:58

网络数据库

2024-10-30 11:06:59

SpringAI模型

2017-06-13 10:15:50

人工智能深度学习神经网络

2024-06-04 12:59:41

2011-07-26 14:34:28

openSUSEpostgresql

2024-05-22 12:07:12

向量数据库AI

2023-05-24 08:52:12

2019-12-11 14:27:39

数据库集群Kubernetes

2023-09-15 08:00:20

2022-08-03 10:45:04

人工智能网络安全

2009-01-19 09:14:31

.NETMySQLMySql驱动包

2024-09-12 09:16:11

2024-03-26 08:00:00

LLMVLMRaspberry

2010-09-07 10:29:34

DB2数据库

2024-05-08 17:05:44

点赞
收藏

51CTO技术栈公众号