Skip to content

Commit 78f7239

Browse files
authored
Merge pull request #223 from quarkiverse/openshift-ai
Introduce first version of OpenShift AI extension
2 parents ecb9a72 + cc53ca3 commit 78f7239

File tree

16 files changed

+590
-0
lines changed

16 files changed

+590
-0
lines changed

openshift-ai/deployment/pom.xml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.quarkiverse.langchain4j</groupId>
8+
<artifactId>quarkus-langchain4j-openshift-ai-parent</artifactId>
9+
<version>999-SNAPSHOT</version>
10+
</parent>
11+
12+
13+
<artifactId>quarkus-langchain4j-openshift-ai-deployment</artifactId>
14+
<name>Quarkus Langchain4j - OpenShift AI - Deployment</name>
15+
<dependencies>
16+
<dependency>
17+
<groupId>io.quarkiverse.langchain4j</groupId>
18+
<artifactId>quarkus-langchain4j-openshift-ai</artifactId>
19+
<version>${project.version}</version>
20+
</dependency>
21+
<dependency>
22+
<groupId>io.quarkus</groupId>
23+
<artifactId>quarkus-rest-client-reactive-jackson-deployment</artifactId>
24+
</dependency>
25+
<dependency>
26+
<groupId>io.quarkiverse.langchain4j</groupId>
27+
<artifactId>quarkus-langchain4j-core-deployment</artifactId>
28+
<version>${project.version}</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>io.quarkus</groupId>
32+
<artifactId>quarkus-junit5-internal</artifactId>
33+
<scope>test</scope>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.assertj</groupId>
37+
<artifactId>assertj-core</artifactId>
38+
<version>${assertj.version}</version>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.wiremock</groupId>
43+
<artifactId>wiremock-standalone</artifactId>
44+
<version>${wiremock.version}</version>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
<build>
49+
<plugins>
50+
<plugin>
51+
<artifactId>maven-compiler-plugin</artifactId>
52+
<configuration>
53+
<annotationProcessorPaths>
54+
<path>
55+
<groupId>io.quarkus</groupId>
56+
<artifactId>quarkus-extension-processor</artifactId>
57+
<version>${quarkus.version}</version>
58+
</path>
59+
</annotationProcessorPaths>
60+
</configuration>
61+
</plugin>
62+
</plugins>
63+
</build>
64+
65+
</project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.quarkiverse.langchain4j.openshift.ai.deployment;
2+
3+
import java.util.Optional;
4+
5+
import io.quarkus.runtime.annotations.ConfigDocDefault;
6+
import io.quarkus.runtime.annotations.ConfigGroup;
7+
8+
@ConfigGroup
9+
public interface ChatModelBuildConfig {
10+
11+
/**
12+
* Whether the model should be enabled
13+
*/
14+
@ConfigDocDefault("true")
15+
Optional<Boolean> enabled();
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.quarkiverse.langchain4j.openshift.ai.deployment;
2+
3+
import static io.quarkus.runtime.annotations.ConfigPhase.BUILD_TIME;
4+
5+
import io.quarkus.runtime.annotations.ConfigRoot;
6+
import io.smallrye.config.ConfigMapping;
7+
8+
@ConfigRoot(phase = BUILD_TIME)
9+
@ConfigMapping(prefix = "quarkus.langchain4j.openshift-ai")
10+
public interface Langchain4jOpenshiftAiBuildConfig {
11+
12+
/**
13+
* Chat model related settings
14+
*/
15+
ChatModelBuildConfig chatModel();
16+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.quarkiverse.langchain4j.openshift.ai.deployment;
2+
3+
import static io.quarkiverse.langchain4j.deployment.Langchain4jDotNames.CHAT_MODEL;
4+
5+
import java.util.Optional;
6+
7+
import jakarta.enterprise.context.ApplicationScoped;
8+
9+
import io.quarkiverse.langchain4j.deployment.items.ChatModelProviderCandidateBuildItem;
10+
import io.quarkiverse.langchain4j.deployment.items.SelectedChatModelProviderBuildItem;
11+
import io.quarkiverse.langchain4j.openshiftai.runtime.OpenshiftAiRecorder;
12+
import io.quarkiverse.langchain4j.openshiftai.runtime.config.Langchain4jOpenshiftAiConfig;
13+
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
14+
import io.quarkus.deployment.annotations.BuildProducer;
15+
import io.quarkus.deployment.annotations.BuildStep;
16+
import io.quarkus.deployment.annotations.ExecutionTime;
17+
import io.quarkus.deployment.annotations.Record;
18+
import io.quarkus.deployment.builditem.FeatureBuildItem;
19+
20+
public class OpenshiftAiProcessor {
21+
22+
private static final String FEATURE = "langchain4j-openshift-ai";
23+
24+
private static final String PROVIDER = "openshift-ai";
25+
26+
@BuildStep
27+
FeatureBuildItem feature() {
28+
return new FeatureBuildItem(FEATURE);
29+
}
30+
31+
@BuildStep
32+
public void providerCandidates(BuildProducer<ChatModelProviderCandidateBuildItem> chatProducer,
33+
Langchain4jOpenshiftAiBuildConfig config) {
34+
if (config.chatModel().enabled().isEmpty() || config.chatModel().enabled().get()) {
35+
chatProducer.produce(new ChatModelProviderCandidateBuildItem(PROVIDER));
36+
}
37+
}
38+
39+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
40+
@BuildStep
41+
@Record(ExecutionTime.RUNTIME_INIT)
42+
void generateBeans(OpenshiftAiRecorder recorder,
43+
Optional<SelectedChatModelProviderBuildItem> selectedChatItem,
44+
Langchain4jOpenshiftAiConfig config,
45+
BuildProducer<SyntheticBeanBuildItem> beanProducer) {
46+
if (selectedChatItem.isPresent() && PROVIDER.equals(selectedChatItem.get().getProvider())) {
47+
beanProducer.produce(SyntheticBeanBuildItem
48+
.configure(CHAT_MODEL)
49+
.setRuntimeInit()
50+
.defaultBean()
51+
.scope(ApplicationScoped.class)
52+
.supplier(recorder.chatModel(config))
53+
.done());
54+
}
55+
}
56+
}

openshift-ai/pom.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.quarkiverse.langchain4j</groupId>
8+
<artifactId>quarkus-langchain4j-parent</artifactId>
9+
<version>999-SNAPSHOT</version>
10+
</parent>
11+
<artifactId>quarkus-langchain4j-openshift-ai-parent</artifactId>
12+
<name>Quarkus Langchain4j - OpenShift AI - Parent</name>
13+
<packaging>pom</packaging>
14+
15+
<modules>
16+
<module>deployment</module>
17+
<module>runtime</module>
18+
</modules>
19+
20+
21+
</project>

openshift-ai/runtime/pom.xml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.quarkiverse.langchain4j</groupId>
8+
<artifactId>quarkus-langchain4j-openshift-ai-parent</artifactId>
9+
<version>999-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>quarkus-langchain4j-openshift-ai</artifactId>
13+
<name>Quarkus Langchain4j - OpenShift AI - Runtime</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>io.quarkus</groupId>
18+
<artifactId>quarkus-arc</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>io.quarkus</groupId>
22+
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>io.quarkiverse.langchain4j</groupId>
26+
<artifactId>quarkus-langchain4j-core</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
29+
</dependencies>
30+
<build>
31+
<plugins>
32+
<plugin>
33+
<groupId>io.quarkus</groupId>
34+
<artifactId>quarkus-extension-maven-plugin</artifactId>
35+
<version>${quarkus.version}</version>
36+
<executions>
37+
<execution>
38+
<phase>compile</phase>
39+
<goals>
40+
<goal>extension-descriptor</goal>
41+
</goals>
42+
<configuration>
43+
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
44+
</configuration>
45+
</execution>
46+
</executions>
47+
</plugin>
48+
<plugin>
49+
<artifactId>maven-compiler-plugin</artifactId>
50+
<configuration>
51+
<annotationProcessorPaths>
52+
<path>
53+
<groupId>io.quarkus</groupId>
54+
<artifactId>quarkus-extension-processor</artifactId>
55+
<version>${quarkus.version}</version>
56+
</path>
57+
</annotationProcessorPaths>
58+
</configuration>
59+
</plugin>
60+
<plugin>
61+
<artifactId>maven-jar-plugin</artifactId>
62+
<executions>
63+
<execution>
64+
<id>generate-codestart-jar</id>
65+
<phase>generate-resources</phase>
66+
<goals>
67+
<goal>jar</goal>
68+
</goals>
69+
<configuration>
70+
<classesDirectory>${project.basedir}/src/main</classesDirectory>
71+
<includes>
72+
<include>codestarts/**</include>
73+
</includes>
74+
<classifier>codestarts</classifier>
75+
<skipIfEmpty>true</skipIfEmpty>
76+
</configuration>
77+
</execution>
78+
</executions>
79+
</plugin>
80+
</plugins>
81+
</build>
82+
83+
84+
</project>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package io.quarkiverse.langchain4j.openshiftai;
2+
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
5+
import java.net.URL;
6+
import java.time.Duration;
7+
import java.util.List;
8+
import java.util.concurrent.TimeUnit;
9+
10+
import org.jboss.resteasy.reactive.client.api.LoggingScope;
11+
12+
import dev.langchain4j.agent.tool.ToolSpecification;
13+
import dev.langchain4j.data.message.AiMessage;
14+
import dev.langchain4j.data.message.ChatMessage;
15+
import dev.langchain4j.model.chat.ChatLanguageModel;
16+
import dev.langchain4j.model.output.Response;
17+
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
18+
19+
public class OpenshiftAiChatModel implements ChatLanguageModel {
20+
public static final String TLS_TRUST_ALL = "quarkus.tls.trust-all";
21+
private final String modelId;
22+
private final OpenshiftAiRestApi client;
23+
24+
public OpenshiftAiChatModel(Builder config) {
25+
QuarkusRestClientBuilder builder = QuarkusRestClientBuilder.newBuilder()
26+
.baseUri(config.url)
27+
.connectTimeout(config.timeout.toSeconds(), TimeUnit.SECONDS)
28+
.readTimeout(config.timeout.toSeconds(), TimeUnit.SECONDS);
29+
30+
if (config.logRequests || config.logResponses) {
31+
builder.loggingScope(LoggingScope.REQUEST_RESPONSE);
32+
builder.clientLogger(new OpenshiftAiRestApi.OpenshiftAiClientLogger(config.logRequests,
33+
config.logResponses));
34+
}
35+
36+
this.client = builder.build(OpenshiftAiRestApi.class);
37+
this.modelId = config.modelId;
38+
}
39+
40+
public static Builder builder() {
41+
return new Builder();
42+
}
43+
44+
@Override
45+
public Response<AiMessage> generate(List<ChatMessage> messages) {
46+
47+
TextGenerationRequest request = new TextGenerationRequest(modelId, messages.get(0).text());
48+
49+
TextGenerationResponse textGenerationResponse = client.chat(request);
50+
51+
return Response.from(AiMessage.from(textGenerationResponse.generatedText()));
52+
}
53+
54+
@Override
55+
public Response<AiMessage> generate(List<ChatMessage> messages, List<ToolSpecification> toolSpecifications) {
56+
throw new IllegalArgumentException("Tools are currently not supported for OpenShift AI models");
57+
}
58+
59+
@Override
60+
public Response<AiMessage> generate(List<ChatMessage> messages, ToolSpecification toolSpecification) {
61+
throw new IllegalArgumentException("Tools are currently not supported for OpenShift AI models");
62+
}
63+
64+
public static final class Builder {
65+
66+
private String modelId;
67+
private Duration timeout = Duration.ofSeconds(15);
68+
69+
private URI url;
70+
public boolean logResponses;
71+
public boolean logRequests;
72+
73+
public Builder modelId(String modelId) {
74+
this.modelId = modelId;
75+
return this;
76+
}
77+
78+
public Builder url(URL url) {
79+
try {
80+
this.url = url.toURI();
81+
} catch (URISyntaxException e) {
82+
throw new RuntimeException(e);
83+
}
84+
return this;
85+
}
86+
87+
public Builder timeout(Duration timeout) {
88+
this.timeout = timeout;
89+
return this;
90+
}
91+
92+
public OpenshiftAiChatModel build() {
93+
return new OpenshiftAiChatModel(this);
94+
}
95+
96+
public Builder logRequests(boolean logRequests) {
97+
this.logRequests = logRequests;
98+
return this;
99+
}
100+
101+
public Builder logResponses(boolean logResponses) {
102+
this.logResponses = logResponses;
103+
return this;
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)