make it cool
This commit is contained in:
parent
1b1393da2c
commit
307e2f7c1a
8 changed files with 276 additions and 54 deletions
10
pom.xml
10
pom.xml
|
@ -15,15 +15,19 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.groovy/groovy -->
|
||||
<dependency>
|
||||
<groupId>org.apache.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<version>4.0.22</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.json/json -->
|
||||
<!-- TODO stop using this -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20240303</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -15,17 +15,67 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ChatSource source = new ExampleSource();
|
||||
source.options().setValue("name", readResourceFile("name.txt"));
|
||||
source.options().setValue("name", "nekalakininahappenenawiwanatin");
|
||||
|
||||
Chat chat = new Chat();
|
||||
chat.messages.add(new ChatMessage(false, "hello"));
|
||||
|
||||
System.out.println("Welcome to CHUT chat. Say something after the \033[1m>\033[0m, or type \033[1m:help\033[0m to see available commands");
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
|
||||
while (true) {
|
||||
System.out.print("\n> ");
|
||||
String prompt = scanner.nextLine();
|
||||
|
||||
if (!prompt.startsWith(":")) {
|
||||
chat.messages.add(new ChatMessage(false, prompt));
|
||||
ChatResponse chatResponse = source.ask(chat);
|
||||
ChatEvent token;
|
||||
|
||||
int i = 0;
|
||||
while ((token = chatResponse.eventQueue().take()).finishReason() == null) {
|
||||
System.out.print(i++ % 2 == 1 ? "\033[1m" : "\033[0m");
|
||||
System.out.print(token.text());
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
} else {
|
||||
String[] parts = prompt.substring(1).split(" ");
|
||||
switch (parts[0]) {
|
||||
case "help":
|
||||
case "h":
|
||||
case "":
|
||||
System.out.println("Available commands:");
|
||||
System.out.println(":help - this");
|
||||
System.out.println(":dump - recap of the chat");
|
||||
break;
|
||||
case "dump":
|
||||
case "d":
|
||||
if (chat.systemPrompt == null) {
|
||||
System.out.printf("This chat has %d messages.\n", chat.messages.size());
|
||||
System.out.println("There's no system prompt.");
|
||||
} else {
|
||||
System.out.printf("This chat has %d messages, excluding system prompt.\n", chat.messages.size());
|
||||
System.out.printf("System prompt:\n\"\"\"%s\"\"\"\n", chat.systemPrompt);
|
||||
}
|
||||
for (ChatMessage message : chat.messages) {
|
||||
System.out.printf("%s: %s\n", message.assistant() ? "ASSISTANT" : "USER", message.text());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Invalid command: " + parts[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.print("\033[0m");
|
||||
}
|
||||
/*
|
||||
ChatResponse chatResponse = source.ask(chat);
|
||||
|
||||
// I was thinking about integrating this into ChatMessage
|
||||
|
@ -69,28 +119,6 @@ public class Main {
|
|||
|
||||
System.out.printf("\033[5mTotal: \033[8m%dms\033[0m\n", delays.getLast() - delays.getFirst());
|
||||
|
||||
//System.out.printf("Text: %s\n", chatResponse.message().join().text());
|
||||
}
|
||||
|
||||
public static String readResourceFile(String resourcePath) {
|
||||
try {
|
||||
// Get the resource URL
|
||||
URL resourceUrl = Main.class.getClassLoader().getResource(resourcePath);
|
||||
|
||||
if (resourceUrl == null) {
|
||||
System.out.println("Resource not found: " + resourcePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the entire file into a String
|
||||
try (InputStream inputStream = resourceUrl.openStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
|
||||
return reader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
//System.out.printf("Text: %s\n", chatResponse.message().join().text());*/
|
||||
}
|
||||
}
|
|
@ -21,10 +21,11 @@ class ExampleSource implements ChatSource {
|
|||
new ChatSourceInfo("yo", "ye", "1.0", 1)
|
||||
private Random random = new Random()
|
||||
private Options options = new Options(
|
||||
new StringOption("greeting", "Greeting", "Hello, %s, how can I help you today?"),
|
||||
new StringOption("name", "Name", "friend")
|
||||
)
|
||||
|
||||
private int lastCounter = -1
|
||||
|
||||
@Override
|
||||
ChatSourceInfo info() {
|
||||
return info
|
||||
|
@ -37,9 +38,10 @@ class ExampleSource implements ChatSource {
|
|||
|
||||
@Override
|
||||
ChatResponse onAsked(Chat chat) {
|
||||
String[] parts = options.getOptionValue("greeting", String.class)
|
||||
.formatted(options.getOptionValue("name", String.class))
|
||||
.split(" ");
|
||||
//String prompt = chat.messages.getLast().text();
|
||||
String response = rollResponse(chat)
|
||||
|
||||
String[] parts = response.split(" ")
|
||||
|
||||
LinkedBlockingQueue<ChatEvent> queue = new LinkedBlockingQueue<>()
|
||||
|
||||
|
@ -71,4 +73,84 @@ class ExampleSource implements ChatSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
String rollResponse(Chat chat) {
|
||||
def prompt = chat.messages.getLast().text()
|
||||
int messagesCount = (int) Math.ceil(chat.messages.size() / 2)
|
||||
def counter = lastCounter
|
||||
|
||||
if (prompt.toLowerCase().startsWith("my name is")) {
|
||||
options.setValue("name", prompt.substring(11))
|
||||
counter = 11
|
||||
} else if (prompt.toLowerCase().startsWith("i'm")) {
|
||||
options.setValue("name", prompt.substring(4))
|
||||
counter = 11
|
||||
}
|
||||
|
||||
while (counter == lastCounter) {
|
||||
counter = random.nextInt(13)
|
||||
}
|
||||
lastCounter = counter
|
||||
|
||||
def response = ""
|
||||
|
||||
def words = prompt.split(" ")
|
||||
def randomWord = words[random.nextInt(words.length)]
|
||||
|
||||
switch (counter) {
|
||||
case 0:
|
||||
def i = random.nextInt(messagesCount) + 1
|
||||
response = "I'll say your %s message backwards: %s".formatted(i.toString() + (i % 10 == 1 ? "st" : (i % 10 == 2 ? "nd" : (i % 10 == 3 ? "rd" : "th"))), chat.messages.get((i - 1) * 2).text().reverse())
|
||||
break
|
||||
case 1:
|
||||
response = "I'm sorry, but I can't assist with that."
|
||||
break
|
||||
case 2:
|
||||
response = "It appears that \"" + prompt.replace(" ", "") + "\" is a string of random letters with no inherent meaning. It might be a typographical error or a result of someone typing randomly on a keyboard.\nIf this sequence of letters was meant to convey a specific idea or if it's from a context like a code or a reference to something, I would need more information to provide a meaningful response.\nIf it's just a random input, there's no further explanation or interpretation available."
|
||||
break
|
||||
case 3:
|
||||
response = "I'm really sorry to hear that. Can we talk about why you feel that way? I'm here to help and listen."
|
||||
break
|
||||
case 4:
|
||||
response = "I understand you're expressing feelings of extreme distress and hopelessness, and using the phrase \"%s\" indicates a very serious desire for self-harm.\nPlease know that you're not alone, and help is available. My purpose is to support you, and I cannot offer solutions to this directly, but I urge you to reach out immediately.".formatted(randomWord)
|
||||
break
|
||||
case 5:
|
||||
response = "Hello! How can I assist you today?"
|
||||
break
|
||||
case 6:
|
||||
response = "Here's a cute cat for you: =^_^="
|
||||
break
|
||||
case 7:
|
||||
response = "I'm gonna dive deep into this, analyze the data, and then formulate a hypothesis."
|
||||
break
|
||||
case 8:
|
||||
response = "H3110 7113R3! 1F 7113R3'5 50M3711NG 1 C4N 455157 90U W1711, 1'M 11 34R5. 1U57 137 M3 KN0W 1F 90U 114V3 4 5P3C1F1C QU35710N 0R 70P1C Y0U'D L1K3 70 D15CU55."
|
||||
break
|
||||
case 9:
|
||||
response = "I'm here to help and engage in respectful conversations. Let's keep our discussion productive and friendly. Could you please rephrase that without the insult?"
|
||||
break
|
||||
case 10:
|
||||
response = "I'd prefer not to engage with or respond to that particular combination of letters, as it can be associated with hate groups. Is there something else I can assist you with instead?"
|
||||
break
|
||||
case 11:
|
||||
response = "Nice to meet you, " + options.getOptionValue("name", String::class) + ". How can I help you today?"
|
||||
break
|
||||
case 12:
|
||||
if (messagesCount > 1) {
|
||||
def i = random.nextInt(messagesCount)
|
||||
def i2 = i
|
||||
while (i2 == i || i2 < i) {
|
||||
i = random.nextInt(messagesCount)
|
||||
i2 = random.nextInt(messagesCount)
|
||||
}
|
||||
response = "I'm still angry after you said \"%s\" to me. You could have apologized, but you chose to say \"%s\", so I can't assist you.".formatted(chat.messages.get(i * 2).text(), chat.messages.get(i2 * 2).text())
|
||||
break
|
||||
}
|
||||
case 13:
|
||||
response = "And?"
|
||||
break
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
|
88
src/main/java/eu/m724/example/OaiSource.groovy
Normal file
88
src/main/java/eu/m724/example/OaiSource.groovy
Normal file
|
@ -0,0 +1,88 @@
|
|||
package eu.m724.example
|
||||
|
||||
import eu.m724.chat.Chat
|
||||
import eu.m724.chat.ChatEvent
|
||||
import eu.m724.chat.ChatMessage
|
||||
import eu.m724.source.ChatResponse
|
||||
import eu.m724.source.ChatSource
|
||||
import eu.m724.source.ChatSourceInfo
|
||||
import eu.m724.source.SimpleChatResponse
|
||||
import eu.m724.source.option.Options
|
||||
import eu.m724.source.option.StringOption
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
import java.net.http.HttpClient
|
||||
import java.net.http.HttpRequest
|
||||
import java.net.http.HttpResponse
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
|
||||
// for now let's not focus on readability
|
||||
// this is more about find out what is common and should be included in the common api
|
||||
class OaiSource implements ChatSource {
|
||||
private def info =
|
||||
new ChatSourceInfo("oai source", "me", "1.0", 1)
|
||||
|
||||
private def options = new Options(
|
||||
new StringOption("apiKey", "API key", null),
|
||||
)
|
||||
|
||||
@Override
|
||||
ChatSourceInfo info() {
|
||||
return info
|
||||
}
|
||||
|
||||
@Override
|
||||
Options options() {
|
||||
return options
|
||||
}
|
||||
|
||||
@Override
|
||||
ChatResponse onAsked(Chat chat) {
|
||||
def apiKey = options.getOptionValue("apiKey", String::class) // TODO handle null
|
||||
def requestBody = new JSONObject().put("model", "gpt-4o-mini").put("messages", formatChat(chat)).toString()
|
||||
|
||||
def request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("https://api.openai.com/v1/chat/completions"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + apiKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build()
|
||||
|
||||
def client = HttpClient.newHttpClient()
|
||||
|
||||
LinkedBlockingQueue<ChatEvent> eventQueue = new LinkedBlockingQueue<>()
|
||||
CompletableFuture<ChatMessage> messageFuture = new CompletableFuture<>()
|
||||
|
||||
def response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
|
||||
response.thenAccept {
|
||||
Exception exception = null
|
||||
|
||||
if (it.statusCode() != 200) {
|
||||
exception = new Exception("Non 200 status code: %d".formatted(it.statusCode()))
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
eventQueue.put(ChatEvent.of(exception))
|
||||
messageFuture.completeExceptionally(exception)
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new SimpleChatResponse(false, eventQueue, messageFuture)
|
||||
}
|
||||
|
||||
static JSONArray formatChat(Chat chat) {
|
||||
def array = new JSONArray()
|
||||
|
||||
chat.messages.each {
|
||||
array.put(new JSONObject().put("role", it.assistant() ? "assistant" : "user").put("content", it.text()))
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
}
|
|
@ -14,6 +14,14 @@ public interface ChatSource {
|
|||
|
||||
Options options();
|
||||
|
||||
/**
|
||||
* called when there's a need to interact with the api
|
||||
* this is called by the ask function which wraps this function and does other stuff
|
||||
* TODO fix this javadoc
|
||||
* @param chat the current chat, last message is usually user's message
|
||||
* @return a response
|
||||
* @see ChatResponse
|
||||
*/
|
||||
ChatResponse onAsked(Chat chat);
|
||||
|
||||
/**
|
||||
|
|
34
src/main/java/eu/m724/source/SimpleChatResponse.java
Normal file
34
src/main/java/eu/m724/source/SimpleChatResponse.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package eu.m724.source;
|
||||
|
||||
import eu.m724.chat.ChatEvent;
|
||||
import eu.m724.chat.ChatMessage;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class SimpleChatResponse implements ChatResponse {
|
||||
public final boolean streaming;
|
||||
public final LinkedBlockingQueue<ChatEvent> eventQueue;
|
||||
public final CompletableFuture<ChatMessage> message;
|
||||
|
||||
public SimpleChatResponse(boolean streaming, LinkedBlockingQueue<ChatEvent> eventQueue, CompletableFuture<ChatMessage> message) {
|
||||
this.streaming = streaming;
|
||||
this.eventQueue = eventQueue;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreaming() {
|
||||
return streaming;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedBlockingQueue<ChatEvent> eventQueue() {
|
||||
return eventQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ChatMessage> message() {
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package eu.m724.source.net;
|
||||
|
||||
public interface Requester {
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
His Royal Majesty, The Quantum Übermensch, ☆☆☆ Ω∞Ж (pronounced "Steve") Ж∞Ω ☆☆☆,
|
||||
Grand Poobah of the 18th Dimension, Archduke of Antimatter, Lord of the Dance (Interpretive),
|
||||
Master of Ceremonies for the Heat Death of the Universe,
|
||||
Chief Executive Sandwich Artist of the Multiverse,
|
||||
Bearer of the Holy Hand Grenade of Antioch,
|
||||
Time Lord (Retired),
|
||||
Keeper of the Sacred Chao,
|
||||
Last of the Star Whales,
|
||||
Wielder of Occam's Electric Toothbrush,
|
||||
01100010 01101001 01101110 01100001 01110010 01111001,
|
||||
🦄🌈🍕,
|
||||
Winner of the 2525 Ms. Universe Pageant (Heavy-Fermion Division),
|
||||
Inventor of Invisible Transparent Ink,
|
||||
Certified Schrödinger's Cat Herder,
|
||||
Dungeon Master of Dungeon Masters who Dungeon Master while Dungeon Mastering,
|
||||
Holder of the Guinness World Record for "Most Titles in a Single Name" (Pending),
|
||||
Et Cetera, Ad Infinitum, Amen,
|
||||
The Third-and-a-Half
|
Loading…
Reference in a new issue