Moved to LangChain4J for ChatGPT and Gemini modules

This commit is contained in:
Erik C. Thauvin 2024-09-08 19:30:53 -07:00
parent ebc3da70e4
commit c60531f617
Signed by: erik
GPG key ID: 776702A6A2DA330E
14 changed files with 102 additions and 163 deletions

1
.gitignore vendored
View file

@ -60,3 +60,4 @@ local.properties
logs logs
mobibot.properties mobibot.properties
out out
/target/

View file

@ -71,9 +71,6 @@
<ID>ReturnCount:Addons.kt$Addons$fun help(channel: String, topic: String, event: GenericMessageEvent): Boolean</ID> <ID>ReturnCount:Addons.kt$Addons$fun help(channel: String, topic: String, event: GenericMessageEvent): Boolean</ID>
<ID>ReturnCount:ExceptionSanitizer.kt$ExceptionSanitizer$fun ModuleException.sanitize(vararg sanitize: String): ModuleException</ID> <ID>ReturnCount:ExceptionSanitizer.kt$ExceptionSanitizer$fun ModuleException.sanitize(vararg sanitize: String): ModuleException</ID>
<ID>ReturnCount:Seen.kt$Seen$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID> <ID>ReturnCount:Seen.kt$Seen$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
<ID>SwallowedException:GoogleSearchTest.kt$GoogleSearchTest$e: ModuleException</ID>
<ID>SwallowedException:StockQuoteTest.kt$StockQuoteTest$e: ModuleException</ID>
<ID>SwallowedException:WolframAlphaTest.kt$WolframAlphaTest$e: ModuleException</ID>
<ID>ThrowsCount:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?, maxTokens: Int): String</ID> <ID>ThrowsCount:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?, maxTokens: Int): String</ID>
<ID>ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List&lt;Message&gt;</ID> <ID>ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List&lt;Message&gt;</ID>
<ID>ThrowsCount:Joke.kt$Joke.Companion$@JvmStatic @Throws(ModuleException::class) fun randomJoke(): List&lt;Message&gt;</ID> <ID>ThrowsCount:Joke.kt$Joke.Companion$@JvmStatic @Throws(ModuleException::class) fun randomJoke(): List&lt;Message&gt;</ID>
@ -82,7 +79,9 @@
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject</ID> <ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject</ID>
<ID>ThrowsCount:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID> <ID>ThrowsCount:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>ThrowsCount:WolframAlpha.kt$WolframAlpha.Companion$@JvmStatic @Throws(ModuleException::class) fun queryWolfram(query: String, units: String = IMPERIAL, appId: String?): String</ID> <ID>ThrowsCount:WolframAlpha.kt$WolframAlpha.Companion$@JvmStatic @Throws(ModuleException::class) fun queryWolfram(query: String, units: String = IMPERIAL, appId: String?): String</ID>
<ID>TooGenericExceptionCaught:ChatGpt2.kt$ChatGpt2.Companion$e: Exception</ID>
<ID>TooGenericExceptionCaught:Gemini.kt$Gemini.Companion$e: Exception</ID> <ID>TooGenericExceptionCaught:Gemini.kt$Gemini.Companion$e: Exception</ID>
<ID>TooGenericExceptionCaught:Gemini2.kt$Gemini2.Companion$e: Exception</ID>
<ID>TooGenericExceptionCaught:StockQuote.kt$StockQuote.Companion$e: NullPointerException</ID> <ID>TooGenericExceptionCaught:StockQuote.kt$StockQuote.Companion$e: NullPointerException</ID>
<ID>TooGenericExceptionCaught:Weather2.kt$Weather2.Companion$e: NullPointerException</ID> <ID>TooGenericExceptionCaught:Weather2.kt$Weather2.Companion$e: NullPointerException</ID>
<ID>TooManyFunctions:EntryLink.kt$EntryLink : Serializable</ID> <ID>TooManyFunctions:EntryLink.kt$EntryLink : Serializable</ID>
@ -94,6 +93,7 @@
<ID>WildcardImport:FeedMgrTest.kt$import assertk.assertions.*</ID> <ID>WildcardImport:FeedMgrTest.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:FeedReaderTest.kt$import assertk.assertions.*</ID> <ID>WildcardImport:FeedReaderTest.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:FeedsManager.kt$import com.rometools.rome.feed.synd.*</ID> <ID>WildcardImport:FeedsManager.kt$import com.rometools.rome.feed.synd.*</ID>
<ID>WildcardImport:Gemini2Test.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:GeminiTest.kt$import assertk.assertions.*</ID> <ID>WildcardImport:GeminiTest.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:GoogleSearchTest.kt$import assertk.assertions.*</ID> <ID>WildcardImport:GoogleSearchTest.kt$import assertk.assertions.*</ID>
<ID>WildcardImport:JokeTest.kt$import assertk.assertions.*</ID> <ID>WildcardImport:JokeTest.kt$import assertk.assertions.*</ID>

View file

@ -1,7 +1,7 @@
bld.downloadExtensionJavadoc=false bld.downloadExtensionJavadoc=false
bld.downloadExtensionSources=true bld.downloadExtensionSources=true
bld.downloadLocation= bld.downloadLocation=
bld.extension-detekt=com.uwyn.rife2:bld-detekt:0.9.6 bld.extension-detekt=com.uwyn.rife2:bld-detekt:0.9.7
bld.extension-gv=com.uwyn.rife2:bld-generated-version:0.9.9 bld.extension-gv=com.uwyn.rife2:bld-generated-version:0.9.9
bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.8 bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.8
bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.0.1 bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.0.1

50
pom.xml
View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>net.thauvin.erik.mobibot</groupId> <groupId>net.thauvin.erik.mobibot</groupId>
<artifactId>mobibot</artifactId> <artifactId>mobibot</artifactId>
<version>0.8.0-rc+20240809180718</version> <version>0.8.0-rc+20240908192921</version>
<name>mobibot</name> <name>mobibot</name>
<description></description> <description></description>
<url></url> <url></url>
@ -18,7 +18,7 @@
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
<version>3.16.0</version> <version>3.17.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -51,34 +51,28 @@
<version>33.2.1-jre</version> <version>33.2.1-jre</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vertexai</artifactId>
<version>1.7.0</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId> <artifactId>kotlin-stdlib</artifactId>
<version>2.0.10</version> <version>2.0.20</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId> <artifactId>kotlin-stdlib-common</artifactId>
<version>2.0.10</version> <version>2.0.20</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk7</artifactId> <artifactId>kotlin-stdlib-jdk7</artifactId>
<version>2.0.10</version> <version>2.0.20</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId> <artifactId>kotlin-stdlib-jdk8</artifactId>
<version>2.0.10</version> <version>2.0.20</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -96,25 +90,49 @@
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>2.0.15</version> <version>2.0.16</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>2.23.1</version> <version>2.24.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<version>2.23.1</version> <version>2.24.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId> <artifactId>log4j-slf4j2-impl</artifactId>
<version>2.23.1</version> <version>2.24.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.34.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-google-ai-gemini</artifactId>
<version>0.34.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>0.34.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.34.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -45,7 +45,6 @@ disabled-modules=mastodon
# Automatically post links to Mastodon # Automatically post links to Mastodon
#mastodon-auto-post=true #mastodon-auto-post=true
# #
# Get Exchange Rate API key from: https://www.exchangerate-api.com/ # Get Exchange Rate API key from: https://www.exchangerate-api.com/
# #
@ -75,19 +74,13 @@ disabled-modules=mastodon
#wolfram-units=imperial #wolfram-units=imperial
# #
# ChatGPT/OpenAI API key from: https://beta.openai.com/account/api-keys # Get ChatGPT/OpenAI API key from: https://platform.openai.com/api-keys
# #
#chatgpt-api-key= #chatgpt-api-key=
#chatgpt-max-tokens=1024 #chatgpt-max-tokens=1024
# #
# Set a Vertex AI Gemini API project in Google Cloud: # Get Google Gemini API key from https://ai.google.dev/gemini-api/docs/api-key
# https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/quickstart-multimodal
# Don't forget to:
# gcloud config set project PROJECT_ID
# gcloud auth login LOGIN
# gcloud auth application-default login
# #
#gemini-project-id= #gemini-api-key=
#gemini-location=us-west1
#gemini-max-tokens=1024 #gemini-max-tokens=1024

