Skip to content
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
4 changes: 4 additions & 0 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,10 @@
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
<artifactId>helidon-integrations-langchain4j-providers-jlama</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
<artifactId>helidon-integrations-langchain4j-providers-coherence</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.openapi</groupId>
<artifactId>helidon-openapi</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,11 @@
<artifactId>helidon-integrations-langchain4j-providers-jlama</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
<artifactId>helidon-integrations-langchain4j-providers-coherence</artifactId>
<version>${helidon.version}</version>
</dependency>

<!-- OpenAPI support -->
<dependency>
Expand Down
8 changes: 8 additions & 0 deletions dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<version.lib.annotation-api>1.3.5</version.lib.annotation-api>
<version.lib.brave-opentracing>1.0.0</version.lib.brave-opentracing>
<version.lib.bytebuddy>1.17.5</version.lib.bytebuddy>
<version.lib.coherence>25.03.1</version.lib.coherence>
<version.lib.commons-codec>1.16.0</version.lib.commons-codec>
<version.lib.commons-logging>1.2</version.lib.commons-logging>
<version.lib.cron-utils>9.2.1</version.lib.cron-utils>
Expand Down Expand Up @@ -1359,6 +1360,13 @@
<!-- END OF Section 4: Testing -->

<!-- imported boms -->
<dependency>
<groupId>com.oracle.coherence.ce</groupId>
<artifactId>coherence-bom</artifactId>
<version>${version.lib.coherence}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Force upgrade. Used by grpc -->
<dependency>
<groupId>com.google.protobuf</groupId>
Expand Down
95 changes: 95 additions & 0 deletions docs/src/main/asciidoc/se/integrations/langchain4j/coherence.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
///////////////////////////////////////////////////////////////////////////////

Copyright (c) 2025 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

///////////////////////////////////////////////////////////////////////////////

= LangChain4J Coherence Provider
:description: LangChain4J Coherence
:keywords: helidon, AI, LangChain4J, LC4J, Coherence
:feature-name: LangChain4J Integration
:rootdir: {docdir}/../../..

include::{rootdir}/includes/se.adoc[]

== Contents

* <<Overview, Overview>>
* <<Maven Coordinates, Maven Coordinates>>
* <<Components, Components>>
** <<CoherenceEmbeddingModel, CoherenceEmbeddingModel>>
* <<Additional Information, Additional Information>>

== Overview

This module adds support for the Coherence embedding store.

== Maven Coordinates

In addition to the xref:langchain4j.adoc#maven-coordinates[Helidon integration with LangChain4J core dependencies], you must add the following:

[source,xml]
----
<dependency>
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
<artifactId>helidon-integrations-langchain4j-providers-coherence</artifactId>
</dependency>
----

== Components

=== CoherenceEmbeddingModel

To automatically create and add `CoherenceEmbeddingModel` to the service registry add the following lines to `application.yaml`:

[source,yaml]
----
langchain4j:
coherence:
embedding-store:
enabled: true
session: "session"
name: "namedMap"
normalize-embeddings: false
index: "hnsw"
dimension: 768
embedding-model:
service-registry.named: beanName
----

If `enabled` is set to `false`, the configuration is ignored, and the component is not created.

Full list of configuration properties:

[cols="3,3a,5a"]

|===
|Key |Type |Description

|`embedding-model`|string | Allows to configure embedding model by specifying named bean using `embeddingModel.service-registry.named: beanName`. Default embedding model is used otherwise, if exists.
|`dimension` |integer |The number of dimensions in the embeddings that will be stored in vector store.
|`enabled` |boolean |If set to `true`, Coherence embedding store will be enabled.
|`index` |string |Specifies vector index type use to create a vector index used to query embeddings. Only `hnsw` is supported.
|`name` |string |Specifies name of the Coherence `com.tangosol.net.NamedMap` use to store embeddings.
|`normalize-embeddings` |boolean |A flag that when true, forces normalization of embeddings on adding and searching.
|`session` |string |The name of the `com.tangosol.net.Session` use to obtain the `com.tangosol.net.NamedMap` as specified with `name`.

|===


== Additional Information

* xref:langchain4j.adoc[LangChain4J Integration]
* https://docs.langchain4j.dev/integrations/embedding-stores/coherence[langChain4J Coherence Embedding Store Documentation]
10 changes: 6 additions & 4 deletions docs/src/main/asciidoc/se/integrations/langchain4j/core.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ langchain4j:
rag:
embedding-store-content-retriever:
enabled: true
embedding-model: "@default"
embedding-store: "@default"
embedding-model:
service-registry.named: "@default"
embedding-store:
service-registry.named: "@default"
----

