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.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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
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.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;
|
|
@ -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;
|
|
@ -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) {
|
||||
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()));
|
||||
}
|
||||
|
||||
T value = (T) valueObject; // :|
|
||||
@SuppressWarnings("unchecked")
|
||||
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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue