Skip to content

Commit

Permalink
Merge pull request #229 from jmartisk/dev-ui-moderation
Browse files Browse the repository at this point in the history
Moderation model page for the Dev UI
  • Loading branch information
geoand authored Jan 17, 2024
2 parents 29db5c1 + ad5524f commit 1099f85
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 31 deletions.
7 changes: 6 additions & 1 deletion docs/modules/ROOT/pages/dev-ui.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ page is only available if the application contains a chat model.
* *Images* page: allows you to test the outputs of image models and tune its parameters.
This page is provided specifically by the `openai-vanilla` extension and is currently specific
to OpenAI's image models. It appears if the application uses the `openai-vanilla` extension
and doesn't have image models explicitly disabled.
and doesn't have image models explicitly disabled.
* *Moderation* page: allows you to test the outputs of moderation models - you submit a prompt
and receive a list of scores for each appropriateness category (violence, sexual, hate,...).
This page is currently only available with the `openai-vanilla` extension, and it
appears if the application doesn't explicitly disable moderation models.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.quarkiverse.langchain4j.openai.deployment.devui;

import io.quarkiverse.langchain4j.openai.deployment.Langchain4jOpenAiBuildConfig;
import io.quarkiverse.langchain4j.openai.runtime.devui.OpenAiImagesJsonRPCService;
import io.quarkiverse.langchain4j.openai.runtime.devui.OpenAiModerationModelsJsonRPCService;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.Page;

public class OpenAiDevUIProcessor {

@BuildStep(onlyIf = IsDevelopment.class)
CardPageBuildItem cardPage(
BuildProducer<JsonRPCProvidersBuildItem> producers,
Langchain4jOpenAiBuildConfig config) {
CardPageBuildItem card = new CardPageBuildItem();
addImageModelPage(producers, config, card);
addModerationModelPage(producers, config, card);
return card;
}

private void addImageModelPage(
BuildProducer<JsonRPCProvidersBuildItem> producers,
Langchain4jOpenAiBuildConfig config,
CardPageBuildItem card) {
if (config.imageModel().enabled().orElse(true)) {
card.addPage(Page.webComponentPageBuilder().title("Images")
.componentLink("qwc-images.js")
.icon("font-awesome-solid:palette"));
producers.produce(new JsonRPCProvidersBuildItem(OpenAiImagesJsonRPCService.class));
}
}

private void addModerationModelPage(BuildProducer<JsonRPCProvidersBuildItem> producers,
Langchain4jOpenAiBuildConfig config,
CardPageBuildItem card) {
if (config.moderationModel().enabled().orElse(true)) {
card.addPage(Page.webComponentPageBuilder().title("Moderation model")
.componentLink("qwc-moderation.js")
.icon("font-awesome-solid:triangle-exclamation"));
producers.produce(new JsonRPCProvidersBuildItem(OpenAiModerationModelsJsonRPCService.class));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {html, LitElement} from 'lit';
import '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-column.js';
import '@vaadin/text-area';
import '@vaadin/button';
import { JsonRpc } from 'jsonrpc';

export class QwcModerationModels extends LitElement {

jsonRpc = new JsonRpc(this);

supportedModels = [
{ label: "text-moderation-latest", value: "text-moderation-latest"},
{ label: "text-moderation-stable", value: "text-moderation-stable"}]

static properties = {
"_moderationResponse": {state: true}
}

constructor() {
super();
}

render() {
return html`
<h3>Moderation model</h3>
<vaadin-horizontal-layout>
<vaadin-select
label="Model"
id="model-name"
.items="${this.supportedModels}"
.value="${this.supportedModels[0].value}"
></vaadin-select>
</vaadin-horizontal-layout>
<vaadin-text-area id="prompt" label="Prompt" style="width:90%"></vaadin-text-area><br/>
<vaadin-button id="image-submit" @click=${() => this._doGenerate(
this.shadowRoot.getElementById('model-name').value,
this.shadowRoot.getElementById('prompt').value
)}>Moderate the prompt
</vaadin-button>
<br/>
<h3>Moderation response</h3>
${this._moderationResponse}
`;
}

_doGenerate(modelName, prompt) {
this._moderationResponse = html`Retrieving...<br/>`;
this.jsonRpc.moderate({modelName: modelName, prompt: prompt}).then((jsonRpcResponse) => {
this._moderationResponse = this._printResponse(jsonRpcResponse.result);
}).catch((error) => {
this._moderationResponse = html`
<qui-alert level="error" showIcon>
<span>${JSON.stringify(error.error)}</span>
</qui-alert>`
});
}

_printResponse(response) {
if (response) {
return html`
<span>Flagged: ${response.flagged}</span><br/>
<vaadin-grid .items="${response.categories}" theme="no-border">
<vaadin-grid-sort-column auto-width
path="name"
header="Category">
</vaadin-grid-sort-column>
<vaadin-grid-sort-column auto-width
path="flagged"
header="Flagged">
</vaadin-grid-sort-column>
<vaadin-grid-sort-column auto-width
path="score"
header="Score">
</vaadin-grid-sort-column>
</vaadin-grid>`;
} else {
return html``;
}

}

}

customElements.define('qwc-moderation', QwcModerationModels);
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.quarkiverse.langchain4j.openai.runtime.devui;

import java.time.Duration;
import java.util.Optional;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import dev.ai4j.openai4j.OpenAiClient;
import dev.ai4j.openai4j.moderation.Categories;
import dev.ai4j.openai4j.moderation.CategoryScores;
import dev.ai4j.openai4j.moderation.ModerationRequest;
import dev.ai4j.openai4j.moderation.ModerationResponse;
import dev.ai4j.openai4j.moderation.ModerationResult;
import dev.langchain4j.internal.RetryUtils;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

public class OpenAiModerationModelsJsonRPCService {

@Inject
@ConfigProperty(name = "quarkus.langchain4j.openai.base-url")
String baseUrl;

@Inject
@ConfigProperty(name = "quarkus.langchain4j.openai.api-key")
String apiKey;

@Inject
@ConfigProperty(name = "quarkus.langchain4j.openai.timeout")
Duration timeout;

@Inject
@ConfigProperty(name = "quarkus.langchain4j.openai.image-model.user")
Optional<String> user;

@Inject
@ConfigProperty(name = "quarkus.langchain4j.openai.max-retries")
Integer maxRetries;

OpenAiClient client;

@PostConstruct
public void init() {
client = OpenAiClient.builder().openAiApiKey(apiKey).baseUrl(baseUrl)
.callTimeout(timeout).connectTimeout(timeout)
.readTimeout(timeout).writeTimeout(timeout).build();
}

@PreDestroy
public void cleanup() {
if (client != null) {
client.shutdown();
}
}

public JsonObject moderate(String modelName, String prompt) {
ModerationRequest request = ModerationRequest.builder().model(modelName).input(prompt).build();
ModerationResponse response = RetryUtils.withRetry(() -> client.moderation(request).execute(), this.maxRetries);
ModerationResult moderationResult = response.results().get(0);
CategoryScores categoryScores = moderationResult.categoryScores();
Categories categoryFlags = moderationResult.categories();
JsonObject result = new JsonObject();
result.put("flagged", moderationResult.isFlagged());
JsonArray categories = new JsonArray();
addCategoryScore(categories, "sexual", categoryScores.sexual(), categoryFlags.sexual());
addCategoryScore(categories, "hate", categoryScores.hate(), categoryFlags.hate());
addCategoryScore(categories, "hate-threatening", categoryScores.hateThreatening(), categoryFlags.hateThreatening());
addCategoryScore(categories, "self-harm", categoryScores.selfHarm(), categoryFlags.selfHarm());
addCategoryScore(categories, "violence", categoryScores.violence(), categoryFlags.violence());
addCategoryScore(categories, "violence-graphic", categoryScores.violenceGraphic(), categoryFlags.violenceGraphic());
addCategoryScore(categories, "sexual-minors", categoryScores.sexualMinors(), categoryFlags.sexualMinors());
result.put("categories", categories);
return result;
}

private void addCategoryScore(JsonArray categories, String name, Double score, Boolean flagged) {
if (score != null) {
JsonObject categoryScore = new JsonObject();
categoryScore.put("name", name);
categoryScore.put("flagged", flagged);
categoryScore.put("score", score);
categories.add(categoryScore);
}
}

}

0 comments on commit 1099f85

Please sign in to comment.