From 07c85d8e2c89be22b3d69b1f4e378134a703134a Mon Sep 17 00:00:00 2001 From: azerr Date: Tue, 20 Aug 2024 15:54:32 +0200 Subject: [PATCH] feat: Available Ollama models support for quarkus.langchain4j.ollama.chat-model.model-id property value Signed-off-by: azerr --- quarkus.ls.ext/com.redhat.quarkus.ls/pom.xml | 4 +- .../ollama/OllamaItemMetadataProvider.java | 177 ++++++++++++++++++ .../OllamaItemMetadataProviderFactory.java | 33 ++++ .../extensions/ollama/OllamaModel.java | 31 +++ .../extensions/ollama/OllamaModelsResult.java | 37 ++++ ...4mp.extensions.ItemMetadataProviderFactory | 1 + 6 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProvider.java create mode 100644 quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProviderFactory.java create mode 100644 quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModel.java create mode 100644 quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModelsResult.java create mode 100644 quarkus.ls.ext/com.redhat.quarkus.ls/src/main/resources/META-INF/services/org.eclipse.lsp4mp.extensions.ItemMetadataProviderFactory diff --git a/quarkus.ls.ext/com.redhat.quarkus.ls/pom.xml b/quarkus.ls.ext/com.redhat.quarkus.ls/pom.xml index 5a71900da..1cafeaf79 100644 --- a/quarkus.ls.ext/com.redhat.quarkus.ls/pom.xml +++ b/quarkus.ls.ext/com.redhat.quarkus.ls/pom.xml @@ -34,8 +34,8 @@ UTF-8 - 1.8 - 1.8 + 11 + 11 yyyyMMdd-HHmm ${maven.build.timestamp} 0.14.0 diff --git a/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProvider.java b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProvider.java new file mode 100644 index 000000000..754d6cf8c --- /dev/null +++ b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProvider.java @@ -0,0 +1,177 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.quarkus.extensions.ollama; + +import java.io.StringReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.eclipse.lsp4mp.commons.metadata.ItemHint; +import org.eclipse.lsp4mp.commons.metadata.ItemMetadata; +import org.eclipse.lsp4mp.commons.metadata.ValueHint; +import org.eclipse.lsp4mp.commons.utils.StringUtils; +import org.eclipse.lsp4mp.extensions.ExtendedMicroProfileProjectInfo; +import org.eclipse.lsp4mp.extensions.ItemMetadataProvider; +import org.eclipse.lsp4mp.model.Node; +import org.eclipse.lsp4mp.model.Node.NodeType; +import org.eclipse.lsp4mp.model.PropertiesModel; +import org.eclipse.lsp4mp.model.Property; + +import com.google.gson.GsonBuilder; + +/** + * Properties provider to collect available Ollama model values for the property + * 'quarkus.langchain4j.ollama.chat-model.model-id'. + * + * The Ollama models are collected by using Ollama Search Api by using the + * following base url: + * + *
    + *
  • defined with 'quarkus.langchain4j.ollama.base-url' property. + *
  • otherwise with 'http://localhost:11434'
  • + *
+ */ +public class OllamaItemMetadataProvider implements ItemMetadataProvider { + + private static final Logger LOGGER = Logger.getLogger(OllamaItemMetadataProvider.class.getName()); + + private static final String QUARKUS_LANGCHAIN4J_OLLAMA_CHAT_MODEL_MODEL_ID_KEY = "quarkus.langchain4j.ollama.chat-model.model-id"; + + private static final String QUARKUS_LANGCHAIN4J_OLLAMA_BASE_URL_KEY = "quarkus.langchain4j.ollama.base-url"; + + private static final String DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434"; + + private ExtendedMicroProfileProjectInfo projectInfo; + private boolean available; + + private String currentSearchUrl; + + public OllamaItemMetadataProvider(ExtendedMicroProfileProjectInfo projectInfo) { + this.projectInfo = projectInfo; + // the Ollama models are collected only if + // 'quarkus.langchain4j.ollama.chat-model.model-id' property exists. + this.available = this.projectInfo.getProperties().stream() + .anyMatch(p -> QUARKUS_LANGCHAIN4J_OLLAMA_CHAT_MODEL_MODEL_ID_KEY.equals(p.getName())); + } + + @Override + public boolean isAvailable() { + return available; + } + + @Override + public void update(PropertiesModel document) { + // Called when application.properties file is opened or updated (when user type + // something in the file). + boolean hasModelProperty = getProperty(document, QUARKUS_LANGCHAIN4J_OLLAMA_CHAT_MODEL_MODEL_ID_KEY) != null; + if (hasModelProperty) { + // The application.properties declare the + // 'quarkus.langchain4j.ollama.chat-model.model-id' property, + // we need to collect available Ollama models by using Ollama Search Api + String searchUrl = getOllamaSearchUrl(document); + if (!Objects.equals(searchUrl, currentSearchUrl)) { + // The current search url is different with the new, collect Ollama models. + ItemHint hint = projectInfo.getHint(QUARKUS_LANGCHAIN4J_OLLAMA_CHAT_MODEL_MODEL_ID_KEY); + if (hint == null) { + hint = new ItemHint(); + hint.setName(QUARKUS_LANGCHAIN4J_OLLAMA_CHAT_MODEL_MODEL_ID_KEY); + projectInfo.getHints().add(hint); + } + hint.setValues(collectOllamaModels(searchUrl)); + currentSearchUrl = searchUrl; + } + } + } + + private static List collectOllamaModels(String searchUrl) { + try { + // Call Http search Url (ex : http://localhost:11434/v1/models) + HttpRequest request = HttpRequest.newBuilder() // + .uri(new URI(searchUrl)) // + .GET() // + .build(); + + HttpResponse response = HttpClient.newBuilder() // + .build() // + .send(request, BodyHandlers.ofString()); + String result = response.body(); + + // ex : + // {"object":"list","data":[{"id":"qwen2:latest","object":"model","created":1724172988,"owned_by":"library"}]} + OllamaModelsResult r = new GsonBuilder() // + .create()// + .fromJson(new StringReader(result), OllamaModelsResult.class); + if (r != null && r.getData() != null) { + return r.getData() // + .stream() // + .map(m -> { + ValueHint model = new ValueHint(); + String modelId = m.getId(); + if (modelId.endsWith(":latest")) { + modelId = modelId.substring(0, modelId.length() - ":latest".length()); + } + model.setValue(modelId); + return model; + }) // + .collect(Collectors.toList()); + + } + + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while collecting Ollama Models with '" + searchUrl + "'.", e); + } + return Collections.emptyList(); + } + + private static String getOllamaSearchUrl(PropertiesModel document) { + return getSearchBaseUrl(document) + "/v1/models"; + } + + private static String getSearchBaseUrl(PropertiesModel document) { + Property baseUrlProperty = getProperty(document, QUARKUS_LANGCHAIN4J_OLLAMA_BASE_URL_KEY); + if (baseUrlProperty != null) { + String propertyValue = baseUrlProperty.getPropertyValue(); + if (StringUtils.hasText(propertyValue)) { + return propertyValue; + } + } + return DEFAULT_OLLAMA_BASE_URL; + } + + private static Property getProperty(PropertiesModel document, String propertyName) { + for (Node node : document.getChildren()) { + if (node.getNodeType() == NodeType.PROPERTY) { + Property property = (Property) node; + if (propertyName.equals(property.getPropertyName())) { + return property; + } + } + } + return null; + } + + @Override + public List getProperties() { + return null; + } +} diff --git a/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProviderFactory.java b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProviderFactory.java new file mode 100644 index 000000000..184ffe308 --- /dev/null +++ b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaItemMetadataProviderFactory.java @@ -0,0 +1,33 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.quarkus.extensions.ollama; + +import org.eclipse.lsp4mp.extensions.ExtendedMicroProfileProjectInfo; +import org.eclipse.lsp4mp.extensions.ItemMetadataProvider; +import org.eclipse.lsp4mp.extensions.ItemMetadataProviderFactory; + +/** + * Factory for creating {@link ItemMetadataProvider} instance to collect + * available Ollama models. + * + * @author Angelo ZERR + * + */ +public class OllamaItemMetadataProviderFactory implements ItemMetadataProviderFactory { + + @Override + public ItemMetadataProvider create(ExtendedMicroProfileProjectInfo projectInfo) { + return new OllamaItemMetadataProvider(projectInfo); + } +} diff --git a/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModel.java b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModel.java new file mode 100644 index 000000000..d75716d29 --- /dev/null +++ b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModel.java @@ -0,0 +1,31 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.quarkus.extensions.ollama; + +/** + * Ollama model. + */ +public class OllamaModel { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + +} diff --git a/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModelsResult.java b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModelsResult.java new file mode 100644 index 000000000..4c8b3a6ea --- /dev/null +++ b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/java/com/redhat/quarkus/extensions/ollama/OllamaModelsResult.java @@ -0,0 +1,37 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.quarkus.extensions.ollama; + +import java.util.List; + +/** + * Bean class which maps the HTTP response of http://localhost:11434/v1/models + * + * Example: + *

+ * {"object":"list","data":[{"id":"qwen2:latest","object":"model","created":1724172988,"owned_by":"library"}]} + *

+ */ +public class OllamaModelsResult { + + private List data; + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } +} diff --git a/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/resources/META-INF/services/org.eclipse.lsp4mp.extensions.ItemMetadataProviderFactory b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/resources/META-INF/services/org.eclipse.lsp4mp.extensions.ItemMetadataProviderFactory new file mode 100644 index 000000000..6a2de83c2 --- /dev/null +++ b/quarkus.ls.ext/com.redhat.quarkus.ls/src/main/resources/META-INF/services/org.eclipse.lsp4mp.extensions.ItemMetadataProviderFactory @@ -0,0 +1 @@ +com.redhat.quarkus.extensions.ollama.OllamaItemMetadataProviderFactory \ No newline at end of file