-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from phillip-kruger/hooks
Now with hooks into Dev UI and CLI
- Loading branch information
Showing
20 changed files
with
675 additions
and
367 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
@@ -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; | ||
} | ||
|
||
|
@@ -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}} | ||
|
@@ -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() | ||
|
@@ -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); | ||
} | ||
|
||
} |
129 changes: 129 additions & 0 deletions
129
deployment/src/main/java/io/quarkiverse/chappie/deployment/ChappieDevUIProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
Oops, something went wrong.