From 983c108eedebaf5612188340b60a86c26db5610f Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 1 Sep 2024 18:34:21 +0200 Subject: [PATCH] end of coding for this year last day of no school :( :( :( 9 months of wasting time --- src/main/java/eu/m724/Main.java | 36 ++++++++++++++---- .../java/eu/m724/example/OaiSource.groovy | 38 ++++++++++--------- .../java/eu/m724/source/ChatResponse.java | 1 + src/main/java/eu/m724/source/ChatSource.java | 20 ---------- .../m724/source/exception/HttpException.java | 11 ++++++ .../{ => impl}/NonStreamingChatResponse.java | 13 +++++-- .../StreamingChatResponse.java} | 27 ++++++++++--- .../java/eu/m724/source/option/Option.java | 30 ++++++++++----- .../java/eu/m724/source/option/Options.java | 9 +---- 9 files changed, 113 insertions(+), 72 deletions(-) create mode 100644 src/main/java/eu/m724/source/exception/HttpException.java rename src/main/java/eu/m724/source/{ => impl}/NonStreamingChatResponse.java (79%) rename src/main/java/eu/m724/source/{SimpleChatResponse.java => impl/StreamingChatResponse.java} (66%) diff --git a/src/main/java/eu/m724/Main.java b/src/main/java/eu/m724/Main.java index 55cc1b3..e41f6c3 100644 --- a/src/main/java/eu/m724/Main.java +++ b/src/main/java/eu/m724/Main.java @@ -6,26 +6,44 @@ import eu.m724.chat.ChatMessage; import eu.m724.example.OaiSource; import eu.m724.source.ChatResponse; import eu.m724.source.ChatSource; +import eu.m724.source.exception.HttpException; import eu.m724.source.option.Option; import eu.m724.source.option.Options; import java.util.NoSuchElementException; import java.util.Scanner; +import java.util.regex.Pattern; -public class Main { +class Main { public static void main(String[] args) throws InterruptedException { - //ChatSource source = new ExampleSource(); - //source.options().setValue("name", "nekalakininahappenenawiwanatin"); + Scanner scanner = new Scanner(System.in); - ChatSource source = new OaiSource(System.getenv("API_KEY")); + String apiKey = System.getenv("API_KEY"); + boolean complainedApiKey = false; + + if (apiKey == null) { + System.out.print("\nAPI Key: "); + apiKey = scanner.nextLine(); + if (apiKey.isBlank()) { + System.out.println("Wrong"); + return; + } + } + + if (!Pattern.matches("sk-proj-.*?(?:\\s|$)", apiKey)) { + System.out.println("This key looks invalid"); + complainedApiKey = true; + } + + ChatSource source = new OaiSource(apiKey); source.options().setValue("model", "chatgpt-4o-latest"); Chat chat = new Chat("Speak in super wuper uwu wanguage."); - 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"); - System.out.printf("Source: \033[1m%s\033[0m %s (%d) by %s\n", source.info().name(), source.info().versionName(), source.info().version(), source.info().author()); + System.out.println("\nWelcome to CHUT chat. Say something after the \033[1m>\033[0m, or type \033[1m:help\033[0m to see available commands"); + System.out.println("Working directory: " + System.getProperty("user.dir")); + System.out.printf("\033[1m%s\033[0m %s by %s (%s v%d)\n", source.info().name(), source.info().versionName(), source.info().author(), source.getClass().getName(), source.info().version()); - Scanner scanner = new Scanner(System.in); String prompt; while (true) { @@ -54,6 +72,10 @@ public class Main { } } else { System.out.print("Error: " + token.error().toString()); + if (complainedApiKey && token.error() instanceof HttpException && ((HttpException)token.error()).statusCode == 401) { + System.out.print("\nTold you"); + complainedApiKey = false; + } } } while (token.finishReason() == null); diff --git a/src/main/java/eu/m724/example/OaiSource.groovy b/src/main/java/eu/m724/example/OaiSource.groovy index e2fee6a..48e7c1e 100644 --- a/src/main/java/eu/m724/example/OaiSource.groovy +++ b/src/main/java/eu/m724/example/OaiSource.groovy @@ -4,7 +4,8 @@ import eu.m724.chat.Chat import eu.m724.source.ChatResponse import eu.m724.source.ChatSource import eu.m724.source.ChatSourceInfo -import eu.m724.source.SimpleChatResponse +import eu.m724.source.exception.HttpException +import eu.m724.source.impl.StreamingChatResponse import eu.m724.source.option.DoubleOption import eu.m724.source.option.Options import eu.m724.source.option.StringOption @@ -14,6 +15,8 @@ import org.json.JSONObject import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse +import java.util.stream.Collectors + // 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 { @@ -29,7 +32,8 @@ class OaiSource implements ChatSource { private def options = new Options( new StringOption("apiKey", "API key", apiKey), new StringOption("model", "Model", "gpt-4o-mini"), - new DoubleOption("temperature", "Temperature", 1.1) + new DoubleOption("temperature", "Temperature", 1.1), + new DoubleOption("presence_penalty", "Presence penalty", 0.1) ) @Override @@ -47,8 +51,8 @@ class OaiSource implements ChatSource { def apiKey = options.getStringValue("apiKey") // TODO handle null def requestBody = new JSONObject() .put("model", options.getStringValue("model")) - .put("temperature", options.getOptionValue("temperature", Double::class)) - .put("presence_penalty", 0.1) + .put("temperature", options.getDoubleValue("temperature")) + .put("presence_penalty", options.getDoubleValue("presence_penalty")) .put("stream", true) .put("messages", formatChat(chat)).toString() @@ -61,16 +65,25 @@ class OaiSource implements ChatSource { def client = HttpClient.newHttpClient() - def chatResponse = new SimpleChatResponse() + def chatResponse = new StreamingChatResponse() def response = client.sendAsync(request, HttpResponse.BodyHandlers.ofLines()) - response.thenAccept { Exception exception = null + //System.out.println(it.statusCode()); if (it.statusCode() != 200) { - exception = new Exception("Non 200 status code: %d".formatted(it.statusCode())) + String message = "Unknown"; + + try { + String body = it.body().collect(Collectors.joining()); + JSONObject json = new JSONObject(body); + message = json.getJSONObject("error").getString("message") + } catch (Exception ignored) { } + + chatResponse.error(new HttpException(it.statusCode(), message)); + return; } it.body().forEach { @@ -95,17 +108,6 @@ class OaiSource implements ChatSource { } } } -/* - - if (exception != null) { - chatResponse.completeExceptionally(exception) - } else { - def json = new JSONObject(it.body()) - //System.out.println(json) - def completion = json.getJSONArray("choices").getJSONObject(0).getJSONObject("message").getString("content") - chatResponse.complete(completion) - }*/ - } return chatResponse diff --git a/src/main/java/eu/m724/source/ChatResponse.java b/src/main/java/eu/m724/source/ChatResponse.java index 957c034..3ab9b49 100644 --- a/src/main/java/eu/m724/source/ChatResponse.java +++ b/src/main/java/eu/m724/source/ChatResponse.java @@ -10,6 +10,7 @@ public interface ChatResponse { /** * is this response streaming * if it's not, the queue will get one element that is the whole response + * I think about replacing with an abstract class and put this in the constructor * * @return is this response streaming */ diff --git a/src/main/java/eu/m724/source/ChatSource.java b/src/main/java/eu/m724/source/ChatSource.java index 257088e..e18c80c 100644 --- a/src/main/java/eu/m724/source/ChatSource.java +++ b/src/main/java/eu/m724/source/ChatSource.java @@ -32,26 +32,6 @@ public interface ChatSource { default ChatResponse ask(Chat chat) { ChatResponse chatResponse = onAsked(chat); - /* if (chatResponse.streaming()) { - StringBuilder total = new StringBuilder(); - CompletableFuture.runAsync(() -> { - ChatEvent event; - while (true) { - try { - event = chatResponse.eventQueue().take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - total.append(event.text()); - if (event.finishReason() != null) { - chatResponse.message().complete(new ChatMessage(true, total.toString())); - break; - } - } - }); - } this was a draft I'm keeping because I might change my mind */ - - // TODO make sure it works in parallel chatResponse.message().thenAccept(message -> { if (message != null) chat.addMessage(message); }); diff --git a/src/main/java/eu/m724/source/exception/HttpException.java b/src/main/java/eu/m724/source/exception/HttpException.java new file mode 100644 index 0000000..04d0b6a --- /dev/null +++ b/src/main/java/eu/m724/source/exception/HttpException.java @@ -0,0 +1,11 @@ +package eu.m724.source.exception; + +// TODO rename? +public class HttpException extends Exception { + public final int statusCode; + + public HttpException(int statusCode, String message) { + super(message); + this.statusCode = statusCode; + } +} diff --git a/src/main/java/eu/m724/source/NonStreamingChatResponse.java b/src/main/java/eu/m724/source/impl/NonStreamingChatResponse.java similarity index 79% rename from src/main/java/eu/m724/source/NonStreamingChatResponse.java rename to src/main/java/eu/m724/source/impl/NonStreamingChatResponse.java index aceb0c3..c7a61bc 100644 --- a/src/main/java/eu/m724/source/NonStreamingChatResponse.java +++ b/src/main/java/eu/m724/source/impl/NonStreamingChatResponse.java @@ -1,7 +1,8 @@ -package eu.m724.source; +package eu.m724.source.impl; import eu.m724.chat.ChatEvent; import eu.m724.chat.ChatMessage; +import eu.m724.source.ChatResponse; import java.util.concurrent.CompletableFuture; import java.util.concurrent.LinkedBlockingQueue; @@ -31,8 +32,12 @@ public class NonStreamingChatResponse implements ChatResponse { try { eventQueue.put(ChatEvent.of(content, "stop")); } catch (InterruptedException e) { - throw new RuntimeException(e); // TODO probably should handle this + // I don't know what this exception means + // and I don't think how will it cause me problems + // so ignoring it for now + throw new RuntimeException(e); } + message.complete(new ChatMessage(true, content)); return true; @@ -44,8 +49,10 @@ public class NonStreamingChatResponse implements ChatResponse { try { eventQueue.put(ChatEvent.of(throwable)); } catch (InterruptedException e) { - throw new RuntimeException(e); // TODO probably should handle this + // again + throw new RuntimeException(e); } + message.complete(null); return true; diff --git a/src/main/java/eu/m724/source/SimpleChatResponse.java b/src/main/java/eu/m724/source/impl/StreamingChatResponse.java similarity index 66% rename from src/main/java/eu/m724/source/SimpleChatResponse.java rename to src/main/java/eu/m724/source/impl/StreamingChatResponse.java index d50001e..044c2b8 100644 --- a/src/main/java/eu/m724/source/SimpleChatResponse.java +++ b/src/main/java/eu/m724/source/impl/StreamingChatResponse.java @@ -1,31 +1,33 @@ -package eu.m724.source; +package eu.m724.source.impl; import eu.m724.chat.ChatEvent; import eu.m724.chat.ChatMessage; +import eu.m724.source.ChatResponse; import java.util.concurrent.CompletableFuture; import java.util.concurrent.LinkedBlockingQueue; -public class SimpleChatResponse implements ChatResponse { +public class StreamingChatResponse implements ChatResponse { private final boolean streaming; private final LinkedBlockingQueue eventQueue; private final CompletableFuture message; private String total = ""; - public SimpleChatResponse() { + public StreamingChatResponse() { this.streaming = true; this.eventQueue = new LinkedBlockingQueue<>(); this.message = new CompletableFuture<>(); } public void put(String token, String finishReason) { - //System.out.println(System.currentTimeMillis()); - try { eventQueue.put(ChatEvent.of(token, finishReason)); } catch (InterruptedException e) { - throw new RuntimeException(e); // TODO I don't know what this is + // I don't know what this exception means + // and I don't think how will it cause me problems + // so ignoring it for now + throw new RuntimeException(e); } if (token != null) { @@ -45,6 +47,19 @@ public class SimpleChatResponse implements ChatResponse { put(null, finishReason); } + public void error(Throwable throwable) { + try { + eventQueue.put(ChatEvent.of(throwable)); + } catch (InterruptedException e) { + // again + throw new RuntimeException(e); + } + + message.complete(ChatMessage.assistant(total)); + } + + // + @Override public boolean streaming() { return streaming; diff --git a/src/main/java/eu/m724/source/option/Option.java b/src/main/java/eu/m724/source/option/Option.java index ab3e955..4ea5675 100644 --- a/src/main/java/eu/m724/source/option/Option.java +++ b/src/main/java/eu/m724/source/option/Option.java @@ -17,6 +17,10 @@ public abstract class Option { this.value = value; } + /** + * gets the value of this option + * @return the value + */ public T getValue() { return value; } @@ -28,20 +32,26 @@ public abstract class Option { * @throws ClassCastException if type is wrong */ public void setValue(Object valueObject) { - Class type = getType(); + if (valueObject == null) { + this.value = null; + } else { + Class type = getType(); - if (!type.isInstance(valueObject)) { - throw new ClassCastException("Invalid type %s, expected %s".formatted(valueObject.getClass().getName(), getType().getName())); + if (!type.isInstance(valueObject)) { + throw new ClassCastException("Invalid type %s, expected %s".formatted(valueObject.getClass().getName(), getType().getName())); + } + + @SuppressWarnings("unchecked") + T value = (T) valueObject; + + if (!isValid(value)) + throw new IllegalArgumentException("Value invalid"); + + this.value = value; } - - T value = (T) valueObject; // :| - - if (!isValid(value)) - throw new IllegalArgumentException("Value invalid"); - - this.value = value; } + @SuppressWarnings("unchecked") public Class getType() { return (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } diff --git a/src/main/java/eu/m724/source/option/Options.java b/src/main/java/eu/m724/source/option/Options.java index 3b9fcef..3058d16 100644 --- a/src/main/java/eu/m724/source/option/Options.java +++ b/src/main/java/eu/m724/source/option/Options.java @@ -108,14 +108,7 @@ public class Options { * @throws NoSuchElementException if no option with this id */ public void setValue(String id, Object value) { - Option option = getOption(id); - Class type = option.getType(); - - if (!type.isInstance(value)) { - throw new ClassCastException("Option \"%s\" is of type \"%s\". You passed \"%s\".".formatted(id, value.getClass().getName(), type.getName())); - } - - option.setValue(value); + getOption(id).setValue(value); } /**