View file

@ -77,20 +77,20 @@ public class MobibotBuild extends Project {
new Repository("https://jitpack.io"), new Repository("https://jitpack.io"),
SONATYPE_SNAPSHOTS_LEGACY); SONATYPE_SNAPSHOTS_LEGACY);
var log4j = version(2, 23, 1); var log4j = version(2, 24, 0);
var kotlin = version(2, 0, 10); var kotlin = version(2, 0, 20);
var langchain = version(0, 34, 0);
scope(compile) scope(compile)
// PircBotX // PircBotX
.include(dependency("com.github.pircbotx", "pircbotx", "2.3.1")) .include(dependency("com.github.pircbotx", "pircbotx", "2.3.1"))
// Commons (mostly for PircBotX) // Commons (mostly for PircBotX)
.include(dependency("org.apache.commons", "commons-lang3", "3.16.0")) .include(dependency("org.apache.commons", "commons-lang3", "3.17.0"))
.include(dependency("org.apache.commons", "commons-text", "1.12.0")) .include(dependency("org.apache.commons", "commons-text", "1.12.0"))
.include(dependency("commons-codec", "commons-codec", "1.17.1")) .include(dependency("commons-codec", "commons-codec", "1.17.1"))
.include(dependency("commons-net", "commons-net", "3.11.1")) .include(dependency("commons-net", "commons-net", "3.11.1"))
// Google // Google
.include(dependency("com.google.code.gson", "gson", "2.11.0")) .include(dependency("com.google.code.gson", "gson", "2.11.0"))
.include(dependency("com.google.guava", "guava", "33.2.1-jre")) .include(dependency("com.google.guava", "guava", "33.2.1-jre"))
.include(dependency("com.google.cloud", "google-cloud-vertexai", "1.7.0"))
// Kotlin // Kotlin
.include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin)) .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin))
.include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-common", kotlin)) .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-common", kotlin))
@ -99,10 +99,15 @@ public class MobibotBuild extends Project {
.include(dependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.8.1")) .include(dependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.8.1"))
.include(dependency("org.jetbrains.kotlinx", "kotlinx-cli-jvm", "0.3.6")) .include(dependency("org.jetbrains.kotlinx", "kotlinx-cli-jvm", "0.3.6"))
// Logging // Logging
.include(dependency("org.slf4j", "slf4j-api", "2.0.15")) .include(dependency("org.slf4j", "slf4j-api", "2.0.16"))
.include(dependency("org.apache.logging.log4j", "log4j-api", log4j)) .include(dependency("org.apache.logging.log4j", "log4j-api", log4j))
.include(dependency("org.apache.logging.log4j", "log4j-core", log4j)) .include(dependency("org.apache.logging.log4j", "log4j-core", log4j))
.include(dependency("org.apache.logging.log4j", "log4j-slf4j2-impl", log4j)) .include(dependency("org.apache.logging.log4j", "log4j-slf4j2-impl", log4j))
// LangChain4J
.include(dependency("dev.langchain4j", "langchain4j-open-ai", langchain))
.include(dependency("dev.langchain4j", "langchain4j-google-ai-gemini", langchain))
.include(dependency("dev.langchain4j", "langchain4j-core", langchain))
.include(dependency("dev.langchain4j", "langchain4j", langchain))
// Misc. // Misc.
.include(dependency("com.rometools", "rome", "2.1.0")) .include(dependency("com.rometools", "rome", "2.1.0"))
.include(dependency("com.squareup.okhttp3", "okhttp", "4.12.0")) .include(dependency("com.squareup.okhttp3", "okhttp", "4.12.0"))
@ -118,8 +123,8 @@ public class MobibotBuild extends Project {
scope(test) scope(test)
.include(dependency("com.willowtreeapps.assertk", "assertk-jvm", version(0, 28, 1))) .include(dependency("com.willowtreeapps.assertk", "assertk-jvm", version(0, 28, 1)))
.include(dependency("org.jetbrains.kotlin", "kotlin-test-junit5", kotlin)) .include(dependency("org.jetbrains.kotlin", "kotlin-test-junit5", kotlin))
.include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 3))) .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 11, 0)))
.include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 10, 3))); .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 11, 0)));
List<String> jars = new ArrayList<>(); List<String> jars = new ArrayList<>();
runtimeClasspathJars().forEach(f -> jars.add("./lib/" + f.getName())); runtimeClasspathJars().forEach(f -> jars.add("./lib/" + f.getName()));

View file

@ -396,11 +396,11 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
// Load the modules // Load the modules
addons.add(Calc()) addons.add(Calc())
addons.add(ChatGpt()) addons.add(ChatGpt2())
addons.add(CryptoPrices()) addons.add(CryptoPrices())
addons.add(CurrencyConverter()) addons.add(CurrencyConverter())
addons.add(Dice()) addons.add(Dice())
addons.add(Gemini()) addons.add(Gemini2())
addons.add(GoogleSearch()) addons.add(GoogleSearch())
addons.add(Info(tell, seen)) addons.add(Info(tell, seen))
addons.add(Joke()) addons.add(Joke())

View file

@ -14,12 +14,12 @@ import java.time.ZoneId
*/ */
object ReleaseInfo { object ReleaseInfo {
const val PROJECT = "mobibot" const val PROJECT = "mobibot"
const val VERSION = "0.8.0-rc+20240712110931" const val VERSION = "0.8.0-rc+20240908190240"
@JvmField @JvmField
@Suppress("MagicNumber") @Suppress("MagicNumber")
val BUILD_DATE: LocalDateTime = LocalDateTime.ofInstant( val BUILD_DATE: LocalDateTime = LocalDateTime.ofInstant(
Instant.ofEpochMilli(1720807771484L), ZoneId.systemDefault() Instant.ofEpochMilli(1725847361020L), ZoneId.systemDefault()
) )
const val WEBSITE = "https://mobitopia.org/mobibot/" const val WEBSITE = "https://mobitopia.org/mobibot/"

View file

@ -31,23 +31,16 @@
package net.thauvin.erik.mobibot.modules package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Constants import dev.langchain4j.model.openai.OpenAiChatModel
import dev.langchain4j.model.openai.OpenAiChatModelName
import net.thauvin.erik.mobibot.Utils import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.Utils.sendMessage import net.thauvin.erik.mobibot.Utils.sendMessage
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.pircbotx.hooks.types.GenericMessageEvent import org.pircbotx.hooks.types.GenericMessageEvent
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
class ChatGpt : AbstractModule() { class ChatGpt2 : AbstractModule() {
val logger: Logger = LoggerFactory.getLogger(ChatGpt::class.java) val logger: Logger = LoggerFactory.getLogger(ChatGpt2::class.java)
override val name = CHATGPT_NAME override val name = CHATGPT_NAME
@ -93,9 +86,6 @@ class ChatGpt : AbstractModule() {
*/ */
const val MAX_TOKENS_PROP = "chatgpt-max-tokens" const val MAX_TOKENS_PROP = "chatgpt-max-tokens"
// ChatGPT API URL
private const val API_URL = "https://api.openai.com/v1/chat/completions"
// ChatGPT command // ChatGPT command
private const val CHATGPT_CMD = "chatgpt" private const val CHATGPT_CMD = "chatgpt"
@ -103,48 +93,15 @@ class ChatGpt : AbstractModule() {
@Throws(ModuleException::class) @Throws(ModuleException::class)
fun chat(query: String, apiKey: String?, maxTokens: Int): String { fun chat(query: String, apiKey: String?, maxTokens: Int): String {
if (!apiKey.isNullOrEmpty()) { if (!apiKey.isNullOrEmpty()) {
val jsonObject = JSONObject()
jsonObject.put("model", "gpt-3.5-turbo-1106")
jsonObject.put("max_tokens", maxTokens)
val message = JSONObject()
message.put("role", "user")
message.put("content", query)
val messages = JSONArray()
messages.put(message)
jsonObject.put("messages", messages)
val request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer $apiKey")
.header("User-Agent", Constants.USER_AGENT)
.POST(HttpRequest.BodyPublishers.ofString(jsonObject.toString()))
.build()
try { try {
val response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()) val model = OpenAiChatModel.builder()
if (response.statusCode() == 200) { .apiKey(apiKey)
try { .modelName(OpenAiChatModelName.GPT_4_O)
val jsonResponse = JSONObject(response.body()) .maxTokens(maxTokens)
val choices = jsonResponse.getJSONArray("choices") .build()
return choices.getJSONObject(0).getJSONObject("message").getString("content").trim()
} catch (e: JSONException) { return model.generate(query)
throw ModuleException( } catch (e: Exception) {
"$CHATGPT_CMD($query): JSON",
"A JSON error has occurred while conversing with $CHATGPT_NAME.",
e
)
}
} else {
if (response.statusCode() == 429) {
throw ModuleException(
"$CHATGPT_CMD($query): Rate limit reached",
"Rate limit reached. Please try again later."
)
} else {
throw IOException("HTTP Status Code: " + response.statusCode())
}
}
} catch (e: IOException) {
throw ModuleException( throw ModuleException(
"$CHATGPT_CMD($query): IO", "$CHATGPT_CMD($query): IO",
"An IO error has occurred while conversing with $CHATGPT_NAME.", "An IO error has occurred while conversing with $CHATGPT_NAME.",

View file

@ -217,6 +217,6 @@ class CurrencyConverter : AbstractModule() {
init { init {
commands.add(CURRENCY_CMD) commands.add(CURRENCY_CMD)
initProperties(API_KEY_PROP) initProperties(API_KEY_PROP)
loadSymbols(properties[ChatGpt.API_KEY_PROP]) loadSymbols(properties[ChatGpt2.API_KEY_PROP])
} }
} }

View file

@ -31,12 +31,7 @@
package net.thauvin.erik.mobibot.modules package net.thauvin.erik.mobibot.modules
import com.google.cloud.vertexai.VertexAI import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel
import com.google.cloud.vertexai.api.GenerationConfig
import com.google.cloud.vertexai.api.HarmCategory
import com.google.cloud.vertexai.api.SafetySetting
import com.google.cloud.vertexai.generativeai.GenerativeModel
import com.google.cloud.vertexai.generativeai.ResponseHandler
import net.thauvin.erik.mobibot.Utils import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.Utils.sendMessage import net.thauvin.erik.mobibot.Utils.sendMessage
import org.pircbotx.hooks.types.GenericMessageEvent import org.pircbotx.hooks.types.GenericMessageEvent
@ -45,8 +40,8 @@ import org.slf4j.LoggerFactory
import java.util.* import java.util.*
class Gemini : AbstractModule() { class Gemini2 : AbstractModule() {
private val logger: Logger = LoggerFactory.getLogger(Gemini::class.java) private val logger: Logger = LoggerFactory.getLogger(Gemini2::class.java)
override val name = GEMINI_NAME override val name = GEMINI_NAME
@ -55,8 +50,7 @@ class Gemini : AbstractModule() {
try { try {
val answer = chat( val answer = chat(
args.trim(), args.trim(),
properties[PROJECT_ID_PROP], properties[GEMINI_API_KEY],
properties[LOCATION_PROP],
properties.getOrDefault(MAX_TOKENS_PROP, "1024").toInt() properties.getOrDefault(MAX_TOKENS_PROP, "1024").toInt()
) )
if (!answer.isNullOrEmpty()) { if (!answer.isNullOrEmpty()) {
@ -82,17 +76,12 @@ class Gemini : AbstractModule() {
const val GEMINI_NAME = "Gemini" const val GEMINI_NAME = "Gemini"
/** /**
* The Google cloud project ID property. * The API key
*/ */
const val PROJECT_ID_PROP = "gemini-project-id" const val GEMINI_API_KEY = "gemini-api-key"
/** /**
* The Vertex AI location property. * The max number of output tokens property.
*/
const val LOCATION_PROP = "gemini-location"
/**
* The max number of tokens property.
*/ */
const val MAX_TOKENS_PROP = "gemini-max-tokens" const val MAX_TOKENS_PROP = "gemini-max-tokens"
@ -103,40 +92,18 @@ class Gemini : AbstractModule() {
@Throws(ModuleException::class) @Throws(ModuleException::class)
fun chat( fun chat(
query: String, query: String,
projectId: String?, apiKey: String?,
location: String?, maxTokens: Int
maxToken: Int
): String? { ): String? {
if (!projectId.isNullOrEmpty() && !location.isNullOrEmpty()) { if (!apiKey.isNullOrEmpty()) {
try { try {
VertexAI(projectId, location).use { vertexAI -> val gemini = GoogleAiGeminiChatModel.builder()
val generationConfig = GenerationConfig.newBuilder().setMaxOutputTokens(maxToken).build() .apiKey(apiKey)
val safetySettings = listOf( .modelName("gemini-1.5-flash")
SafetySetting.newBuilder() .maxOutputTokens(maxTokens)
.setCategory(HarmCategory.HARM_CATEGORY_HATE_SPEECH) .build()
.setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE)
.build(),
SafetySetting.newBuilder()
.setCategory(HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT)
.setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE)
.build(),
SafetySetting.newBuilder()
.setCategory(HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT)
.setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE)
.build(),
SafetySetting.newBuilder()
.setCategory(HarmCategory.HARM_CATEGORY_HARASSMENT)
.setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE)
.build()
)
val model = GenerativeModel.Builder().setModelName("gemini-1.5-flash-001")
.setGenerationConfig(generationConfig)
.setVertexAi(vertexAI).build()
.withSafetySettings(safetySettings)
val response = model.generateContent(query) return gemini.generate(query)
return ResponseHandler.getText(response)
}
} catch (e: Exception) { } catch (e: Exception) {
throw ModuleException( throw ModuleException(
"$GEMINI_CMD($query): IO", "$GEMINI_CMD($query): IO",
@ -159,7 +126,6 @@ class Gemini : AbstractModule() {
add(Utils.helpFormat("%c $GEMINI_CMD explain quantum computing in simple terms")) add(Utils.helpFormat("%c $GEMINI_CMD explain quantum computing in simple terms"))
add(Utils.helpFormat("%c $GEMINI_CMD how do I make an HTTP request in Javascript?")) add(Utils.helpFormat("%c $GEMINI_CMD how do I make an HTTP request in Javascript?"))
} }
initProperties(PROJECT_ID_PROP, LOCATION_PROP, MAX_TOKENS_PROP) initProperties(GEMINI_API_KEY, MAX_TOKENS_PROP)
} }
} }

View file

@ -39,10 +39,10 @@ import net.thauvin.erik.mobibot.DisableOnCi
import net.thauvin.erik.mobibot.LocalProperties import net.thauvin.erik.mobibot.LocalProperties
import kotlin.test.Test import kotlin.test.Test
class ChatGptTest : LocalProperties() { class ChatGpt2Test : LocalProperties() {
@Test @Test
fun testApiKey() { fun testApiKey() {
assertFailure { ChatGpt.chat("1 gallon to liter", "", 0) } assertFailure { ChatGpt2.chat("1 gallon to liter", "", 0) }
.isInstanceOf(ModuleException::class.java) .isInstanceOf(ModuleException::class.java)
.hasNoCause() .hasNoCause()
} }
@ -51,7 +51,7 @@ class ChatGptTest : LocalProperties() {
fun testChatOnCoverage() { fun testChatOnCoverage() {
if (System.getenv("CI") == null || System.getenv("COVERAGE_JDK") != null) { if (System.getenv("CI") == null || System.getenv("COVERAGE_JDK") != null) {
assertThat( assertThat(
ChatGpt.chat("how do I encode a URL in java?", getProperty(ChatGpt.API_KEY_PROP), 60) ChatGpt2.chat("how do I encode a URL in java?", getProperty(ChatGpt2.API_KEY_PROP), 60)
).contains("URLEncoder") ).contains("URLEncoder")
} }
} }
@ -59,12 +59,12 @@ class ChatGptTest : LocalProperties() {
@Test @Test
@DisableOnCi @DisableOnCi
fun testChat() { fun testChat() {
val apiKey = getProperty(ChatGpt.API_KEY_PROP) val apiKey = getProperty(ChatGpt2.API_KEY_PROP)
assertThat( assertThat(
ChatGpt.chat("how do I make an HTTP request in Javascript?", apiKey, 100) ChatGpt2.chat("how do I make an HTTP request in Javascript?", apiKey, 200)
).contains("XMLHttpRequest") ).contains("XMLHttpRequest")
assertFailure { ChatGpt.chat("1 liter to gallon", apiKey, -1) } assertFailure { ChatGpt2.chat("1 liter to gallon", apiKey, -1) }
.isInstanceOf(ModuleException::class.java) .isInstanceOf(ModuleException::class.java)
} }
} }

View file

@ -37,10 +37,10 @@ import net.thauvin.erik.mobibot.DisableOnCi
import net.thauvin.erik.mobibot.LocalProperties import net.thauvin.erik.mobibot.LocalProperties
import kotlin.test.Test import kotlin.test.Test
class GeminiTest : LocalProperties() { class Gemini2Test : LocalProperties() {
@Test @Test
fun testApiKey() { fun testApiKey() {
assertFailure { Gemini.chat("1 gallon to liter", "", "", 1024) } assertFailure { Gemini2.chat("1 gallon to liter", "", 0) }
.isInstanceOf(ModuleException::class.java) .isInstanceOf(ModuleException::class.java)
.hasNoCause() .hasNoCause()
} }
@ -48,19 +48,18 @@ class GeminiTest : LocalProperties() {
@Test @Test
@DisableOnCi @DisableOnCi
fun chatPrompt() { fun chatPrompt() {
val projectId = getProperty(Gemini.PROJECT_ID_PROP) val apiKey = getProperty(Gemini2.GEMINI_API_KEY)
val location = getProperty(Gemini.LOCATION_PROP) val maxTokens = getProperty(Gemini2.MAX_TOKENS_PROP).toInt()
val maxTokens = getProperty(Gemini.MAX_TOKENS_PROP).toInt()
assertThat( assertThat(
Gemini.chat("how do I make an HTTP request in Javascript?", projectId, location, maxTokens) Gemini2.chat("how do I make an HTTP request in Javascript?", apiKey, maxTokens)
).isNotNull().contains("XMLHttpRequest") ).isNotNull().contains("XMLHttpRequest")
assertThat( assertThat(
Gemini.chat("how do I encode a URL in java?", projectId, location, 60) Gemini2.chat("how do I encode a URL in java?", apiKey, 60)
).isNotNull().contains("URLEncoder") ).isNotNull().contains("URLEncoder")
assertFailure { Gemini.chat("1 liter to gallon", projectId, "blah", 40) } assertFailure { Gemini2.chat("1 liter to gallon", "foo", 40) }
.isInstanceOf(ModuleException::class.java) .isInstanceOf(ModuleException::class.java)
} }
} }

View file

@ -38,10 +38,10 @@
<li><a href="https://commons.apache.org/proper/commons-net/">Apache Commons Net</a></li> <li><a href="https://commons.apache.org/proper/commons-net/">Apache Commons Net</a></li>
<li><a href="https://github.com/ethauvin/cryptoprice">CryptoPrice</a></li> <li><a href="https://github.com/ethauvin/cryptoprice">CryptoPrice</a></li>
<li><a href="https://www.objecthunter.net/exp4j/">exp4j</a></li> <li><a href="https://www.objecthunter.net/exp4j/">exp4j</a></li>
<li><a href="https://github.com/googleapis/google-cloud-java/tree/main/java-vertexai">Google Vertex AI</a></li>
<li><a href="https://github.com/ethauvin/jokeapi">JokeAPI</a></li> <li><a href="https://github.com/ethauvin/jokeapi">JokeAPI</a></li>
<li><a href="https://jsoup.org/">jsoup</a></li> <li><a href="https://jsoup.org/">jsoup</a></li>
<li><a href="https://github.com/Kotlin/kotlinx-cli">kotlinx-cli</a></li> <li><a href="https://github.com/Kotlin/kotlinx-cli">kotlinx-cli</a></li>
<li><a href="https://docs.langchain4j.dev/">LangChain4J</a></li>
<li><a href="https://square.github.io/okhttp/">OkHttp</a></li> <li><a href="https://square.github.io/okhttp/">OkHttp</a></li>
<li><a href="https://bitbucket.org/aksinghnet/owm-japis">OWM JAPIs</a></li> <li><a href="https://bitbucket.org/aksinghnet/owm-japis">OWM JAPIs</a></li>
<li><a href="https://github.com/ethauvin/pinboard-poster">Pinboard Poster</a></li> <li><a href="https://github.com/ethauvin/pinboard-poster">Pinboard Poster</a></li>