If `enabled` is set to `false`, the configuration is ignored, and the component is not created.
Expand All @@ -69,8 +71,8 @@ Full list of configuration properties:
|`enabled` |boolean |If set to `false`, embedding store content retriever will be disabled even if configured.
|`max-results` |int |Maximum number of results.
|`min-score` |double |Minimum score threshold.
|`embedding-model` |string |Name of the service in the service registry that implements `dev.langchain4j.model.embedding.EmbeddingModel`. Use `@default` (the default option) to refer to the unnamed service.
|`embedding-store` |string |Name of the service in the service registry that implements `dev.langchain4j.model.embedding.EmbeddingStore<TextSegment>`. Use `@default` (the default option) to refer to the unnamed service.
|`embedding-model` |string |Service in the service registry that implements `dev.langchain4j.model.embedding.EmbeddingModel`. Use `embedding-model.service-registry.named: "serviceName"` to select a named service bean.
|`embedding-store` |string |Name of the service in the service registry that implements `dev.langchain4j.model.embedding.EmbeddingStore<TextSegment>`. Use `embedding-model.service-registry.named: "serviceName"` to select a named service bean.

|===

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Supports https://docs.langchain4j.dev/tutorials/ai-services/[LangChain4J's AI Se
** xref:cohere.adoc#_coherescoringmodel[`CohereScoringModel`]
* xref:oracle.adoc[*Oracle*]
** xref:oracle.adoc#_oracleembeddingmodel[`OracleEmbeddingStore`]
* xref:coherence.adoc[*Coherence*]
** xref:coherence.adoc#_coherenceembeddingmodel[`CoherenceEmbeddingStore`]
* xref:jlama.adoc[*Jlama*]
** xref:jlama.adoc#_jlamachatmodel[`JlamaChatModel`]
** xref:jlama.adoc#_jlamastreamingchatmodel[`JlamaStreamingChatModel`]
Expand Down Expand Up @@ -313,6 +315,7 @@ For more details, read the https://docs.langchain4j.dev/tutorials/tools#high-lev
** xref:ollama.adoc[Ollama]
** xref:cohere.adoc[Cohere]
** xref:oracle.adoc[Oracle]
** xref:coherence.adoc[Coherence]
** xref:jlama.adoc[Jlama]
** xref:codegen-provider.adoc[Code generated Lc4j Provider]
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,19 @@ class ConfigureMethodBuilder {
.addDescriptionLine("<b>Skipped:</b>")
.addDescriptionLine("<ul>");

if (parentTypeInfo.elementInfo().stream()
var customBuilderMappingMethod = parentTypeInfo.elementInfo().stream()
.filter(m -> m.kind().equals(ElementKind.METHOD))
.filter(m -> m.parameterArguments().isEmpty())
.filter(m -> m.typeName().equals(builderTypeInfo.typeName()))
.anyMatch(m -> m.elementName().equals("configuredBuilder"))) {
.filter(m -> m.elementName().equals("configuredBuilder"))
.findFirst();

if (customBuilderMappingMethod.isPresent()) {
confMethodBuilder
.addContent("var modelBuilder = ")
.addContent(parentTypeInfo.typeName())
.addContentLine(".super.configuredBuilder();");
.addContent(".super.configuredBuilder(")
.addContentLine(");");
} else {
confMethodBuilder.addContent("var modelBuilder = ").addContent(modelTypeName).addContentLine(".builder();");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,17 @@ private static boolean isInjectedByDefault(TypedElementInfo modelBldMethod) {
return false;
}

void addArrayProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod) {
void addArrayProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod, boolean skipBuilderMapping) {
var methodBuilder = createMethodBuilder(modelBldMethod)
.name(propName);

methodBuilder.addAnnotation(BLDR_SINGULAR_ANNOTATION);

methodBuilder.returnType(TypeName.builder(LIST).addTypeArgument(propType).build());

confMethodBuilder.configureProperty(propName, propType, false, true, false);
if (!skipBuilderMapping) {
confMethodBuilder.configureProperty(propName, propType, false, true, false);
}

if (overrideProps.containsKey(propName)) {
confMethodBuilder.commentOverriddenProperty(overrideProps.get(propName));
Expand All @@ -184,8 +186,7 @@ void addArrayProperty(String propName, TypeName propType, TypedElementInfo model
}
}

void addOptionalProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod) {
var custBuilderMappingAnnotation = modelBldMethod.findAnnotation(LangchainTypes.MODEL_CUSTOM_BUILDER_MAPPING);
void addOptionalProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod, boolean skipBuilderMapping) {
var methodBuilder = createMethodBuilder(modelBldMethod)
.name(propName);

Expand All @@ -198,20 +199,21 @@ void addOptionalProperty(String propName, TypeName propType, TypedElementInfo mo

if (overrideProps.containsKey(propName)) {
confMethodBuilder.commentOverriddenProperty(overrideProps.get(propName));
confMethodBuilder.configureProperty(propName, propType, false, false,
// Overridden non-optional props are kept mandatory
overrideProps.get(propName).typeName().isOptional());
if (!skipBuilderMapping) {
confMethodBuilder.configureProperty(propName, propType, false, false,
// Overridden non-optional props are kept mandatory
overrideProps.get(propName).typeName().isOptional());
}

} else {
classModelBuilder().addMethod(methodBuilder.build());
if (custBuilderMappingAnnotation.isEmpty()) {
if (!skipBuilderMapping) {
confMethodBuilder.configureProperty(propName, propType, false, false, true);
}
}
}

void addCollectionProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod) {
var custBuilderMappingAnnotation = modelBldMethod.findAnnotation(LangchainTypes.MODEL_CUSTOM_BUILDER_MAPPING);
void addCollectionProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod, boolean skipBuilderMapping) {
var methodBuilder = createMethodBuilder(modelBldMethod)
.name(propName);

Expand All @@ -222,7 +224,7 @@ void addCollectionProperty(String propName, TypeName propType, TypedElementInfo
.type(propType)
.build());

if (custBuilderMappingAnnotation.isEmpty()) {
if (!skipBuilderMapping) {
confMethodBuilder.configureProperty(propName, propType, false, false, false);
}

Expand Down Expand Up @@ -363,15 +365,16 @@ IntrospectionBlueprintBuilder introspectBuilder(List<String> skips, Set<String>
for (var m : propertyMap.values()) {
var methodName = m.signature().name();
var paramType = m.signature().parameterTypes().getFirst();
var skipBuilderMapping = m.findAnnotation(LangchainTypes.MODEL_CUSTOM_BUILDER_MAPPING).isPresent();

if (paramType.array() && !hasCollectionAlternative(m, propertyList)) {
this.addArrayProperty(methodName, paramType, m);
this.addArrayProperty(methodName, paramType, m, skipBuilderMapping);

} else if (paramType.isList() || paramType.isSet() || paramType.isMap()) {
this.addCollectionProperty(methodName, paramType, m);
this.addCollectionProperty(methodName, paramType, m, skipBuilderMapping);

} else {
this.addOptionalProperty(methodName, paramType, m);
this.addOptionalProperty(methodName, paramType, m, skipBuilderMapping);

}
}
Expand Down Expand Up @@ -404,13 +407,13 @@ private void introspectOverrides() {
} else {

if (propertyType.array() && !hasCollectionAlternative(m, new ArrayList<>(overrideProps.values()))) {
this.addArrayProperty(propertyName, propertyType, m);
this.addArrayProperty(propertyName, propertyType, m, skipBuilderMapping);

} else if (propertyType.isList() || propertyType.isSet() || propertyType.isMap()) {
this.addCollectionProperty(propertyName, propertyType, m);
this.addCollectionProperty(propertyName, propertyType, m, skipBuilderMapping);

} else {
this.addOptionalProperty(propertyName, propertyType, m);
this.addOptionalProperty(propertyName, propertyType, m, skipBuilderMapping);

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ interface EmbeddingStoreContentRetrieverConfigBlueprint {
/**
* Embedding store to use in the content retriever.
*
* @return an {@link java.util.Optional} containing the embedding store
* @return an {@link java.util.Optional} default service bean is injected or {@code embedding-model.service-registry.named}
* can be used to select a named bean
*/
@Option.Configured
@Option.RegistryService
EmbeddingStore<TextSegment> embeddingStore();

/**
* Explicit embedding model to use in the content retriever.
*
* @return an {@link java.util.Optional} containing the embedding model bean name or "discovery:auto" if the bean must be
* discovered automatically
* @return an {@link java.util.Optional} default service bean is injected or {@code embedding-model.service-registry.named}
* can be used to select a named bean
*/
@Option.Configured
@Option.RegistryService
Optional<EmbeddingModel> embeddingModel();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,9 @@
import io.helidon.common.Weight;
import io.helidon.common.Weighted;
import io.helidon.common.config.Config;
import io.helidon.common.types.TypeName;
import io.helidon.service.registry.Service;

import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;

import static io.helidon.integrations.langchain4j.EmbeddingStoreContentRetrieverConfigBlueprint.CONFIG_ROOT;

Expand All @@ -41,17 +37,11 @@
@Service.Singleton
@Weight(Weighted.DEFAULT_WEIGHT - 10)
public class EmbeddingStoreContentRetrieverFactory implements Supplier<Optional<EmbeddingStoreContentRetriever>> {
private static final TypeName STORE_TYPE = TypeName.builder()
.type(EmbeddingStore.class)
.addTypeArgument(TypeName.create(TextSegment.class))
.build();

private final LazyValue<Optional<EmbeddingStoreContentRetriever>> contentRetriever;

@Service.Inject
EmbeddingStoreContentRetrieverFactory(Supplier<EmbeddingStore<TextSegment>> embeddingStore,
Supplier<Optional<EmbeddingModel>> embeddingModel,
Config config) {
EmbeddingStoreContentRetrieverFactory(Config config) {
var configBuilder =
EmbeddingStoreContentRetrieverConfig.builder().config(config.get(CONFIG_ROOT));

Expand All @@ -60,9 +50,6 @@ public class EmbeddingStoreContentRetrieverFactory implements Supplier<Optional<
if (!configBuilder.enabled()) {
return Optional.empty();
}
configBuilder.embeddingStore(embeddingStore.get());
embeddingModel.get().ifPresent(configBuilder::embeddingModel);

return Optional.of(create(configBuilder.build()));
});
} else {
Expand Down
Loading