end of coding for this year
last day of no school :( :( :( 9 months of wasting time
This commit is contained in:
parent
b23600e216
commit
983c108eed
9 changed files with 113 additions and 72 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
11
src/main/java/eu/m724/source/exception/HttpException.java
Normal file
11
src/main/java/eu/m724/source/exception/HttpException.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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) {
|
||||||
|
if (valueObject == null) {
|
||||||
|
this.value = null;
|
||||||
|
} else {
|
||||||
Class<T> type = getType();
|
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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
T value = (T) valueObject; // :|
|
@SuppressWarnings("unchecked")
|
||||||
|
T value = (T) valueObject;
|
||||||
|
|
||||||
if (!isValid(value))
|
if (!isValid(value))
|
||||||
throw new IllegalArgumentException("Value invalid");
|
throw new IllegalArgumentException("Value invalid");
|
||||||
|
|
||||||
this.value = value;
|
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];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue