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.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);

View file

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

View file

@ -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
*/

View file

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

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.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;

View file

@ -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<ChatEvent> eventQueue;
private final CompletableFuture<ChatMessage> 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;

View file

@ -17,6 +17,10 @@ public abstract class Option<T> {
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<T> {
* @throws ClassCastException if type is wrong
*/
public void setValue(Object valueObject) {
Class<T> type = getType();
if (valueObject == null) {
this.value = null;
} else {
Class<T> 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<T> getType() {
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
*/
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);
}
/**