end of coding for this year

last day of no school :( :( :(
9 months of wasting time
This commit is contained in:
Minecon724 2024-09-01 18:34:21 +02:00
parent b23600e216
commit 983c108eed
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
9 changed files with 113 additions and 72 deletions

View file

@ -6,26 +6,44 @@ import eu.m724.chat.ChatMessage;
import eu.m724.example.OaiSource; import eu.m724.example.OaiSource;
import eu.m724.source.ChatResponse; import eu.m724.source.ChatResponse;
import eu.m724.source.ChatSource; import eu.m724.source.ChatSource;
import eu.m724.source.exception.HttpException;
import eu.m724.source.option.Option; import eu.m724.source.option.Option;
import eu.m724.source.option.Options; import eu.m724.source.option.Options;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Scanner; import java.util.Scanner;
import java.util.regex.Pattern;
public class Main { class Main {
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) throws InterruptedException {
//ChatSource source = new ExampleSource(); Scanner scanner = new Scanner(System.in);
//source.options().setValue("name", "nekalakininahappenenawiwanatin");
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"); source.options().setValue("model", "chatgpt-4o-latest");
Chat chat = new Chat("Speak in super wuper uwu wanguage."); 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.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.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("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; String prompt;
while (true) { while (true) {
@ -54,6 +72,10 @@ public class Main {
} }
} else { } else {
System.out.print("Error: " + token.error().toString()); 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); } while (token.finishReason() == null);

View file

@ -4,7 +4,8 @@ import eu.m724.chat.Chat
import eu.m724.source.ChatResponse import eu.m724.source.ChatResponse
import eu.m724.source.ChatSource import eu.m724.source.ChatSource
import eu.m724.source.ChatSourceInfo 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.DoubleOption
import eu.m724.source.option.Options import eu.m724.source.option.Options
import eu.m724.source.option.StringOption import eu.m724.source.option.StringOption
@ -14,6 +15,8 @@ import org.json.JSONObject
import java.net.http.HttpClient import java.net.http.HttpClient
import java.net.http.HttpRequest import java.net.http.HttpRequest
import java.net.http.HttpResponse import java.net.http.HttpResponse
import java.util.stream.Collectors
// for now let's not focus on readability // 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 // this is more about find out what is common and should be included in the common api
class OaiSource implements ChatSource { class OaiSource implements ChatSource {
@ -29,7 +32,8 @@ class OaiSource implements ChatSource {
private def options = new Options( private def options = new Options(
new StringOption("apiKey", "API key", apiKey), new StringOption("apiKey", "API key", apiKey),
new StringOption("model", "Model", "gpt-4o-mini"), 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 @Override
@ -47,8 +51,8 @@ class OaiSource implements ChatSource {
def apiKey = options.getStringValue("apiKey") // TODO handle null def apiKey = options.getStringValue("apiKey") // TODO handle null
def requestBody = new JSONObject() def requestBody = new JSONObject()
.put("model", options.getStringValue("model")) .put("model", options.getStringValue("model"))
.put("temperature", options.getOptionValue("temperature", Double::class)) .put("temperature", options.getDoubleValue("temperature"))
.put("presence_penalty", 0.1) .put("presence_penalty", options.getDoubleValue("presence_penalty"))
.put("stream", true) .put("stream", true)
.put("messages", formatChat(chat)).toString() .put("messages", formatChat(chat)).toString()
@ -61,16 +65,25 @@ class OaiSource implements ChatSource {
def client = HttpClient.newHttpClient() def client = HttpClient.newHttpClient()
def chatResponse = new SimpleChatResponse() def chatResponse = new StreamingChatResponse()
def response = client.sendAsync(request, HttpResponse.BodyHandlers.ofLines()) def response = client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
response.thenAccept { response.thenAccept {
Exception exception = null Exception exception = null
//System.out.println(it.statusCode());
if (it.statusCode() != 200) { 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 { 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 return chatResponse

View file

@ -10,6 +10,7 @@ public interface ChatResponse {
/** /**
* is this response streaming * is this response streaming
* if it's not, the queue will get one element that is the whole response * 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 * @return is this response streaming
*/ */

View file

@ -32,26 +32,6 @@ public interface ChatSource {
default ChatResponse ask(Chat chat) { default ChatResponse ask(Chat chat) {
ChatResponse chatResponse = onAsked(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 -> { chatResponse.message().thenAccept(message -> {
if (message != null) chat.addMessage(message); if (message != null) chat.addMessage(message);
}); });

View file

@ -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;
}
}

View file

@ -1,7 +1,8 @@
package eu.m724.source; package eu.m724.source.impl;
import eu.m724.chat.ChatEvent; import eu.m724.chat.ChatEvent;
import eu.m724.chat.ChatMessage; import eu.m724.chat.ChatMessage;
import eu.m724.source.ChatResponse;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@ -31,8 +32,12 @@ public class NonStreamingChatResponse implements ChatResponse {
try { try {
eventQueue.put(ChatEvent.of(content, "stop")); eventQueue.put(ChatEvent.of(content, "stop"));
} catch (InterruptedException e) { } 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)); message.complete(new ChatMessage(true, content));
return true; return true;
@ -44,8 +49,10 @@ public class NonStreamingChatResponse implements ChatResponse {
try { try {
eventQueue.put(ChatEvent.of(throwable)); eventQueue.put(ChatEvent.of(throwable));
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); // TODO probably should handle this // again
throw new RuntimeException(e);
} }
message.complete(null); message.complete(null);
return true; return true;

View file

@ -1,31 +1,33 @@
package eu.m724.source; package eu.m724.source.impl;
import eu.m724.chat.ChatEvent; import eu.m724.chat.ChatEvent;
import eu.m724.chat.ChatMessage; import eu.m724.chat.ChatMessage;
import eu.m724.source.ChatResponse;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
public class SimpleChatResponse implements ChatResponse { public class StreamingChatResponse implements ChatResponse {
private final boolean streaming; private final boolean streaming;
private final LinkedBlockingQueue<ChatEvent> eventQueue; private final LinkedBlockingQueue<ChatEvent> eventQueue;
private final CompletableFuture<ChatMessage> message; private final CompletableFuture<ChatMessage> message;
private String total = ""; private String total = "";
public SimpleChatResponse() { public StreamingChatResponse() {
this.streaming = true; this.streaming = true;
this.eventQueue = new LinkedBlockingQueue<>(); this.eventQueue = new LinkedBlockingQueue<>();
this.message = new CompletableFuture<>(); this.message = new CompletableFuture<>();
} }
public void put(String token, String finishReason) { public void put(String token, String finishReason) {
//System.out.println(System.currentTimeMillis());
try { try {
eventQueue.put(ChatEvent.of(token, finishReason)); eventQueue.put(ChatEvent.of(token, finishReason));
} catch (InterruptedException e) { } 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) { if (token != null) {
@ -45,6 +47,19 @@ public class SimpleChatResponse implements ChatResponse {
put(null, finishReason); 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 @Override
public boolean streaming() { public boolean streaming() {
return streaming; return streaming;

View file

@ -17,6 +17,10 @@ public abstract class Option<T> {
this.value = value; this.value = value;
} }
/**
* gets the value of this option
* @return the value
*/
public T getValue() { public T getValue() {
return value; return value;
} }
@ -28,20 +32,26 @@ public abstract class Option<T> {
* @throws ClassCastException if type is wrong * @throws ClassCastException if type is wrong
*/ */
public void setValue(Object valueObject) { public void setValue(Object valueObject) {
Class<T> type = getType(); if (valueObject == null) {
this.value = null;
} else {
Class<T> type = getType();
if (!type.isInstance(valueObject)) { if (!type.isInstance(valueObject)) {
throw new ClassCastException("Invalid type %s, expected %s".formatted(valueObject.getClass().getName(), getType().getName())); 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<T> getType() { public Class<T> getType() {
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
} }

View file

@ -108,14 +108,7 @@ public class Options {
* @throws NoSuchElementException if no option with this id * @throws NoSuchElementException if no option with this id
*/ */
public void setValue(String id, Object value) { public void setValue(String id, Object value) {
Option<?> option = getOption(id); getOption(id).setValue(value);
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);
} }
/** /**