Skip to content

Commit 2d4bf6b

Browse files
authored
Add Coherence Embedding Store Support (#10419)
1 parent 712e17b commit 2d4bf6b

File tree

21 files changed

+821
-41
lines changed

21 files changed

+821
-41
lines changed

all/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,10 @@
843843
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
844844
<artifactId>helidon-integrations-langchain4j-providers-jlama</artifactId>
845845
</dependency>
846+
<dependency>
847+
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
848+
<artifactId>helidon-integrations-langchain4j-providers-coherence</artifactId>
849+
</dependency>
846850
<dependency>
847851
<groupId>io.helidon.openapi</groupId>
848852
<artifactId>helidon-openapi</artifactId>

bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,11 @@
11241124
<artifactId>helidon-integrations-langchain4j-providers-jlama</artifactId>
11251125
<version>${helidon.version}</version>
11261126
</dependency>
1127+
<dependency>
1128+
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
1129+
<artifactId>helidon-integrations-langchain4j-providers-coherence</artifactId>
1130+
<version>${helidon.version}</version>
1131+
</dependency>
11271132

11281133
<!-- OpenAPI support -->
11291134
<dependency>

dependencies/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<version.lib.annotation-api>1.3.5</version.lib.annotation-api>
4242
<version.lib.brave-opentracing>1.0.0</version.lib.brave-opentracing>
4343
<version.lib.bytebuddy>1.17.5</version.lib.bytebuddy>
44+
<version.lib.coherence>25.03.1</version.lib.coherence>
4445
<version.lib.commons-codec>1.16.0</version.lib.commons-codec>
4546
<version.lib.commons-logging>1.2</version.lib.commons-logging>
4647
<version.lib.cron-utils>9.2.1</version.lib.cron-utils>
@@ -1359,6 +1360,13 @@
13591360
<!-- END OF Section 4: Testing -->
13601361

13611362
<!-- imported boms -->
1363+
<dependency>
1364+
<groupId>com.oracle.coherence.ce</groupId>
1365+
<artifactId>coherence-bom</artifactId>
1366+
<version>${version.lib.coherence}</version>
1367+
<type>pom</type>
1368+
<scope>import</scope>
1369+
</dependency>
13621370
<!-- Force upgrade. Used by grpc -->
13631371
<dependency>
13641372
<groupId>com.google.protobuf</groupId>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
///////////////////////////////////////////////////////////////////////////////
2+
3+
Copyright (c) 2025 Oracle and/or its affiliates.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
///////////////////////////////////////////////////////////////////////////////
18+
19+
= LangChain4J Coherence Provider
20+
:description: LangChain4J Coherence
21+
:keywords: helidon, AI, LangChain4J, LC4J, Coherence
22+
:feature-name: LangChain4J Integration
23+
:rootdir: {docdir}/../../..
24+
25+
include::{rootdir}/includes/se.adoc[]
26+
27+
== Contents
28+
29+
* <<Overview, Overview>>
30+
* <<Maven Coordinates, Maven Coordinates>>
31+
* <<Components, Components>>
32+
** <<CoherenceEmbeddingModel, CoherenceEmbeddingModel>>
33+
* <<Additional Information, Additional Information>>
34+
35+
== Overview
36+
37+
This module adds support for the Coherence embedding store.
38+
39+
== Maven Coordinates
40+
41+
In addition to the xref:langchain4j.adoc#maven-coordinates[Helidon integration with LangChain4J core dependencies], you must add the following:
42+
43+
[source,xml]
44+
----
45+
<dependency>
46+
<groupId>io.helidon.integrations.langchain4j.providers</groupId>
47+
<artifactId>helidon-integrations-langchain4j-providers-coherence</artifactId>
48+
</dependency>
49+
----
50+
51+
== Components
52+
53+
=== CoherenceEmbeddingModel
54+
55+
To automatically create and add `CoherenceEmbeddingModel` to the service registry add the following lines to `application.yaml`:
56+
57+
[source,yaml]
58+
----
59+
langchain4j:
60+
coherence:
61+
embedding-store:
62+
enabled: true
63+
session: "session"
64+
name: "namedMap"
65+
normalize-embeddings: false
66+
index: "hnsw"
67+
dimension: 768
68+
embedding-model:
69+
service-registry.named: beanName
70+
----
71+
72+
If `enabled` is set to `false`, the configuration is ignored, and the component is not created.
73+
74+
Full list of configuration properties:
75+
76+
[cols="3,3a,5a"]
77+
78+
|===
79+
|Key |Type |Description
80+
81+
|`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.
82+
|`dimension` |integer |The number of dimensions in the embeddings that will be stored in vector store.
83+
|`enabled` |boolean |If set to `true`, Coherence embedding store will be enabled.
84+
|`index` |string |Specifies vector index type use to create a vector index used to query embeddings. Only `hnsw` is supported.
85+
|`name` |string |Specifies name of the Coherence `com.tangosol.net.NamedMap` use to store embeddings.
86+
|`normalize-embeddings` |boolean |A flag that when true, forces normalization of embeddings on adding and searching.
87+
|`session` |string |The name of the `com.tangosol.net.Session` use to obtain the `com.tangosol.net.NamedMap` as specified with `name`.
88+
89+
|===
90+
91+
92+
== Additional Information
93+
94+
* xref:langchain4j.adoc[LangChain4J Integration]
95+
* https://docs.langchain4j.dev/integrations/embedding-stores/coherence[langChain4J Coherence Embedding Store Documentation]

docs/src/main/asciidoc/se/integrations/langchain4j/core.adoc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ langchain4j:
5252
rag:
5353
embedding-store-content-retriever:
5454
enabled: true
55-
embedding-model: "@default"
56-
embedding-store: "@default"
55+
embedding-model:
56+
service-registry.named: "@default"
57+
embedding-store:
58+
service-registry.named: "@default"
5759
----
5860
5961
If `enabled` is set to `false`, the configuration is ignored, and the component is not created.
@@ -69,8 +71,8 @@ Full list of configuration properties:
6971
|`enabled` |boolean |If set to `false`, embedding store content retriever will be disabled even if configured.
7072
|`max-results` |int |Maximum number of results.
7173
|`min-score` |double |Minimum score threshold.
72-
|`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.
73-
|`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.
74+
|`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.
75+
|`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.
7476
7577
|===
7678

docs/src/main/asciidoc/se/integrations/langchain4j/langchain4j.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ Supports https://docs.langchain4j.dev/tutorials/ai-services/[LangChain4J's AI Se
9090
** xref:cohere.adoc#_coherescoringmodel[`CohereScoringModel`]
9191
* xref:oracle.adoc[*Oracle*]
9292
** xref:oracle.adoc#_oracleembeddingmodel[`OracleEmbeddingStore`]
93+
* xref:coherence.adoc[*Coherence*]
94+
** xref:coherence.adoc#_coherenceembeddingmodel[`CoherenceEmbeddingStore`]
9395
* xref:jlama.adoc[*Jlama*]
9496
** xref:jlama.adoc#_jlamachatmodel[`JlamaChatModel`]
9597
** xref:jlama.adoc#_jlamastreamingchatmodel[`JlamaStreamingChatModel`]
@@ -313,6 +315,7 @@ For more details, read the https://docs.langchain4j.dev/tutorials/tools#high-lev
313315
** xref:ollama.adoc[Ollama]
314316
** xref:cohere.adoc[Cohere]
315317
** xref:oracle.adoc[Oracle]
318+
** xref:coherence.adoc[Coherence]
316319
** xref:jlama.adoc[Jlama]
317320
** xref:codegen-provider.adoc[Code generated Lc4j Provider]
318321

integrations/langchain4j/codegen/src/main/java/io/helidon/integrations/langchain4j/codegen/ConfigureMethodBuilder.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,19 @@ class ConfigureMethodBuilder {
4444
.addDescriptionLine("<b>Skipped:</b>")
4545
.addDescriptionLine("<ul>");
4646

47-
if (parentTypeInfo.elementInfo().stream()
47+
var customBuilderMappingMethod = parentTypeInfo.elementInfo().stream()
4848
.filter(m -> m.kind().equals(ElementKind.METHOD))
4949
.filter(m -> m.parameterArguments().isEmpty())
5050
.filter(m -> m.typeName().equals(builderTypeInfo.typeName()))
51-
.anyMatch(m -> m.elementName().equals("configuredBuilder"))) {
51+
.filter(m -> m.elementName().equals("configuredBuilder"))
52+
.findFirst();
53+
54+
if (customBuilderMappingMethod.isPresent()) {
5255
confMethodBuilder
5356
.addContent("var modelBuilder = ")
5457
.addContent(parentTypeInfo.typeName())
55-
.addContentLine(".super.configuredBuilder();");
58+
.addContent(".super.configuredBuilder(")
59+
.addContentLine(");");
5660
} else {
5761
confMethodBuilder.addContent("var modelBuilder = ").addContent(modelTypeName).addContentLine(".builder();");
5862
}

integrations/langchain4j/codegen/src/main/java/io/helidon/integrations/langchain4j/codegen/IntrospectionBlueprintBuilder.java

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,17 @@ private static boolean isInjectedByDefault(TypedElementInfo modelBldMethod) {
167167
return false;
168168
}
169169

170-
void addArrayProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod) {
170+
void addArrayProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod, boolean skipBuilderMapping) {
171171
var methodBuilder = createMethodBuilder(modelBldMethod)
172172
.name(propName);
173173

174174
methodBuilder.addAnnotation(BLDR_SINGULAR_ANNOTATION);
175175

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

178-
confMethodBuilder.configureProperty(propName, propType, false, true, false);
178+
if (!skipBuilderMapping) {
179+
confMethodBuilder.configureProperty(propName, propType, false, true, false);
180+
}
179181

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

187-
void addOptionalProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod) {
188-
var custBuilderMappingAnnotation = modelBldMethod.findAnnotation(LangchainTypes.MODEL_CUSTOM_BUILDER_MAPPING);
189+
void addOptionalProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod, boolean skipBuilderMapping) {
189190
var methodBuilder = createMethodBuilder(modelBldMethod)
190191
.name(propName);
191192

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

199200
if (overrideProps.containsKey(propName)) {
200201
confMethodBuilder.commentOverriddenProperty(overrideProps.get(propName));
201-
confMethodBuilder.configureProperty(propName, propType, false, false,
202-
// Overridden non-optional props are kept mandatory
203-
overrideProps.get(propName).typeName().isOptional());
202+
if (!skipBuilderMapping) {
203+
confMethodBuilder.configureProperty(propName, propType, false, false,
204+
// Overridden non-optional props are kept mandatory
205+
overrideProps.get(propName).typeName().isOptional());
206+
}
204207

205208
} else {
206209
classModelBuilder().addMethod(methodBuilder.build());
207-
if (custBuilderMappingAnnotation.isEmpty()) {
210+
if (!skipBuilderMapping) {
208211
confMethodBuilder.configureProperty(propName, propType, false, false, true);
209212
}
210213
}
211214
}
212215

213-
void addCollectionProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod) {
214-
var custBuilderMappingAnnotation = modelBldMethod.findAnnotation(LangchainTypes.MODEL_CUSTOM_BUILDER_MAPPING);
216+
void addCollectionProperty(String propName, TypeName propType, TypedElementInfo modelBldMethod, boolean skipBuilderMapping) {
215217
var methodBuilder = createMethodBuilder(modelBldMethod)
216218
.name(propName);
217219

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

225-
if (custBuilderMappingAnnotation.isEmpty()) {
227+
if (!skipBuilderMapping) {
226228
confMethodBuilder.configureProperty(propName, propType, false, false, false);
227229
}
228230

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

367370
if (paramType.array() && !hasCollectionAlternative(m, propertyList)) {
368-
this.addArrayProperty(methodName, paramType, m);
371+
this.addArrayProperty(methodName, paramType, m, skipBuilderMapping);
369372

370373
} else if (paramType.isList() || paramType.isSet() || paramType.isMap()) {
371-
this.addCollectionProperty(methodName, paramType, m);
374+
this.addCollectionProperty(methodName, paramType, m, skipBuilderMapping);
372375

373376
} else {
374-
this.addOptionalProperty(methodName, paramType, m);
377+
this.addOptionalProperty(methodName, paramType, m, skipBuilderMapping);
375378

376379
}
377380
}
@@ -404,13 +407,13 @@ private void introspectOverrides() {
404407
} else {
405408

406409
if (propertyType.array() && !hasCollectionAlternative(m, new ArrayList<>(overrideProps.values()))) {
407-
this.addArrayProperty(propertyName, propertyType, m);
410+
this.addArrayProperty(propertyName, propertyType, m, skipBuilderMapping);
408411

409412
} else if (propertyType.isList() || propertyType.isSet() || propertyType.isMap()) {
410-
this.addCollectionProperty(propertyName, propertyType, m);
413+
this.addCollectionProperty(propertyName, propertyType, m, skipBuilderMapping);
411414

412415
} else {
413-
this.addOptionalProperty(propertyName, propertyType, m);
416+
this.addOptionalProperty(propertyName, propertyType, m, skipBuilderMapping);
414417

415418
}
416419
}

integrations/langchain4j/langchain4j/src/main/java/io/helidon/integrations/langchain4j/EmbeddingStoreContentRetrieverConfigBlueprint.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,21 @@ interface EmbeddingStoreContentRetrieverConfigBlueprint {
4949
/**
5050
* Embedding store to use in the content retriever.
5151
*
52-
* @return an {@link java.util.Optional} containing the embedding store
52+
* @return an {@link java.util.Optional} default service bean is injected or {@code embedding-model.service-registry.named}
53+
* can be used to select a named bean
5354
*/
55+
@Option.Configured
56+
@Option.RegistryService
5457
EmbeddingStore<TextSegment> embeddingStore();
5558

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

6469
/**

integrations/langchain4j/langchain4j/src/main/java/io/helidon/integrations/langchain4j/EmbeddingStoreContentRetrieverFactory.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,9 @@
2323
import io.helidon.common.Weight;
2424
import io.helidon.common.Weighted;
2525
import io.helidon.common.config.Config;
26-
import io.helidon.common.types.TypeName;
2726
import io.helidon.service.registry.Service;
2827

29-
import dev.langchain4j.data.segment.TextSegment;
30-
import dev.langchain4j.model.embedding.EmbeddingModel;
3128
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
32-
import dev.langchain4j.store.embedding.EmbeddingStore;
3329

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

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

4941
private final LazyValue<Optional<EmbeddingStoreContentRetriever>> contentRetriever;
5042

5143
@Service.Inject
52-
EmbeddingStoreContentRetrieverFactory(Supplier<EmbeddingStore<TextSegment>> embeddingStore,
53-
Supplier<Optional<EmbeddingModel>> embeddingModel,
54-
Config config) {
44+
EmbeddingStoreContentRetrieverFactory(Config config) {
5545
var configBuilder =
5646
EmbeddingStoreContentRetrieverConfig.builder().config(config.get(CONFIG_ROOT));
5747

@@ -60,9 +50,6 @@ public class EmbeddingStoreContentRetrieverFactory implements Supplier<Optional<
6050
if (!configBuilder.enabled()) {
6151
return Optional.empty();
6252
}
63-
configBuilder.embeddingStore(embeddingStore.get());
64-
embeddingModel.get().ifPresent(configBuilder::embeddingModel);
65-
6653
return Optional.of(create(configBuilder.build()));
6754
});
6855
} else {

0 commit comments

Comments
 (0)