Skip to content

Commit

Permalink
Merge pull request #6 from phillip-kruger/hooks
Browse files Browse the repository at this point in the history
Now with hooks into Dev UI and CLI
  • Loading branch information
phillip-kruger authored Jul 25, 2024
2 parents e2eed6a + 556b745 commit 9ae1631
Show file tree
Hide file tree
Showing 20 changed files with 675 additions and 367 deletions.
15 changes: 15 additions & 0 deletions deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@
<artifactId>quarkus-chappie-deployment</artifactId>
<name>Quarkus Chappie - Deployment</name>

<properties>
<langchain4j.version>0.33.0</langchain4j.version>
</properties>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<!-- To ask AI about the exception -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package io.quarkiverse.chappie.runtime;
package io.quarkiverse.chappie.deployment;

import static dev.langchain4j.data.message.SystemMessage.systemMessage;
import static dev.langchain4j.data.message.UserMessage.userMessage;
import static java.util.Arrays.asList;

import java.util.List;
import java.util.Map;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.jboss.logging.Logger;
import java.util.concurrent.CompletableFuture;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -29,29 +24,17 @@
*
* @author Phillip Kruger ([email protected])
*/
@ApplicationScoped
public class AIAssistant {
private static final Logger log = Logger.getLogger(AIAssistant.class.getName());

@Inject
ObjectMapper objectMapper;
private static AIAssistant INSTANCE;

private Prompt systemMessagePrompt;
private String apiKey;
private String modelName;

public void setVersion(String version) {
this.systemMessagePrompt = systemMessageTemplate.apply(Map.of("version", version));
}
private final ObjectMapper objectMapper = new ObjectMapper();
private final Prompt systemMessagePrompt;
private final String apiKey;
private final String modelName;

public void setApiKey(String apiKey) {
public AIAssistant(String quarkusVersion, String apiKey, String modelName) {
this.systemMessagePrompt = systemMessageTemplate.apply(Map.of("version", quarkusVersion));
this.apiKey = apiKey;
}

public void setModelName(String modelName) {
if (modelName == null || modelName.isBlank())
throw new NullPointerException(
"Please supply an OpenAI Model Name as a config property [quarkus.chappie.model-name]");
this.modelName = modelName;
}

Expand All @@ -71,7 +54,7 @@ public void setModelName(String modelName) {
private final PromptTemplate systemMessageTemplate = PromptTemplate
.from(SYSTEM_MESSAGE);

private static final String USER_MESSAGE = """
private static final String USER_MESSAGE_WITH_SOURCE = """
I have the following java exception:
```
{{stacktrace}}
Expand All @@ -85,17 +68,31 @@ public void setModelName(String modelName) {
Please help me fix it.
""";

private final PromptTemplate userMessageTemplate = PromptTemplate
.from(USER_MESSAGE);
private static final String USER_MESSAGE_WITHOUT_SOURCE = """
I have the following java exception in my Quarkus app:
```
{{stacktrace}}
```
Please help me fix it.
""";

private final PromptTemplate userMessageWithSourceTemplate = PromptTemplate
.from(USER_MESSAGE_WITH_SOURCE);

public SuggestedFix helpFix(
String stacktrace,
String source) {
private final PromptTemplate userMessageWithoutSourceTemplate = PromptTemplate
.from(USER_MESSAGE_WITHOUT_SOURCE);

public CompletableFuture<SuggestedFix> helpFix(String stacktrace, String source) {
if (apiKey != null && !apiKey.isBlank() && !apiKey.equals("apiKey")) {
Prompt userMessagePrompt = userMessageTemplate.apply(Map.of("stacktrace", stacktrace, "source", source));
Prompt userMessagePrompt;
if (source != null) {
userMessagePrompt = userMessageWithSourceTemplate.apply(Map.of("stacktrace", stacktrace, "source", source));
} else {
userMessagePrompt = userMessageWithoutSourceTemplate.apply(Map.of("stacktrace", stacktrace));
}

List<ChatMessage> messages = asList(systemMessage(systemMessagePrompt.text()),
List<ChatMessage> messages = List.of(systemMessage(systemMessagePrompt.text()),
userMessage(userMessagePrompt.text()));

ChatLanguageModel model = OpenAiChatModel.builder()
Expand All @@ -105,21 +102,20 @@ public SuggestedFix helpFix(
.responseFormat("json_object")
.build();

Response<AiMessage> response = model.generate(messages);
System.out.println("FINISH REASON = " + response.finishReason());
System.out.println("TOTAL TOKENS = " + response.tokenUsage().totalTokenCount());
System.out.println("RESPONSE TYPE = " + response.content().type());
System.out.println("RESPONSE = " + response.content().text());

try {
return objectMapper.readValue(response.content().text(), SuggestedFix.class);
} catch (JsonProcessingException ex) {
throw new RuntimeException("Error while parsing the response from AI", ex);
}
return CompletableFuture.supplyAsync(() -> {
Response<AiMessage> response = model.generate(messages);
try {
return objectMapper.readValue(response.content().text(), SuggestedFix.class);
} catch (JsonProcessingException ex) {
throw new RuntimeException("Error while parsing the response from AI", ex);
}
});
} else {
return CompletableFuture.completedFuture(new SuggestedFix(
"The quarkus-chappie extension could not assist with this exception",
"You need to provide a `quarkus.chappie.api-key` config property that is set to your OpenAI API key",
null,
null));
}
return new SuggestedFix("The quarkus-chappie extension could not assist with this exception",
"You need to provice a `quarkus.chappie.api-key` config property that is set to your OpenAI api key", null,
null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.quarkiverse.chappie.deployment;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;

import io.quarkiverse.chappie.runtime.ChappieJsonRPCService;
import io.quarkiverse.chappie.runtime.LastException;
import io.quarkus.builder.Version;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
import io.quarkus.deployment.dev.ExceptionNotificationBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.buildtime.BuildTimeActionBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor;

class ChappieDevUIProcessor {

static volatile AtomicReference<LastException> lastExceptionReference;

@BuildStep(onlyIf = IsDevelopment.class)
LastExceptionBuildItem createBroadcasters() {
BroadcastProcessor<LastException> leb = BroadcastProcessor.create();
return new LastExceptionBuildItem(leb);
}

@Consume(ConsoleInstalledBuildItem.class)
@BuildStep(onlyIf = IsDevelopment.class)
void setupExceptionHandler(BuildProducer<ExceptionNotificationBuildItem> exceptionNotificationProducer,
LastExceptionBuildItem lastExceptionBuildItem) {

lastExceptionReference = ChappieProcessorHelper
.getLastException(exceptionNotificationProducer);

exceptionNotificationProducer
.produce(new ExceptionNotificationBuildItem(new BiConsumer<Throwable, StackTraceElement>() {
@Override
public void accept(Throwable throwable, StackTraceElement stackTraceElement) {
LastException lastException = new LastException(stackTraceElement, throwable);
lastExceptionReference.set(lastException);
lastExceptionBuildItem.getLastExceptionBroadcastProcessor().onNext(lastException);
}
}));
}

@BuildStep(onlyIf = IsDevelopment.class)
public CardPageBuildItem pages() {
CardPageBuildItem chappiePage = new CardPageBuildItem();

chappiePage.addPage(Page.webComponentPageBuilder()
.icon("font-awesome-solid:circle-question")
.title("AI Assistance")
.componentLink("qwc-chappie-exception.js"));

return chappiePage;
}

@BuildStep(onlyIf = IsDevelopment.class)
JsonRPCProvidersBuildItem createJsonRPCService(LastExceptionBuildItem lastExceptionBuildItem) {

DevConsoleManager.register("chappie-exception-notification", (t) -> {
return lastExceptionBuildItem.getLastExceptionPublisher();
});

return new JsonRPCProvidersBuildItem(ChappieJsonRPCService.class);
}

@BuildStep(onlyIf = IsDevelopment.class)
BuildTimeActionBuildItem createBuildTimeActions(CurateOutcomeBuildItem curateOutcomeBuildItem,
OutputTargetBuildItem outputTargetBuildItem,
ChappieConfig chappieConfig) {

Path srcMainJava = ChappieProcessorHelper.getSourceRoot(curateOutcomeBuildItem.getApplicationModel(),
outputTargetBuildItem.getOutputDirectory());

BuildTimeActionBuildItem generateManifestActions = new BuildTimeActionBuildItem();
generateManifestActions.addAction("getLastException", ignored -> {
LastException lastException = lastExceptionReference.get();
if (lastException != null) {
return lastException;
}
return null;
});

// TODO: Get last suggested Fix

generateManifestActions.addAction("helpFix", ignored -> {
LastException lastException = lastExceptionReference.get();
if (lastException != null) {
StackTraceElement stackTraceElement = lastException.stackTraceElement();
String className = stackTraceElement.getClassName();
String file = stackTraceElement.getFileName();
if (className.contains(".")) {
file = className.substring(0, className.lastIndexOf('.') + 1).replace('.',
File.separatorChar)
+ file;
}

AIAssistant aiAssistant = new AIAssistant(Version.getVersion(), chappieConfig.apiKey, chappieConfig.modelName);

try {
Path filePath = srcMainJava.resolve(file);
String sourceString = Files.readString(filePath);
String stacktraceString = lastException.getStackTraceString();

return aiAssistant.helpFix(stacktraceString, sourceString);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
return null;
});

return generateManifestActions;
}

}
Loading

0 comments on commit 9ae1631

Please sign in to comment.